Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c2a6eb4d39 |
@@ -1,63 +0,0 @@
|
||||
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: --workspace
|
||||
args: --all
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --all-features
|
||||
args: --all --all-features
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
@@ -57,19 +57,19 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace -- -D warnings
|
||||
args: -- -D warnings
|
||||
|
||||
- name: Build all binaries with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --features=coconut
|
||||
args: --all --features=coconut
|
||||
|
||||
- name: Run all tests with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --features=coconut
|
||||
args: --all --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 --workspace --target wasm32-unknown-unknown
|
||||
args: --manifest-path contracts/Cargo.toml --all --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 --workspace -- -D warnings
|
||||
args: --manifest-path contracts/Cargo.toml --all -- -D warnings
|
||||
@@ -16,9 +16,8 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
node-version: '14'
|
||||
- run: npm install
|
||||
- name: Run ESLint
|
||||
# GitHub should automatically annotate the PR
|
||||
run: yarn && yarn lint
|
||||
run: npm run lint
|
||||
@@ -19,17 +19,14 @@ jobs:
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
node-version: '14'
|
||||
- run: npm install
|
||||
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: yarn test
|
||||
# continue-on-error: true
|
||||
- run: yarn && yarn build
|
||||
- run: npm run test
|
||||
continue-on-error: true
|
||||
- run: npm run 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: --workspace
|
||||
args: --all
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace
|
||||
args: --all
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets -- -D warnings
|
||||
args: -- -D warnings
|
||||
|
||||
|
||||
# COCONUT stuff
|
||||
@@ -81,20 +81,20 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --features=coconut
|
||||
args: --all --features=coconut
|
||||
|
||||
- name: Run all tests with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --features=coconut
|
||||
args: --all --features=coconut
|
||||
|
||||
- name: Run clippy with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets --features=coconut -- -D warnings
|
||||
args: --features=coconut -- -D warnings
|
||||
notification:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
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: ['ts-packages', 'network-explorer', 'nightly'],
|
||||
kinds: ['network-explorer', 'nightly'],
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
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,
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
|
||||
> :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 }}
|
||||
```
|
||||
@@ -1,16 +0,0 @@
|
||||
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
|
||||
> :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,6 +35,4 @@ contracts/mixnet/Justfile
|
||||
contracts/mixnet/Makefile
|
||||
validator-config
|
||||
*.patch
|
||||
validator-api-config.toml
|
||||
dist
|
||||
storybook-static
|
||||
validator-api-config.toml
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"mainnet":[{
|
||||
"nymd_url":"https://rpc.nyx.nodes.guru/",
|
||||
"api_url":"https://api.nyx.nodes.guru/"
|
||||
}]
|
||||
}
|
||||
@@ -69,9 +69,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.56"
|
||||
version = "1.0.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27"
|
||||
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
@@ -93,9 +93,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.3"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e"
|
||||
checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625"
|
||||
dependencies = [
|
||||
"async-stream-impl",
|
||||
"futures-core",
|
||||
@@ -103,9 +103,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-stream-impl"
|
||||
version = "0.3.3"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
|
||||
checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -218,9 +218,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.4.1"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71acf5509fc522cce1b100ac0121c635129bfd4d91cdf036bcc9b9935f97ccf5"
|
||||
checksum = "874f8444adcb4952a8bc51305c8be95c8ec8237bb0d2e78d2e039f771f8828a0"
|
||||
|
||||
[[package]]
|
||||
name = "binascii"
|
||||
@@ -245,7 +245,7 @@ checksum = "873faa4363bfc54c36a48321da034c92a0645a363eed34d948683ffc1706e37f"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"hmac 0.11.0",
|
||||
"k256 0.10.4",
|
||||
"k256 0.10.2",
|
||||
"once_cell",
|
||||
"pbkdf2",
|
||||
"rand_core 0.6.3",
|
||||
@@ -373,24 +373,11 @@ dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ff 0.10.1",
|
||||
"group 0.10.0",
|
||||
"pairing 0.20.0",
|
||||
"pairing",
|
||||
"rand_core 0.6.3",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bls12_381"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ff 0.11.0",
|
||||
"group 0.11.0",
|
||||
"pairing 0.21.0",
|
||||
"rand_core 0.6.3",
|
||||
"subtle 2.4.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bs58"
|
||||
version = "0.4.0"
|
||||
@@ -532,9 +519,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.1.6"
|
||||
version = "3.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123"
|
||||
checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
@@ -749,7 +736,7 @@ dependencies = [
|
||||
"ecdsa 0.13.4",
|
||||
"eyre",
|
||||
"getrandom 0.2.5",
|
||||
"k256 0.10.4",
|
||||
"k256 0.10.2",
|
||||
"prost",
|
||||
"prost-types",
|
||||
"rand_core 0.6.3",
|
||||
@@ -763,9 +750,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "1.0.0-beta6"
|
||||
version = "1.0.0-beta5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dddc1443004c6340e55ca66d98e9d2f1a44aadf4ce2bed2c4f29baa8a15e7b7"
|
||||
checksum = "8904127a5b9e325ef5d6b2b3f997dcd74943cd35097139b1a4d15b1b6bccae66"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ed25519-zebra",
|
||||
@@ -776,23 +763,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "1.0.0-beta6"
|
||||
version = "1.0.0-beta5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe4f0f10f165b8bcc558a13cddb498140960544519aa0581532c766dd80b5598"
|
||||
checksum = "a14364ac4d9d085867929d0cf3e94b1d2100121ce02c33c72961406830002613"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "1.0.0-beta6"
|
||||
version = "1.0.0-beta5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0f3145097b692b2d95fa5d2c7c6fdd60f193ccc709857e7e1987a608725300"
|
||||
checksum = "e2ece12e5bbde434b93937d7b2107e6291f11d69ffa72398c50e8bab41d451d3"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
"cosmwasm-derive",
|
||||
"forward_ref",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde-json-wasm",
|
||||
@@ -837,7 +823,6 @@ dependencies = [
|
||||
name = "credentials"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bls12_381 0.5.0",
|
||||
"coconut-interface",
|
||||
"crypto",
|
||||
"network-defaults",
|
||||
@@ -884,9 +869,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.3"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdbfe11fe19ff083c48923cf179540e8cd0535903dc35e178a1fdeeb59aef51f"
|
||||
checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
@@ -905,11 +890,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.8"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"
|
||||
checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
@@ -919,9 +903,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.5"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2"
|
||||
checksum = "4dd435b205a4842da59efd07628f921c096bc1cc0a156835b4fa0bcb9a19bcce"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
@@ -929,9 +913,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.8"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
|
||||
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
@@ -1071,9 +1055,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "3.2.0"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
|
||||
checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"digest 0.9.0",
|
||||
@@ -1093,17 +1077,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw-storage-plus"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c087ff98fb0475db4c2b5298a5fd12b2848d2854b39d1115d930ee6da24d1eed"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.1"
|
||||
@@ -1282,9 +1255,9 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.5"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28"
|
||||
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
@@ -1566,9 +1539,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fixed"
|
||||
version = "1.13.1"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3342b127378d13cfdbd56de8d7e7feec33a9772d5657b9605e59a6e4a337e36"
|
||||
checksum = "4d38004fb072c44da88a322343c7c784bd1ff6f2e3f5b332050de60c5eea3e0e"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bytemuck",
|
||||
@@ -1619,9 +1592,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.10.12"
|
||||
version = "0.10.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843c03199d0c0ca54bc1ea90ac0d507274c28abcc4f691ae8b4eaa375087c76a"
|
||||
checksum = "0b279436a715a9de95dcd26b151db590a71961cc06e54918b24fe0dd5b7d3fc4"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -1675,12 +1648,6 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "forward_ref"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
|
||||
|
||||
[[package]]
|
||||
name = "fs2"
|
||||
version = "0.4.3"
|
||||
@@ -1943,9 +1910,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.14.2"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c"
|
||||
checksum = "6e7d3b96ec1fcaa8431cf04a4f1ef5caafe58d5cf7bcc31f09c1626adddb0ffe"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
@@ -1990,7 +1957,6 @@ version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"ff 0.11.0",
|
||||
"rand_core 0.6.3",
|
||||
"subtle 2.4.1",
|
||||
@@ -1998,9 +1964,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.12"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62eeb471aa3e3c9197aa4bfeabfe02982f6dc96f750486c0bb0009ac58b26d2b"
|
||||
checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@@ -2216,9 +2182,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "humantime-serde"
|
||||
version = "1.1.1"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c"
|
||||
checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058"
|
||||
dependencies = [
|
||||
"humantime 2.1.0",
|
||||
"serde",
|
||||
@@ -2419,9 +2385,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.4.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35e70ee094dc02fd9c13fdad4940090f22dbd6ac7c9e7094a46cf0232a50bc7c"
|
||||
checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
|
||||
|
||||
[[package]]
|
||||
name = "ipnetwork"
|
||||
@@ -2516,9 +2482,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.10.4"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d"
|
||||
checksum = "1cc5937366afd3b38071f400d1ce5bd8b1d40b5083cc14e6f8dbcc4032a7f5bb"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ecdsa 0.13.4",
|
||||
@@ -2548,15 +2514,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.120"
|
||||
version = "0.2.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad5c14e80759d0939d013e6ca49930e59fc53dd8e5009132f76240c179380c09"
|
||||
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.13.2+1.4.2"
|
||||
version = "0.13.1+1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b"
|
||||
checksum = "43e598aa7a4faedf1ea1b4608f582b06f0f40211eec551b7ef36019ae3f62def"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -2583,9 +2549,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.5"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f35facd4a5673cb5a48822be2be1d4236c1c99cb4113cab7061ac720d5bf859"
|
||||
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -2704,15 +2670,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.1"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ba42135c6a5917b9db9cd7b293e5409e1c6b041e6f9825e92e55a894c63b6f8"
|
||||
checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"miow",
|
||||
"ntapi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@@ -2838,12 +2803,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.1"
|
||||
version = "7.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
|
||||
checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2907,9 +2873,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.5"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0"
|
||||
checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -2957,7 +2923,7 @@ dependencies = [
|
||||
"bandwidth-claim-contract",
|
||||
"bip39",
|
||||
"bs58",
|
||||
"clap 3.1.6",
|
||||
"clap 3.1.5",
|
||||
"coconut-interface",
|
||||
"colored",
|
||||
"config",
|
||||
@@ -3000,7 +2966,7 @@ version = "0.12.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
"clap 3.1.6",
|
||||
"clap 3.1.5",
|
||||
"colored",
|
||||
"config",
|
||||
"crypto",
|
||||
@@ -3132,60 +3098,12 @@ dependencies = [
|
||||
"version-checker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym_compact_ecash"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bls12_381 0.6.0",
|
||||
"bs58",
|
||||
"criterion",
|
||||
"digest 0.9.0",
|
||||
"ff 0.11.0",
|
||||
"group 0.11.0",
|
||||
"itertools",
|
||||
"rand 0.8.5",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym_offline_divisible_ecash"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bls12_381 0.6.0",
|
||||
"bs58",
|
||||
"criterion",
|
||||
"digest 0.9.0",
|
||||
"ff 0.11.0",
|
||||
"group 0.11.0",
|
||||
"itertools",
|
||||
"rand 0.8.5",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym_online_divisible_ecash"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bls12_381 0.5.0",
|
||||
"bs58",
|
||||
"criterion",
|
||||
"digest 0.9.0",
|
||||
"ff 0.10.1",
|
||||
"group 0.10.0",
|
||||
"itertools",
|
||||
"rand 0.8.5",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nymcoconut"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bls12_381 0.5.0",
|
||||
"bls12_381",
|
||||
"bs58",
|
||||
"criterion",
|
||||
"digest 0.9.0",
|
||||
@@ -3333,9 +3251,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.10.0"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
|
||||
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
@@ -3413,15 +3331,6 @@ dependencies = [
|
||||
"group 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pairing"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42"
|
||||
dependencies = [
|
||||
"group 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-scale-codec"
|
||||
version = "2.3.1"
|
||||
@@ -4197,9 +4106,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.5"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -4232,9 +4141,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.10"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb"
|
||||
checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
@@ -5287,9 +5196,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.89"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5340,7 +5249,7 @@ dependencies = [
|
||||
"ed25519-dalek",
|
||||
"flex-error",
|
||||
"futures",
|
||||
"k256 0.10.4",
|
||||
"k256 0.10.2",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"prost",
|
||||
@@ -5794,9 +5703,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.32"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f"
|
||||
checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"log",
|
||||
@@ -5807,9 +5716,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.20"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b"
|
||||
checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5818,9 +5727,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.23"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c"
|
||||
checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"valuable",
|
||||
@@ -6137,7 +6046,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus 0.12.1",
|
||||
"cw-storage-plus",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -6151,7 +6060,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus 0.11.1",
|
||||
"cw-storage-plus",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -6191,12 +6100,6 @@ version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.78"
|
||||
@@ -6459,9 +6362,9 @@ checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||
checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
@@ -6474,9 +6377,9 @@ checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
||||
|
||||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f"
|
||||
checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"rand_core 0.5.1",
|
||||
@@ -6491,9 +6394,9 @@ checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.4.3"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
|
||||
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
@@ -33,9 +33,6 @@ 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,10 +1,9 @@
|
||||
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-contracts build-wallet build-main
|
||||
build: build-contracts build-wallet build-main
|
||||
fmt: fmt-main fmt-contracts fmt-wallet
|
||||
|
||||
clippy-happy-main:
|
||||
@@ -13,20 +12,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 --workspace --all-features -- -D warnings
|
||||
cargo clippy --all-features -- -D warnings
|
||||
|
||||
clippy-all-contracts:
|
||||
cargo clippy --workspace --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
cargo clippy --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
|
||||
clippy-all-wallet:
|
||||
cargo clippy --workspace --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
|
||||
clippy-all-wallet:
|
||||
cargo clippy --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
|
||||
|
||||
test-main:
|
||||
cargo test --all-features --workspace
|
||||
cargo test --all-features --all
|
||||
|
||||
test-contracts:
|
||||
cargo test --manifest-path contracts/Cargo.toml --all-features
|
||||
@@ -35,13 +34,13 @@ test-wallet:
|
||||
cargo test --manifest-path nym-wallet/Cargo.toml --all-features
|
||||
|
||||
build-main:
|
||||
cargo build --workspace
|
||||
cargo build --all
|
||||
|
||||
build-contracts:
|
||||
cargo build --manifest-path contracts/Cargo.toml --workspace
|
||||
cargo build --manifest-path contracts/Cargo.toml --all
|
||||
|
||||
build-wallet:
|
||||
cargo build --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
cargo build --manifest-path nym-wallet/Cargo.toml --all
|
||||
|
||||
fmt-main:
|
||||
cargo fmt --all
|
||||
|
||||
@@ -28,8 +28,6 @@ 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.
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# 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.
|
||||
|
Before Width: | Height: | Size: 545 KiB |
@@ -1,10 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 672 B |
@@ -1,6 +0,0 @@
|
||||
@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;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 714 B |
@@ -1,13 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,5 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,5 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,5 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -6085,9 +6085,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"version": "1.5.7",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.7.tgz",
|
||||
"integrity": "sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
@@ -11733,9 +11733,9 @@
|
||||
}
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"version": "1.5.7",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.7.tgz",
|
||||
"integrity": "sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"querystringify": "^2.1.1",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::{validator_api, ValidatorClientError};
|
||||
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
|
||||
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, Interval, MixNodeBond};
|
||||
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
use url::Url;
|
||||
use validator_api_requests::models::{
|
||||
@@ -20,7 +20,7 @@ use mixnet_contract_common::ContractStateParams;
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use mixnet_contract_common::{
|
||||
Delegation, IdentityKey, MixnetContractVersion, MixnodeRewardingStatusResponse,
|
||||
Delegation, IdentityKey, Interval, MixnetContractVersion, MixnodeRewardingStatusResponse,
|
||||
RewardedSetNodeStatus, RewardedSetUpdateDetails,
|
||||
};
|
||||
#[cfg(feature = "nymd-client")]
|
||||
@@ -92,7 +92,7 @@ impl Config {
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
pub struct Client<C> {
|
||||
pub network: network_defaults::all::Network,
|
||||
network: network_defaults::all::Network,
|
||||
mixnet_contract_address: Option<cosmrs::AccountId>,
|
||||
vesting_contract_address: Option<cosmrs::AccountId>,
|
||||
erc20_bridge_contract_address: Option<cosmrs::AccountId>,
|
||||
@@ -248,35 +248,6 @@ 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,
|
||||
@@ -305,6 +276,13 @@ 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,
|
||||
@@ -326,13 +304,6 @@ 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: Clone>(
|
||||
pub fn connect_with_signer<U>(
|
||||
endpoint: U,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: GasPrice,
|
||||
|
||||
@@ -641,7 +641,7 @@ pub struct Client {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn connect_with_signer<U: Clone>(
|
||||
pub fn connect_with_signer<U>(
|
||||
endpoint: U,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: GasPrice,
|
||||
|
||||
@@ -47,12 +47,9 @@ pub enum Operation {
|
||||
CreatePeriodicVestingAccount,
|
||||
|
||||
AdvanceCurrentInterval,
|
||||
AdvanceCurrentEpoch,
|
||||
WriteRewardedSet,
|
||||
ClearRewardedSet,
|
||||
UpdateMixnetAddress,
|
||||
CheckpointMixnodes,
|
||||
ReconcileDelegations,
|
||||
}
|
||||
|
||||
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
|
||||
@@ -94,9 +91,6 @@ 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,9 +132,6 @@ 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,8 +9,7 @@ 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;
|
||||
use cosmrs::rpc::HttpClientUrl;
|
||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
|
||||
use cosmwasm_std::{Coin, Uint128};
|
||||
pub use fee::gas_price::GasPrice;
|
||||
use fee::helpers::Operation;
|
||||
@@ -83,7 +82,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: Clone>(
|
||||
pub fn connect_with_signer<U>(
|
||||
network: config::defaults::all::Network,
|
||||
endpoint: U,
|
||||
mixnet_contract_address: Option<AccountId>,
|
||||
@@ -114,7 +113,7 @@ impl NymdClient<SigningNymdClient> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn connect_with_mnemonic<U: Clone>(
|
||||
pub fn connect_with_mnemonic<U>(
|
||||
network: config::defaults::all::Network,
|
||||
endpoint: U,
|
||||
mixnet_contract_address: Option<AccountId>,
|
||||
@@ -290,43 +289,6 @@ 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,
|
||||
@@ -406,6 +368,16 @@ 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,
|
||||
@@ -456,16 +428,6 @@ 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
|
||||
@@ -534,7 +496,7 @@ impl<C> NymdClient<C> {
|
||||
pub async fn get_mix_delegations_paged(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
start_after: Option<(String, u64)>,
|
||||
start_after: Option<String>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedMixDelegationsResponse, NymdError>
|
||||
where
|
||||
@@ -553,7 +515,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, Vec<u8>, u64)>,
|
||||
start_after: Option<(IdentityKey, String)>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedAllDelegationsResponse, NymdError>
|
||||
where
|
||||
@@ -1213,58 +1175,20 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn advance_current_epoch(&self) -> Result<ExecuteResult, NymdError>
|
||||
pub async fn advance_current_interval(&self) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.operation_fee(Operation::AdvanceCurrentEpoch);
|
||||
let fee = self.operation_fee(Operation::AdvanceCurrentInterval);
|
||||
|
||||
let req = ExecuteMsg::AdvanceCurrentEpoch {};
|
||||
let req = ExecuteMsg::AdvanceCurrentInterval {};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.mixnet_contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"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",
|
||||
"Advancing current interval",
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -83,6 +83,8 @@ 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,
|
||||
@@ -91,11 +93,13 @@ 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())
|
||||
|
||||
@@ -37,12 +37,8 @@ impl Delegation {
|
||||
}
|
||||
|
||||
// TODO: change that to use .joined_key() and return Vec<u8>
|
||||
pub fn storage_key(&self) -> (IdentityKey, Vec<u8>, u64) {
|
||||
(
|
||||
self.node_identity(),
|
||||
self.owner().as_bytes().to_vec(),
|
||||
self.block_height(),
|
||||
)
|
||||
pub fn storage_key(&self) -> (IdentityKey, Addr) {
|
||||
(self.node_identity(), self.owner())
|
||||
}
|
||||
|
||||
pub fn increment_amount(&mut self, amount: Uint128, at_height: Option<u64>) {
|
||||
@@ -82,14 +78,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, u64)>,
|
||||
pub start_next_after: Option<String>,
|
||||
}
|
||||
|
||||
impl PagedMixDelegationsResponse {
|
||||
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<(Addr, u64)>) -> Self {
|
||||
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<Addr>) -> Self {
|
||||
PagedMixDelegationsResponse {
|
||||
delegations,
|
||||
start_next_after: start_next_after.map(|(s, h)| (s.to_string(), h)),
|
||||
start_next_after: start_next_after.map(|s| s.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,17 +108,17 @@ impl PagedDelegatorDelegationsResponse {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedAllDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
|
||||
pub start_next_after: Option<(IdentityKey, String)>,
|
||||
}
|
||||
|
||||
impl PagedAllDelegationsResponse {
|
||||
pub fn new(
|
||||
delegations: Vec<Delegation>,
|
||||
start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
|
||||
start_next_after: Option<(IdentityKey, Addr)>,
|
||||
) -> Self {
|
||||
PagedAllDelegationsResponse {
|
||||
delegations,
|
||||
start_next_after: start_next_after.map(|(id, addr, height)| (id, addr, height)),
|
||||
start_next_after: start_next_after.map(|(id, addr)| (id, addr.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,4 @@ 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,17 +1,15 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::mixnode::NodeRewardResult;
|
||||
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer};
|
||||
use crate::{ContractStateParams, Delegation, 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";
|
||||
@@ -21,10 +19,6 @@ 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";
|
||||
@@ -76,19 +70,6 @@ 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,
|
||||
@@ -108,74 +89,11 @@ 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);
|
||||
|
||||
@@ -185,26 +103,14 @@ pub fn new_undelegation_event(
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(AMOUNT_KEY, old_delegation.amount.to_string())
|
||||
.add_attribute(
|
||||
DELEGATION_HEIGHT_KEY,
|
||||
old_delegation.block_height.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>,
|
||||
@@ -374,6 +280,9 @@ 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())
|
||||
@@ -386,6 +295,15 @@ 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(
|
||||
@@ -412,18 +330,16 @@ 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())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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};
|
||||
@@ -66,28 +65,14 @@ pub struct Interval {
|
||||
}
|
||||
|
||||
impl Interval {
|
||||
/// 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()
|
||||
/// Creates new interval instance.
|
||||
pub const fn new(id: u32, start: OffsetDateTime, length: Duration) -> Self {
|
||||
Interval { id, start, length }
|
||||
}
|
||||
|
||||
/// Returns the next interval.
|
||||
#[must_use]
|
||||
pub fn next(&self) -> Self {
|
||||
pub fn next_interval(&self) -> Self {
|
||||
Interval {
|
||||
id: self.id + 1,
|
||||
start: self.end(),
|
||||
@@ -95,19 +80,8 @@ 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(&self) -> Option<Self> {
|
||||
pub fn previous_interval(&self) -> Option<Self> {
|
||||
if self.id > 0 {
|
||||
Some(Interval {
|
||||
id: self.id - 1,
|
||||
@@ -151,14 +125,14 @@ impl Interval {
|
||||
if candidate.contains(now) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.next();
|
||||
candidate = candidate.next_interval();
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
if candidate.contains(now) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.previous()?;
|
||||
candidate = candidate.previous_interval()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,14 +151,14 @@ impl Interval {
|
||||
if candidate.contains_timestamp(now_unix) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.next();
|
||||
candidate = candidate.next_interval();
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
if candidate.contains_timestamp(now_unix) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.previous()?;
|
||||
candidate = candidate.previous_interval()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,7 +239,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn previous() {
|
||||
fn previous_interval() {
|
||||
let interval = Interval {
|
||||
id: 1,
|
||||
start: time::macros::datetime!(2021-08-23 12:00 UTC),
|
||||
@@ -276,18 +250,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().unwrap());
|
||||
assert_eq!(expected, interval.previous_interval().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().is_none());
|
||||
assert!(genesis_interval.previous_interval().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next() {
|
||||
fn next_interval() {
|
||||
let interval = Interval {
|
||||
id: 0,
|
||||
start: time::macros::datetime!(2021-08-23 12:00 UTC),
|
||||
@@ -299,7 +273,7 @@ mod tests {
|
||||
length: Duration::from_secs(24 * 60 * 60),
|
||||
};
|
||||
|
||||
assert_eq!(expected, interval.next())
|
||||
assert_eq!(expected, interval.next_interval())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -317,8 +291,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().end()));
|
||||
assert!(!interval.contains(interval.previous().unwrap().start()));
|
||||
assert!(!interval.contains(interval.next_interval().end()));
|
||||
assert!(!interval.contains(interval.previous_interval().unwrap().start()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -331,7 +305,10 @@ mod tests {
|
||||
|
||||
// interval just before
|
||||
let fake_now = first_interval.start - Duration::from_secs(123);
|
||||
assert_eq!(first_interval.previous(), first_interval.current(fake_now));
|
||||
assert_eq!(
|
||||
first_interval.previous_interval(),
|
||||
first_interval.current(fake_now)
|
||||
);
|
||||
|
||||
// this interval (start boundary)
|
||||
assert_eq!(
|
||||
@@ -352,7 +329,7 @@ mod tests {
|
||||
// next interval
|
||||
let fake_now = first_interval.end() + Duration::from_secs(123);
|
||||
assert_eq!(
|
||||
first_interval.next(),
|
||||
first_interval.next_interval(),
|
||||
first_interval.current(fake_now).unwrap()
|
||||
);
|
||||
|
||||
@@ -363,11 +340,11 @@ mod tests {
|
||||
- first_interval.length;
|
||||
assert_eq!(
|
||||
first_interval
|
||||
.previous()
|
||||
.previous_interval()
|
||||
.unwrap()
|
||||
.previous()
|
||||
.previous_interval()
|
||||
.unwrap()
|
||||
.previous()
|
||||
.previous_interval()
|
||||
.unwrap(),
|
||||
first_interval.current(fake_now).unwrap()
|
||||
);
|
||||
@@ -378,7 +355,10 @@ mod tests {
|
||||
+ first_interval.length
|
||||
+ first_interval.length;
|
||||
assert_eq!(
|
||||
first_interval.next().next().next(),
|
||||
first_interval
|
||||
.next_interval()
|
||||
.next_interval()
|
||||
.next_interval(),
|
||||
first_interval.current(fake_now).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ mod gateway;
|
||||
mod interval;
|
||||
pub mod mixnode;
|
||||
mod msg;
|
||||
pub mod reward_params;
|
||||
mod types;
|
||||
|
||||
pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250;
|
||||
@@ -25,9 +24,3 @@ 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,19 +1,23 @@
|
||||
// due to code generated by JsonSchema
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::reward_params::RewardParams;
|
||||
use crate::{Delegation, IdentityKey, SphinxKey};
|
||||
use crate::{ONE, U128};
|
||||
use crate::{IdentityKey, SphinxKey};
|
||||
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(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
@@ -22,7 +26,7 @@ use std::fmt::Display;
|
||||
export_to = "../../../nym-wallet/src/types/rust/rewardedsetnodestatus.ts"
|
||||
)
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
||||
pub enum RewardedSetNodeStatus {
|
||||
Active,
|
||||
Standby,
|
||||
@@ -34,52 +38,6 @@ impl RewardedSetNodeStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
@@ -129,6 +87,112 @@ 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 {
|
||||
@@ -162,7 +226,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 {
|
||||
reward_params: RewardParams,
|
||||
node_reward_params: NodeRewardParams,
|
||||
|
||||
// to be completely honest I don't understand all consequences of using `#[schemars(with = "String")]`
|
||||
// for U128 here, but it seems that CosmWasm is using the same attribute for their Uint128
|
||||
@@ -178,24 +242,19 @@ pub struct DelegatorRewardParams {
|
||||
}
|
||||
|
||||
impl DelegatorRewardParams {
|
||||
pub fn new(
|
||||
sigma: U128,
|
||||
profit_margin: U128,
|
||||
node_profit: U128,
|
||||
reward_params: RewardParams,
|
||||
) -> Self {
|
||||
pub fn new(mixnode_bond: &MixNodeBond, node_reward_params: NodeRewardParams) -> Self {
|
||||
DelegatorRewardParams {
|
||||
sigma,
|
||||
profit_margin,
|
||||
node_profit,
|
||||
reward_params,
|
||||
sigma: mixnode_bond.sigma(&node_reward_params),
|
||||
profit_margin: mixnode_bond.profit_margin(),
|
||||
node_profit: mixnode_bond.node_profit(&node_reward_params),
|
||||
node_reward_params,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn determine_delegation_reward(&self, delegation_amount: Uint128) -> u128 {
|
||||
// change all values into their fixed representations
|
||||
let delegation_amount = U128::from_num(delegation_amount.u128());
|
||||
let circulating_supply = U128::from_num(self.reward_params.circulating_supply());
|
||||
let circulating_supply = U128::from_num(self.node_reward_params.circulating_supply());
|
||||
|
||||
let scaled_delegation_amount = delegation_amount / circulating_supply;
|
||||
let delegator_reward =
|
||||
@@ -213,56 +272,8 @@ impl DelegatorRewardParams {
|
||||
}
|
||||
}
|
||||
|
||||
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)?,
|
||||
),
|
||||
})
|
||||
pub fn node_reward_params(&self) -> &NodeRewardParams {
|
||||
&self.node_reward_params
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +307,6 @@ pub struct MixNodeBond {
|
||||
pub block_height: u64,
|
||||
pub mix_node: MixNode,
|
||||
pub proxy: Option<Addr>,
|
||||
pub accumulated_rewards: Option<Uint128>,
|
||||
}
|
||||
|
||||
impl MixNodeBond {
|
||||
@@ -316,14 +326,9 @@ 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)
|
||||
}
|
||||
@@ -344,16 +349,11 @@ 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()
|
||||
+ self.accumulated_rewards().u128(),
|
||||
)
|
||||
Some(self.pledge_amount.amount.u128() + self.total_delegation.amount.u128())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,10 +366,6 @@ 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)
|
||||
}
|
||||
@@ -379,46 +375,26 @@ impl MixNodeBond {
|
||||
/ U128::from_num(circulating_supply)
|
||||
}
|
||||
|
||||
pub fn lambda(&self, params: &RewardParams) -> U128 {
|
||||
pub fn lambda(&self, params: &NodeRewardParams) -> 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: &RewardParams) -> U128 {
|
||||
pub fn sigma(&self, params: &NodeRewardParams) -> 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 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 {
|
||||
pub fn reward(&self, params: &NodeRewardParams) -> NodeRewardResult {
|
||||
let lambda = self.lambda(params);
|
||||
let sigma = self.sigma(params);
|
||||
|
||||
let reward = params.performance()
|
||||
* params.epoch_reward_pool()
|
||||
* params.period_reward_pool()
|
||||
* (sigma * params.omega()
|
||||
+ params.alpha() * lambda * sigma * params.rewarded_set_size())
|
||||
/ (ONE + params.alpha());
|
||||
@@ -430,22 +406,22 @@ impl MixNodeBond {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node_profit(&self, params: &RewardParams) -> U128 {
|
||||
if self.reward(params).reward() < params.node.operator_cost() {
|
||||
U128::from_num(0u128)
|
||||
pub fn node_profit(&self, params: &NodeRewardParams) -> U128 {
|
||||
if self.reward(params).reward() < params.operator_cost() {
|
||||
U128::from_num(0)
|
||||
} else {
|
||||
self.reward(params).reward() - params.node.operator_cost()
|
||||
self.reward(params).reward() - params.operator_cost()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operator_reward(&self, params: &RewardParams) -> u128 {
|
||||
pub fn operator_reward(&self, params: &NodeRewardParams) -> u128 {
|
||||
let reward = self.reward(params);
|
||||
let profit = if reward.reward < params.node.operator_cost() {
|
||||
U128::from_num(0u128)
|
||||
let profit = if reward.reward < params.operator_cost() {
|
||||
U128::from_num(0)
|
||||
} else {
|
||||
reward.reward - params.node.operator_cost()
|
||||
reward.reward - params.operator_cost()
|
||||
};
|
||||
let operator_base_reward = reward.reward.min(params.node.operator_cost());
|
||||
let operator_base_reward = reward.reward.min(params.operator_cost());
|
||||
let operator_reward = (self.profit_margin()
|
||||
+ (ONE - self.profit_margin()) * reward.lambda / reward.sigma)
|
||||
* profit;
|
||||
@@ -464,7 +440,7 @@ impl MixNodeBond {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sigma_ratio(&self, params: &RewardParams) -> U128 {
|
||||
pub fn sigma_ratio(&self, params: &NodeRewardParams) -> U128 {
|
||||
if self.total_bond_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
|
||||
{
|
||||
self.total_bond_to_circulating_supply(params.circulating_supply())
|
||||
@@ -473,13 +449,8 @@ impl MixNodeBond {
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
);
|
||||
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &NodeRewardParams) -> u128 {
|
||||
let reward_params = DelegatorRewardParams::new(self, *params);
|
||||
reward_params.determine_delegation_reward(delegation_amount)
|
||||
}
|
||||
}
|
||||
@@ -618,7 +589,6 @@ mod tests {
|
||||
block_height: 100,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
let mix2 = MixNodeBond {
|
||||
@@ -629,7 +599,6 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
let mix3 = MixNodeBond {
|
||||
@@ -640,7 +609,6 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
let mix4 = MixNodeBond {
|
||||
@@ -651,7 +619,6 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
let mix5 = MixNodeBond {
|
||||
@@ -662,7 +629,6 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
// summary:
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::reward_params::NodeRewardParams;
|
||||
use crate::mixnode::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,
|
||||
@@ -18,19 +15,6 @@ 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,
|
||||
@@ -66,11 +50,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,
|
||||
@@ -99,8 +83,7 @@ pub enum ExecuteMsg {
|
||||
rewarded_set: Vec<IdentityKey>,
|
||||
expected_active_set_size: u32,
|
||||
},
|
||||
// AdvanceCurrentInterval {},
|
||||
AdvanceCurrentEpoch {},
|
||||
AdvanceCurrentInterval {},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
@@ -125,7 +108,7 @@ pub enum QueryMsg {
|
||||
// gets all [paged] delegations in the entire network
|
||||
// TODO: do we even want that?
|
||||
GetAllNetworkDelegations {
|
||||
start_after: Option<(IdentityKey, DelegateAddress, BlockHeight)>,
|
||||
start_after: Option<(IdentityKey, String)>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
// gets all [paged] delegations associated with particular mixnode
|
||||
@@ -133,7 +116,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, u64)>,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
// gets all [paged] delegations associated with particular delegator
|
||||
@@ -164,18 +147,13 @@ 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)]
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
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,7 +73,8 @@ impl Display for ContractStateParams {
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RewardingResult {
|
||||
pub node_reward: Uint128,
|
||||
pub operator_reward: Uint128,
|
||||
pub total_delegator_reward: Uint128,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -123,7 +124,7 @@ pub type IdentityKey = String;
|
||||
pub type IdentityKeyRef<'a> = &'a str;
|
||||
pub type SphinxKey = String;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedRewardedSetResponse {
|
||||
pub identities: Vec<(IdentityKey, RewardedSetNodeStatus)>,
|
||||
pub start_next_after: Option<IdentityKey>,
|
||||
|
||||
@@ -6,7 +6,6 @@ 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"
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// 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, BlindSignRequest, BlindSignRequestBody, Credential,
|
||||
Parameters, Signature, SignatureShare, VerificationKey,
|
||||
ElGamalKeyPair, Parameters, Signature, SignatureShare, VerificationKey,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
@@ -65,13 +64,14 @@ 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,
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
elgamal_keypair: &ElGamalKeyPair,
|
||||
) -> Result<Signature, Error> {
|
||||
let blind_sign_request_body = BlindSignRequestBody::new(
|
||||
blind_sign_request,
|
||||
elgamal_keypair.public_key(),
|
||||
public_attributes,
|
||||
(public_attributes.len() + private_attributes.len()) as u32,
|
||||
);
|
||||
@@ -83,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)
|
||||
@@ -110,17 +110,22 @@ 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 elgamal_keypair = coconut_interface::elgamal_keygen(params);
|
||||
let blind_sign_request = prepare_blind_sign(
|
||||
params,
|
||||
&elgamal_keypair,
|
||||
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,
|
||||
&blind_sign_request,
|
||||
&elgamal_keypair,
|
||||
)
|
||||
.await?;
|
||||
shares.push(SignatureShare::new(first, 1));
|
||||
@@ -133,10 +138,10 @@ pub async fn obtain_aggregate_signature(
|
||||
params,
|
||||
public_attributes,
|
||||
private_attributes,
|
||||
&pedersen_commitments_openings,
|
||||
&blind_sign_request,
|
||||
&client,
|
||||
&validator_partial_vk.key,
|
||||
&blind_sign_request,
|
||||
&elgamal_keypair,
|
||||
)
|
||||
.await?;
|
||||
let share = SignatureShare::new(signature, (id + 1) as u64);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, fmt, str::FromStr};
|
||||
use std::{collections::HashMap, fmt, ops::Deref, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
DefaultNetworkDetails, ValidatorDetails, MAINNET_DEFAULTS, QA_DEFAULTS, SANDBOX_DEFAULTS,
|
||||
@@ -98,7 +98,7 @@ pub struct NetworkDetails {
|
||||
}
|
||||
|
||||
impl From<&DefaultNetworkDetails<'_>> for NetworkDetails {
|
||||
fn from(details: &DefaultNetworkDetails<'_>) -> Self {
|
||||
fn from(details: &DefaultNetworkDetails) -> Self {
|
||||
NetworkDetails {
|
||||
bech32_prefix: details.bech32_prefix.into(),
|
||||
denom: details.denom.into(),
|
||||
@@ -118,12 +118,20 @@ pub struct SupportedNetworks {
|
||||
|
||||
impl SupportedNetworks {
|
||||
pub fn new(support: Vec<Network>) -> Self {
|
||||
SupportedNetworks {
|
||||
networks: support
|
||||
.into_iter()
|
||||
.map(|n| (n, n.details().into()))
|
||||
.collect(),
|
||||
let mut networks = HashMap::new();
|
||||
|
||||
for network in support {
|
||||
match network {
|
||||
Network::MAINNET => {
|
||||
networks.insert(Network::MAINNET, MAINNET_DEFAULTS.deref().into())
|
||||
}
|
||||
Network::SANDBOX => {
|
||||
networks.insert(Network::SANDBOX, SANDBOX_DEFAULTS.deref().into())
|
||||
}
|
||||
Network::QA => networks.insert(Network::QA, QA_DEFAULTS.deref().into()),
|
||||
};
|
||||
}
|
||||
SupportedNetworks { networks }
|
||||
}
|
||||
|
||||
pub fn bech32_prefix(&self, network: Network) -> Option<&str> {
|
||||
@@ -162,11 +170,9 @@ impl SupportedNetworks {
|
||||
.map(|network_details| network_details.rewarding_validator_address.as_str())
|
||||
}
|
||||
|
||||
pub fn validators(&self, network: Network) -> impl Iterator<Item = &ValidatorDetails> {
|
||||
pub fn validators(&self, network: Network) -> Option<&Vec<ValidatorDetails>> {
|
||||
self.networks
|
||||
.get(&network)
|
||||
.map(|network_details| &network_details.validators)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,9 +95,10 @@ 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.map(ToString::to_string),
|
||||
api_url,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,14 +118,14 @@ impl ValidatorDetails {
|
||||
pub fn default_nymd_endpoints() -> Vec<Url> {
|
||||
DEFAULT_NETWORK
|
||||
.validators()
|
||||
.map(ValidatorDetails::nymd_url)
|
||||
.map(|validator| validator.nymd_url())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn default_api_endpoints() -> Vec<Url> {
|
||||
DEFAULT_NETWORK
|
||||
.validators()
|
||||
.filter_map(ValidatorDetails::api_url)
|
||||
.filter_map(|validator| validator.api_url())
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -178,8 +179,7 @@ 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 = 55_556; // 40$/1hr at 1 Nym == 1$
|
||||
pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$
|
||||
|
||||
// TODO: is there a way to get this from the chain
|
||||
pub const TOTAL_SUPPLY: u128 = 1_000_000_000_000_000;
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
[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
|
||||
@@ -1,360 +0,0 @@
|
||||
// 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);
|
||||
@@ -1,49 +0,0 @@
|
||||
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,
|
||||
},
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
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()
|
||||
}
|
||||
@@ -1,777 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,405 +0,0 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
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),
|
||||
})
|
||||
}
|
||||
@@ -1,384 +0,0 @@
|
||||
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()));
|
||||
}
|
||||
}
|
||||
@@ -1,442 +0,0 @@
|
||||
use core::borrow::Borrow;
|
||||
use core::iter::Sum;
|
||||
use core::ops::{Add, Mul};
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use bls12_381::{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)
|
||||
}
|
||||
@@ -1,593 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar};
|
||||
use ff::Field;
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::utils::{hash_g1, Signature};
|
||||
|
||||
const ATTRIBUTES_LEN: usize = 3;
|
||||
|
||||
pub struct GroupParameters {
|
||||
/// Generator of the G1 group
|
||||
g1: G1Affine,
|
||||
/// Generator of the G2 group
|
||||
g2: G2Affine,
|
||||
/// Additional generators of the G1 group
|
||||
gammas: Vec<G1Projective>,
|
||||
// Additional generator of the G1 group
|
||||
delta: G1Projective,
|
||||
/// Precomputed G2 generator used for the miller loop
|
||||
_g2_prepared_miller: G2Prepared,
|
||||
}
|
||||
|
||||
impl GroupParameters {
|
||||
pub fn new() -> Result<GroupParameters> {
|
||||
let gammas = (1..=ATTRIBUTES_LEN)
|
||||
.map(|i| hash_g1(format!("gamma{}", i)))
|
||||
.collect();
|
||||
|
||||
let delta = hash_g1("delta");
|
||||
|
||||
Ok(GroupParameters {
|
||||
g1: G1Affine::generator(),
|
||||
g2: G2Affine::generator(),
|
||||
gammas,
|
||||
delta,
|
||||
_g2_prepared_miller: G2Prepared::from(G2Affine::generator()),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn gen1(&self) -> &G1Affine {
|
||||
&self.g1
|
||||
}
|
||||
|
||||
pub(crate) fn gen2(&self) -> &G2Affine {
|
||||
&self.g2
|
||||
}
|
||||
|
||||
pub(crate) fn gammas(&self) -> &Vec<G1Projective> {
|
||||
&self.gammas
|
||||
}
|
||||
|
||||
pub(crate) fn gamma1(&self) -> &G1Projective {
|
||||
&self.gammas[0]
|
||||
}
|
||||
|
||||
pub(crate) fn gamma2(&self) -> Option<&G1Projective> {
|
||||
self.gammas.get(2)
|
||||
}
|
||||
|
||||
pub(crate) fn delta(&self) -> &G1Projective { &self.delta }
|
||||
|
||||
pub fn random_scalar(&self) -> Scalar {
|
||||
// lazily-initialized thread-local random number generator, seeded by the system
|
||||
let mut rng = thread_rng();
|
||||
Scalar::random(&mut rng)
|
||||
}
|
||||
|
||||
pub fn n_random_scalars(&self, n: usize) -> Vec<Scalar> {
|
||||
(0..n).map(|_| self.random_scalar()).collect()
|
||||
}
|
||||
|
||||
pub(crate) fn prepared_miller_g2(&self) -> &G2Prepared {
|
||||
&self._g2_prepared_miller
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct SecretKeyRP {
|
||||
pub(crate) x: Scalar,
|
||||
pub(crate) y: Scalar,
|
||||
}
|
||||
|
||||
impl SecretKeyRP {
|
||||
pub fn public_key(&self, params: &GroupParameters) -> PublicKeyRP {
|
||||
PublicKeyRP {
|
||||
alpha: params.gen2() * self.x,
|
||||
beta: params.gen2() * self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct PublicKeyRP {
|
||||
pub(crate) alpha: G2Projective,
|
||||
pub(crate) beta: G2Projective,
|
||||
}
|
||||
|
||||
pub struct Parameters {
|
||||
/// group parameters
|
||||
grp: GroupParameters,
|
||||
/// Public Key for range proof verification
|
||||
pk_rp: PublicKeyRP,
|
||||
/// Max value of wallet
|
||||
L: u64,
|
||||
/// list of signatures for values l in [0, L]
|
||||
signs: HashMap<u64, Signature>,
|
||||
}
|
||||
|
||||
impl Parameters {
|
||||
pub fn grp(&self) -> &GroupParameters {
|
||||
&self.grp
|
||||
}
|
||||
pub fn pk_rp(&self) -> &PublicKeyRP {
|
||||
&self.pk_rp
|
||||
}
|
||||
pub fn L(&self) -> u64 {
|
||||
self.L
|
||||
}
|
||||
pub fn signs(&self) -> &HashMap<u64, Signature> {
|
||||
&self.signs
|
||||
}
|
||||
pub fn get_sign_by_idx(&self, idx: u64) -> Result<&Signature> {
|
||||
match self.signs.get(&idx) {
|
||||
Some(val) => return Ok(val),
|
||||
None => {
|
||||
return Err(CompactEcashError::RangeProofOutOfBound(
|
||||
"Cannot find the range proof signature for the given value. \
|
||||
Check if the requested value is within the bound 0..L"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup(L: u64) -> Parameters {
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let x = grp.random_scalar();
|
||||
let y = grp.random_scalar();
|
||||
let sk_rp = SecretKeyRP { x, y };
|
||||
let pk_rp = sk_rp.public_key(&grp);
|
||||
let mut signs = HashMap::new();
|
||||
for l in 0..L {
|
||||
let r = grp.random_scalar();
|
||||
let h = grp.gen1() * r;
|
||||
signs.insert(
|
||||
l,
|
||||
Signature {
|
||||
0: h,
|
||||
1: h * (x + y * Scalar::from(l)),
|
||||
},
|
||||
);
|
||||
}
|
||||
Parameters {
|
||||
grp,
|
||||
pk_rp,
|
||||
L,
|
||||
signs,
|
||||
}
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar};
|
||||
use group::{Curve, GroupEncoding};
|
||||
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::proofs::proof_withdrawal::{
|
||||
WithdrawalReqInstance, WithdrawalReqProof, WithdrawalReqWitness,
|
||||
};
|
||||
use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser, VerificationKeyAuth};
|
||||
use crate::scheme::PartialWallet;
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
use crate::utils::{check_bilinear_pairing, hash_g1, try_deserialize_g1_projective};
|
||||
use crate::utils::{BlindedSignature, Signature};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct WithdrawalRequest {
|
||||
com_hash: G1Projective,
|
||||
com: G1Projective,
|
||||
pc_coms: Vec<G1Projective>,
|
||||
zk_proof: WithdrawalReqProof,
|
||||
}
|
||||
|
||||
impl WithdrawalRequest {
|
||||
pub fn to_bytes(&self) -> Vec<u8>{
|
||||
let com_hash_bytes = self.com_hash.to_affine().to_compressed();
|
||||
let com_bytes = self.com.to_affine().to_compressed();
|
||||
let pr_coms_len = self.pc_coms.len() as u64;
|
||||
let zk_proof_bytes = self.zk_proof.to_bytes();
|
||||
|
||||
let mut bytes = Vec::with_capacity(48 + 48 + 8 + pr_coms_len as usize * 48 + zk_proof_bytes.len());
|
||||
bytes.extend_from_slice(&com_hash_bytes);
|
||||
bytes.extend_from_slice(&com_bytes);
|
||||
bytes.extend_from_slice(&pr_coms_len.to_le_bytes());
|
||||
for c in &self.pc_coms {
|
||||
bytes.extend_from_slice(&c.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&zk_proof_bytes);
|
||||
|
||||
bytes
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for WithdrawalRequest {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<WithdrawalRequest> {
|
||||
if bytes.len() < 48 + 48 + 8 + 48{
|
||||
return Err(CompactEcashError::DeserializationMinLength {
|
||||
min: 48 + 48 + 8 + 48,
|
||||
actual: bytes.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut j = 0;
|
||||
let commitment_hash_bytes_len = 48;
|
||||
let commitment_bytes_len = 48;
|
||||
|
||||
let com_hash_bytes = bytes[..j + commitment_hash_bytes_len].try_into().unwrap();
|
||||
let com_hash = try_deserialize_g1_projective(
|
||||
&com_hash_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize compressed commitment hash".to_string(),
|
||||
),
|
||||
)?;
|
||||
j += commitment_hash_bytes_len;
|
||||
|
||||
let com_bytes = bytes[j..j + commitment_bytes_len].try_into().unwrap();
|
||||
let com = try_deserialize_g1_projective(
|
||||
&com_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize compressed commitment".to_string(),
|
||||
),
|
||||
)?;
|
||||
j += commitment_bytes_len;
|
||||
|
||||
let pc_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
|
||||
j += 8;
|
||||
if bytes[j..].len() < pc_len as usize * 48 {
|
||||
return Err(CompactEcashError::DeserializationMinLength {
|
||||
min: pc_len as usize * 48,
|
||||
actual: bytes[56..].len(),
|
||||
});
|
||||
}
|
||||
let mut pc_coms = Vec::with_capacity(pc_len as usize);
|
||||
for i in 0..pc_len as usize {
|
||||
let start = j + i * 48;
|
||||
let end = start + 48;
|
||||
|
||||
let pc_com_bytes = bytes[start..end].try_into().unwrap();
|
||||
let pc_com = try_deserialize_g1_projective(
|
||||
&pc_com_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize compressed Pedersen commitment".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
pc_coms.push(pc_com)
|
||||
}
|
||||
|
||||
let zk_proof = WithdrawalReqProof::try_from(&bytes[j + pc_len as usize * 48..])?;
|
||||
|
||||
Ok(WithdrawalRequest{
|
||||
com_hash,
|
||||
com,
|
||||
pc_coms,
|
||||
zk_proof,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RequestInfo {
|
||||
com_hash: G1Projective,
|
||||
com_opening: Scalar,
|
||||
pc_coms_openings: Vec<Scalar>,
|
||||
v: Scalar,
|
||||
}
|
||||
|
||||
impl RequestInfo {
|
||||
pub fn get_com(&self) -> G1Projective {
|
||||
self.com_hash
|
||||
}
|
||||
pub fn get_com_openings(&self) -> Scalar {
|
||||
self.com_opening
|
||||
}
|
||||
pub fn get_pc_coms_openings(&self) -> &Vec<Scalar> {
|
||||
&self.pc_coms_openings
|
||||
}
|
||||
pub fn get_v(&self) -> Scalar {
|
||||
self.v
|
||||
}
|
||||
}
|
||||
|
||||
pub fn withdrawal_request(
|
||||
params: &GroupParameters,
|
||||
sk_user: &SecretKeyUser,
|
||||
) -> Result<(WithdrawalRequest, RequestInfo)> {
|
||||
let v = params.random_scalar();
|
||||
|
||||
let attributes = vec![sk_user.sk, v];
|
||||
let gammas = params.gammas();
|
||||
let com_opening = params.random_scalar();
|
||||
let com = params.gen1() * com_opening
|
||||
+ attributes
|
||||
.iter()
|
||||
.zip(gammas)
|
||||
.map(|(&m, gamma)| gamma * m)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
// Value h in the paper
|
||||
let com_hash = hash_g1(com.to_bytes());
|
||||
|
||||
// For each private attribute we compute a pedersen commitment
|
||||
let pc_coms_openings = params.n_random_scalars(attributes.len());
|
||||
|
||||
// Compute Pedersen commitment for each attribute
|
||||
let pc_coms = pc_coms_openings
|
||||
.iter()
|
||||
.zip(attributes.iter())
|
||||
.map(|(o_j, m_j)| params.gen1() * o_j + com_hash * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// construct a zk proof of knowledge proving possession of m1, m2, m3, o, o1, o2, o3
|
||||
let instance = WithdrawalReqInstance {
|
||||
com,
|
||||
h: com_hash,
|
||||
pc_coms: pc_coms.clone(),
|
||||
pk_user: PublicKeyUser {
|
||||
pk: params.gen1() * sk_user.sk,
|
||||
},
|
||||
};
|
||||
|
||||
let witness = WithdrawalReqWitness {
|
||||
attributes,
|
||||
com_opening,
|
||||
pc_coms_openings: pc_coms_openings.clone(),
|
||||
};
|
||||
|
||||
let zk_proof = WithdrawalReqProof::construct(¶ms, &instance, &witness);
|
||||
|
||||
let req = WithdrawalRequest {
|
||||
com_hash,
|
||||
com,
|
||||
pc_coms: pc_coms.clone(),
|
||||
zk_proof,
|
||||
};
|
||||
|
||||
let req_info = RequestInfo {
|
||||
com_hash,
|
||||
com_opening,
|
||||
pc_coms_openings: pc_coms_openings.clone(),
|
||||
v,
|
||||
};
|
||||
|
||||
Ok((req, req_info))
|
||||
}
|
||||
|
||||
pub fn issue_wallet(
|
||||
params: &GroupParameters,
|
||||
sk_auth: SecretKeyAuth,
|
||||
pk_user: PublicKeyUser,
|
||||
withdrawal_req: &WithdrawalRequest,
|
||||
) -> Result<BlindedSignature> {
|
||||
let h = hash_g1(withdrawal_req.com.to_bytes());
|
||||
if !(h == withdrawal_req.com_hash) {
|
||||
return Err(CompactEcashError::WithdrawalRequestVerification(
|
||||
"Failed to verify the commitment hash".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// verify zk proof
|
||||
let instance = WithdrawalReqInstance {
|
||||
com: withdrawal_req.com,
|
||||
h: withdrawal_req.com_hash,
|
||||
pc_coms: withdrawal_req.pc_coms.clone(),
|
||||
pk_user,
|
||||
};
|
||||
if !withdrawal_req.zk_proof.verify(¶ms, &instance) {
|
||||
return Err(CompactEcashError::WithdrawalRequestVerification(
|
||||
"Failed to verify the proof of knowledge".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let sig = withdrawal_req
|
||||
.pc_coms
|
||||
.iter()
|
||||
.zip(sk_auth.ys.iter())
|
||||
.map(|(pc, yi)| pc * yi)
|
||||
.chain(std::iter::once(h * sk_auth.x))
|
||||
.sum();
|
||||
|
||||
Ok(BlindedSignature(h, sig))
|
||||
}
|
||||
|
||||
pub fn issue_verify(
|
||||
params: &GroupParameters,
|
||||
vk_auth: &VerificationKeyAuth,
|
||||
sk_user: &SecretKeyUser,
|
||||
blind_signature: &BlindedSignature,
|
||||
req_info: &RequestInfo,
|
||||
) -> Result<PartialWallet> {
|
||||
// Parse the blinded signature
|
||||
let h = blind_signature.0;
|
||||
let c = blind_signature.1;
|
||||
|
||||
// Verify the integrity of the response from the authority
|
||||
if !(req_info.com_hash == h) {
|
||||
return Err(CompactEcashError::IssuanceVfy(
|
||||
"Integrity verification failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Unblind the blinded signature on the partial wallet
|
||||
let blinding_removers = vk_auth
|
||||
.beta_g1
|
||||
.iter()
|
||||
.zip(req_info.pc_coms_openings.iter())
|
||||
.map(|(beta, opening)| beta * opening)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let unblinded_c = c - blinding_removers;
|
||||
|
||||
let attr = vec![sk_user.sk, req_info.v];
|
||||
|
||||
let signed_attributes = attr
|
||||
.iter()
|
||||
.zip(vk_auth.beta_g2.iter())
|
||||
.map(|(attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
// Verify the signature correctness on the wallet share
|
||||
if !check_bilinear_pairing(
|
||||
&h.to_affine(),
|
||||
&G2Prepared::from((vk_auth.alpha + signed_attributes).to_affine()),
|
||||
&unblinded_c.to_affine(),
|
||||
params.prepared_miller_g2(),
|
||||
) {
|
||||
return Err(CompactEcashError::IssuanceVfy(
|
||||
"Verification of wallet share failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(PartialWallet {
|
||||
sig: Signature(h, unblinded_c),
|
||||
v: req_info.v,
|
||||
idx: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
use itertools::izip;
|
||||
|
||||
use crate::error::CompactEcashError;
|
||||
use crate::scheme::aggregation::{
|
||||
aggregate_verification_keys, aggregate_wallets,
|
||||
};
|
||||
use crate::scheme::keygen::{
|
||||
generate_keypair_user, ttp_keygen, VerificationKeyAuth,
|
||||
};
|
||||
use crate::scheme::{Wallet, PartialWallet, Payment};
|
||||
use crate::scheme::PayInfo;
|
||||
use crate::scheme::setup::setup;
|
||||
use crate::scheme::withdrawal::{issue_verify, issue_wallet, withdrawal_request, WithdrawalRequest};
|
||||
|
||||
#[test]
|
||||
fn main() -> Result<(), CompactEcashError> {
|
||||
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 req_bytes = req.to_bytes();
|
||||
let req2 = WithdrawalRequest::try_from(req_bytes.as_slice()).unwrap();
|
||||
assert_eq!(req, req2);
|
||||
|
||||
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]))?;
|
||||
|
||||
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();
|
||||
|
||||
let partial_wallet = unblinded_wallet_shares.get(0).unwrap().clone();
|
||||
let partial_wallet_bytes = partial_wallet.to_bytes();
|
||||
let partial_wallet2 = PartialWallet::try_from(&partial_wallet_bytes[..]).unwrap();
|
||||
assert_eq!(partial_wallet, partial_wallet2);
|
||||
|
||||
// Aggregate partial wallets
|
||||
let aggr_wallet = aggregate_wallets(
|
||||
&grparams,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&unblinded_wallet_shares,
|
||||
&req_info,
|
||||
)?;
|
||||
|
||||
let wallet_bytes = aggr_wallet.to_bytes();
|
||||
let wallet = Wallet::try_from(&wallet_bytes[..]).unwrap();
|
||||
assert_eq!(aggr_wallet, wallet);
|
||||
|
||||
// Let's try to spend some coins
|
||||
let pay_info = PayInfo { info: [6u8; 32] };
|
||||
let spend_vv = 1;
|
||||
|
||||
let (payment, _) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info,
|
||||
false,
|
||||
spend_vv,
|
||||
)?;
|
||||
|
||||
assert!(payment
|
||||
.spend_verify(¶ms, &verification_key, &pay_info)
|
||||
.unwrap());
|
||||
|
||||
let payment_bytes = payment.to_bytes();
|
||||
let payment2 = Payment::try_from(&payment_bytes[..]).unwrap();
|
||||
assert_eq!(payment, payment2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
mod e2e;
|
||||
@@ -1,22 +0,0 @@
|
||||
use crate::CompactEcashError;
|
||||
|
||||
pub trait Bytable
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn to_byte_vec(&self) -> Vec<u8>;
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CompactEcashError>;
|
||||
}
|
||||
|
||||
pub trait Base58
|
||||
where
|
||||
Self: Bytable,
|
||||
{
|
||||
fn try_from_bs58<S: AsRef<str>>(x: S) -> Result<Self, CompactEcashError> {
|
||||
Self::try_from_byte_slice(&bs58::decode(x.as_ref()).into_vec().unwrap())
|
||||
}
|
||||
fn to_bs58(&self) -> String {
|
||||
bs58::encode(self.to_byte_vec()).into_string()
|
||||
}
|
||||
}
|
||||
@@ -1,432 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use core::iter::Sum;
|
||||
use core::ops::Mul;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::ops::Neg;
|
||||
|
||||
use bls12_381::{
|
||||
G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, multi_miller_loop, Scalar,
|
||||
};
|
||||
use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField};
|
||||
use ff::Field;
|
||||
use group::{Curve, Group};
|
||||
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
use crate::traits::Bytable;
|
||||
|
||||
pub struct Polynomial {
|
||||
coefficients: Vec<Scalar>,
|
||||
}
|
||||
|
||||
impl Polynomial {
|
||||
// for polynomial of degree n, we generate n+1 values
|
||||
// (for example for degree 1, like y = x + 2, we need [2,1])
|
||||
pub fn new_random(params: &GroupParameters, degree: u64) -> Self {
|
||||
Polynomial {
|
||||
coefficients: params.n_random_scalars((degree + 1) as usize),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluates the polynomial at point x.
|
||||
pub fn evaluate(&self, x: &Scalar) -> Scalar {
|
||||
if self.coefficients.is_empty() {
|
||||
Scalar::zero()
|
||||
// if x is zero then we can ignore most of the expensive computation and
|
||||
// just return the last term of the polynomial
|
||||
} else if x.is_zero().unwrap_u8() == 1 {
|
||||
// we checked that coefficients are not empty so unwrap here is fine
|
||||
*self.coefficients.first().unwrap()
|
||||
} else {
|
||||
self.coefficients
|
||||
.iter()
|
||||
.enumerate()
|
||||
// coefficient[n] * x ^ n
|
||||
.map(|(i, coefficient)| coefficient * x.pow(&[i as u64, 0, 0, 0]))
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn generate_lagrangian_coefficients_at_origin(points: &[u64]) -> Vec<Scalar> {
|
||||
let x = Scalar::zero();
|
||||
|
||||
points
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, point_i)| {
|
||||
let mut numerator = Scalar::one();
|
||||
let mut denominator = Scalar::one();
|
||||
let xi = Scalar::from(*point_i);
|
||||
|
||||
for (j, point_j) in points.iter().enumerate() {
|
||||
if j != i {
|
||||
let xj = Scalar::from(*point_j);
|
||||
|
||||
// numerator = (x - xs[0]) * ... * (x - xs[j]), j != i
|
||||
numerator *= x - xj;
|
||||
|
||||
// denominator = (xs[i] - x[0]) * ... * (xs[i] - x[j]), j != i
|
||||
denominator *= xi - xj;
|
||||
}
|
||||
}
|
||||
// numerator / denominator
|
||||
numerator * denominator.invert().unwrap()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Performs a Lagrange interpolation at the origin for a polynomial defined by `points` and `values`.
|
||||
/// It can be used for Scalars, G1 and G2 points.
|
||||
pub(crate) fn perform_lagrangian_interpolation_at_origin<T>(
|
||||
points: &[SignerIndex],
|
||||
values: &[T],
|
||||
) -> Result<T>
|
||||
where
|
||||
T: Sum,
|
||||
for<'a> &'a T: Mul<Scalar, Output=T>,
|
||||
{
|
||||
if points.is_empty() || values.is_empty() {
|
||||
return Err(CompactEcashError::Interpolation(
|
||||
"Tried to perform lagrangian interpolation for an empty set of coordinates".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if points.len() != values.len() {
|
||||
return Err(CompactEcashError::Interpolation(
|
||||
"Tried to perform lagrangian interpolation for an incomplete set of coordinates"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let coefficients = generate_lagrangian_coefficients_at_origin(points);
|
||||
|
||||
Ok(coefficients
|
||||
.into_iter()
|
||||
.zip(values.iter())
|
||||
.map(|(coeff, val)| val * coeff)
|
||||
.sum())
|
||||
}
|
||||
|
||||
// A temporary way of hashing particular message into G1.
|
||||
// Implementation idea was taken from `threshold_crypto`:
|
||||
// https://github.com/poanetwork/threshold_crypto/blob/7709462f2df487ada3bb3243060504b5881f2628/src/lib.rs#L691
|
||||
// Eventually it should get replaced by, most likely, the osswu map
|
||||
// method once ideally it's implemented inside the pairing crate.
|
||||
|
||||
// note: I have absolutely no idea what are the correct domains for those. I just used whatever
|
||||
// was given in the test vectors of `Hashing to Elliptic Curves draft-irtf-cfrg-hash-to-curve-11`
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.9.1
|
||||
const G1_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_";
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-K.1
|
||||
const SCALAR_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-expander";
|
||||
|
||||
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
|
||||
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
|
||||
}
|
||||
|
||||
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
|
||||
let mut output = vec![Scalar::zero()];
|
||||
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
|
||||
msg.as_ref(),
|
||||
SCALAR_HASH_DOMAIN,
|
||||
&mut output,
|
||||
);
|
||||
output[0]
|
||||
}
|
||||
|
||||
pub fn try_deserialize_scalar_vec(
|
||||
expected_len: u64,
|
||||
bytes: &[u8],
|
||||
err: CompactEcashError,
|
||||
) -> Result<Vec<Scalar>> {
|
||||
if bytes.len() != expected_len as usize * 32 {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let mut out = Vec::with_capacity(expected_len as usize);
|
||||
for i in 0..expected_len as usize {
|
||||
let s_bytes = bytes[i * 32..(i + 1) * 32].try_into().unwrap();
|
||||
let s = match Into::<Option<Scalar>>::into(Scalar::from_bytes(&s_bytes)) {
|
||||
None => return Err(err),
|
||||
Some(scalar) => scalar,
|
||||
};
|
||||
out.push(s)
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn try_deserialize_scalar(bytes: &[u8; 32], err: CompactEcashError) -> Result<Scalar> {
|
||||
Into::<Option<Scalar>>::into(Scalar::from_bytes(bytes)).ok_or(err)
|
||||
}
|
||||
|
||||
pub fn try_deserialize_g1_projective(
|
||||
bytes: &[u8; 48],
|
||||
err: CompactEcashError,
|
||||
) -> Result<G1Projective> {
|
||||
Into::<Option<G1Affine>>::into(G1Affine::from_compressed(bytes))
|
||||
.ok_or(err)
|
||||
.map(G1Projective::from)
|
||||
}
|
||||
|
||||
pub fn try_deserialize_g2_projective(
|
||||
bytes: &[u8; 96],
|
||||
err: CompactEcashError,
|
||||
) -> Result<G2Projective> {
|
||||
Into::<Option<G2Affine>>::into(G2Affine::from_compressed(bytes))
|
||||
.ok_or(err)
|
||||
.map(G2Projective::from)
|
||||
}
|
||||
|
||||
/// Checks whether e(P, Q) * e(-R, S) == id
|
||||
pub fn check_bilinear_pairing(p: &G1Affine, q: &G2Prepared, r: &G1Affine, s: &G2Prepared) -> bool {
|
||||
// checking e(P, Q) * e(-R, S) == id
|
||||
// is equivalent to checking e(P, Q) == e(R, S)
|
||||
// but requires only a single final exponentiation rather than two of them
|
||||
// and therefore, as seen via benchmarks.rs, is almost 50% faster
|
||||
// (1.47ms vs 2.45ms, tested on R9 5900X)
|
||||
|
||||
let multi_miller = multi_miller_loop(&[(p, q), (&r.neg(), s)]);
|
||||
multi_miller.final_exponentiation().is_identity().into()
|
||||
}
|
||||
|
||||
pub type SignerIndex = u64;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
// #[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective);
|
||||
|
||||
pub type PartialSignature = Signature;
|
||||
|
||||
impl TryFrom<&[u8]> for Signature {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Signature> {
|
||||
if bytes.len() != 96 {
|
||||
return Err(CompactEcashError::Deserialization(format!(
|
||||
"Signature must be exactly 96 bytes, got {}",
|
||||
bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let sig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
|
||||
let sig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
|
||||
|
||||
let sig1 = try_deserialize_g1_projective(
|
||||
sig1_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize compressed sig1".to_string()),
|
||||
)?;
|
||||
|
||||
let sig2 = try_deserialize_g1_projective(
|
||||
sig2_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize compressed sig2".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(Signature(sig1, sig2))
|
||||
}
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
pub(crate) fn sig1(&self) -> &G1Projective {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub(crate) fn sig2(&self) -> &G1Projective {
|
||||
&self.1
|
||||
}
|
||||
|
||||
pub fn randomise(&self, params: &GroupParameters) -> (Signature, Scalar) {
|
||||
let r = params.random_scalar();
|
||||
let r_prime = params.random_scalar();
|
||||
let h_prime = self.0 * r_prime;
|
||||
let s_prime = (self.1 * r_prime) + (h_prime * r);
|
||||
(Signature(h_prime, s_prime), r)
|
||||
}
|
||||
|
||||
pub fn to_bytes(self) -> [u8; 96] {
|
||||
let mut bytes = [0u8; 96];
|
||||
bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed());
|
||||
bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed());
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Signature> {
|
||||
Signature::try_from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for Signature {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.to_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
Signature::from_bytes(slice)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct BlindedSignature(pub(crate) G1Projective, pub(crate) G1Projective);
|
||||
|
||||
pub struct SignatureShare {
|
||||
signature: Signature,
|
||||
index: SignerIndex,
|
||||
}
|
||||
|
||||
impl SignatureShare {
|
||||
pub fn new(signature: Signature, index: SignerIndex) -> Self {
|
||||
SignatureShare { signature, index }
|
||||
}
|
||||
|
||||
pub fn signature(&self) -> &Signature {
|
||||
&self.signature
|
||||
}
|
||||
|
||||
pub fn index(&self) -> SignerIndex {
|
||||
self.index
|
||||
}
|
||||
|
||||
// pub fn aggregate(shares: &[Self]) -> Result<Signature> {
|
||||
// aggregate_signature_shares(shares)
|
||||
// }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::RngCore;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn polynomial_evaluation() {
|
||||
// y = 42 (it should be 42 regardless of x)
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![Scalar::from(42)],
|
||||
};
|
||||
|
||||
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(1)));
|
||||
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(0)));
|
||||
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(10)));
|
||||
|
||||
// y = x + 10, at x = 2 (exp: 12)
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![Scalar::from(10), Scalar::from(1)],
|
||||
};
|
||||
|
||||
assert_eq!(Scalar::from(12), poly.evaluate(&Scalar::from(2)));
|
||||
|
||||
// y = x^4 - 5x^2 + 2x - 3, at x = 3 (exp: 39)
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![
|
||||
(-Scalar::from(3)),
|
||||
Scalar::from(2),
|
||||
(-Scalar::from(5)),
|
||||
Scalar::zero(),
|
||||
Scalar::from(1),
|
||||
],
|
||||
};
|
||||
|
||||
assert_eq!(Scalar::from(39), poly.evaluate(&Scalar::from(3)));
|
||||
|
||||
// empty polynomial
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![],
|
||||
};
|
||||
|
||||
// should always be 0
|
||||
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(1)));
|
||||
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(0)));
|
||||
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(10)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn performing_lagrangian_scalar_interpolation_at_origin() {
|
||||
// x^2 + 3
|
||||
// x, f(x):
|
||||
// 1, 4,
|
||||
// 2, 7,
|
||||
// 3, 12,
|
||||
let points = vec![1, 2, 3];
|
||||
let values = vec![Scalar::from(4), Scalar::from(7), Scalar::from(12)];
|
||||
|
||||
assert_eq!(
|
||||
Scalar::from(3),
|
||||
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
|
||||
);
|
||||
|
||||
// x^3 + 3x^2 - 5x + 11
|
||||
// x, f(x):
|
||||
// 1, 10
|
||||
// 2, 21
|
||||
// 3, 50
|
||||
// 4, 103
|
||||
let points = vec![1, 2, 3, 4];
|
||||
let values = vec![
|
||||
Scalar::from(10),
|
||||
Scalar::from(21),
|
||||
Scalar::from(50),
|
||||
Scalar::from(103),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
Scalar::from(11),
|
||||
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
|
||||
);
|
||||
|
||||
// more points than it is required
|
||||
// x^2 + x + 10
|
||||
// x, f(x)
|
||||
// 1, 12
|
||||
// 2, 16
|
||||
// 3, 22
|
||||
// 4, 30
|
||||
// 5, 40
|
||||
let points = vec![1, 2, 3, 4, 5];
|
||||
let values = vec![
|
||||
Scalar::from(12),
|
||||
Scalar::from(16),
|
||||
Scalar::from(22),
|
||||
Scalar::from(30),
|
||||
Scalar::from(40),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
Scalar::from(10),
|
||||
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_g1_sanity_check() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut msg1 = [0u8; 1024];
|
||||
rng.fill_bytes(&mut msg1);
|
||||
let mut msg2 = [0u8; 1024];
|
||||
rng.fill_bytes(&mut msg2);
|
||||
|
||||
assert_eq!(hash_g1(msg1), hash_g1(msg1));
|
||||
assert_eq!(hash_g1(msg2), hash_g1(msg2));
|
||||
assert_ne!(hash_g1(msg1), hash_g1(msg2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_scalar_sanity_check() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut msg1 = [0u8; 1024];
|
||||
rng.fill_bytes(&mut msg1);
|
||||
let mut msg2 = [0u8; 1024];
|
||||
rng.fill_bytes(&mut msg2);
|
||||
|
||||
assert_eq!(hash_to_scalar(msg1), hash_to_scalar(msg1));
|
||||
assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2));
|
||||
assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2));
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
[package]
|
||||
name = "nym_offline_divisible_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 = { git = "https://github.com/jstuczyn/bls12_381", branch = "gt-serialisation", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
|
||||
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
|
||||
@@ -1,329 +0,0 @@
|
||||
use std::collections::HashSet;
|
||||
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 rand::thread_rng;
|
||||
|
||||
use nym_offline_divisible_ecash::{aggregate_verification_keys, aggregate_wallets,
|
||||
issue, issue_verify, PartialWallet,
|
||||
PayInfo, PublicKeyUser, SecretKeyUser, ttp_keygen_authorities, ttp_keygen_users, VerificationKeyAuth, withdrawal_request};
|
||||
use nym_offline_divisible_ecash::identification::{identify, IdentifyResult};
|
||||
use nym_offline_divisible_ecash::setup::{GroupParameters, Parameters};
|
||||
|
||||
#[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_divisible_ecash(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("benchmark-divisible-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: 10,
|
||||
case_nr_pub_keys: 99,
|
||||
};
|
||||
|
||||
// SETUP PHASE
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
|
||||
// KEY GENERATION FOR THE AUTHORITIES
|
||||
let threshold = (case.threshold_p * case.num_authorities as f32).round() as u64;
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 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();
|
||||
|
||||
// KEY GENERATION FOR THE USER
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = SecretKeyUser::public_key(&sk_user, &grp);
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut pk_all_users = HashSet::new();
|
||||
for i in 0..50 {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
pk_all_users.insert(pk_user);
|
||||
}
|
||||
pk_all_users.insert(pk_user.clone());
|
||||
|
||||
// WITHDRAWAL REQUEST
|
||||
let (withdrawal_req, req_info) = withdrawal_request(¶ms, &sk_user).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(¶ms, &sk_user).unwrap()),
|
||||
);
|
||||
|
||||
// ISSUE PARTIAL WALLETS
|
||||
// first one meaningful one just for benchmark
|
||||
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(
|
||||
¶ms,
|
||||
&withdrawal_req,
|
||||
pk_user.clone(),
|
||||
&keypair.secret_key(),
|
||||
).unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
let mut wallet_blinded_signatures = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req,
|
||||
pk_user.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
).unwrap();
|
||||
wallet_blinded_signatures.push(blind_signature);
|
||||
}
|
||||
|
||||
// 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, &sk_user, w, &req_info).unwrap()),
|
||||
);
|
||||
|
||||
let partial_wallets: Vec<PartialWallet> = izip!(
|
||||
wallet_blinded_signatures.iter(),
|
||||
verification_keys_auth.iter()
|
||||
)
|
||||
.map(|(w, vk)| issue_verify(&grp, &vk, &sk_user, &w, &req_info).unwrap())
|
||||
.collect();
|
||||
|
||||
|
||||
// AGGREGATE WALLET
|
||||
let mut wallet = aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets).unwrap();
|
||||
|
||||
// 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, &sk_user, &partial_wallets)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
let pay_info = PayInfo { info: [67u8; 32] };
|
||||
let (payment, wallet) = wallet.spend(¶ms, &verification_key, &sk_user, &pay_info, case.spend_vv, false).unwrap();
|
||||
|
||||
// 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(|| {
|
||||
wallet.spend(¶ms, &verification_key, &sk_user, &pay_info, case.spend_vv, true)
|
||||
.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 = wallet.l();
|
||||
wallet.l.set(current_l - 1);
|
||||
|
||||
let pay_info2 = PayInfo { info: [52u8; 32] };
|
||||
let (payment2, wallet) = wallet.spend(¶ms, &verification_key, &sk_user, &pay_info2, 10, false).unwrap();
|
||||
|
||||
// MERCHANT BENCHMARK: identify double spending
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Merchant] identify_L_{}_threshold_{}_spend_vv_{}_pks_{}",
|
||||
case.L, case.threshold_p, case.spend_vv, pk_all_users.len()
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
identify(¶ms, &verification_key, &pk_all_users, payment.clone(), payment2.clone(), pay_info, pay_info2).unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
let identify_result = identify(¶ms, &verification_key, &pk_all_users, payment.clone(), payment2.clone(), pay_info, pay_info2).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(pk_user));
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_divisible_ecash);
|
||||
criterion_main!(benches);
|
||||
@@ -1,2 +0,0 @@
|
||||
/// Max value of wallet
|
||||
pub(crate) const L: u64 = 100;
|
||||
@@ -1,39 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, DivisibleEcashError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DivisibleEcashError {
|
||||
#[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("Identify Verification related error: {0}")]
|
||||
Identify(String),
|
||||
|
||||
#[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,
|
||||
},
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
use bls12_381::{G1Projective, G2Prepared, G2Projective, pairing, Scalar};
|
||||
|
||||
pub use scheme::aggregation::aggregate_verification_keys;
|
||||
pub use scheme::aggregation::aggregate_wallets;
|
||||
pub use scheme::identification;
|
||||
pub use scheme::keygen::{PublicKeyUser, SecretKeyUser, VerificationKeyAuth};
|
||||
pub use scheme::keygen::ttp_keygen_authorities;
|
||||
pub use scheme::keygen::ttp_keygen_users;
|
||||
pub use scheme::PartialWallet;
|
||||
pub use scheme::PayInfo;
|
||||
pub use scheme::setup;
|
||||
pub use scheme::withdrawal::issue;
|
||||
pub use scheme::withdrawal::issue_verify;
|
||||
pub use scheme::withdrawal::withdrawal_request;
|
||||
pub use traits::Base58;
|
||||
|
||||
use crate::error::DivisibleEcashError;
|
||||
use crate::traits::Bytable;
|
||||
|
||||
mod error;
|
||||
mod proofs;
|
||||
mod scheme;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod traits;
|
||||
mod utils;
|
||||
mod constants;
|
||||
|
||||
pub type Attribute = Scalar;
|
||||
@@ -1,64 +0,0 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use bls12_381::{G1Affine, G1Projective, Scalar};
|
||||
use digest::Digest;
|
||||
use digest::generic_array::typenum::Unsigned;
|
||||
use group::GroupEncoding;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::error::{DivisibleEcashError, Result};
|
||||
use crate::scheme::keygen::PublicKeyUser;
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
use crate::utils::try_deserialize_g1_projective;
|
||||
|
||||
pub mod proof_withdrawal;
|
||||
pub mod proof_spend;
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -1,613 +0,0 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::ops::Neg;
|
||||
|
||||
use bls12_381::{G1Projective, G2Projective, Gt, Scalar};
|
||||
use group::GroupEncoding;
|
||||
|
||||
use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produce_responses};
|
||||
use crate::scheme::{Phi, VarPhi, Wallet};
|
||||
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::error::{DivisibleEcashError, Result};
|
||||
use crate::utils::{try_deserialize_scalar};
|
||||
|
||||
pub struct SpendInstance {
|
||||
pub kappa: G2Projective,
|
||||
pub phi: Phi,
|
||||
pub varphi: VarPhi,
|
||||
pub rr: Scalar,
|
||||
pub rr_prime: G1Projective,
|
||||
pub ss_prime: G1Projective,
|
||||
pub tt_prime: G2Projective,
|
||||
pub varsig_prime1: G1Projective,
|
||||
pub theta_prime1: G1Projective,
|
||||
pub pg_eq1: Gt,
|
||||
pub pg_eq2: Gt,
|
||||
pub pg_eq3: Gt,
|
||||
pub pg_eq4: Gt,
|
||||
pub psi_g1: G1Projective,
|
||||
pub psi_g2: G2Projective,
|
||||
pub pg_psi0_delta: Gt,
|
||||
pub pg_psi0_gen2: Gt,
|
||||
pub pg_psi0_yy: Gt,
|
||||
pub pg_psi0_ww1: Gt,
|
||||
pub pg_psi0_ww2: Gt,
|
||||
pub pg_rr_psi1: Gt,
|
||||
pub pg_psi0_tt: Gt,
|
||||
pub pg_psi0_psi1: Gt,
|
||||
}
|
||||
|
||||
impl SpendInstance {
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(96 + 96 + 3 * 96 + 5 * 48 + 12 * 288);
|
||||
bytes.extend_from_slice(self.kappa.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.phi.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.varphi.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.rr.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.rr_prime.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.ss_prime.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.tt_prime.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.varsig_prime1.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.theta_prime1.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.pg_eq1.to_compressed().as_ref());
|
||||
bytes.extend_from_slice(self.pg_eq2.to_compressed().as_ref());
|
||||
bytes.extend_from_slice(self.pg_eq3.to_compressed().as_ref());
|
||||
bytes.extend_from_slice(self.pg_eq4.to_compressed().as_ref());
|
||||
bytes.extend_from_slice(self.psi_g1.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.psi_g2.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.pg_psi0_delta.to_compressed().as_ref());
|
||||
bytes.extend_from_slice(self.pg_psi0_gen2.to_compressed().as_ref());
|
||||
bytes.extend_from_slice(self.pg_psi0_yy.to_compressed().as_ref());
|
||||
bytes.extend_from_slice(self.pg_psi0_ww1.to_compressed().as_ref());
|
||||
bytes.extend_from_slice(self.pg_psi0_ww2.to_compressed().as_ref());
|
||||
bytes.extend_from_slice(self.pg_rr_psi1.to_compressed().as_ref());
|
||||
bytes.extend_from_slice(self.pg_psi0_tt.to_compressed().as_ref());
|
||||
bytes.extend_from_slice(self.pg_psi0_psi1.to_compressed().as_ref());
|
||||
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpendWitness {
|
||||
pub sk_u: SecretKeyUser,
|
||||
pub v: Scalar,
|
||||
pub r: Scalar,
|
||||
pub r1: Scalar,
|
||||
pub r2: Scalar,
|
||||
pub r_varsig1: Scalar,
|
||||
pub r_theta1: Scalar,
|
||||
pub r_varsig2: Scalar,
|
||||
pub r_theta2: Scalar,
|
||||
pub r_rr: Scalar,
|
||||
pub r_ss: Scalar,
|
||||
pub r_tt: Scalar,
|
||||
pub rho1: Scalar,
|
||||
pub rho2: Scalar,
|
||||
pub rho3: Scalar,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SpendProof {
|
||||
challenge: Scalar,
|
||||
response_r: Scalar,
|
||||
response_r_sk_u: Scalar,
|
||||
response_r_v: Scalar,
|
||||
response_r_r: Scalar,
|
||||
response_r_r1: Scalar,
|
||||
response_r_r2: Scalar,
|
||||
response_r_varsig1: Scalar,
|
||||
response_r_theta1: Scalar,
|
||||
response_r_varsig2: Scalar,
|
||||
response_r_theta2: Scalar,
|
||||
response_r_rr: Scalar,
|
||||
response_r_ss: Scalar,
|
||||
response_r_tt: Scalar,
|
||||
response_r_rho1: Scalar,
|
||||
response_r_rho2: Scalar,
|
||||
response_r_rho3: Scalar,
|
||||
}
|
||||
|
||||
impl SpendProof {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(32 * 17); // 17 fields, each 32 bytes
|
||||
|
||||
bytes.extend_from_slice(&self.challenge.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_sk_u.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_v.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_r.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_r1.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_r2.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_varsig1.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_theta1.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_varsig2.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_theta2.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_rr.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_ss.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_tt.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_rho1.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_rho2.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_r_rho3.to_bytes());
|
||||
|
||||
bytes
|
||||
}
|
||||
pub fn construct(
|
||||
params: &Parameters,
|
||||
instance: &SpendInstance,
|
||||
witness: &SpendWitness,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
vv: u64) -> Self {
|
||||
let grp = params.get_grp();
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
// generate random values to replace each witness
|
||||
let r_attributes = grp.n_random_scalars(2);
|
||||
let r_sk_u = r_attributes[0];
|
||||
let r_v = r_attributes[1];
|
||||
let r_r = grp.random_scalar();
|
||||
let r_r1 = grp.random_scalar();
|
||||
let r_r2 = grp.random_scalar();
|
||||
let r_r_varsig1 = grp.random_scalar();
|
||||
let r_r_theta1 = grp.random_scalar();
|
||||
let r_r_varsig2 = grp.random_scalar();
|
||||
let r_r_theta2 = grp.random_scalar();
|
||||
let r_r_rr = grp.random_scalar();
|
||||
let r_r_ss = grp.random_scalar();
|
||||
let r_r_tt = grp.random_scalar();
|
||||
let r_rho1 = grp.random_scalar();
|
||||
let r_rho2 = grp.random_scalar();
|
||||
let r_rho3 = grp.random_scalar();
|
||||
|
||||
let g1 = grp.gen1();
|
||||
|
||||
// compute zkp commitment for each instance
|
||||
let zkcm_kappa = grp.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_phi0 = g1 * r_r1;
|
||||
let zkcm_phi1 = instance.varsig_prime1 * r_v + instance.psi_g1 * r_rho1 + params_u.get_ith_eta(vv as usize) * r_r1;
|
||||
let zkcm_varphi0 = g1 * r_r2;
|
||||
let zkcm_varphi1 = (g1 * instance.rr) * r_sk_u + instance.theta_prime1 * r_v + instance.psi_g1 * r_rho2 + params_u.get_ith_eta(vv as usize) * r_r2;
|
||||
let zkcm_pg_eq1 = instance.pg_psi0_delta * r_r_varsig1 + instance.pg_psi0_gen2 * r_r_varsig2.neg();
|
||||
let zkcm_pg_eq2 = instance.pg_psi0_delta * r_r_theta1 + instance.pg_psi0_gen2 * r_r_theta2.neg();
|
||||
let zkcm_pg_eq3 = instance.pg_psi0_yy * r_r_rr + instance.pg_psi0_gen2 * r_r_ss + instance.pg_psi0_ww1 * r_r_varsig2 + instance.pg_psi0_ww2 * r_r_theta2;
|
||||
let zkcm_pg_eq4 = instance.pg_rr_psi1 * r_r_tt + instance.pg_psi0_tt * r_r_rr + instance.pg_psi0_psi1 * r_rho3.neg();
|
||||
|
||||
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(g1.to_bytes().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_phi0.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_phi1.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_varphi0.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_varphi1.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_pg_eq1.to_compressed().as_ref()))
|
||||
.chain(std::iter::once(zkcm_pg_eq2.to_compressed().as_ref()))
|
||||
.chain(std::iter::once(zkcm_pg_eq3.to_compressed().as_ref()))
|
||||
.chain(std::iter::once(zkcm_pg_eq4.to_compressed().as_ref()))
|
||||
);
|
||||
|
||||
// compute response for each witness
|
||||
let response_r = produce_response(&r_r, &challenge, &witness.r);
|
||||
let response_r_sk_u = produce_response(&r_sk_u, &challenge, &witness.sk_u.sk);
|
||||
let response_r_v = produce_response(&r_v, &challenge, &witness.v);
|
||||
let response_r_r = produce_response(&r_r, &challenge, &witness.r);
|
||||
let response_r_r1 = produce_response(&r_r1, &challenge, &witness.r1);
|
||||
let response_r_r2 = produce_response(&r_r2, &challenge, &witness.r2);
|
||||
let response_r_varsig1 = produce_response(&r_r_varsig1, &challenge, &witness.r_varsig1);
|
||||
let response_r_theta1 = produce_response(&r_r_theta1, &challenge, &witness.r_theta1);
|
||||
let response_r_varsig2 = produce_response(&r_r_varsig2, &challenge, &witness.r_varsig2);
|
||||
let response_r_theta2 = produce_response(&r_r_theta2, &challenge, &witness.r_theta2);
|
||||
let response_r_rr = produce_response(&r_r_rr, &challenge, &witness.r_rr);
|
||||
let response_r_ss = produce_response(&r_r_ss, &challenge, &witness.r_ss);
|
||||
let response_r_tt = produce_response(&r_r_tt, &challenge, &witness.r_tt);
|
||||
let response_r_rho1 = produce_response(&r_rho1, &challenge, &witness.rho1);
|
||||
let response_r_rho2 = produce_response(&r_rho2, &challenge, &witness.rho2);
|
||||
let response_r_rho3 = produce_response(&r_rho3, &challenge, &witness.rho3);
|
||||
|
||||
|
||||
SpendProof {
|
||||
challenge,
|
||||
response_r,
|
||||
response_r_sk_u,
|
||||
response_r_v,
|
||||
response_r_r,
|
||||
response_r_r1,
|
||||
response_r_r2,
|
||||
response_r_varsig1,
|
||||
response_r_theta1,
|
||||
response_r_varsig2,
|
||||
response_r_theta2,
|
||||
response_r_rr,
|
||||
response_r_ss,
|
||||
response_r_tt,
|
||||
response_r_rho1,
|
||||
response_r_rho2,
|
||||
response_r_rho3,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
instance: &SpendInstance,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
vv: u64,
|
||||
) -> bool {
|
||||
let grp = params.get_grp();
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
let g1 = grp.gen1();
|
||||
|
||||
// re-compute each zkp commitment
|
||||
let zkcm_kappa = instance.kappa * self.challenge
|
||||
+ grp.gen2() * self.response_r
|
||||
+ verification_key.alpha * (Scalar::one() - self.challenge)
|
||||
+ [self.response_r_sk_u, self.response_r_v]
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
let zkcm_phi0 = g1 * self.response_r_r1 + instance.phi.0 * self.challenge;
|
||||
let zkcm_phi1 = instance.varsig_prime1 * self.response_r_v
|
||||
+ instance.psi_g1 * self.response_r_rho1
|
||||
+ params_u.get_ith_eta(vv as usize) * self.response_r_r1
|
||||
+ instance.phi.1 * self.challenge;
|
||||
let zkcm_varphi0 = g1 * self.response_r_r2 + instance.varphi.0 * self.challenge;
|
||||
let zkcm_varphi1 = (g1 * instance.rr) * self.response_r_sk_u
|
||||
+ instance.theta_prime1 * self.response_r_v
|
||||
+ instance.psi_g1 * self.response_r_rho2
|
||||
+ params_u.get_ith_eta(vv as usize) * self.response_r_r2
|
||||
+ instance.varphi.1 * self.challenge;
|
||||
let zkcm_pg_eq1 = instance.pg_psi0_delta * self.response_r_varsig1
|
||||
+ instance.pg_psi0_gen2 * self.response_r_varsig2.neg()
|
||||
+ instance.pg_eq1 * self.challenge;
|
||||
let zkcm_pg_eq2 = instance.pg_psi0_delta * self.response_r_theta1
|
||||
+ instance.pg_psi0_gen2 * self.response_r_theta2.neg()
|
||||
+ instance.pg_eq2 * self.challenge;
|
||||
|
||||
let zkcm_pg_eq3 = instance.pg_psi0_yy * self.response_r_rr
|
||||
+ instance.pg_psi0_gen2 * self.response_r_ss
|
||||
+ instance.pg_psi0_ww1 * self.response_r_varsig2
|
||||
+ instance.pg_psi0_ww2 * self.response_r_theta2
|
||||
+ instance.pg_eq3 * self.challenge;
|
||||
|
||||
let zkcm_pg_eq4 = instance.pg_rr_psi1 * self.response_r_tt
|
||||
+ instance.pg_psi0_tt * self.response_r_rr
|
||||
+ instance.pg_psi0_psi1 * self.response_r_rho3.neg()
|
||||
+ instance.pg_eq4 * self.challenge;
|
||||
|
||||
// re-compute the challenge
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(g1.to_bytes().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_phi0.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_phi1.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_varphi0.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_varphi1.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_pg_eq1.to_compressed().as_ref()))
|
||||
.chain(std::iter::once(zkcm_pg_eq2.to_compressed().as_ref()))
|
||||
.chain(std::iter::once(zkcm_pg_eq3.to_compressed().as_ref()))
|
||||
.chain(std::iter::once(zkcm_pg_eq4.to_compressed().as_ref()))
|
||||
);
|
||||
|
||||
challenge == self.challenge
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for SpendProof {
|
||||
type Error = DivisibleEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self> {
|
||||
const FIELD_SIZE: usize = 32; // Each Scalar field is 32 bytes
|
||||
|
||||
if bytes.len() != FIELD_SIZE * 17 {
|
||||
return Err(DivisibleEcashError::Deserialization(
|
||||
"Invalid byte array for SpendProof deserialization".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let challenge_bytes = bytes[0..FIELD_SIZE].try_into().unwrap();
|
||||
let challenge = try_deserialize_scalar(
|
||||
&challenge_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize challenge".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_bytes = bytes[FIELD_SIZE..FIELD_SIZE * 2].try_into().unwrap();
|
||||
let response_r = try_deserialize_scalar(
|
||||
&response_r_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_sk_u_bytes = bytes[FIELD_SIZE * 2..FIELD_SIZE * 3].try_into().unwrap();
|
||||
let response_r_sk_u = try_deserialize_scalar(
|
||||
&response_r_sk_u_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_sk_u".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_v_bytes = bytes[FIELD_SIZE * 3..FIELD_SIZE * 4].try_into().unwrap();
|
||||
let response_r_v = try_deserialize_scalar(
|
||||
&response_r_v_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_v".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_r_bytes = bytes[FIELD_SIZE * 4..FIELD_SIZE * 5].try_into().unwrap();
|
||||
let response_r_r = try_deserialize_scalar(
|
||||
&response_r_r_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_r".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_r1_bytes = bytes[FIELD_SIZE * 5..FIELD_SIZE * 6].try_into().unwrap();
|
||||
let response_r_r1 = try_deserialize_scalar(
|
||||
&response_r_r1_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_r1".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_r2_bytes = bytes[FIELD_SIZE * 6..FIELD_SIZE * 7].try_into().unwrap();
|
||||
let response_r_r2 = try_deserialize_scalar(
|
||||
&response_r_r2_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_r2".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_varsig1_bytes = bytes[FIELD_SIZE * 7..FIELD_SIZE * 8].try_into().unwrap();
|
||||
let response_r_varsig1 = try_deserialize_scalar(
|
||||
response_r_varsig1_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_varsig1".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_theta1_bytes = bytes[FIELD_SIZE * 8..FIELD_SIZE * 9].try_into().unwrap();
|
||||
let response_r_theta1 = try_deserialize_scalar(
|
||||
&response_r_theta1_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_theta1".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_varsig2_bytes = bytes[FIELD_SIZE * 9..FIELD_SIZE * 10].try_into().unwrap();
|
||||
let response_r_varsig2 = try_deserialize_scalar(
|
||||
&response_r_varsig2_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_varsig2".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_theta2_bytes = bytes[FIELD_SIZE * 10..FIELD_SIZE * 11].try_into().unwrap();
|
||||
let response_r_theta2 = try_deserialize_scalar(
|
||||
&response_r_theta2_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_theta2".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_rr_bytes = bytes[FIELD_SIZE * 11..FIELD_SIZE * 12].try_into().unwrap();
|
||||
let response_r_rr = try_deserialize_scalar(
|
||||
&response_r_rr_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_rr".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_ss_bytes = bytes[FIELD_SIZE * 12..FIELD_SIZE * 13].try_into().unwrap();
|
||||
let response_r_ss = try_deserialize_scalar(
|
||||
&response_r_ss_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_ss".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_tt_bytes = bytes[FIELD_SIZE * 13..FIELD_SIZE * 14].try_into().unwrap();
|
||||
let response_r_tt = try_deserialize_scalar(
|
||||
&response_r_tt_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_tt".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_rho1_bytes = bytes[FIELD_SIZE * 14..FIELD_SIZE * 15].try_into().unwrap();
|
||||
let response_r_rho1 = try_deserialize_scalar(
|
||||
&response_r_rho1_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_rho1".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_rho2_bytes = bytes[FIELD_SIZE * 15..FIELD_SIZE * 16].try_into().unwrap();
|
||||
let response_r_rho2 = try_deserialize_scalar(
|
||||
&response_r_rho2_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_rho2".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r_rho3_bytes = bytes[FIELD_SIZE * 16..FIELD_SIZE * 17].try_into().unwrap();
|
||||
let response_r_rho3 = try_deserialize_scalar(
|
||||
&response_r_rho3_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize response_r_rho3".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(SpendProof {
|
||||
challenge,
|
||||
response_r,
|
||||
response_r_sk_u,
|
||||
response_r_v,
|
||||
response_r_r,
|
||||
response_r_r1,
|
||||
response_r_r2,
|
||||
response_r_varsig1,
|
||||
response_r_theta1,
|
||||
response_r_varsig2,
|
||||
response_r_theta2,
|
||||
response_r_rr,
|
||||
response_r_ss,
|
||||
response_r_tt,
|
||||
response_r_rho1,
|
||||
response_r_rho2,
|
||||
response_r_rho3,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ops::Neg;
|
||||
|
||||
use bls12_381::{G2Projective, pairing};
|
||||
use group::Curve;
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
|
||||
use crate::scheme::{PayInfo, Phi, VarPhi};
|
||||
use crate::scheme::aggregation::aggregate_verification_keys;
|
||||
use crate::scheme::keygen::{PublicKeyUser, SecretKeyUser, ttp_keygen_authorities, VerificationKeyAuth};
|
||||
use crate::scheme::setup::{GroupParameters, Parameters};
|
||||
use crate::utils::hash_to_scalar;
|
||||
|
||||
#[test]
|
||||
fn spend_proof_construct_and_verify() {
|
||||
let rng = thread_rng();
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
let sk = grp.random_scalar();
|
||||
let pk_user = PublicKeyUser {
|
||||
pk: grp.gen1() * sk,
|
||||
};
|
||||
let v = grp.random_scalar();
|
||||
let attributes = vec![sk, v];
|
||||
let l: usize = 10;
|
||||
let vv: u64 = 20;
|
||||
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 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 r = grp.random_scalar();
|
||||
let kappa = grp.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 r1 = grp.random_scalar();
|
||||
let r2 = grp.random_scalar();
|
||||
let phi = Phi(grp.gen1() * r1, params_u.get_ith_sigma(l as usize) * v + params_u.get_ith_eta(vv as usize) * r1);
|
||||
|
||||
let pay_info = PayInfo { info: [78u8; 32] };
|
||||
let rr = hash_to_scalar(pay_info.info);
|
||||
let varphi = VarPhi(grp.gen1() * r2, (grp.gen1() * rr) * sk + params_u.get_ith_theta(l as usize) * v + params_u.get_ith_eta(vv as usize) * r2);
|
||||
|
||||
// random value used to compute blinded bases
|
||||
let r_varsig1 = grp.random_scalar();
|
||||
let r_theta1 = grp.random_scalar();
|
||||
let r_varsig2 = grp.random_scalar();
|
||||
let r_theta2 = grp.random_scalar();
|
||||
let r_rr = grp.random_scalar();
|
||||
let r_ss = grp.random_scalar();
|
||||
let r_tt = grp.random_scalar();
|
||||
|
||||
|
||||
// compute blinded bases
|
||||
let psi_g1 = params_u.get_psi_g1();
|
||||
let psi_g2 = params_u.get_psi_g2();
|
||||
let varsig_prime1 = params_u.get_ith_sigma(l as usize) + (psi_g1 * r_varsig1);
|
||||
let theta_prime1 = params_u.get_ith_theta(l as usize) + (psi_g1 * r_theta1);
|
||||
let varsig_prime2 = params_u.get_ith_sigma(l as usize + vv as usize - 1) + (psi_g1 * r_varsig2);
|
||||
let theta_prime2 = params_u.get_ith_theta(l as usize + vv as usize - 1) + (psi_g1 * r_theta2);
|
||||
let rr_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).rr + (psi_g1 * r_rr);
|
||||
let ss_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).ss + (psi_g1 * r_ss);
|
||||
let tt_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).tt + (psi_g2 * r_tt);
|
||||
|
||||
|
||||
let rho1 = v.neg() * r_varsig1;
|
||||
let rho2 = v.neg() * r_theta1;
|
||||
let rho3 = r_rr * r_tt;
|
||||
|
||||
|
||||
let pg_varsigpr1_delta = pairing(&varsig_prime1.to_affine(), ¶ms_a.get_ith_delta((vv - 1) as usize).to_affine());
|
||||
let pg_psi0_delta = pairing(&psi_g1.to_affine(), ¶ms_a.get_ith_delta((vv - 1) as usize).to_affine());
|
||||
let pg_varsigpr2_gen2 = pairing(&varsig_prime2.to_affine(), grp.gen2());
|
||||
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
|
||||
let pg_thetapr1_delta = pairing(&theta_prime1.to_affine(), ¶ms_a.get_ith_delta((vv - 1) as usize).to_affine());
|
||||
let pg_thetapr2_gen2 = pairing(&theta_prime2.to_affine(), grp.gen2());
|
||||
let yy = params_u.get_sps_pk().get_yy();
|
||||
let pg_rrprime_yy = pairing(&rr_prime.to_affine(), &yy.to_affine());
|
||||
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
|
||||
let pg_ssprime_gen2 = pairing(&ss_prime.to_affine(), grp.gen2());
|
||||
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
|
||||
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
|
||||
let pg_varsigpr2_ww1 = pairing(&varsig_prime2.to_affine(), &ww1.to_affine());
|
||||
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
|
||||
let pg_thetapr2_ww2 = pairing(&theta_prime2.to_affine(), &ww2.to_affine());
|
||||
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
|
||||
let pg_gen1_zz = pairing(grp.gen1(), ¶ms_u.get_sps_pk().get_zz().to_affine());
|
||||
let pg_rr_tt = pairing(&rr_prime.to_affine(), &tt_prime.to_affine());
|
||||
let pg_rr_psi1 = pairing(&rr_prime.to_affine(), &psi_g2.to_affine());
|
||||
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &tt_prime.to_affine());
|
||||
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
|
||||
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
|
||||
|
||||
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
|
||||
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
|
||||
let pg_eq3 = pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 - pg_gen1_zz;
|
||||
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
|
||||
|
||||
|
||||
let instance = SpendInstance {
|
||||
kappa,
|
||||
phi,
|
||||
varphi,
|
||||
rr,
|
||||
rr_prime,
|
||||
ss_prime: ss_prime,
|
||||
tt_prime: tt_prime,
|
||||
varsig_prime1,
|
||||
theta_prime1,
|
||||
pg_eq1,
|
||||
pg_eq2,
|
||||
pg_eq3,
|
||||
pg_eq4,
|
||||
psi_g1: *psi_g1,
|
||||
psi_g2: *psi_g2,
|
||||
pg_psi0_delta,
|
||||
pg_psi0_gen2,
|
||||
pg_psi0_yy,
|
||||
pg_psi0_ww1,
|
||||
pg_psi0_ww2,
|
||||
pg_rr_psi1,
|
||||
pg_psi0_tt,
|
||||
pg_psi0_psi1,
|
||||
};
|
||||
|
||||
let witness = SpendWitness {
|
||||
sk_u: SecretKeyUser { sk },
|
||||
v,
|
||||
r,
|
||||
r1,
|
||||
r2,
|
||||
r_varsig1,
|
||||
r_theta1,
|
||||
r_varsig2,
|
||||
r_theta2,
|
||||
r_rr,
|
||||
r_ss,
|
||||
r_tt,
|
||||
rho1,
|
||||
rho2,
|
||||
rho3,
|
||||
};
|
||||
|
||||
// compute the zk proof
|
||||
let zk_proof = SpendProof::construct(¶ms, &instance, &witness, &verification_key, vv);
|
||||
assert!(zk_proof.verify(¶ms, &instance, &verification_key, vv));
|
||||
|
||||
// do a to and from bytes check
|
||||
let zk_proof_bytes = zk_proof.to_bytes();
|
||||
let zk_proof2 = SpendProof::try_from(&zk_proof_bytes[..]).unwrap();
|
||||
assert_eq!(zk_proof, zk_proof2);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,335 +0,0 @@
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use bls12_381::{G1Projective, Scalar};
|
||||
use group::GroupEncoding;
|
||||
use itertools::izip;
|
||||
|
||||
use crate::error::{DivisibleEcashError, Result};
|
||||
use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produce_responses};
|
||||
use crate::scheme::keygen::PublicKeyUser;
|
||||
use crate::scheme::setup::{GroupParameters, Parameters};
|
||||
use crate::utils::try_deserialize_g1_projective;
|
||||
|
||||
#[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 = DivisibleEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<WithdrawalReqInstance> {
|
||||
if bytes.len() < 48 * 4 + 8 || (bytes.len() - 8) % 48 != 0 {
|
||||
return Err(DivisibleEcashError::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,
|
||||
DivisibleEcashError::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,
|
||||
DivisibleEcashError::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(DivisibleEcashError::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,
|
||||
DivisibleEcashError::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,
|
||||
DivisibleEcashError::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>,
|
||||
}
|
||||
|
||||
pub struct WithdrawalReqProof {
|
||||
challenge: Scalar,
|
||||
response_opening: Scalar,
|
||||
response_openings: Vec<Scalar>,
|
||||
response_attributes: Vec<Scalar>,
|
||||
}
|
||||
|
||||
impl WithdrawalReqProof {
|
||||
pub(crate) fn construct(
|
||||
params: &Parameters,
|
||||
instance: &WithdrawalReqInstance,
|
||||
witness: &WithdrawalReqWitness,
|
||||
) -> Self {
|
||||
let grp = params.get_grp();
|
||||
let g1 = grp.gen1();
|
||||
let params_u = params.get_params_u();
|
||||
|
||||
// generate random values to replace the witnesses
|
||||
let r_com_opening = grp.random_scalar();
|
||||
let r_pedcom_openings = grp.n_random_scalars(witness.pc_coms_openings.len());
|
||||
let r_attributes = grp.n_random_scalars(witness.attributes.len());
|
||||
|
||||
// compute zkp commitments for each instance
|
||||
let zkcm_com = g1 * r_com_opening
|
||||
+ r_attributes
|
||||
.iter()
|
||||
.zip(params_u.get_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)| g1 * o_j + instance.h * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_user_sk = g1 * r_attributes[0];
|
||||
|
||||
// covert to bytes
|
||||
let gammas_bytes = params_u
|
||||
.get_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(g1.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: &Parameters,
|
||||
instance: &WithdrawalReqInstance,
|
||||
) -> bool {
|
||||
let grp = params.get_grp();
|
||||
let g1 = grp.gen1();
|
||||
let params_u = params.get_params_u();
|
||||
|
||||
// recompute zk commitments for each instance
|
||||
let zkcm_com = instance.com * self.challenge
|
||||
+ g1 * self.response_opening
|
||||
+ self
|
||||
.response_attributes
|
||||
.iter()
|
||||
.zip(params_u.get_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 + g1 * resp_o_j + instance.h * resp_m_j
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zk_commitment_user_sk =
|
||||
instance.pk_user.pk * self.challenge + g1 * self.response_attributes[0];
|
||||
|
||||
// covert to bytes
|
||||
let gammas_bytes = params_u
|
||||
.get_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(g1.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
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use group::Group;
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::scheme::setup::Parameters;
|
||||
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 grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
|
||||
|
||||
let sk = grp.random_scalar();
|
||||
let pk_user = PublicKeyUser {
|
||||
pk: grp.gen1() * sk,
|
||||
};
|
||||
let v = grp.random_scalar();
|
||||
let t = grp.random_scalar();
|
||||
let attr = vec![sk, v, t];
|
||||
|
||||
let com_opening = grp.random_scalar();
|
||||
let com = grp.gen1() * com_opening
|
||||
+ attr
|
||||
.iter()
|
||||
.zip(params.get_params_u().get_gammas())
|
||||
.map(|(&m, gamma)| gamma * m)
|
||||
.sum::<G1Projective>();
|
||||
let h = hash_g1(com.to_bytes());
|
||||
|
||||
let pc_openings = grp.n_random_scalars(attr.len());
|
||||
let pc_coms = pc_openings
|
||||
.iter()
|
||||
.zip(attr.iter())
|
||||
.map(|(o_j, m_j)| grp.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))
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
use core::iter::Sum;
|
||||
use core::ops::Mul;
|
||||
use std::cell::Cell;
|
||||
|
||||
use bls12_381::{G2Prepared, G2Projective, pairing, Scalar};
|
||||
use group::Curve;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::Attribute;
|
||||
use crate::error::{DivisibleEcashError, Result};
|
||||
use crate::scheme::{PartialWallet, Wallet};
|
||||
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
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(DivisibleEcashError::Aggregation(
|
||||
"Empty set of values".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(indices) = indices {
|
||||
if !Self::check_unique_indices(indices) {
|
||||
return Err(DivisibleEcashError::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(|| DivisibleEcashError::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(DivisibleEcashError::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(DivisibleEcashError::Aggregation(
|
||||
"Verification of the aggregated signature failed".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(signature)
|
||||
}
|
||||
|
||||
pub fn aggregate_wallets(
|
||||
grp: &GroupParameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
sk_user: &SecretKeyUser,
|
||||
wallets: &[PartialWallet],
|
||||
) -> Result<Wallet> {
|
||||
let signature_shares: Vec<SignatureShare> = wallets
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, wallet)| SignatureShare::new(*wallet.signature(), (idx + 1) as u64))
|
||||
.collect();
|
||||
|
||||
let v = wallets.get(0).unwrap().v;
|
||||
let attributes = vec![sk_user.sk, v];
|
||||
let aggregated_signature =
|
||||
aggregate_signature_shares(&grp, &verification_key, &attributes, &signature_shares)?;
|
||||
|
||||
Ok(Wallet {
|
||||
sig: aggregated_signature,
|
||||
v,
|
||||
l: Cell::new(1),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,381 +0,0 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::ops::Neg;
|
||||
|
||||
use bls12_381::{Gt, pairing, Scalar};
|
||||
use group::Curve;
|
||||
|
||||
use crate::error::{DivisibleEcashError, Result};
|
||||
use crate::scheme::{PayInfo, Payment};
|
||||
use crate::scheme::identification::IdentifyResult::DoubleSpendingPublicKeys;
|
||||
use crate::scheme::keygen::{PublicKeyUser, VerificationKeyAuth};
|
||||
use crate::scheme::setup::Parameters;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum IdentifyResult {
|
||||
NotADuplicatePayment,
|
||||
DuplicatePayInfo(PayInfo),
|
||||
DoubleSpendingPublicKeys(PublicKeyUser),
|
||||
Whatever,
|
||||
}
|
||||
|
||||
// how do we get the list of all pkU ?
|
||||
pub fn identify(
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
public_keys_u: &HashSet<PublicKeyUser>,
|
||||
payment1: Payment,
|
||||
payment2: Payment,
|
||||
pay_info1: PayInfo,
|
||||
pay_info2: PayInfo) -> Result<IdentifyResult> {
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
// compute the serial numbers for k1 in [0, V1-1]
|
||||
let mut serial_numbers = HashMap::new();
|
||||
|
||||
for k in 0..payment1.vv {
|
||||
let sn = pairing(&payment1.phi.1.to_affine(), ¶ms_a.get_ith_delta(k as usize).to_affine())
|
||||
+ pairing(&payment1.phi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment1.vv as usize, k as usize).to_affine());
|
||||
serial_numbers.insert(sn, k);
|
||||
}
|
||||
|
||||
// compute the serial numbers fo k2 in [0, V2-1]
|
||||
let mut k1 = 0;
|
||||
let mut k2 = 0;
|
||||
let mut duplicate_serial_numbers: Vec<(Gt, u64, u64)> = Default::default();
|
||||
for j in 0..payment2.vv {
|
||||
let sn = pairing(&payment2.phi.1.to_affine(), ¶ms_a.get_ith_delta(j as usize).to_affine())
|
||||
+ pairing(&payment2.phi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment2.vv as usize, j as usize).to_affine());
|
||||
if !serial_numbers.contains_key(&sn) {
|
||||
serial_numbers.insert(sn, j);
|
||||
} else {
|
||||
k1 = *serial_numbers.get(&sn).unwrap() as u64;
|
||||
k2 = j.clone();
|
||||
break;
|
||||
}
|
||||
return Ok(IdentifyResult::NotADuplicatePayment);
|
||||
}
|
||||
|
||||
if pay_info1 == pay_info2 {
|
||||
Ok(IdentifyResult::DuplicatePayInfo(pay_info1))
|
||||
} else {
|
||||
let delta_k1 = params_a.get_ith_delta(k1 as usize);
|
||||
let delta_k2 = params_a.get_ith_delta(k2 as usize);
|
||||
let tt1 = pairing(&payment1.varphi.1.to_affine(), &delta_k1.to_affine())
|
||||
+ pairing(&payment1.varphi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment1.vv as usize, k1 as usize).to_affine());
|
||||
let tt2 = pairing(&payment2.varphi.1.to_affine(), &delta_k2.to_affine())
|
||||
+ pairing(&payment2.varphi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment2.vv as usize, k2 as usize).to_affine());
|
||||
|
||||
|
||||
for pk_u in public_keys_u.iter() {
|
||||
let pg_pku_deltas = pairing(&pk_u.pk.to_affine(), &(delta_k1 * payment1.rr + delta_k2 * payment2.rr.neg()).to_affine());
|
||||
if tt1 - tt2 == pg_pku_deltas {
|
||||
return Ok(IdentifyResult::DoubleSpendingPublicKeys(pk_u.clone()));
|
||||
}
|
||||
}
|
||||
return Err(DivisibleEcashError::Identify(
|
||||
"A duplicate serial number was detected, the payinfo1 and payinfo2 are different, but we failed to identify the double-spending public key".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use bls12_381::pairing;
|
||||
use group::Curve;
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::scheme::{PayInfo, Payment};
|
||||
use crate::scheme::aggregation::{aggregate_verification_keys, aggregate_wallets};
|
||||
use crate::scheme::identification::{identify, IdentifyResult};
|
||||
use crate::scheme::keygen::{PublicKeyUser, SecretKeyUser, ttp_keygen_authorities, VerificationKeyAuth};
|
||||
use crate::scheme::setup::{GroupParameters, Parameters};
|
||||
use crate::scheme::withdrawal::{issue, issue_verify, withdrawal_request};
|
||||
use crate::utils::hash_g1;
|
||||
|
||||
#[test]
|
||||
fn duplicate_payments_with_the_same_pay_info() {
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
// KEY GENERATION FOR THE AUTHORITIES
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 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();
|
||||
|
||||
// KEY GENERATION FOR THE USER1
|
||||
let sk1 = grp.random_scalar();
|
||||
let sk_user1 = SecretKeyUser { sk: sk1 };
|
||||
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
|
||||
|
||||
// KEY GENERATION FOR THE USER2
|
||||
let sk2 = grp.random_scalar();
|
||||
let sk_user2 = SecretKeyUser { sk: sk2 };
|
||||
let pk_user2 = SecretKeyUser::public_key(&sk_user2, &grp);
|
||||
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER1
|
||||
let (withdrawal_req1, req_info1) = withdrawal_request(¶ms, &sk_user1).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER1
|
||||
let mut partial_wallets1 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req1,
|
||||
pk_user1.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
).unwrap();
|
||||
let partial_wallet1 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user1, &blind_signature, &req_info1).unwrap();
|
||||
partial_wallets1.push(partial_wallet1);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER1
|
||||
let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
|
||||
|
||||
let pay_info1 = PayInfo { info: [67u8; 32] };
|
||||
let (payment1, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
|
||||
|
||||
// SPEND VERIFICATION for USER1
|
||||
assert!(payment1.spend_verify(¶ms, &verification_key, &pay_info1).unwrap());
|
||||
|
||||
let payment2 = payment1.clone();
|
||||
// SPEND VERIFICATION for the duplicate payment
|
||||
assert!(payment1.spend_verify(¶ms, &verification_key, &pay_info1).unwrap());
|
||||
|
||||
let pay_info2 = pay_info1.clone();
|
||||
|
||||
let public_keys = HashSet::from([pk_user1, pk_user2]);
|
||||
let identify_result = identify(¶ms, &verification_key, &public_keys, payment1, payment2, pay_info1, pay_info2).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::DuplicatePayInfo(pay_info1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_payments_with_one_repeating_serial_number_but_different_pay_info() {
|
||||
let rng = thread_rng();
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
// KEY GENERATION FOR THE AUTHORITIES
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 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();
|
||||
|
||||
// KEY GENERATION FOR THE USER1
|
||||
let sk1 = grp.random_scalar();
|
||||
let sk_user1 = SecretKeyUser { sk: sk1 };
|
||||
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
|
||||
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut pk_all_users = HashSet::new();
|
||||
for i in 0..50 {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
pk_all_users.insert(pk_user);
|
||||
}
|
||||
pk_all_users.insert(pk_user1.clone());
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER1
|
||||
let (withdrawal_req1, req_info1) = withdrawal_request(¶ms, &sk_user1).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER1
|
||||
let mut partial_wallets1 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req1,
|
||||
pk_user1.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
).unwrap();
|
||||
let partial_wallet1 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user1, &blind_signature, &req_info1).unwrap();
|
||||
partial_wallets1.push(partial_wallet1);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER1
|
||||
let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
|
||||
|
||||
let pay_info1 = PayInfo { info: [67u8; 32] };
|
||||
let (payment1, new_wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = wallet1.l.get();
|
||||
wallet1.l.set(current_l - 1);
|
||||
|
||||
let pay_info2 = PayInfo { info: [52u8; 32] };
|
||||
let (payment2, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info2, 10, false).unwrap();
|
||||
|
||||
|
||||
let identify_result = identify(¶ms, &verification_key, &pk_all_users, payment1, payment2, pay_info1, pay_info2).unwrap();
|
||||
|
||||
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(pk_user1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_payments_with_multiple_repeating_serial_number_but_different_pay_info() {
|
||||
let rng = thread_rng();
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
// KEY GENERATION FOR THE AUTHORITIES
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 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();
|
||||
|
||||
// KEY GENERATION FOR THE USER1
|
||||
let sk1 = grp.random_scalar();
|
||||
let sk_user1 = SecretKeyUser { sk: sk1 };
|
||||
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut public_keys = HashSet::new();
|
||||
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.insert(pk_user);
|
||||
}
|
||||
public_keys.insert(pk_user1.clone());
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER1
|
||||
let (withdrawal_req1, req_info1) = withdrawal_request(¶ms, &sk_user1).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER1
|
||||
let mut partial_wallets1 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req1,
|
||||
pk_user1.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
).unwrap();
|
||||
let partial_wallet1 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user1, &blind_signature, &req_info1).unwrap();
|
||||
partial_wallets1.push(partial_wallet1);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER1
|
||||
let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
|
||||
|
||||
let pay_info1 = PayInfo { info: [67u8; 32] };
|
||||
let (payment1, new_wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = wallet1.l.get();
|
||||
wallet1.l.set(current_l - 7);
|
||||
|
||||
let pay_info2 = PayInfo { info: [52u8; 32] };
|
||||
let (payment2, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info2, 10, false).unwrap();
|
||||
|
||||
|
||||
let identify_result = identify(¶ms, &verification_key, &public_keys, payment1, payment2, pay_info1, pay_info2).unwrap();
|
||||
|
||||
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(pk_user1));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn ok_if_two_different_payments() {
|
||||
let rng = thread_rng();
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
// KEY GENERATION FOR THE AUTHORITIES
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 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();
|
||||
|
||||
// KEY GENERATION FOR THE USER1
|
||||
let sk1 = grp.random_scalar();
|
||||
let sk_user1 = SecretKeyUser { sk: sk1 };
|
||||
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
|
||||
|
||||
// KEY GENERATION FOR THE USER2
|
||||
let sk2 = grp.random_scalar();
|
||||
let sk_user2 = SecretKeyUser { sk: sk2 };
|
||||
let pk_user2 = SecretKeyUser::public_key(&sk_user2, &grp);
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER1
|
||||
let (withdrawal_req1, req_info1) = withdrawal_request(¶ms, &sk_user1).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER1
|
||||
let mut partial_wallets1 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req1,
|
||||
pk_user1.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
).unwrap();
|
||||
let partial_wallet1 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user1, &blind_signature, &req_info1).unwrap();
|
||||
partial_wallets1.push(partial_wallet1);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER1
|
||||
let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
|
||||
|
||||
let pay_info1 = PayInfo { info: [67u8; 32] };
|
||||
let (payment1, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
|
||||
|
||||
// SPEND VERIFICATION for USER1
|
||||
assert!(payment1.spend_verify(¶ms, &verification_key, &pay_info1).unwrap());
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER2
|
||||
let (withdrawal_req2, req_info2) = withdrawal_request(¶ms, &sk_user2).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER2
|
||||
let mut partial_wallets2 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req2,
|
||||
pk_user2.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
).unwrap();
|
||||
let partial_wallet2 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user2, &blind_signature, &req_info2).unwrap();
|
||||
partial_wallets2.push(partial_wallet2);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER2
|
||||
let mut wallet2 = aggregate_wallets(&grp, &verification_key, &sk_user2, &partial_wallets2).unwrap();
|
||||
|
||||
let pay_info2 = PayInfo { info: [67u8; 32] };
|
||||
let (payment2, wallet2) = wallet2.spend(¶ms, &verification_key, &sk_user2, &pay_info2, 10, false).unwrap();
|
||||
|
||||
// SPEND VERIFICATION for USER2
|
||||
assert!(payment2.spend_verify(¶ms, &verification_key, &pay_info2).unwrap());
|
||||
|
||||
let public_keys = HashSet::from([pk_user1, pk_user2]);
|
||||
let identify_result = identify(¶ms, &verification_key, &public_keys, payment1, payment2, pay_info1, pay_info2).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::NotADuplicatePayment);
|
||||
}
|
||||
}
|
||||
@@ -1,432 +0,0 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::iter::Sum;
|
||||
use std::ops::{Add, Mul};
|
||||
|
||||
use bls12_381::{G1Projective, G2Projective, Scalar};
|
||||
use group::Curve;
|
||||
|
||||
use crate::error::{DivisibleEcashError, Result};
|
||||
use crate::scheme::aggregation::aggregate_verification_keys;
|
||||
use crate::scheme::setup::{GroupParameters, Parameters};
|
||||
use crate::utils::{Polynomial, SignerIndex, try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar, try_deserialize_scalar_vec};
|
||||
|
||||
#[derive(Eq, Debug, PartialEq, Clone)]
|
||||
pub struct SecretKeyAuth {
|
||||
pub(crate) x: Scalar,
|
||||
pub(crate) ys: Vec<Scalar>,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for SecretKeyAuth {
|
||||
type Error = DivisibleEcashError;
|
||||
|
||||
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(DivisibleEcashError::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(DivisibleEcashError::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,
|
||||
DivisibleEcashError::Deserialization(
|
||||
"Failed to deserialize secret key scalar".to_string(),
|
||||
),
|
||||
)?;
|
||||
let ys = try_deserialize_scalar_vec(
|
||||
ys_len,
|
||||
&bytes[40..],
|
||||
DivisibleEcashError::Deserialization(
|
||||
"Failed to deserialize secret key scalars".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
Ok(SecretKeyAuth { x, ys })
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretKeyAuth {
|
||||
pub fn verification_key(&self, grp: &GroupParameters) -> VerificationKeyAuth {
|
||||
let g1 = grp.gen1();
|
||||
let g2 = grp.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(Eq, 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 = DivisibleEcashError;
|
||||
|
||||
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(DivisibleEcashError::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(
|
||||
DivisibleEcashError::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,
|
||||
DivisibleEcashError::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,
|
||||
DivisibleEcashError::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,
|
||||
DivisibleEcashError::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(Eq, PartialEq, Debug, Clone)]
|
||||
pub struct KeyPairUser {
|
||||
secret_key: SecretKeyUser,
|
||||
public_key: PublicKeyUser,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, 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(Hash, Eq, Debug, PartialEq, Clone)]
|
||||
pub struct PublicKeyUser {
|
||||
pub(crate) pk: G1Projective,
|
||||
}
|
||||
|
||||
impl KeyPairUser {
|
||||
pub fn secret_key(&self) -> SecretKeyUser {
|
||||
self.secret_key.clone()
|
||||
}
|
||||
|
||||
pub fn public_key(&self) -> PublicKeyUser {
|
||||
self.public_key.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn ttp_keygen_authorities(
|
||||
params: &Parameters,
|
||||
threshold: u64,
|
||||
num_authorities: u64) -> Result<Vec<KeyPairAuth>> {
|
||||
if threshold == 0 {
|
||||
return Err(DivisibleEcashError::Setup(
|
||||
"Tried to generate threshold keys with a 0 threshold value".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if threshold > num_authorities {
|
||||
return Err(
|
||||
DivisibleEcashError::Setup(
|
||||
"Tried to generate threshold keys for threshold value being higher than number of the signing authorities".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let grp = params.get_grp();
|
||||
// generate polynomials
|
||||
let v = Polynomial::new_random(&grp, threshold - 1);
|
||||
let ws = (0..2)
|
||||
.map(|_| Polynomial::new_random(&grp, threshold - 1))
|
||||
.collect::<Vec<_>>();
|
||||
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<_>>()
|
||||
});
|
||||
|
||||
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(&grp);
|
||||
KeyPairAuth {
|
||||
secret_key,
|
||||
verification_key,
|
||||
index: Some(*index),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(keypairs)
|
||||
}
|
||||
|
||||
pub fn ttp_keygen_users(params: &Parameters) -> KeyPairUser {
|
||||
let grp = params.get_grp();
|
||||
let sk_user = SecretKeyUser { sk: grp.random_scalar() };
|
||||
let pk_user = PublicKeyUser { pk: grp.gen1() * sk_user.sk };
|
||||
|
||||
KeyPairUser {
|
||||
secret_key: sk_user,
|
||||
public_key: pk_user,
|
||||
}
|
||||
}
|
||||
@@ -1,660 +0,0 @@
|
||||
use std::cell::Cell;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::ops::Neg;
|
||||
|
||||
use bls12_381::{G1Projective, G2Prepared, G2Projective, pairing, Scalar};
|
||||
use group::{Curve, GroupEncoding};
|
||||
|
||||
use crate::Attribute;
|
||||
use crate::constants::L;
|
||||
use crate::error::{DivisibleEcashError, 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 keygen;
|
||||
pub mod setup;
|
||||
pub mod structure_preserving_signature;
|
||||
pub mod withdrawal;
|
||||
pub mod identification;
|
||||
|
||||
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(Eq, PartialEq, Debug, Clone, Copy)]
|
||||
pub struct Phi(pub(crate) G1Projective, pub(crate) G1Projective);
|
||||
|
||||
impl Phi {
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(48 + 48);
|
||||
bytes.extend_from_slice(self.0.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.1.to_bytes().as_ref());
|
||||
bytes
|
||||
}
|
||||
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
if bytes.len() < 48 * 2 || (bytes.len()) % 48 != 0 {
|
||||
return Err(DivisibleEcashError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len(),
|
||||
target: 48 * 2,
|
||||
modulus: 48,
|
||||
object: "phi".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let elem_0_bytes = bytes[0..48].try_into().unwrap();
|
||||
let elem_0 = try_deserialize_g1_projective(
|
||||
elem_0_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize element 0 of Phi".to_string()),
|
||||
)?;
|
||||
|
||||
let elem_1_bytes = bytes[48..96].try_into().unwrap();
|
||||
let elem_1 = try_deserialize_g1_projective(
|
||||
elem_1_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize element 1 of Phi".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(Phi(elem_0, elem_1))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
|
||||
pub struct VarPhi(pub(crate) G1Projective, pub(crate) G1Projective);
|
||||
|
||||
impl VarPhi {
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(48 + 48);
|
||||
bytes.extend_from_slice(self.0.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.1.to_bytes().as_ref());
|
||||
bytes
|
||||
}
|
||||
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
if bytes.len() < 48 * 2 || (bytes.len()) % 48 != 0 {
|
||||
return Err(DivisibleEcashError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len(),
|
||||
target: 48 * 2,
|
||||
modulus: 48,
|
||||
object: "varphi".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let elem_0_bytes = bytes[0..48].try_into().unwrap();
|
||||
let elem_0 = try_deserialize_g1_projective(
|
||||
elem_0_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize element 0 of VarPhi".to_string()),
|
||||
)?;
|
||||
|
||||
let elem_1_bytes = bytes[48..96].try_into().unwrap();
|
||||
let elem_1 = try_deserialize_g1_projective(
|
||||
elem_1_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize element 1 of VarPhi".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(VarPhi(elem_0, elem_1))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct PayInfo {
|
||||
pub info: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Payment {
|
||||
pub kappa: G2Projective,
|
||||
pub sig: Signature,
|
||||
pub phi: Phi,
|
||||
pub varphi: VarPhi,
|
||||
pub varsig_prime1: G1Projective,
|
||||
pub varsig_prime2: G1Projective,
|
||||
pub theta_prime1: G1Projective,
|
||||
pub theta_prime2: G1Projective,
|
||||
pub rr_prime: G1Projective,
|
||||
pub ss_prime: G1Projective,
|
||||
pub tt_prime: G2Projective,
|
||||
pub rr: Scalar,
|
||||
pub zk_proof: SpendProof,
|
||||
pub vv: u64,
|
||||
}
|
||||
|
||||
impl Payment {
|
||||
pub fn get_kappa(&self) -> G2Projective { self.kappa }
|
||||
pub fn get_sig(&self) -> Signature { self.sig }
|
||||
pub fn get_phi(&self) -> Phi { self.phi }
|
||||
pub fn get_varphi(&self) -> VarPhi { self.varphi }
|
||||
pub fn get_varsig_prime1(&self) -> G1Projective { self.varsig_prime1 }
|
||||
pub fn get_varsig_prime2(&self) -> G1Projective { self.varsig_prime2 }
|
||||
pub fn get_theta_prime1(&self) -> G1Projective { self.theta_prime1 }
|
||||
pub fn get_theta_prime2(&self) -> G1Projective { self.theta_prime2 }
|
||||
pub fn get_rr_prime(&self) -> G1Projective { self.rr_prime }
|
||||
pub fn get_ss_prime(&self) -> G1Projective { self.ss_prime }
|
||||
pub fn get_tt_prime(&self) -> G2Projective { self.tt_prime }
|
||||
pub fn get_rr(&self) -> Scalar { self.rr }
|
||||
pub fn get_zk_proof(&self) -> SpendProof { self.zk_proof.clone() }
|
||||
pub fn get_vv(&self) -> u64 { self.vv }
|
||||
pub fn to_bytes(&self) -> Vec<u8>{
|
||||
let kappa_bytes = self.kappa.to_affine().to_compressed();
|
||||
let sig_bytes = self.sig.to_bytes();
|
||||
let phi_bytes = self.phi.to_bytes();
|
||||
let varphi_bytes = self.varphi.to_bytes();
|
||||
let varsig_prime1_bytes = self.varsig_prime1.to_affine().to_compressed();
|
||||
let varsig_prime2_bytes = self.varsig_prime2.to_affine().to_compressed();
|
||||
let theta_prime1_bytes = self.theta_prime1.to_affine().to_compressed();
|
||||
let theta_prime2 = self.theta_prime2.to_affine().to_compressed();
|
||||
let rr_prime_bytes = self.rr_prime.to_affine().to_compressed();
|
||||
let ss_prime_bytes = self.ss_prime.to_affine().to_compressed();
|
||||
let tt_prime_bytes = self.tt_prime.to_affine().to_compressed();
|
||||
let rr_bytes = self.rr.to_bytes();
|
||||
let zk_proof_bytes = self.zk_proof.to_bytes();
|
||||
let vv_bytes = self.vv.to_le_bytes();
|
||||
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(760);
|
||||
bytes.extend_from_slice(&kappa_bytes);
|
||||
bytes.extend_from_slice(&sig_bytes);
|
||||
bytes.extend_from_slice(&phi_bytes);
|
||||
bytes.extend_from_slice(&varphi_bytes);
|
||||
bytes.extend_from_slice(&varsig_prime1_bytes);
|
||||
bytes.extend_from_slice(&varsig_prime2_bytes);
|
||||
bytes.extend_from_slice(&theta_prime1_bytes);
|
||||
bytes.extend_from_slice(&theta_prime2);
|
||||
bytes.extend_from_slice(&rr_prime_bytes);
|
||||
bytes.extend_from_slice(&ss_prime_bytes);
|
||||
bytes.extend_from_slice(&tt_prime_bytes);
|
||||
bytes.extend_from_slice(&rr_bytes);
|
||||
bytes.extend_from_slice(&vv_bytes);
|
||||
bytes.extend_from_slice(&zk_proof_bytes);
|
||||
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for Payment {
|
||||
type Error = DivisibleEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self> {
|
||||
if bytes.len() < 760 {
|
||||
return Err(DivisibleEcashError::Deserialization(
|
||||
"Invalid byte array for Payment deserialization".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut idx = 0;
|
||||
let kappa_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap();
|
||||
let kappa = try_deserialize_g2_projective(
|
||||
&kappa_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize kappa".to_string()),
|
||||
)?;
|
||||
idx += 96;
|
||||
|
||||
let sig_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap();
|
||||
let sig = Signature::try_from(sig_bytes.as_slice())?;
|
||||
idx += 96;
|
||||
|
||||
let phi_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap();
|
||||
let phi = Phi::from_bytes(&phi_bytes).unwrap();
|
||||
idx += 96;
|
||||
|
||||
let varphi_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap();
|
||||
let varphi = VarPhi::from_bytes(&varphi_bytes).unwrap();
|
||||
idx += 96;
|
||||
|
||||
let varsig_prime1_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap();
|
||||
let varsig_prime1 = try_deserialize_g1_projective(
|
||||
&varsig_prime1_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize varsig_prime1".to_string()),
|
||||
)?;
|
||||
idx += 48;
|
||||
|
||||
let varsig_prime2_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap();
|
||||
let varsig_prime2 = try_deserialize_g1_projective(
|
||||
&varsig_prime2_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize varsig_prime2".to_string()),
|
||||
)?;
|
||||
idx += 48;
|
||||
|
||||
let theta_prime1_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap();
|
||||
let theta_prime1 = try_deserialize_g1_projective(
|
||||
&theta_prime1_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize theta_prime1".to_string()),
|
||||
)?;
|
||||
idx += 48;
|
||||
|
||||
let theta_prime2_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap();
|
||||
let theta_prime2 = try_deserialize_g1_projective(
|
||||
&theta_prime2_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize theta_prime2".to_string()),
|
||||
)?;
|
||||
idx += 48;
|
||||
|
||||
let rr_prime_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap();
|
||||
let rr_prime = try_deserialize_g1_projective(
|
||||
&rr_prime_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize rr_prime".to_string()),
|
||||
)?;
|
||||
idx += 48;
|
||||
|
||||
let ss_prime_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap();
|
||||
let ss_prime = try_deserialize_g1_projective(
|
||||
&ss_prime_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize ss_prime".to_string()),
|
||||
)?;
|
||||
idx += 48;
|
||||
|
||||
let tt_prime_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap();
|
||||
let tt_prime = try_deserialize_g2_projective(
|
||||
&tt_prime_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize tt_prime".to_string()),
|
||||
)?;
|
||||
idx += 96;
|
||||
|
||||
let rr_bytes: [u8; 32] = bytes[idx..idx+32].try_into().unwrap();
|
||||
let rr = try_deserialize_scalar(
|
||||
&rr_bytes,
|
||||
DivisibleEcashError::Deserialization("Failed to deserialize rr element".to_string()),
|
||||
)?;
|
||||
idx += 32;
|
||||
|
||||
let vv = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap());
|
||||
idx += 8;
|
||||
|
||||
// Deserialize the SpendProof struct
|
||||
let zk_proof_bytes = &bytes[idx..];
|
||||
let zk_proof = SpendProof::try_from(zk_proof_bytes)?;
|
||||
|
||||
Ok(Payment{
|
||||
kappa,
|
||||
sig,
|
||||
phi,
|
||||
varphi,
|
||||
varsig_prime1,
|
||||
varsig_prime2,
|
||||
theta_prime1,
|
||||
theta_prime2,
|
||||
rr_prime,
|
||||
ss_prime,
|
||||
tt_prime,
|
||||
rr,
|
||||
zk_proof,
|
||||
vv,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 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 spend(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
sk_user: &SecretKeyUser,
|
||||
pay_info: &PayInfo,
|
||||
vv: u64,
|
||||
bench_flag: bool,
|
||||
) -> Result<(Payment, &Self)> {
|
||||
if self.l() + vv >= L {
|
||||
return Err(DivisibleEcashError::Spend(
|
||||
"The counter l is higher than max L".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let grp = params.get_grp();
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
// randomize signature in the wallet
|
||||
let (signature_prime, sign_blinding_factor) = self.signature().randomise(grp);
|
||||
// construct kappa i.e., blinded attributes for show
|
||||
let attributes = vec![sk_user.sk, self.v()];
|
||||
// compute kappa
|
||||
let kappa = compute_kappa(
|
||||
&grp,
|
||||
&verification_key,
|
||||
&attributes,
|
||||
sign_blinding_factor,
|
||||
);
|
||||
|
||||
let r1 = grp.random_scalar();
|
||||
let r2 = grp.random_scalar();
|
||||
let phi = Phi(grp.gen1() * r1, params_u.get_ith_sigma(self.l() as usize) * self.v + params_u.get_ith_eta(vv as usize) * r1);
|
||||
|
||||
// compute hash of the payment info
|
||||
let rr = hash_to_scalar(pay_info.info);
|
||||
let varphi = VarPhi(grp.gen1() * r2, (grp.gen1() * rr) * sk_user.sk + params_u.get_ith_theta(self.l() as usize) * self.v + params_u.get_ith_eta(vv as usize) * r2);
|
||||
|
||||
|
||||
// random value used to compute blinded bases
|
||||
let r_varsig1 = grp.random_scalar();
|
||||
let r_theta1 = grp.random_scalar();
|
||||
let r_varsig2 = grp.random_scalar();
|
||||
let r_theta2 = grp.random_scalar();
|
||||
let r_rr = grp.random_scalar();
|
||||
let r_ss = grp.random_scalar();
|
||||
let r_tt = grp.random_scalar();
|
||||
|
||||
// compute blinded bases
|
||||
let psi_g1 = params_u.get_psi_g1();
|
||||
let psi_g2 = params_u.get_psi_g2();
|
||||
let varsig_prime1 = params_u.get_ith_sigma(self.l() as usize) + (psi_g1 * r_varsig1);
|
||||
let theta_prime1 = params_u.get_ith_theta(self.l() as usize) + (psi_g1 * r_theta1);
|
||||
let varsig_prime2 = params_u.get_ith_sigma(self.l() as usize + vv as usize - 1) + (psi_g1 * r_varsig2);
|
||||
let theta_prime2 = params_u.get_ith_theta(self.l() as usize + vv as usize - 1) + (psi_g1 * r_theta2);
|
||||
|
||||
let tau_l_vv = params_u.get_ith_sps_sign(self.l() as usize + vv as usize - 1);
|
||||
let rr_prime = tau_l_vv.rr + (psi_g1 * r_rr);
|
||||
let ss_prime = tau_l_vv.ss + (psi_g1 * r_ss);
|
||||
let tt_prime = tau_l_vv.tt + (psi_g2 * r_tt);
|
||||
|
||||
let rho1 = self.v.neg() * r_varsig1;
|
||||
let rho2 = self.v.neg() * r_theta1;
|
||||
let rho3 = r_rr * r_tt;
|
||||
|
||||
let pg_varsigpr1_delta = pairing(&varsig_prime1.to_affine(), ¶ms_a.get_ith_delta((vv - 1) as usize).to_affine());
|
||||
let pg_psi0_delta = pairing(&psi_g1.to_affine(), ¶ms_a.get_ith_delta((vv - 1) as usize).to_affine());
|
||||
let pg_varsigpr2_gen2 = pairing(&varsig_prime2.to_affine(), grp.gen2());
|
||||
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
|
||||
let pg_thetapr1_delta = pairing(&theta_prime1.to_affine(), ¶ms_a.get_ith_delta((vv - 1) as usize).to_affine());
|
||||
let pg_thetapr2_gen2 = pairing(&theta_prime2.to_affine(), grp.gen2());
|
||||
let yy = params_u.get_sps_pk().get_yy();
|
||||
let pg_rrprime_yy = pairing(&rr_prime.to_affine(), &yy.to_affine());
|
||||
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
|
||||
let pg_ssprime_gen2 = pairing(&ss_prime.to_affine(), grp.gen2());
|
||||
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
|
||||
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
|
||||
let pg_varsigpr2_ww1 = pairing(&varsig_prime2.to_affine(), &ww1.to_affine());
|
||||
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
|
||||
let pg_thetapr2_ww2 = pairing(&theta_prime2.to_affine(), &ww2.to_affine());
|
||||
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
|
||||
let pg_gen1_zz = pairing(grp.gen1(), ¶ms_u.get_sps_pk().get_zz().to_affine());
|
||||
let pg_rr_tt = pairing(&rr_prime.to_affine(), &tt_prime.to_affine());
|
||||
let pg_rr_psi1 = pairing(&rr_prime.to_affine(), &psi_g2.to_affine());
|
||||
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &tt_prime.to_affine());
|
||||
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
|
||||
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
|
||||
|
||||
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
|
||||
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
|
||||
let pg_eq3 = pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 + pg_gen1_zz.neg();
|
||||
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
|
||||
|
||||
let instance = SpendInstance {
|
||||
kappa,
|
||||
phi,
|
||||
varphi,
|
||||
rr,
|
||||
rr_prime,
|
||||
ss_prime,
|
||||
tt_prime,
|
||||
varsig_prime1,
|
||||
theta_prime1,
|
||||
pg_eq1,
|
||||
pg_eq2,
|
||||
pg_eq3,
|
||||
pg_eq4,
|
||||
psi_g1: *psi_g1,
|
||||
psi_g2: *psi_g2,
|
||||
pg_psi0_delta,
|
||||
pg_psi0_gen2,
|
||||
pg_psi0_yy,
|
||||
pg_psi0_ww1,
|
||||
pg_psi0_ww2,
|
||||
pg_rr_psi1,
|
||||
pg_psi0_tt,
|
||||
pg_psi0_psi1,
|
||||
};
|
||||
|
||||
let witness = SpendWitness {
|
||||
sk_u: sk_user.clone(),
|
||||
v: self.v,
|
||||
r: sign_blinding_factor,
|
||||
r1,
|
||||
r2,
|
||||
r_varsig1,
|
||||
r_theta1,
|
||||
r_varsig2,
|
||||
r_theta2,
|
||||
r_rr,
|
||||
r_ss,
|
||||
r_tt,
|
||||
rho1,
|
||||
rho2,
|
||||
rho3,
|
||||
};
|
||||
|
||||
// compute the zk proof
|
||||
let zk_proof = SpendProof::construct(params, &instance, &witness, &verification_key, vv);
|
||||
|
||||
// output pay and updated wallet
|
||||
let pay = Payment {
|
||||
kappa,
|
||||
sig: signature_prime,
|
||||
phi,
|
||||
varphi,
|
||||
varsig_prime1,
|
||||
varsig_prime2,
|
||||
theta_prime1,
|
||||
theta_prime2,
|
||||
rr_prime,
|
||||
ss_prime,
|
||||
tt_prime,
|
||||
rr,
|
||||
zk_proof,
|
||||
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 + vv);
|
||||
}
|
||||
Ok((pay, self))
|
||||
}
|
||||
}
|
||||
|
||||
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(DivisibleEcashError::Spend(
|
||||
"The element h of the signature equals the identity".to_string(),
|
||||
));
|
||||
}
|
||||
let grp = params.get_grp();
|
||||
let params_a = params.get_params_a();
|
||||
let params_u = params.get_params_u();
|
||||
|
||||
if !check_bilinear_pairing(
|
||||
&self.sig.0.to_affine(),
|
||||
&G2Prepared::from(self.kappa.to_affine()),
|
||||
&self.sig.1.to_affine(),
|
||||
grp.prepared_miller_g2(),
|
||||
) {
|
||||
return Err(DivisibleEcashError::Spend(
|
||||
"The bilinear check for kappa failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if bool::from(self.sig.0.is_identity()) {
|
||||
return Err(DivisibleEcashError::Spend(
|
||||
"The element h of the signature on l equals the identity".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// verify integrity of R
|
||||
if !(self.rr == hash_to_scalar(pay_info.info)) {
|
||||
return Err(DivisibleEcashError::Spend(
|
||||
"Integrity of R does not hold".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
//TODO: verify whether payinfo contains merchent's identifier
|
||||
|
||||
let psi_g1 = params_u.get_psi_g1();
|
||||
let psi_g2 = params_u.get_psi_g2();
|
||||
let pg_varsigpr1_delta = pairing(&self.varsig_prime1.to_affine(), ¶ms_a.get_ith_delta((self.vv - 1) as usize).to_affine());
|
||||
let pg_psi0_delta = pairing(&psi_g1.to_affine(), ¶ms_a.get_ith_delta((self.vv - 1) as usize).to_affine());
|
||||
let pg_varsigpr2_gen2 = pairing(&self.varsig_prime2.to_affine(), grp.gen2());
|
||||
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
|
||||
let pg_thetapr1_delta = pairing(&self.theta_prime1.to_affine(), ¶ms_a.get_ith_delta((self.vv - 1) as usize).to_affine());
|
||||
let pg_thetapr2_gen2 = pairing(&self.theta_prime2.to_affine(), grp.gen2());
|
||||
let yy = params_u.get_sps_pk().get_yy();
|
||||
let pg_rrprime_yy = pairing(&self.rr_prime.to_affine(), &yy.to_affine());
|
||||
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
|
||||
let pg_ssprime_gen2 = pairing(&self.ss_prime.to_affine(), grp.gen2());
|
||||
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
|
||||
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
|
||||
let pg_varsigpr2_ww1 = pairing(&self.varsig_prime2.to_affine(), &ww1.to_affine());
|
||||
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
|
||||
let pg_thetapr2_ww2 = pairing(&self.theta_prime2.to_affine(), &ww2.to_affine());
|
||||
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
|
||||
let pg_gen1_zz = pairing(grp.gen1(), ¶ms_u.get_sps_pk().get_zz().to_affine());
|
||||
let pg_rr_tt = pairing(&self.rr_prime.to_affine(), &self.tt_prime.to_affine());
|
||||
let pg_rr_psi1 = pairing(&self.rr_prime.to_affine(), &psi_g2.to_affine());
|
||||
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &self.tt_prime.to_affine());
|
||||
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
|
||||
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
|
||||
|
||||
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
|
||||
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
|
||||
let pg_eq3 = pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 + pg_gen1_zz.neg();
|
||||
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
|
||||
|
||||
let instance = SpendInstance {
|
||||
kappa: self.kappa,
|
||||
phi: self.phi,
|
||||
varphi: self.varphi,
|
||||
rr: self.rr,
|
||||
rr_prime: self.rr_prime,
|
||||
ss_prime: self.ss_prime,
|
||||
tt_prime: self.tt_prime,
|
||||
varsig_prime1: self.varsig_prime1,
|
||||
theta_prime1: self.theta_prime1,
|
||||
pg_eq1,
|
||||
pg_eq2,
|
||||
pg_eq3,
|
||||
pg_eq4,
|
||||
psi_g1: *psi_g1,
|
||||
psi_g2: *psi_g2,
|
||||
pg_psi0_delta,
|
||||
pg_psi0_gen2,
|
||||
pg_psi0_yy,
|
||||
pg_psi0_ww1,
|
||||
pg_psi0_ww2,
|
||||
pg_rr_psi1,
|
||||
pg_psi0_tt,
|
||||
pg_psi0_psi1,
|
||||
};
|
||||
|
||||
Ok(self.zk_proof.verify(¶ms, &instance, &verification_key, self.vv))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::scheme::{PayInfo, Phi, VarPhi, Wallet};
|
||||
use crate::scheme::aggregation::aggregate_verification_keys;
|
||||
use crate::scheme::keygen::{PublicKeyUser, ttp_keygen_authorities, VerificationKeyAuth};
|
||||
use crate::scheme::setup::{GroupParameters, Parameters};
|
||||
use crate::utils::hash_g1;
|
||||
|
||||
#[test]
|
||||
fn phi_to_and_from_bytes() {
|
||||
let phi = Phi(hash_g1("Element 0 of Phi"), hash_g1("Element 1 of Phi"));
|
||||
let phi_bytes = phi.to_bytes();
|
||||
let phi_from_bytes = Phi::from_bytes(&phi_bytes).unwrap();
|
||||
assert_eq!(phi, phi_from_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn varphi_to_and_from_bytes() {
|
||||
let varphi = VarPhi(hash_g1("Element 0 of VarPhi"), hash_g1("Element 1 of VarPhi"));
|
||||
let varphi_bytes = varphi.to_bytes();
|
||||
let varphi_from_bytes = VarPhi::from_bytes(&varphi_bytes).unwrap();
|
||||
assert_eq!(varphi, varphi_from_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spend_verification_is_correct() {
|
||||
let rng = thread_rng();
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
let sk = grp.random_scalar();
|
||||
let pk_user = PublicKeyUser {
|
||||
pk: grp.gen1() * sk,
|
||||
};
|
||||
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 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();
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::net::ToSocketAddrs;
|
||||
|
||||
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, pairing, Scalar};
|
||||
use ff::Field;
|
||||
use group::Curve;
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::constants::L;
|
||||
use crate::error::Result;
|
||||
use crate::scheme::structure_preserving_signature::{SPSKeyPair, SPSSignature, SPSVerificationKey};
|
||||
use crate::utils::{hash_g1, hash_g2};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GroupParameters {
|
||||
/// Generator of the G1 group
|
||||
g1: G1Affine,
|
||||
/// Generator of the G2 group
|
||||
g2: G2Affine,
|
||||
/// Precomputed G2 generator used for the miller loop
|
||||
_g2_prepared_miller: G2Prepared,
|
||||
}
|
||||
|
||||
|
||||
impl GroupParameters {
|
||||
pub fn new() -> Result<GroupParameters> {
|
||||
Ok(GroupParameters {
|
||||
g1: G1Affine::generator(),
|
||||
g2: G2Affine::generator(),
|
||||
_g2_prepared_miller: G2Prepared::from(G2Affine::generator()),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn gen1(&self) -> &G1Affine {
|
||||
&self.g1
|
||||
}
|
||||
|
||||
pub(crate) fn gen2(&self) -> &G2Affine {
|
||||
&self.g2
|
||||
}
|
||||
|
||||
pub(crate) fn prepared_miller_g2(&self) -> &G2Prepared {
|
||||
&self._g2_prepared_miller
|
||||
}
|
||||
|
||||
pub fn random_scalar(&self) -> Scalar {
|
||||
// lazily-initialized thread-local random number generator, seeded by the system
|
||||
let mut rng = thread_rng();
|
||||
Scalar::random(&mut rng)
|
||||
}
|
||||
|
||||
pub(crate) fn n_random_scalars(&self, n: usize) -> Vec<Scalar> {
|
||||
(0..n).map(|_| self.random_scalar()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Parameters {
|
||||
grp: GroupParameters,
|
||||
params_u: ParametersUser,
|
||||
params_a: ParametersAuthority,
|
||||
tmp_sigma: G1Projective,
|
||||
pub y: Scalar,
|
||||
}
|
||||
|
||||
impl Parameters {
|
||||
pub(crate) fn get_grp(&self) -> &GroupParameters { &self.grp }
|
||||
|
||||
pub(crate) fn get_params_u(&self) -> &ParametersUser { &self.params_u }
|
||||
|
||||
pub(crate) fn get_params_a(&self) -> &ParametersAuthority { &self.params_a }
|
||||
|
||||
pub fn new(grp: GroupParameters) -> Parameters {
|
||||
let g1 = grp.gen1();
|
||||
let g2 = grp.gen2();
|
||||
|
||||
let psi_g1 = hash_g1("psi1");
|
||||
let psi_g2 = hash_g2("psi2");
|
||||
let gamma1 = hash_g1("gamma1");
|
||||
let gamma2 = hash_g1("gamma2");
|
||||
let eta = hash_g1("eta");
|
||||
|
||||
let z = grp.random_scalar();
|
||||
let y = grp.random_scalar();
|
||||
|
||||
let vec_a = grp.n_random_scalars(L as usize);
|
||||
|
||||
let sigma = g1 * z;
|
||||
let theta = eta * z;
|
||||
|
||||
let sigmas_u: Vec<G1Projective> = (1..=L)
|
||||
.map(|i| sigma * (y.pow(&[i as u64, 0, 0, 0])))
|
||||
.collect();
|
||||
|
||||
let thetas_u: Vec<G1Projective> = (1..=L)
|
||||
.map(|i| theta * (y.pow(&[i as u64, 0, 0, 0])))
|
||||
.collect();
|
||||
|
||||
let deltas_a: Vec<G2Projective> = (0..=L - 1)
|
||||
.map(|i| g2 * (y.pow(&[i as u64, 0, 0, 0])))
|
||||
.collect();
|
||||
|
||||
let etas_u: Vec<G1Projective> = vec_a.iter().map(|x| g1 * x).collect();
|
||||
|
||||
let mut etas_a: Vec<Vec<G2Projective>> = Default::default();
|
||||
for l in 1..=L {
|
||||
let mut etas_a_l: Vec<G2Projective> = Default::default();
|
||||
for k in 0..=l - 1 {
|
||||
etas_a_l.push(g2 * (vec_a[l as usize - 1].neg() * (y.pow(&[k as u64, 0, 0, 0]))));
|
||||
}
|
||||
etas_a.push(etas_a_l);
|
||||
}
|
||||
|
||||
let sps_keypair = SPSKeyPair::new(grp.clone(), 2, 0);
|
||||
|
||||
let sps_signatures: Vec<SPSSignature> = sigmas_u
|
||||
.iter()
|
||||
.zip(thetas_u.iter())
|
||||
.map(|(sigma, theta)| sps_keypair.sps_sk.sign(&grp, Some(&vec![*sigma, *theta]), None))
|
||||
.collect();
|
||||
|
||||
|
||||
// Compute signature for each pair sigma, theta
|
||||
let params_u = ParametersUser {
|
||||
gammas: vec![gamma1, gamma2],
|
||||
psi_g1,
|
||||
psi_g2,
|
||||
eta,
|
||||
etas: etas_u,
|
||||
sigmas: sigmas_u,
|
||||
thetas: thetas_u,
|
||||
sps_signatures,
|
||||
sps_pk: sps_keypair.sps_vk,
|
||||
};
|
||||
|
||||
let params_a = ParametersAuthority {
|
||||
deltas: deltas_a,
|
||||
etas: etas_a,
|
||||
};
|
||||
|
||||
return Parameters {
|
||||
grp,
|
||||
params_u,
|
||||
params_a,
|
||||
tmp_sigma: sigma,
|
||||
y,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Parameters {
|
||||
pub(crate) fn get_sigma(&self) -> &G1Projective { &self.tmp_sigma }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParametersUser {
|
||||
gammas: Vec<G1Projective>,
|
||||
psi_g1: G1Projective,
|
||||
psi_g2: G2Projective,
|
||||
eta: G1Projective,
|
||||
etas: Vec<G1Projective>,
|
||||
sigmas: Vec<G1Projective>,
|
||||
thetas: Vec<G1Projective>,
|
||||
sps_signatures: Vec<SPSSignature>,
|
||||
sps_pk: SPSVerificationKey,
|
||||
}
|
||||
|
||||
impl ParametersUser {
|
||||
pub(crate) fn get_gammas(&self) -> &Vec<G1Projective> { &self.gammas }
|
||||
|
||||
pub(crate) fn get_psi_g1(&self) -> &G1Projective { &self.psi_g1 }
|
||||
|
||||
pub(crate) fn get_psi_g2(&self) -> &G2Projective { &self.psi_g2 }
|
||||
|
||||
pub(crate) fn get_eta(&self) -> &G1Projective { &self.eta }
|
||||
|
||||
pub(crate) fn get_etas(&self) -> &[G1Projective] { &self.etas }
|
||||
|
||||
pub(crate) fn get_ith_eta(&self, idx: usize) -> &G1Projective { self.etas.get(idx - 1).unwrap() }
|
||||
|
||||
pub(crate) fn get_sigmas(&self) -> &[G1Projective] { &self.sigmas }
|
||||
|
||||
pub(crate) fn get_ith_sigma(&self, idx: usize) -> &G1Projective { self.sigmas.get(idx - 1).unwrap() }
|
||||
|
||||
pub(crate) fn get_thetas(&self) -> &[G1Projective] { &self.thetas }
|
||||
|
||||
pub(crate) fn get_ith_theta(&self, idx: usize) -> &G1Projective { self.thetas.get(idx - 1).unwrap() }
|
||||
|
||||
pub(crate) fn get_sps_signs(&self) -> &[SPSSignature] { &self.sps_signatures }
|
||||
|
||||
pub(crate) fn get_ith_sps_sign(&self, idx: usize) -> &SPSSignature { &self.sps_signatures.get(idx - 1).unwrap() }
|
||||
|
||||
pub(crate) fn get_sps_pk(&self) -> &SPSVerificationKey { &self.sps_pk }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParametersAuthority {
|
||||
deltas: Vec<G2Projective>,
|
||||
etas: Vec<Vec<G2Projective>>,
|
||||
}
|
||||
|
||||
impl ParametersAuthority {
|
||||
pub(crate) fn get_deltas(&self) -> &[G2Projective] { &self.deltas }
|
||||
|
||||
pub(crate) fn get_ith_delta(&self, idx: usize) -> &G2Projective { self.deltas.get(idx).unwrap() }
|
||||
|
||||
pub(crate) fn get_etas(&self) -> &Vec<Vec<G2Projective>> { &self.etas }
|
||||
|
||||
pub(crate) fn get_eta_ith_row(&self, idx: usize) -> &[G2Projective] { self.etas.get(idx).unwrap() }
|
||||
|
||||
pub(crate) fn get_etas_ith_jth_elem(&self, row: usize, column: usize) -> &G2Projective { self.etas.get(row - 1).unwrap().get(column).unwrap() }
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Neg;
|
||||
|
||||
use bls12_381::{G1Projective, G2Projective, Gt, pairing, Scalar};
|
||||
use group::Curve;
|
||||
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SPSVerificationKey {
|
||||
pub grp: GroupParameters,
|
||||
pub uus: Vec<G1Projective>,
|
||||
pub wws: Vec<G2Projective>,
|
||||
pub yy: G2Projective,
|
||||
pub zz: G2Projective,
|
||||
}
|
||||
|
||||
pub struct SPSSecretKey {
|
||||
sps_vk: SPSVerificationKey,
|
||||
us: Vec<Scalar>,
|
||||
ws: Vec<Scalar>,
|
||||
y: Scalar,
|
||||
z: Scalar,
|
||||
}
|
||||
|
||||
impl SPSSecretKey {
|
||||
pub fn z(&self) -> Scalar {
|
||||
self.z
|
||||
}
|
||||
|
||||
pub fn y(&self) -> Scalar {
|
||||
self.y
|
||||
}
|
||||
|
||||
pub fn sign(&self, grp: &GroupParameters, messages_a: Option<&[G1Projective]>, messages_b: Option<&[G2Projective]>) -> SPSSignature {
|
||||
let r = grp.random_scalar();
|
||||
let rr = grp.gen1() * r;
|
||||
let ss: G1Projective = match messages_a {
|
||||
Some(msgs_a) => {
|
||||
let prod_s: Vec<G1Projective> = msgs_a
|
||||
.iter()
|
||||
.zip(self.ws.iter())
|
||||
.map(|(m_i, w_i)| m_i * w_i.neg())
|
||||
.collect();
|
||||
grp.gen1() * (self.z() - r * self.y()) + prod_s.iter().fold(G1Projective::identity(), |acc, elem| acc + elem)
|
||||
}
|
||||
None => grp.gen1() * (self.z() - r * self.y())
|
||||
};
|
||||
let tt = match messages_b {
|
||||
Some(msgs_b) => {
|
||||
let prod_t: Vec<G2Projective> = msgs_b
|
||||
.iter()
|
||||
.zip(self.us.iter())
|
||||
.map(|(m_i, u_i)| m_i * u_i.neg())
|
||||
.collect();
|
||||
(grp.gen2() + prod_t.iter().fold(G2Projective::identity(), |acc, elem| acc + elem)) * r.invert().unwrap()
|
||||
}
|
||||
None => grp.gen2() * r.invert().unwrap()
|
||||
};
|
||||
|
||||
SPSSignature
|
||||
{
|
||||
rr,
|
||||
ss,
|
||||
tt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SPSVerificationKey {
|
||||
pub fn verify(&self, grp: &GroupParameters, signature: SPSSignature, messages_a: &[G1Projective], messages_b: Option<&[G2Projective]>) -> bool {
|
||||
let pg_rr_yy = pairing(&signature.rr.to_affine(), &self.yy.to_affine());
|
||||
let pg_ss_g2 = pairing(&signature.ss.to_affine(), grp.gen2());
|
||||
let pg_g1_zz = pairing(grp.gen1(), &self.zz.to_affine());
|
||||
let pg_ma_ww: Vec<Gt> = messages_a.iter()
|
||||
.zip(self.wws.iter())
|
||||
.map(|(ma, ww)| pairing(&ma.to_affine(), &ww.to_affine()))
|
||||
.collect();
|
||||
|
||||
let mut prod_pg_ma_ww = Gt::identity();
|
||||
for elem in pg_ma_ww.iter() {
|
||||
prod_pg_ma_ww = prod_pg_ma_ww + elem;
|
||||
}
|
||||
|
||||
// let prod_pg_ma_ww = pg_ma_ww.iter().fold(Gt::identity() | acc, elem | acc + elem);
|
||||
|
||||
assert_eq!(pg_rr_yy + pg_ss_g2 + prod_pg_ma_ww, pg_g1_zz);
|
||||
|
||||
let result = match messages_b {
|
||||
Some(msgs_b) => {
|
||||
let pg_rr_tt = pairing(&signature.rr.to_affine(), &signature.tt.to_affine());
|
||||
let pg_g1_g2 = pairing(grp.gen1(), grp.gen2());
|
||||
let pg_uu_mb: Vec<Gt> = self.uus.iter()
|
||||
.zip(msgs_b.iter())
|
||||
.map(|(uu, mb)| pairing(&uu.to_affine(), &mb.to_affine()))
|
||||
.collect();
|
||||
|
||||
let mut prod_pg_uu_mb = Gt::identity();
|
||||
for elem in pg_uu_mb.iter() {
|
||||
prod_pg_uu_mb = prod_pg_uu_mb + elem;
|
||||
}
|
||||
// let prod_pg_uu_mb = pg_uu_mb.iter().fold(Gt::identity() | acc, elem | acc + elem);
|
||||
if pg_rr_tt + prod_pg_uu_mb == pg_g1_g2 {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
None => {
|
||||
let pg_sign_rr_yy = pairing(&signature.rr.to_affine(), &self.yy.to_affine());
|
||||
let pg_sign_ss_gen2 = pairing(&signature.ss.to_affine(), &grp.gen2());
|
||||
let pg_ma_wws: Vec<Gt> = messages_a.iter()
|
||||
.zip(self.wws.iter())
|
||||
.map(|(ma, ww)| pairing(&ma.to_affine(), &ww.to_affine()))
|
||||
.collect();
|
||||
|
||||
let mut prod_pg_ma_wws = Gt::identity();
|
||||
for elem in pg_ma_wws.iter() {
|
||||
prod_pg_ma_wws = prod_pg_ma_wws + elem;
|
||||
}
|
||||
|
||||
let pg_gen1_zz = pairing(&grp.gen1(), &self.zz.to_affine());
|
||||
|
||||
let pg_rr_tt = pairing(&signature.rr.to_affine(), &signature.tt.to_affine());
|
||||
let pg_gen1_gen2 = pairing(&grp.gen1(), &grp.gen2());
|
||||
|
||||
assert_eq!(pg_sign_rr_yy + pg_sign_ss_gen2 + prod_pg_ma_wws, pg_gen1_zz);
|
||||
assert_eq!(pg_rr_tt, pg_gen1_gen2);
|
||||
|
||||
if pg_sign_rr_yy + pg_sign_ss_gen2 + prod_pg_ma_wws == pg_gen1_zz && pg_rr_tt == pg_gen1_gen2 {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn get_ith_ww(&self, idx: usize) -> &G2Projective { return self.wws.get(idx).unwrap(); }
|
||||
|
||||
pub fn get_zz(&self) -> &G2Projective { return &self.zz; }
|
||||
|
||||
pub fn get_yy(&self) -> &G2Projective { return &self.yy; }
|
||||
}
|
||||
|
||||
pub struct SPSKeyPair {
|
||||
pub sps_sk: SPSSecretKey,
|
||||
pub sps_vk: SPSVerificationKey,
|
||||
}
|
||||
|
||||
impl SPSKeyPair {
|
||||
pub fn new(grp: GroupParameters, a: usize, b: usize) -> SPSKeyPair {
|
||||
let us = grp.n_random_scalars(b);
|
||||
let ws = grp.n_random_scalars(a);
|
||||
let y = grp.random_scalar();
|
||||
let z = grp.random_scalar();
|
||||
let uus: Vec<G1Projective> = us.iter().map(|u| grp.gen1() * u).collect();
|
||||
let yy = grp.gen2() * y;
|
||||
let wws: Vec<G2Projective> = ws.iter().map(|w| grp.gen2() * w).collect();
|
||||
let zz = grp.gen2() * z;
|
||||
|
||||
let sps_vk = SPSVerificationKey {
|
||||
grp: grp.clone(),
|
||||
uus,
|
||||
wws,
|
||||
yy,
|
||||
zz,
|
||||
};
|
||||
let sps_sk = SPSSecretKey {
|
||||
sps_vk: sps_vk.clone(),
|
||||
us,
|
||||
ws,
|
||||
y,
|
||||
z,
|
||||
};
|
||||
SPSKeyPair { sps_sk, sps_vk }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SPSSignature {
|
||||
pub rr: G1Projective,
|
||||
pub ss: G1Projective,
|
||||
pub tt: G2Projective,
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
use crate::scheme::structure_preserving_signature::SPSKeyPair;
|
||||
use crate::utils::{hash_g1, hash_g2};
|
||||
|
||||
#[test]
|
||||
fn sign_and_verify_for_two_msg_in_G1_and_two_msgs_in_G2() {
|
||||
let rng = thread_rng();
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let sps_keypair = SPSKeyPair::new(grp.clone(), 2, 2);
|
||||
let msgs_a = vec![hash_g1("messageA1"), hash_g1("messageA2")];
|
||||
let msgs_b = vec![hash_g2("messageB1"), hash_g2("messageB2")];
|
||||
let signature = sps_keypair.sps_sk.sign(&grp, Some(&msgs_a), Some(&msgs_b));
|
||||
assert!(sps_keypair.sps_vk.verify(&grp, signature, &msgs_a, Some(&msgs_b)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign_and_verify_for_two_msg_in_G1_and_no_msgs_in_G2() {
|
||||
let rng = thread_rng();
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let sps_keypair = SPSKeyPair::new(grp.clone(), 2, 2);
|
||||
let msgs_a = vec![hash_g1("messageA1"), hash_g1("messageA2")];
|
||||
let signature = sps_keypair.sps_sk.sign(&grp, Some(&msgs_a), None);
|
||||
assert!(sps_keypair.sps_vk.verify(&grp, signature, &msgs_a, None));
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar};
|
||||
use group::{Curve, GroupEncoding};
|
||||
|
||||
use crate::error::{DivisibleEcashError, Result};
|
||||
use crate::proofs::proof_withdrawal::{WithdrawalReqInstance, WithdrawalReqProof, WithdrawalReqWitness};
|
||||
use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser, VerificationKeyAuth};
|
||||
use crate::scheme::PartialWallet;
|
||||
use crate::scheme::setup::{GroupParameters, Parameters};
|
||||
use crate::utils::{BlindedSignature, check_bilinear_pairing, hash_g1, Signature};
|
||||
|
||||
pub struct WithdrawalRequest {
|
||||
com_hash: G1Projective,
|
||||
com: G1Projective,
|
||||
pc_coms: Vec<G1Projective>,
|
||||
zk_proof: WithdrawalReqProof,
|
||||
}
|
||||
|
||||
pub struct RequestInfo {
|
||||
com_hash: G1Projective,
|
||||
pc_coms_openings: Vec<Scalar>,
|
||||
v: Scalar,
|
||||
}
|
||||
|
||||
pub fn withdrawal_request(params: &Parameters, sk_user: &SecretKeyUser) -> Result<(WithdrawalRequest, RequestInfo)> {
|
||||
let grp = params.get_grp();
|
||||
let g1 = grp.gen1();
|
||||
let params_u = params.get_params_u();
|
||||
let v = grp.random_scalar();
|
||||
let attributes = vec![sk_user.sk, v];
|
||||
let com_opening = grp.random_scalar();
|
||||
let commitment = g1 * com_opening
|
||||
+ attributes
|
||||
.iter()
|
||||
.zip(params_u.get_gammas())
|
||||
.map(|(&m, gamma)| gamma * m)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
// Value h in the paper
|
||||
let com_hash = hash_g1(commitment.to_bytes());
|
||||
let pc_coms_openings = grp.n_random_scalars(attributes.len());
|
||||
// Compute Pedersen commitment for each attribute
|
||||
let pc_coms = pc_coms_openings
|
||||
.iter()
|
||||
.zip(attributes.iter())
|
||||
.map(|(o_j, m_j)| g1 * o_j + com_hash * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
// construct a zk proof of knowledge proving possession of m1, m2, o, o1, o2
|
||||
let instance = WithdrawalReqInstance {
|
||||
com: commitment,
|
||||
h: com_hash,
|
||||
pc_coms: pc_coms.clone(),
|
||||
pk_user: sk_user.public_key(¶ms.get_grp()),
|
||||
};
|
||||
|
||||
let witness = WithdrawalReqWitness {
|
||||
attributes,
|
||||
com_opening,
|
||||
pc_coms_openings: pc_coms_openings.clone(),
|
||||
};
|
||||
|
||||
let zk_proof = WithdrawalReqProof::construct(¶ms, &instance, &witness);
|
||||
|
||||
let req = WithdrawalRequest {
|
||||
com_hash,
|
||||
com: commitment,
|
||||
pc_coms: pc_coms.clone(),
|
||||
zk_proof,
|
||||
};
|
||||
|
||||
let req_info = RequestInfo {
|
||||
com_hash,
|
||||
pc_coms_openings,
|
||||
v,
|
||||
};
|
||||
|
||||
Ok((req, req_info))
|
||||
}
|
||||
|
||||
pub fn issue(params: &Parameters, req: &WithdrawalRequest, pk_u: PublicKeyUser, sk_a: &SecretKeyAuth) -> Result<BlindedSignature> {
|
||||
let h = hash_g1(req.com.to_bytes());
|
||||
if !(h == req.com_hash) {
|
||||
return Err(DivisibleEcashError::WithdrawalRequestVerification(
|
||||
"Failed to verify the commitment hash".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// verify zk proof
|
||||
let instance = WithdrawalReqInstance {
|
||||
com: req.com,
|
||||
h: req.com_hash,
|
||||
pc_coms: req.pc_coms.clone(),
|
||||
pk_user: pk_u,
|
||||
};
|
||||
if !req.zk_proof.verify(¶ms, &instance) {
|
||||
return Err(DivisibleEcashError::WithdrawalRequestVerification(
|
||||
"Failed to verify the proof of knowledge".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let sig = req
|
||||
.pc_coms
|
||||
.iter()
|
||||
.zip(sk_a.ys.iter())
|
||||
.map(|(pc, yi)| pc * yi)
|
||||
.chain(std::iter::once(h * sk_a.x))
|
||||
.sum();
|
||||
|
||||
Ok(BlindedSignature(h, sig))
|
||||
}
|
||||
|
||||
pub fn issue_verify(
|
||||
params: &GroupParameters,
|
||||
vk_auth: &VerificationKeyAuth,
|
||||
sk_user: &SecretKeyUser,
|
||||
blind_signature: &BlindedSignature,
|
||||
req_info: &RequestInfo) -> Result<PartialWallet> {
|
||||
|
||||
// Parse the blinded signature
|
||||
let h = blind_signature.0;
|
||||
let c = blind_signature.1;
|
||||
|
||||
// Verify the integrity of the response from the authority
|
||||
if !(req_info.com_hash == h) {
|
||||
return Err(DivisibleEcashError::IssuanceVfy(
|
||||
"Integrity verification failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Unblind the blinded signature on the partial wallet
|
||||
let blinding_removers = vk_auth
|
||||
.beta_g1
|
||||
.iter()
|
||||
.zip(req_info.pc_coms_openings.iter())
|
||||
.map(|(beta, opening)| beta * opening)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let unblinded_c = c - blinding_removers;
|
||||
|
||||
let attr = vec![sk_user.sk, req_info.v];
|
||||
|
||||
let signed_attributes = attr
|
||||
.iter()
|
||||
.zip(vk_auth.beta_g2.iter())
|
||||
.map(|(attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
// Verify the signature correctness on the wallet share
|
||||
if !check_bilinear_pairing(
|
||||
&h.to_affine(),
|
||||
&G2Prepared::from((vk_auth.alpha + signed_attributes).to_affine()),
|
||||
&unblinded_c.to_affine(),
|
||||
params.prepared_miller_g2(),
|
||||
) {
|
||||
return Err(DivisibleEcashError::IssuanceVfy(
|
||||
"Verification of wallet share failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(PartialWallet {
|
||||
sig: Signature(h, unblinded_c),
|
||||
v: req_info.v,
|
||||
idx: None,
|
||||
})
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::error::DivisibleEcashError;
|
||||
use crate::scheme::{PayInfo, Payment};
|
||||
use crate::scheme::aggregation::{aggregate_signatures, aggregate_verification_keys, aggregate_wallets};
|
||||
use crate::scheme::identification::identify;
|
||||
use crate::scheme::keygen::{PublicKeyUser, SecretKeyUser, ttp_keygen_authorities, VerificationKeyAuth};
|
||||
use crate::scheme::setup::{GroupParameters, Parameters};
|
||||
use crate::scheme::withdrawal::{issue, issue_verify, withdrawal_request};
|
||||
|
||||
#[test]
|
||||
// Test wa full end to end flow of withdrawal request, issuance,
|
||||
// and spending.
|
||||
fn main() -> Result<(), DivisibleEcashError> {
|
||||
// SETUP PHASE
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
|
||||
// KEY GENERATION FOR THE AUTHORITIES
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 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();
|
||||
|
||||
// KEY GENERATION FOR THE USER
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = SecretKeyUser::public_key(&sk_user, &grp);
|
||||
|
||||
// WITHDRAWAL REQUEST
|
||||
let (withdrawal_req, req_info) = withdrawal_request(¶ms, &sk_user)?;
|
||||
|
||||
// ISSUE PARTIAL WALLETS
|
||||
let mut partial_wallets = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req,
|
||||
pk_user.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
)?;
|
||||
let partial_wallet = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user, &blind_signature, &req_info)?;
|
||||
partial_wallets.push(partial_wallet);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET
|
||||
let mut wallet = aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets)?;
|
||||
|
||||
let pay_info = PayInfo { info: [67u8; 32] };
|
||||
let (payment, wallet) = wallet.spend(¶ms, &verification_key, &sk_user, &pay_info, 10, false)?;
|
||||
|
||||
// SPEND VERIFICATION
|
||||
assert!(payment.spend_verify(¶ms, &verification_key, &pay_info).unwrap());
|
||||
let payment_bytes = payment.to_bytes();
|
||||
let payment2 = Payment::try_from(&payment_bytes[..]).unwrap();
|
||||
assert_eq!(payment, payment2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
mod e2e;
|
||||
@@ -1,22 +0,0 @@
|
||||
use crate::error::DivisibleEcashError;
|
||||
|
||||
pub trait Bytable
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn to_byte_vec(&self) -> Vec<u8>;
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, DivisibleEcashError>;
|
||||
}
|
||||
|
||||
pub trait Base58
|
||||
where
|
||||
Self: Bytable,
|
||||
{
|
||||
fn try_from_bs58<S: AsRef<str>>(x: S) -> Result<Self, DivisibleEcashError> {
|
||||
Self::try_from_byte_slice(&bs58::decode(x.as_ref()).into_vec().unwrap())
|
||||
}
|
||||
fn to_bs58(&self) -> String {
|
||||
bs58::encode(self.to_byte_vec()).into_string()
|
||||
}
|
||||
}
|
||||
@@ -1,442 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use core::iter::Sum;
|
||||
use core::ops::Mul;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::ops::Neg;
|
||||
|
||||
use bls12_381::{
|
||||
G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, multi_miller_loop, Scalar,
|
||||
};
|
||||
use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField};
|
||||
use ff::Field;
|
||||
use group::{Curve, Group};
|
||||
|
||||
use crate::error::{DivisibleEcashError, Result};
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
use crate::traits::Bytable;
|
||||
|
||||
pub struct Polynomial {
|
||||
coefficients: Vec<Scalar>,
|
||||
}
|
||||
|
||||
impl Polynomial {
|
||||
// for polynomial of degree n, we generate n+1 values
|
||||
// (for example for degree 1, like y = x + 2, we need [2,1])
|
||||
pub fn new_random(grp: &GroupParameters, degree: u64) -> Self {
|
||||
Polynomial {
|
||||
coefficients: grp.n_random_scalars((degree + 1) as usize),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluates the polynomial at point x.
|
||||
pub fn evaluate(&self, x: &Scalar) -> Scalar {
|
||||
if self.coefficients.is_empty() {
|
||||
Scalar::zero()
|
||||
// if x is zero then we can ignore most of the expensive computation and
|
||||
// just return the last term of the polynomial
|
||||
} else if x.is_zero().unwrap_u8() == 1 {
|
||||
// we checked that coefficients are not empty so unwrap here is fine
|
||||
*self.coefficients.first().unwrap()
|
||||
} else {
|
||||
self.coefficients
|
||||
.iter()
|
||||
.enumerate()
|
||||
// coefficient[n] * x ^ n
|
||||
.map(|(i, coefficient)| coefficient * x.pow(&[i as u64, 0, 0, 0]))
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn generate_lagrangian_coefficients_at_origin(points: &[u64]) -> Vec<Scalar> {
|
||||
let x = Scalar::zero();
|
||||
|
||||
points
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, point_i)| {
|
||||
let mut numerator = Scalar::one();
|
||||
let mut denominator = Scalar::one();
|
||||
let xi = Scalar::from(*point_i);
|
||||
|
||||
for (j, point_j) in points.iter().enumerate() {
|
||||
if j != i {
|
||||
let xj = Scalar::from(*point_j);
|
||||
|
||||
// numerator = (x - xs[0]) * ... * (x - xs[j]), j != i
|
||||
numerator *= x - xj;
|
||||
|
||||
// denominator = (xs[i] - x[0]) * ... * (xs[i] - x[j]), j != i
|
||||
denominator *= xi - xj;
|
||||
}
|
||||
}
|
||||
// numerator / denominator
|
||||
numerator * denominator.invert().unwrap()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Performs a Lagrange interpolation at the origin for a polynomial defined by `points` and `values`.
|
||||
/// It can be used for Scalars, G1 and G2 points.
|
||||
pub(crate) fn perform_lagrangian_interpolation_at_origin<T>(
|
||||
points: &[SignerIndex],
|
||||
values: &[T],
|
||||
) -> Result<T>
|
||||
where
|
||||
T: Sum,
|
||||
for<'a> &'a T: Mul<Scalar, Output=T>,
|
||||
{
|
||||
if points.is_empty() || values.is_empty() {
|
||||
return Err(DivisibleEcashError::Interpolation(
|
||||
"Tried to perform lagrangian interpolation for an empty set of coordinates".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if points.len() != values.len() {
|
||||
return Err(DivisibleEcashError::Interpolation(
|
||||
"Tried to perform lagrangian interpolation for an incomplete set of coordinates"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let coefficients = generate_lagrangian_coefficients_at_origin(points);
|
||||
|
||||
Ok(coefficients
|
||||
.into_iter()
|
||||
.zip(values.iter())
|
||||
.map(|(coeff, val)| val * coeff)
|
||||
.sum())
|
||||
}
|
||||
|
||||
// A temporary way of hashing particular message into G1.
|
||||
// Implementation idea was taken from `threshold_crypto`:
|
||||
// https://github.com/poanetwork/threshold_crypto/blob/7709462f2df487ada3bb3243060504b5881f2628/src/lib.rs#L691
|
||||
// Eventually it should get replaced by, most likely, the osswu map
|
||||
// method once ideally it's implemented inside the pairing crate.
|
||||
|
||||
// note: I have absolutely no idea what are the correct domains for those. I just used whatever
|
||||
// was given in the test vectors of `Hashing to Elliptic Curves draft-irtf-cfrg-hash-to-curve-11`
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.9.1
|
||||
const G1_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_";
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.10.1
|
||||
const G2_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_";
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-K.1
|
||||
const SCALAR_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-expander";
|
||||
|
||||
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
|
||||
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
|
||||
}
|
||||
|
||||
pub fn hash_g2<M: AsRef<[u8]>>(msg: M) -> G2Projective {
|
||||
<G2Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G2_HASH_DOMAIN)
|
||||
}
|
||||
|
||||
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
|
||||
let mut output = vec![Scalar::zero()];
|
||||
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
|
||||
msg.as_ref(),
|
||||
SCALAR_HASH_DOMAIN,
|
||||
&mut output,
|
||||
);
|
||||
output[0]
|
||||
}
|
||||
|
||||
pub fn try_deserialize_scalar_vec(
|
||||
expected_len: u64,
|
||||
bytes: &[u8],
|
||||
err: DivisibleEcashError,
|
||||
) -> Result<Vec<Scalar>> {
|
||||
if bytes.len() != expected_len as usize * 32 {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let mut out = Vec::with_capacity(expected_len as usize);
|
||||
for i in 0..expected_len as usize {
|
||||
let s_bytes = bytes[i * 32..(i + 1) * 32].try_into().unwrap();
|
||||
let s = match Into::<Option<Scalar>>::into(Scalar::from_bytes(&s_bytes)) {
|
||||
None => return Err(err),
|
||||
Some(scalar) => scalar,
|
||||
};
|
||||
out.push(s)
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn try_deserialize_scalar(bytes: &[u8; 32], err: DivisibleEcashError) -> Result<Scalar> {
|
||||
Into::<Option<Scalar>>::into(Scalar::from_bytes(bytes)).ok_or(err)
|
||||
}
|
||||
|
||||
pub fn try_deserialize_g1_projective(
|
||||
bytes: &[u8; 48],
|
||||
err: DivisibleEcashError,
|
||||
) -> Result<G1Projective> {
|
||||
Into::<Option<G1Affine>>::into(G1Affine::from_compressed(bytes))
|
||||
.ok_or(err)
|
||||
.map(G1Projective::from)
|
||||
}
|
||||
|
||||
pub fn try_deserialize_g2_projective(
|
||||
bytes: &[u8; 96],
|
||||
err: DivisibleEcashError,
|
||||
) -> Result<G2Projective> {
|
||||
Into::<Option<G2Affine>>::into(G2Affine::from_compressed(bytes))
|
||||
.ok_or(err)
|
||||
.map(G2Projective::from)
|
||||
}
|
||||
|
||||
/// Checks whether e(P, Q) * e(-R, S) == id
|
||||
pub fn check_bilinear_pairing(p: &G1Affine, q: &G2Prepared, r: &G1Affine, s: &G2Prepared) -> bool {
|
||||
// checking e(P, Q) * e(-R, S) == id
|
||||
// is equivalent to checking e(P, Q) == e(R, S)
|
||||
// but requires only a single final exponentiation rather than two of them
|
||||
// and therefore, as seen via benchmarks.rs, is almost 50% faster
|
||||
// (1.47ms vs 2.45ms, tested on R9 5900X)
|
||||
|
||||
let multi_miller = multi_miller_loop(&[(p, q), (&r.neg(), s)]);
|
||||
multi_miller.final_exponentiation().is_identity().into()
|
||||
}
|
||||
|
||||
pub type SignerIndex = u64;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective);
|
||||
|
||||
pub type PartialSignature = Signature;
|
||||
|
||||
impl TryFrom<&[u8]> for Signature {
|
||||
type Error = DivisibleEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Signature> {
|
||||
if bytes.len() != 96 {
|
||||
return Err(DivisibleEcashError::Deserialization(format!(
|
||||
"Signature must be exactly 96 bytes, got {}",
|
||||
bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let sig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
|
||||
let sig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
|
||||
|
||||
let sig1 = try_deserialize_g1_projective(
|
||||
sig1_bytes,
|
||||
DivisibleEcashError::Deserialization(
|
||||
"Failed to deserialize compressed sig1".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
let sig2 = try_deserialize_g1_projective(
|
||||
sig2_bytes,
|
||||
DivisibleEcashError::Deserialization(
|
||||
"Failed to deserialize compressed sig2".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
Ok(Signature(sig1, sig2))
|
||||
}
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
pub(crate) fn sig1(&self) -> &G1Projective {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub(crate) fn sig2(&self) -> &G1Projective {
|
||||
&self.1
|
||||
}
|
||||
|
||||
pub fn randomise(&self, grp: &GroupParameters) -> (Signature, Scalar) {
|
||||
let r = grp.random_scalar();
|
||||
let r_prime = grp.random_scalar();
|
||||
let h_prime = self.0 * r_prime;
|
||||
let s_prime = (self.1 * r_prime) + (h_prime * r);
|
||||
(Signature(h_prime, s_prime), r)
|
||||
}
|
||||
|
||||
pub fn to_bytes(self) -> [u8; 96] {
|
||||
let mut bytes = [0u8; 96];
|
||||
bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed());
|
||||
bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed());
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Signature> {
|
||||
Signature::try_from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for Signature {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.to_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
Signature::from_bytes(slice)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct BlindedSignature(pub(crate) G1Projective, pub(crate) G1Projective);
|
||||
|
||||
pub struct SignatureShare {
|
||||
signature: Signature,
|
||||
index: SignerIndex,
|
||||
}
|
||||
|
||||
impl SignatureShare {
|
||||
pub fn new(signature: Signature, index: SignerIndex) -> Self {
|
||||
SignatureShare { signature, index }
|
||||
}
|
||||
|
||||
pub fn signature(&self) -> &Signature {
|
||||
&self.signature
|
||||
}
|
||||
|
||||
pub fn index(&self) -> SignerIndex {
|
||||
self.index
|
||||
}
|
||||
|
||||
// pub fn aggregate(shares: &[Self]) -> Result<Signature> {
|
||||
// aggregate_signature_shares(shares)
|
||||
// }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::RngCore;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn polynomial_evaluation() {
|
||||
// y = 42 (it should be 42 regardless of x)
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![Scalar::from(42)],
|
||||
};
|
||||
|
||||
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(1)));
|
||||
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(0)));
|
||||
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(10)));
|
||||
|
||||
// y = x + 10, at x = 2 (exp: 12)
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![Scalar::from(10), Scalar::from(1)],
|
||||
};
|
||||
|
||||
assert_eq!(Scalar::from(12), poly.evaluate(&Scalar::from(2)));
|
||||
|
||||
// y = x^4 - 5x^2 + 2x - 3, at x = 3 (exp: 39)
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![
|
||||
(-Scalar::from(3)),
|
||||
Scalar::from(2),
|
||||
(-Scalar::from(5)),
|
||||
Scalar::zero(),
|
||||
Scalar::from(1),
|
||||
],
|
||||
};
|
||||
|
||||
assert_eq!(Scalar::from(39), poly.evaluate(&Scalar::from(3)));
|
||||
|
||||
// empty polynomial
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![],
|
||||
};
|
||||
|
||||
// should always be 0
|
||||
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(1)));
|
||||
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(0)));
|
||||
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(10)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn performing_lagrangian_scalar_interpolation_at_origin() {
|
||||
// x^2 + 3
|
||||
// x, f(x):
|
||||
// 1, 4,
|
||||
// 2, 7,
|
||||
// 3, 12,
|
||||
let points = vec![1, 2, 3];
|
||||
let values = vec![Scalar::from(4), Scalar::from(7), Scalar::from(12)];
|
||||
|
||||
assert_eq!(
|
||||
Scalar::from(3),
|
||||
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
|
||||
);
|
||||
|
||||
// x^3 + 3x^2 - 5x + 11
|
||||
// x, f(x):
|
||||
// 1, 10
|
||||
// 2, 21
|
||||
// 3, 50
|
||||
// 4, 103
|
||||
let points = vec![1, 2, 3, 4];
|
||||
let values = vec![
|
||||
Scalar::from(10),
|
||||
Scalar::from(21),
|
||||
Scalar::from(50),
|
||||
Scalar::from(103),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
Scalar::from(11),
|
||||
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
|
||||
);
|
||||
|
||||
// more points than it is required
|
||||
// x^2 + x + 10
|
||||
// x, f(x)
|
||||
// 1, 12
|
||||
// 2, 16
|
||||
// 3, 22
|
||||
// 4, 30
|
||||
// 5, 40
|
||||
let points = vec![1, 2, 3, 4, 5];
|
||||
let values = vec![
|
||||
Scalar::from(12),
|
||||
Scalar::from(16),
|
||||
Scalar::from(22),
|
||||
Scalar::from(30),
|
||||
Scalar::from(40),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
Scalar::from(10),
|
||||
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_g1_sanity_check() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut msg1 = [0u8; 1024];
|
||||
rng.fill_bytes(&mut msg1);
|
||||
let mut msg2 = [0u8; 1024];
|
||||
rng.fill_bytes(&mut msg2);
|
||||
|
||||
assert_eq!(hash_g1(msg1), hash_g1(msg1));
|
||||
assert_eq!(hash_g1(msg2), hash_g1(msg2));
|
||||
assert_ne!(hash_g1(msg1), hash_g1(msg2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_scalar_sanity_check() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut msg1 = [0u8; 1024];
|
||||
rng.fill_bytes(&mut msg1);
|
||||
let mut msg2 = [0u8; 1024];
|
||||
rng.fill_bytes(&mut msg2);
|
||||
|
||||
assert_eq!(hash_to_scalar(msg1), hash_to_scalar(msg1));
|
||||
assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2));
|
||||
assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2));
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
[package]
|
||||
name = "nym_online_divisible_ecash"
|
||||
version = "0.1.0"
|
||||
author = ["Ania Piotrowska <ania@nymtech.net>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
|
||||
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.10"
|
||||
default-features = false
|
||||
|
||||
[dependencies.group]
|
||||
version = "0.10"
|
||||
default-features = false
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
mod error;
|
||||
mod scheme;
|
||||
mod traits;
|
||||
mod utils;
|
||||
@@ -1 +0,0 @@
|
||||
mod e2e;
|
||||
@@ -32,9 +32,9 @@ doc-comment = "0.3"
|
||||
[dev-dependencies.bincode]
|
||||
version = "1"
|
||||
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
#[[bench]]
|
||||
#name = "benchmarks"
|
||||
#harness = false
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -1,340 +0,0 @@
|
||||
// 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, multi_miller_loop, Scalar};
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use ff::Field;
|
||||
use group::{Curve, Group};
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
use nymcoconut::{
|
||||
aggregate_signature_shares, aggregate_verification_keys, Attribute, blind_sign,
|
||||
BlindedSignature, elgamal_keygen, Parameters, prepare_blind_sign, prove_bandwidth_credential,
|
||||
setup, Signature, SignatureShare, ttp_keygen, VerificationKey, verify_credential,
|
||||
};
|
||||
|
||||
#[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 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 bench_pairings(c: &mut Criterion) {
|
||||
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);
|
||||
|
||||
c.bench_function("double pairing", |b| {
|
||||
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
|
||||
});
|
||||
|
||||
c.bench_function("multi miller in affine", |b| {
|
||||
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
|
||||
});
|
||||
|
||||
c.bench_function("multi miller with prepared g2", |b| {
|
||||
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
|
||||
});
|
||||
|
||||
c.bench_function("multi miller with semi-prepared g2", |b| {
|
||||
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
|
||||
});
|
||||
}
|
||||
|
||||
#[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(clippy::too_many_arguments)]
|
||||
fn unblind_and_aggregate(
|
||||
params: &Parameters,
|
||||
blinded_signatures: &[BlindedSignature],
|
||||
partial_verification_keys: &[VerificationKey],
|
||||
private_attributes: &[Attribute],
|
||||
public_attributes: &[Attribute],
|
||||
commitment_hash: &G1Projective,
|
||||
pedersen_commitments_openings: &[Scalar],
|
||||
verification_key: &VerificationKey,
|
||||
) -> Signature {
|
||||
// Unblind all partial signatures
|
||||
let unblinded_signatures: Vec<Signature> = blinded_signatures
|
||||
.iter()
|
||||
.zip(partial_verification_keys.iter())
|
||||
.map(|(signature, partial_verification_key)| {
|
||||
signature
|
||||
.unblind(
|
||||
params,
|
||||
partial_verification_key,
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
commitment_hash,
|
||||
pedersen_commitments_openings,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let unblinded_signature_shares: Vec<SignatureShare> = unblinded_signatures
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
|
||||
.collect();
|
||||
|
||||
let mut attributes = vec![];
|
||||
attributes.extend_from_slice(private_attributes);
|
||||
attributes.extend_from_slice(public_attributes);
|
||||
aggregate_signature_shares(
|
||||
params,
|
||||
verification_key,
|
||||
&attributes,
|
||||
&unblinded_signature_shares,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
struct BenchCase {
|
||||
num_authorities: u64,
|
||||
threshold_p: f32,
|
||||
num_public_attrs: u32,
|
||||
num_private_attrs: u32,
|
||||
}
|
||||
|
||||
impl BenchCase {
|
||||
fn threshold(&self) -> u64 {
|
||||
(self.num_authorities as f32 * self.threshold_p).round() as u64
|
||||
}
|
||||
|
||||
fn num_attrs(&self) -> u32 {
|
||||
self.num_public_attrs + self.num_private_attrs
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_coconut(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("benchmark-coconut");
|
||||
group.measurement_time(Duration::from_secs(1000));
|
||||
let case = BenchCase {
|
||||
num_authorities: 100,
|
||||
threshold_p: 0.7,
|
||||
num_public_attrs: 2,
|
||||
num_private_attrs: 2,
|
||||
};
|
||||
|
||||
let params = setup(case.num_public_attrs + case.num_private_attrs).unwrap();
|
||||
|
||||
let public_attributes = params.n_random_scalars(case.num_public_attrs as usize);
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![serial_number, binding_number];
|
||||
|
||||
let _elgamal_keypair = elgamal_keygen(¶ms);
|
||||
|
||||
// The prepare blind sign is performed by the user
|
||||
let (pedersen_commitments_openings, blind_sign_request) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
|
||||
// CLIENT BENCHMARK: Data needed to ask for a credential
|
||||
// Let's benchmark the operations the client has to perform
|
||||
// to ask for a credential
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Client] prepare_blind_sign_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap())
|
||||
},
|
||||
);
|
||||
|
||||
// keys for the validators
|
||||
let coconut_keypairs = ttp_keygen(¶ms, case.threshold(), case.num_authorities).unwrap();
|
||||
|
||||
// VALIDATOR BENCHMARK: Issue partial credential
|
||||
// we pick only one key pair, as we want to validate how much does it
|
||||
// take for a single validator to issue a partial credential
|
||||
let mut rng = rand::thread_rng();
|
||||
let keypair = coconut_keypairs.choose(&mut rng).unwrap();
|
||||
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Validator] compute_single_blind_sign_for_credential_with_{}_attributes",
|
||||
case.num_attrs(),
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
blind_sign(
|
||||
¶ms,
|
||||
&keypair.secret_key(),
|
||||
&blind_sign_request,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// computing all partial credentials
|
||||
// NOTE: in reality, each validator computes only single signature
|
||||
let mut blinded_signatures = Vec::new();
|
||||
for keypair in coconut_keypairs.iter() {
|
||||
let blinded_signature = blind_sign(
|
||||
¶ms,
|
||||
&keypair.secret_key(),
|
||||
&blind_sign_request,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap();
|
||||
blinded_signatures.push(blinded_signature)
|
||||
}
|
||||
|
||||
let verification_keys: Vec<VerificationKey> = coconut_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
// Lets bench worse case, ie aggregating all
|
||||
let indices: Vec<u64> = (1..=case.num_authorities).collect();
|
||||
// aggregate verification keys
|
||||
let aggr_verification_key =
|
||||
aggregate_verification_keys(&verification_keys, Some(&indices)).unwrap();
|
||||
|
||||
// CLIENT OPERATION: Unblind partial singatures and aggregate into single signature
|
||||
let aggregated_signature = unblind_and_aggregate(
|
||||
¶ms,
|
||||
&blinded_signatures,
|
||||
&verification_keys,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&pedersen_commitments_openings,
|
||||
&aggr_verification_key,
|
||||
);
|
||||
|
||||
// CLIENT BENCHMARK: aggregate all partial credentials
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Client] unblind_and_aggregate_partial_credentials_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
unblind_and_aggregate(
|
||||
¶ms,
|
||||
&blinded_signatures,
|
||||
&verification_keys,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&pedersen_commitments_openings,
|
||||
&aggr_verification_key)
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// CLIENT OPERATION: Randomize credentials and generate any cryptographic material to verify them
|
||||
let theta = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
&aggr_verification_key,
|
||||
&aggregated_signature,
|
||||
serial_number,
|
||||
binding_number,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// CLIENT BENCHMARK
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Client] randomize_and_prove_credential_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
prove_bandwidth_credential(
|
||||
¶ms,
|
||||
&aggr_verification_key,
|
||||
&aggregated_signature,
|
||||
serial_number,
|
||||
binding_number,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// VERIFIER OPERATION
|
||||
// Verify credentials
|
||||
verify_credential(¶ms, &aggr_verification_key, &theta, &public_attributes);
|
||||
|
||||
// VERIFICATION BENCHMARK
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Verifier] verify_credentials_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
verify_credential(¶ms, &aggr_verification_key, &theta, &public_attributes)
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
criterion_group!(benches, bench_coconut);
|
||||
criterion_main!(benches);
|
||||
@@ -13,7 +13,6 @@ use crate::error::{CoconutError, Result};
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::traits::{Base58, Bytable};
|
||||
use crate::utils::{try_deserialize_g1_projective, try_deserialize_scalar};
|
||||
use crate::Attribute;
|
||||
|
||||
/// Type alias for the ephemeral key generated during ElGamal encryption
|
||||
pub type EphemeralKey = Scalar;
|
||||
@@ -223,18 +222,6 @@ pub fn elgamal_keygen(params: &Parameters) -> ElGamalKeyPair {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_attribute_encryption(
|
||||
params: &Parameters,
|
||||
private_attributes: &[Attribute],
|
||||
pub_key: &PublicKey,
|
||||
commitment_hash: G1Projective,
|
||||
) -> (Vec<Ciphertext>, Vec<EphemeralKey>) {
|
||||
private_attributes
|
||||
.iter()
|
||||
.map(|m| pub_key.encrypt(params, &commitment_hash, m))
|
||||
.unzip()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -38,7 +38,7 @@ mod scheme;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod traits;
|
||||
pub mod utils;
|
||||
mod utils;
|
||||
|
||||
pub type Attribute = Scalar;
|
||||
pub type PrivateAttribute = Attribute;
|
||||
|
||||
@@ -13,11 +13,12 @@ use group::GroupEncoding;
|
||||
use itertools::izip;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::elgamal::Ciphertext;
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::scheme::VerificationKey;
|
||||
use crate::utils::{hash_g1, try_deserialize_scalar, try_deserialize_scalar_vec};
|
||||
use crate::Attribute;
|
||||
use crate::{elgamal, Attribute, ElGamalKeyPair};
|
||||
|
||||
// as per the reference python implementation
|
||||
type ChallengeDigest = Sha256;
|
||||
@@ -27,7 +28,8 @@ type ChallengeDigest = Sha256;
|
||||
pub struct ProofCmCs {
|
||||
challenge: Scalar,
|
||||
response_opening: Scalar,
|
||||
response_openings: Vec<Scalar>,
|
||||
response_private_elgamal_key: Scalar,
|
||||
response_keys: Vec<Scalar>,
|
||||
response_attributes: Vec<Scalar>,
|
||||
}
|
||||
|
||||
@@ -86,11 +88,12 @@ impl ProofCmCs {
|
||||
/// using the Fiat-Shamir heuristic.
|
||||
pub(crate) fn construct(
|
||||
params: &Parameters,
|
||||
elgamal_keypair: &ElGamalKeyPair,
|
||||
ephemeral_keys: &[elgamal::EphemeralKey],
|
||||
commitment: &G1Projective,
|
||||
commitment_opening: &Scalar,
|
||||
commitments: &[G1Projective],
|
||||
pedersen_commitments_openings: &[Scalar],
|
||||
private_attributes: &[Attribute],
|
||||
priv_attributes_ciphertexts: &[Ciphertext],
|
||||
) -> Self {
|
||||
// note: this is only called from `prepare_blind_sign` that already checks
|
||||
// whether private attributes are non-empty and whether we don't have too many
|
||||
@@ -98,9 +101,10 @@ impl ProofCmCs {
|
||||
// we also know, due to the single call place, that ephemeral_keys.len() == private_attributes.len()
|
||||
|
||||
// witness creation
|
||||
|
||||
let witness_commitment_opening = params.random_scalar();
|
||||
let witness_pedersen_commitments_openings =
|
||||
params.n_random_scalars(pedersen_commitments_openings.len());
|
||||
let witness_private_elgamal_key = params.random_scalar();
|
||||
let witness_keys = params.n_random_scalars(ephemeral_keys.len());
|
||||
let witness_attributes = params.n_random_scalars(private_attributes.len());
|
||||
|
||||
// recompute h
|
||||
@@ -114,6 +118,22 @@ impl ProofCmCs {
|
||||
let g1 = params.gen1();
|
||||
|
||||
// compute commitments
|
||||
let commitment_private_key_elgamal = g1 * witness_private_elgamal_key;
|
||||
|
||||
// Aw[i] = (wk[i] * g1)
|
||||
let commitment_keys1_bytes = witness_keys
|
||||
.iter()
|
||||
.map(|wk_i| g1 * wk_i)
|
||||
.map(|witness| witness.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Bw[i] = (wm[i] * h) + (wk[i] * gamma)
|
||||
let commitment_keys2_bytes = witness_keys
|
||||
.iter()
|
||||
.zip(witness_attributes.iter())
|
||||
.map(|(wk_i, wm_i)| elgamal_keypair.public_key() * wk_i + h * wm_i)
|
||||
.map(|witness| witness.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// zkp commitment for the attributes commitment cm
|
||||
// Ccm = (wr * g1) + (wm[0] * hs[0]) + ... + (wm[i] * hs[i])
|
||||
@@ -124,21 +144,9 @@ impl ProofCmCs {
|
||||
.map(|(wm_i, hs_i)| hs_i * wm_i)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
// zkp commitments for the individual attributes
|
||||
let commitments_attributes = witness_pedersen_commitments_openings
|
||||
let ciphertexts_bytes = priv_attributes_ciphertexts
|
||||
.iter()
|
||||
.zip(witness_attributes.iter())
|
||||
.map(|(o_j, m_j)| g1 * o_j + h * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_bytes = commitments
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_attributes_bytes = commitments_attributes
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.map(|c| c.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// compute challenge
|
||||
@@ -146,20 +154,28 @@ impl ProofCmCs {
|
||||
std::iter::once(params.gen1().to_bytes().as_ref())
|
||||
.chain(hs_bytes.iter().map(|hs| hs.as_ref()))
|
||||
.chain(std::iter::once(h.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(
|
||||
elgamal_keypair.public_key().to_bytes().as_ref(),
|
||||
))
|
||||
.chain(std::iter::once(commitment.to_bytes().as_ref()))
|
||||
.chain(commitments_bytes.iter().map(|cm| cm.as_ref()))
|
||||
.chain(std::iter::once(commitment_attributes.to_bytes().as_ref()))
|
||||
.chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())),
|
||||
.chain(std::iter::once(
|
||||
commitment_private_key_elgamal.to_bytes().as_ref(),
|
||||
))
|
||||
.chain(commitment_keys1_bytes.iter().map(|aw| aw.as_ref()))
|
||||
.chain(commitment_keys2_bytes.iter().map(|bw| bw.as_ref()))
|
||||
.chain(ciphertexts_bytes.iter().map(|c| c.as_ref())),
|
||||
);
|
||||
|
||||
// Responses
|
||||
let response_opening =
|
||||
produce_response(&witness_commitment_opening, &challenge, commitment_opening);
|
||||
let response_openings = produce_responses(
|
||||
&witness_pedersen_commitments_openings,
|
||||
let response_private_elgamal_key = produce_response(
|
||||
&witness_private_elgamal_key,
|
||||
&challenge,
|
||||
&pedersen_commitments_openings.iter().collect::<Vec<_>>(),
|
||||
&elgamal_keypair.private_key().0,
|
||||
);
|
||||
let response_keys = produce_responses(&witness_keys, &challenge, ephemeral_keys);
|
||||
let response_attributes = produce_responses(
|
||||
&witness_attributes,
|
||||
&challenge,
|
||||
@@ -169,7 +185,8 @@ impl ProofCmCs {
|
||||
ProofCmCs {
|
||||
challenge,
|
||||
response_opening,
|
||||
response_openings,
|
||||
response_private_elgamal_key,
|
||||
response_keys,
|
||||
response_attributes,
|
||||
}
|
||||
}
|
||||
@@ -177,11 +194,11 @@ impl ProofCmCs {
|
||||
pub(crate) fn verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
pub_key: &elgamal::PublicKey,
|
||||
commitment: &G1Projective,
|
||||
commitments: &[G1Projective],
|
||||
public_attributes: &[Attribute],
|
||||
attributes_ciphertexts: &[elgamal::Ciphertext],
|
||||
) -> bool {
|
||||
if self.response_attributes.len() != commitments.len() {
|
||||
if self.response_keys.len() != attributes_ciphertexts.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -196,14 +213,32 @@ impl ProofCmCs {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// recompute witnesses commitments
|
||||
// Cw = (cm * c) + (rr * g1) + (rm[0] * hs[0]) + ... + (rm[n] * hs[n])
|
||||
let commitment_attributes = (commitment
|
||||
- public_attributes
|
||||
let commitment_private_key_elgamal =
|
||||
pub_key * &self.challenge + g1 * self.response_private_elgamal_key;
|
||||
|
||||
// Aw[i] = (c * c1[i]) + (rk[i] * g1)
|
||||
let commitment_keys1_bytes = attributes_ciphertexts
|
||||
.iter()
|
||||
.map(|ciphertext| ciphertext.c1())
|
||||
.zip(self.response_keys.iter())
|
||||
.map(|(c1, res_k)| c1 * self.challenge + g1 * res_k)
|
||||
.map(|witness| witness.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Bw[i] = (c * c2[i]) + (rk[i] * gamma) + (rm[i] * h)
|
||||
let commitment_keys2_bytes = izip!(
|
||||
attributes_ciphertexts
|
||||
.iter()
|
||||
.zip(params.gen_hs().iter().skip(self.response_attributes.len()))
|
||||
.map(|(pub_attr, hs)| hs * pub_attr)
|
||||
.sum::<G1Projective>())
|
||||
* self.challenge
|
||||
.map(|ciphertext| ciphertext.c2()),
|
||||
self.response_keys.iter(),
|
||||
self.response_attributes.iter()
|
||||
)
|
||||
.map(|(c2, res_key, res_attr)| c2 * self.challenge + pub_key * res_key + h * res_attr)
|
||||
.map(|witness| witness.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Cw = (cm * c) + (rr * g1) + (rm[0] * hs[0]) + ... + (rm[n] * hs[n])
|
||||
let commitment_attributes = commitment * self.challenge
|
||||
+ g1 * self.response_opening
|
||||
+ self
|
||||
.response_attributes
|
||||
@@ -212,22 +247,9 @@ impl ProofCmCs {
|
||||
.map(|(res_attr, hs)| hs * res_attr)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let commitments_attributes = izip!(
|
||||
commitments.iter(),
|
||||
self.response_openings.iter(),
|
||||
self.response_attributes.iter()
|
||||
)
|
||||
.map(|(cm_j, r_o_j, r_m_j)| cm_j * self.challenge + g1 * r_o_j + h * r_m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_bytes = commitments
|
||||
let ciphertexts_bytes = attributes_ciphertexts
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_attributes_bytes = commitments_attributes
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.map(|c| c.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// re-compute the challenge
|
||||
@@ -235,32 +257,38 @@ impl ProofCmCs {
|
||||
std::iter::once(params.gen1().to_bytes().as_ref())
|
||||
.chain(hs_bytes.iter().map(|hs| hs.as_ref()))
|
||||
.chain(std::iter::once(h.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(pub_key.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(commitment.to_bytes().as_ref()))
|
||||
.chain(commitments_bytes.iter().map(|cm| cm.as_ref()))
|
||||
.chain(std::iter::once(commitment_attributes.to_bytes().as_ref()))
|
||||
.chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())),
|
||||
.chain(std::iter::once(
|
||||
commitment_private_key_elgamal.to_bytes().as_ref(),
|
||||
))
|
||||
.chain(commitment_keys1_bytes.iter().map(|aw| aw.as_ref()))
|
||||
.chain(commitment_keys2_bytes.iter().map(|bw| bw.as_ref()))
|
||||
.chain(ciphertexts_bytes.iter().map(|c| c.as_ref())),
|
||||
);
|
||||
|
||||
challenge == self.challenge
|
||||
}
|
||||
|
||||
// challenge || response opening || openings len || response openings || attributes len ||
|
||||
// response attributes
|
||||
// challenge || response opening || response private elgamal key || keys len || response keys || attributes len || response attributes
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let openings_len = self.response_openings.len() as u64;
|
||||
let keys_len = self.response_keys.len() as u64;
|
||||
let attributes_len = self.response_attributes.len() as u64;
|
||||
|
||||
let mut bytes = Vec::with_capacity(16 + (2 + openings_len + attributes_len) as usize * 32);
|
||||
let mut bytes = Vec::with_capacity(16 + (keys_len + attributes_len + 3) as usize * 32);
|
||||
|
||||
bytes.extend_from_slice(&self.challenge.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_opening.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_private_elgamal_key.to_bytes());
|
||||
bytes.extend_from_slice(&keys_len.to_le_bytes());
|
||||
|
||||
bytes.extend_from_slice(&openings_len.to_le_bytes());
|
||||
for ro in &self.response_openings {
|
||||
bytes.extend_from_slice(&ro.to_bytes());
|
||||
for rk in &self.response_keys {
|
||||
bytes.extend_from_slice(&rk.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&attributes_len.to_le_bytes());
|
||||
|
||||
for rm in &self.response_attributes {
|
||||
bytes.extend_from_slice(&rm.to_bytes());
|
||||
}
|
||||
@@ -270,11 +298,11 @@ impl ProofCmCs {
|
||||
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
// at the very minimum there must be a single attribute being proven
|
||||
if bytes.len() < 32 * 4 + 16 || (bytes.len() - 16) % 32 != 0 {
|
||||
return Err(CoconutError::Deserialization(
|
||||
"tried to deserialize proof of commitments with bytes of invalid length"
|
||||
.to_string(),
|
||||
));
|
||||
if bytes.len() < 32 * 5 + 16 || (bytes.len() - 16) % 32 != 0 {
|
||||
return Err(
|
||||
CoconutError::Deserialization(
|
||||
"tried to deserialize proof of ciphertexts and commitment with bytes of invalid length".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
let mut idx = 0;
|
||||
@@ -282,46 +310,54 @@ impl ProofCmCs {
|
||||
idx += 32;
|
||||
let response_opening_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
let response_private_elgamal_key_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
|
||||
let challenge = try_deserialize_scalar(
|
||||
&challenge_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize challenge".to_string()),
|
||||
)?;
|
||||
|
||||
let response_opening = try_deserialize_scalar(
|
||||
&response_opening_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize the response to the random".to_string(),
|
||||
),
|
||||
)?;
|
||||
let response_private_elgamal_key = try_deserialize_scalar(
|
||||
&response_private_elgamal_key_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize the response to the private ElGamal key".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
let ro_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
|
||||
let rk_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
|
||||
idx += 8;
|
||||
if bytes[idx..].len() < ro_len as usize * 32 + 8 {
|
||||
if bytes[idx..].len() < rk_len as usize * 32 + 8 {
|
||||
return Err(
|
||||
CoconutError::Deserialization(
|
||||
"tried to deserialize proof of ciphertexts and commitment with insufficient number of bytes provided".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
let ro_end = idx + ro_len as usize * 32;
|
||||
let response_openings = try_deserialize_scalar_vec(
|
||||
ro_len,
|
||||
&bytes[idx..ro_end],
|
||||
CoconutError::Deserialization("Failed to deserialize openings response".to_string()),
|
||||
let rk_end = idx + rk_len as usize * 32;
|
||||
let response_keys = try_deserialize_scalar_vec(
|
||||
rk_len,
|
||||
&bytes[idx..rk_end],
|
||||
CoconutError::Deserialization("Failed to deserialize keys response".to_string()),
|
||||
)?;
|
||||
|
||||
let rm_len = u64::from_le_bytes(bytes[ro_end..ro_end + 8].try_into().unwrap());
|
||||
let rm_len = u64::from_le_bytes(bytes[rk_end..rk_end + 8].try_into().unwrap());
|
||||
let response_attributes = try_deserialize_scalar_vec(
|
||||
rm_len,
|
||||
&bytes[ro_end + 8..],
|
||||
&bytes[rk_end + 8..],
|
||||
CoconutError::Deserialization("Failed to deserialize attributes response".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(ProofCmCs {
|
||||
challenge,
|
||||
response_opening,
|
||||
response_openings,
|
||||
response_private_elgamal_key,
|
||||
response_keys,
|
||||
response_attributes,
|
||||
})
|
||||
}
|
||||
@@ -356,7 +392,7 @@ impl ProofKappaZeta {
|
||||
let witness_attributes = vec![witness_serial_number, witness_binding_number];
|
||||
|
||||
let beta_bytes = verification_key
|
||||
.beta_g2
|
||||
.beta
|
||||
.iter()
|
||||
.map(|beta_i| beta_i.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
@@ -367,7 +403,7 @@ impl ProofKappaZeta {
|
||||
+ verification_key.alpha
|
||||
+ witness_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.zip(verification_key.beta.iter())
|
||||
.map(|(wm_i, beta_i)| beta_i * wm_i)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
@@ -411,7 +447,7 @@ impl ProofKappaZeta {
|
||||
zeta: &G2Projective,
|
||||
) -> bool {
|
||||
let beta_bytes = verification_key
|
||||
.beta_g2
|
||||
.beta
|
||||
.iter()
|
||||
.map(|beta_i| beta_i.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
@@ -424,7 +460,7 @@ impl ProofKappaZeta {
|
||||
+ verification_key.alpha * (Scalar::one() - self.challenge)
|
||||
+ response_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.zip(verification_key.beta.iter())
|
||||
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
@@ -514,6 +550,7 @@ mod tests {
|
||||
use group::Group;
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::scheme::issuance::{compute_attribute_encryption, compute_commitment_hash};
|
||||
use crate::scheme::keygen::keygen;
|
||||
use crate::scheme::setup::setup;
|
||||
use crate::scheme::verification::{compute_kappa, compute_zeta};
|
||||
@@ -523,32 +560,51 @@ mod tests {
|
||||
#[test]
|
||||
fn proof_cm_cs_bytes_roundtrip() {
|
||||
let mut rng = thread_rng();
|
||||
let mut params = setup(1).unwrap();
|
||||
|
||||
let params = setup(1).unwrap();
|
||||
let cm = G1Projective::random(&mut rng);
|
||||
let r = params.random_scalar();
|
||||
let cms: [G1Projective; 1] = [G1Projective::random(&mut rng)];
|
||||
let rs = params.n_random_scalars(1);
|
||||
let elgamal_keypair = elgamal::elgamal_keygen(¶ms);
|
||||
let private_attributes = params.n_random_scalars(1);
|
||||
|
||||
// we don't care about 'correctness' of the proof. only whether we can correctly recover it from bytes
|
||||
let cm = G1Projective::random(&mut rng);
|
||||
let r = params.random_scalar();
|
||||
|
||||
let commitment_hash = compute_commitment_hash(cm);
|
||||
let (attributes_ciphertexts, _): (Vec<_>, Vec<_>) = compute_attribute_encryption(
|
||||
¶ms,
|
||||
private_attributes.as_ref(),
|
||||
elgamal_keypair.public_key(),
|
||||
commitment_hash,
|
||||
);
|
||||
let ephemeral_keys = params.n_random_scalars(1);
|
||||
|
||||
// 0 public 1 private
|
||||
let pi_s = ProofCmCs::construct(¶ms, &cm, &r, &cms, &rs, &private_attributes);
|
||||
let pi_s = ProofCmCs::construct(
|
||||
&mut params,
|
||||
&elgamal_keypair,
|
||||
&ephemeral_keys,
|
||||
&cm,
|
||||
&r,
|
||||
&private_attributes,
|
||||
&*attributes_ciphertexts,
|
||||
);
|
||||
|
||||
let bytes = pi_s.to_bytes();
|
||||
assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s);
|
||||
|
||||
let params = setup(2).unwrap();
|
||||
let cm = G1Projective::random(&mut rng);
|
||||
let r = params.random_scalar();
|
||||
let cms: [G1Projective; 2] = [
|
||||
G1Projective::random(&mut rng),
|
||||
G1Projective::random(&mut rng),
|
||||
];
|
||||
let rs = params.n_random_scalars(2);
|
||||
// 2 private
|
||||
let private_attributes = params.n_random_scalars(2);
|
||||
let ephemeral_keys = params.n_random_scalars(2);
|
||||
|
||||
// 0 public 2 privates
|
||||
let pi_s = ProofCmCs::construct(¶ms, &cm, &r, &cms, &rs, &private_attributes);
|
||||
let pi_s = ProofCmCs::construct(
|
||||
&mut params,
|
||||
&elgamal_keypair,
|
||||
&ephemeral_keys,
|
||||
&cm,
|
||||
&r,
|
||||
&private_attributes,
|
||||
&*attributes_ciphertexts,
|
||||
);
|
||||
|
||||
let bytes = pi_s.to_bytes();
|
||||
assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s);
|
||||
@@ -556,9 +612,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn proof_kappa_zeta_bytes_roundtrip() {
|
||||
let params = setup(4).unwrap();
|
||||
let mut params = setup(4).unwrap();
|
||||
|
||||
let keypair = keygen(¶ms);
|
||||
let keypair = keygen(&mut params);
|
||||
|
||||
// we don't care about 'correctness' of the proof. only whether we can correctly recover it from bytes
|
||||
let serial_number = params.random_scalar();
|
||||
@@ -571,7 +627,7 @@ mod tests {
|
||||
|
||||
// 0 public 2 private
|
||||
let pi_v = ProofKappaZeta::construct(
|
||||
¶ms,
|
||||
&mut params,
|
||||
&keypair.verification_key(),
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
@@ -586,11 +642,11 @@ mod tests {
|
||||
assert_eq!(proof_from_bytes, pi_v);
|
||||
|
||||
// 2 public 2 private
|
||||
let params = setup(4).unwrap();
|
||||
let keypair = keygen(¶ms);
|
||||
let mut params = setup(4).unwrap();
|
||||
let keypair = keygen(&mut params);
|
||||
|
||||
let pi_v = ProofKappaZeta::construct(
|
||||
¶ms,
|
||||
&mut params,
|
||||
&keypair.verification_key(),
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
|
||||
@@ -64,8 +64,7 @@ impl Aggregatable for PartialSignature {
|
||||
|
||||
/// Ensures all provided verification keys were generated to verify the same number of attributes.
|
||||
fn check_same_key_size(keys: &[VerificationKey]) -> bool {
|
||||
keys.iter().map(|vk| vk.beta_g1.len()).all_equal()
|
||||
&& keys.iter().map(|vk| vk.beta_g2.len()).all_equal()
|
||||
keys.iter().map(|vk| vk.beta.len()).all_equal()
|
||||
}
|
||||
|
||||
pub fn aggregate_verification_keys(
|
||||
@@ -99,7 +98,7 @@ pub fn aggregate_signatures(
|
||||
|
||||
let tmp = attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.zip(verification_key.beta.iter())
|
||||
.map(|(attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
@@ -150,8 +149,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn key_aggregation_works_for_any_subset_of_keys() {
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
let mut params = Parameters::new(2).unwrap();
|
||||
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
|
||||
|
||||
let vks = keypairs
|
||||
.into_iter()
|
||||
@@ -214,7 +213,7 @@ mod tests {
|
||||
let mut params = Parameters::new(2).unwrap();
|
||||
let attributes = params.n_random_scalars(2);
|
||||
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
|
||||
|
||||
let (sks, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
@@ -311,9 +310,9 @@ mod tests {
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_for_empty_set_of_signatures() {
|
||||
let signatures: Vec<Signature> = vec![];
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let mut params = Parameters::new(2).unwrap();
|
||||
let attributes = params.n_random_scalars(2);
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
|
||||
|
||||
let (_, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
@@ -329,9 +328,9 @@ mod tests {
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_if_indices_have_invalid_length() {
|
||||
let signatures = vec![random_signature()];
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let mut params = Parameters::new(2).unwrap();
|
||||
let attributes = params.n_random_scalars(2);
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
|
||||
let (_, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| (keypair.secret_key(), keypair.verification_key()))
|
||||
@@ -355,9 +354,9 @@ mod tests {
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_for_non_unique_indices() {
|
||||
let signatures = vec![random_signature(), random_signature()];
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let mut params = Parameters::new(2).unwrap();
|
||||
let attributes = params.n_random_scalars(2);
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
|
||||
let (_, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| (keypair.secret_key(), keypair.verification_key()))
|
||||
|
||||
@@ -7,15 +7,16 @@ use std::convert::TryInto;
|
||||
use bls12_381::{G1Affine, G1Projective, Scalar};
|
||||
use group::{Curve, GroupEncoding};
|
||||
|
||||
use crate::elgamal::{Ciphertext, EphemeralKey};
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::proofs::ProofCmCs;
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::scheme::BlindedSignature;
|
||||
use crate::scheme::SecretKey;
|
||||
use crate::Attribute;
|
||||
/// Creates a Coconut Signature under a given secret key on a set of public attributes only.
|
||||
#[cfg(test)]
|
||||
use crate::Signature;
|
||||
use crate::{elgamal, Attribute, ElGamalKeyPair};
|
||||
// TODO: possibly completely remove those two functions.
|
||||
// They only exist to have a simpler and smaller code snippets to test
|
||||
// basic functionalities.
|
||||
@@ -32,7 +33,7 @@ pub struct BlindSignRequest {
|
||||
// h
|
||||
commitment_hash: G1Projective,
|
||||
// c
|
||||
private_attributes_commitments: Vec<G1Projective>,
|
||||
private_attributes_ciphertexts: Vec<elgamal::Ciphertext>,
|
||||
// pi_s
|
||||
pi_s: ProofCmCs,
|
||||
}
|
||||
@@ -41,9 +42,9 @@ impl TryFrom<&[u8]> for BlindSignRequest {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<BlindSignRequest> {
|
||||
if bytes.len() < 48 + 48 + 8 + 48 {
|
||||
if bytes.len() < 48 + 48 + 8 + 96 {
|
||||
return Err(CoconutError::DeserializationMinLength {
|
||||
min: 48 + 48 + 8 + 48,
|
||||
min: 48 + 48 + 8 + 96,
|
||||
actual: bytes.len(),
|
||||
});
|
||||
}
|
||||
@@ -72,35 +73,26 @@ impl TryFrom<&[u8]> for BlindSignRequest {
|
||||
|
||||
let c_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
|
||||
j += 8;
|
||||
if bytes[j..].len() < c_len as usize * 48 {
|
||||
if bytes[j..].len() < c_len as usize * 96 {
|
||||
return Err(CoconutError::DeserializationMinLength {
|
||||
min: c_len as usize * 48,
|
||||
min: c_len as usize * 96,
|
||||
actual: bytes[56..].len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut private_attributes_commitments = Vec::with_capacity(c_len as usize);
|
||||
let mut private_attributes_ciphertexts = Vec::with_capacity(c_len as usize);
|
||||
for i in 0..c_len as usize {
|
||||
let start = j + i * 48;
|
||||
let end = start + 48;
|
||||
|
||||
let private_attributes_commitment_bytes = bytes[start..end].try_into().unwrap();
|
||||
let private_attributes_commitment = try_deserialize_g1_projective(
|
||||
&private_attributes_commitment_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize compressed commitment".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
private_attributes_commitments.push(private_attributes_commitment)
|
||||
let start = j + i * 96;
|
||||
let end = start + 96;
|
||||
private_attributes_ciphertexts.push(Ciphertext::try_from(&bytes[start..end])?)
|
||||
}
|
||||
|
||||
let pi_s = ProofCmCs::from_bytes(&bytes[j + c_len as usize * 48..])?;
|
||||
let pi_s = ProofCmCs::from_bytes(&bytes[j + c_len as usize * 96..])?;
|
||||
|
||||
Ok(BlindSignRequest {
|
||||
commitment,
|
||||
commitment_hash,
|
||||
private_attributes_commitments,
|
||||
private_attributes_ciphertexts,
|
||||
pi_s,
|
||||
})
|
||||
}
|
||||
@@ -110,16 +102,16 @@ impl Bytable for BlindSignRequest {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
let cm_bytes = self.commitment.to_affine().to_compressed();
|
||||
let cm_hash_bytes = self.commitment_hash.to_affine().to_compressed();
|
||||
let c_len = self.private_attributes_commitments.len() as u64;
|
||||
let c_len = self.private_attributes_ciphertexts.len() as u64;
|
||||
let proof_bytes = self.pi_s.to_bytes();
|
||||
|
||||
let mut bytes = Vec::with_capacity(48 + 48 + 8 + c_len as usize * 48 + proof_bytes.len());
|
||||
let mut bytes = Vec::with_capacity(48 + 48 + 8 + c_len as usize * 96 + proof_bytes.len());
|
||||
|
||||
bytes.extend_from_slice(&cm_bytes);
|
||||
bytes.extend_from_slice(&cm_hash_bytes);
|
||||
bytes.extend_from_slice(&c_len.to_le_bytes());
|
||||
for c in &self.private_attributes_commitments {
|
||||
bytes.extend_from_slice(&c.to_affine().to_compressed());
|
||||
for c in &self.private_attributes_ciphertexts {
|
||||
bytes.extend_from_slice(&c.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&proof_bytes);
|
||||
@@ -135,12 +127,12 @@ impl Bytable for BlindSignRequest {
|
||||
impl Base58 for BlindSignRequest {}
|
||||
|
||||
impl BlindSignRequest {
|
||||
fn verify_proof(&self, params: &Parameters, public_attributes: &[Attribute]) -> bool {
|
||||
fn verify_proof(&self, params: &Parameters, pub_key: &elgamal::PublicKey) -> bool {
|
||||
self.pi_s.verify(
|
||||
params,
|
||||
pub_key,
|
||||
&self.commitment,
|
||||
&self.private_attributes_commitments,
|
||||
public_attributes,
|
||||
&self.private_attributes_ciphertexts,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -148,10 +140,6 @@ impl BlindSignRequest {
|
||||
self.commitment_hash
|
||||
}
|
||||
|
||||
pub fn get_private_attributes_pedersen_commitments(&self) -> Vec<G1Projective> {
|
||||
self.private_attributes_commitments.clone()
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
self.to_byte_vec()
|
||||
}
|
||||
@@ -161,19 +149,17 @@ impl BlindSignRequest {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_attributes_commitment(
|
||||
pub fn compute_private_attributes_commitment(
|
||||
params: &Parameters,
|
||||
private_attributes: &[Attribute],
|
||||
public_attributes: &[Attribute],
|
||||
hs: &[G1Affine],
|
||||
) -> (Scalar, G1Projective) {
|
||||
let commitment_opening = params.random_scalar();
|
||||
|
||||
// Produces h0 ^ m0 * h1^m1 * .... * hn^mn
|
||||
// where m0, m1, ...., mn are attributes
|
||||
// where m0, m1, ...., mn are private attributes
|
||||
let attr_cm = private_attributes
|
||||
.iter()
|
||||
.chain(public_attributes.iter())
|
||||
.zip(hs)
|
||||
.map(|(&m, h)| h * m)
|
||||
.sum::<G1Projective>();
|
||||
@@ -183,34 +169,29 @@ pub fn compute_attributes_commitment(
|
||||
(commitment_opening, commitment)
|
||||
}
|
||||
|
||||
pub fn compute_pedersen_commitments_for_private_attributes(
|
||||
params: &Parameters,
|
||||
private_attributes: &[Attribute],
|
||||
h: &G1Projective,
|
||||
) -> (Vec<Scalar>, Vec<G1Projective>) {
|
||||
// Generate openings for Pedersen commitment for each private attribute
|
||||
let commitments_openings = params.n_random_scalars(private_attributes.len());
|
||||
|
||||
// Compute Pedersen commitment for each private attribute
|
||||
let pedersen_commitments = commitments_openings
|
||||
.iter()
|
||||
.zip(private_attributes.iter())
|
||||
.map(|(o_j, m_j)| params.gen1() * o_j + h * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
(commitments_openings, pedersen_commitments)
|
||||
}
|
||||
|
||||
pub fn compute_commitment_hash(commitment: G1Projective) -> G1Projective {
|
||||
hash_g1(commitment.to_bytes())
|
||||
}
|
||||
|
||||
pub fn compute_attribute_encryption(
|
||||
params: &Parameters,
|
||||
private_attributes: &[Attribute],
|
||||
pub_key: &elgamal::PublicKey,
|
||||
commitment_hash: G1Projective,
|
||||
) -> (Vec<Ciphertext>, Vec<EphemeralKey>) {
|
||||
private_attributes
|
||||
.iter()
|
||||
.map(|m| pub_key.encrypt(params, &commitment_hash, m))
|
||||
.unzip()
|
||||
}
|
||||
|
||||
/// Builds cryptographic material required for blind sign.
|
||||
pub fn prepare_blind_sign(
|
||||
params: &Parameters,
|
||||
elgamal_keypair: &ElGamalKeyPair,
|
||||
private_attributes: &[Attribute],
|
||||
public_attributes: &[Attribute],
|
||||
) -> Result<(Vec<Scalar>, BlindSignRequest)> {
|
||||
) -> Result<BlindSignRequest> {
|
||||
if private_attributes.is_empty() {
|
||||
return Err(CoconutError::Issuance(
|
||||
"Tried to prepare blind sign request for an empty set of private attributes"
|
||||
@@ -227,45 +208,45 @@ pub fn prepare_blind_sign(
|
||||
}
|
||||
|
||||
let (commitment_opening, commitment) =
|
||||
compute_attributes_commitment(params, private_attributes, public_attributes, hs);
|
||||
compute_private_attributes_commitment(params, private_attributes, hs);
|
||||
|
||||
// Compute the challenge as the commitment hash
|
||||
let commitment_hash = compute_commitment_hash(commitment);
|
||||
|
||||
let (pedersen_commitments_openings, pedersen_commitments) =
|
||||
compute_pedersen_commitments_for_private_attributes(
|
||||
// build ElGamal encryption
|
||||
let (private_attributes_ciphertexts, ephemeral_keys): (Vec<_>, Vec<_>) =
|
||||
compute_attribute_encryption(
|
||||
params,
|
||||
private_attributes,
|
||||
&commitment_hash,
|
||||
elgamal_keypair.public_key(),
|
||||
commitment_hash,
|
||||
);
|
||||
|
||||
let pi_s = ProofCmCs::construct(
|
||||
params,
|
||||
elgamal_keypair,
|
||||
&ephemeral_keys,
|
||||
&commitment,
|
||||
&commitment_opening,
|
||||
&pedersen_commitments,
|
||||
&pedersen_commitments_openings,
|
||||
private_attributes,
|
||||
&*private_attributes_ciphertexts,
|
||||
);
|
||||
|
||||
Ok((
|
||||
pedersen_commitments_openings,
|
||||
BlindSignRequest {
|
||||
commitment,
|
||||
commitment_hash,
|
||||
private_attributes_commitments: pedersen_commitments,
|
||||
pi_s,
|
||||
},
|
||||
))
|
||||
Ok(BlindSignRequest {
|
||||
commitment,
|
||||
commitment_hash,
|
||||
private_attributes_ciphertexts,
|
||||
pi_s,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn blind_sign(
|
||||
params: &Parameters,
|
||||
signing_secret_key: &SecretKey,
|
||||
prover_pub_key: &elgamal::PublicKey,
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
public_attributes: &[Attribute],
|
||||
) -> Result<BlindedSignature> {
|
||||
let num_private = blind_sign_request.private_attributes_commitments.len();
|
||||
let num_private = blind_sign_request.private_attributes_ciphertexts.len();
|
||||
let hs = params.gen_hs();
|
||||
|
||||
if num_private + public_attributes.len() > hs.len() {
|
||||
@@ -284,7 +265,7 @@ pub fn blind_sign(
|
||||
}
|
||||
|
||||
// Verify the ZK proof
|
||||
if !blind_sign_request.verify_proof(params, public_attributes) {
|
||||
if !blind_sign_request.verify_proof(params, prover_pub_key) {
|
||||
return Err(CoconutError::Issuance(
|
||||
"Failed to verify the proof of knowledge".to_string(),
|
||||
));
|
||||
@@ -299,17 +280,27 @@ pub fn blind_sign(
|
||||
.map(|(attr, yi)| attr * yi)
|
||||
.sum::<Scalar>();
|
||||
|
||||
// h ^ x + c[0] ^ y[0] + ... c[m] ^ y[m] + h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n])
|
||||
let sig = blind_sign_request
|
||||
.private_attributes_commitments
|
||||
// c1[0] ^ y[0] * ... * c1[m] ^ y[m]
|
||||
let sig_1 = blind_sign_request
|
||||
.private_attributes_ciphertexts
|
||||
.iter()
|
||||
.map(|ciphertext| ciphertext.c1())
|
||||
.zip(signing_secret_key.ys.iter())
|
||||
.map(|(c, yi)| c * yi)
|
||||
.map(|(c1, yi)| c1 * yi)
|
||||
.sum();
|
||||
|
||||
// h ^ x + c2[0] ^ y[0] + ... c2[m] ^ y[m] + h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n])
|
||||
let sig_2 = blind_sign_request
|
||||
.private_attributes_ciphertexts
|
||||
.iter()
|
||||
.map(|ciphertext| ciphertext.c2())
|
||||
.zip(signing_secret_key.ys.iter())
|
||||
.map(|(c2, yi)| c2 * yi)
|
||||
.chain(std::iter::once(h * signing_secret_key.x))
|
||||
.chain(std::iter::once(signed_public))
|
||||
.sum();
|
||||
|
||||
Ok(BlindedSignature(h, sig))
|
||||
Ok(BlindedSignature(h, elgamal::Ciphertext(sig_1, sig_2)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -351,27 +342,36 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn blind_sign_request_bytes_roundtrip() {
|
||||
// 0 public and 1 private attribute
|
||||
let params = Parameters::new(1).unwrap();
|
||||
let private_attributes = params.n_random_scalars(1);
|
||||
let mut params = Parameters::new(1).unwrap();
|
||||
let public_attributes = params.n_random_scalars(0);
|
||||
let private_attributes = params.n_random_scalars(1);
|
||||
let elgamal_keypair = elgamal::elgamal_keygen(¶ms);
|
||||
|
||||
let (_commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
let lambda = prepare_blind_sign(
|
||||
&mut params,
|
||||
&elgamal_keypair,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let bytes = lambda.to_bytes();
|
||||
println!("{:?}", bytes.len());
|
||||
assert_eq!(
|
||||
BlindSignRequest::try_from(bytes.as_slice()).unwrap(),
|
||||
lambda
|
||||
);
|
||||
|
||||
// 2 public and 2 private attributes
|
||||
let params = Parameters::new(4).unwrap();
|
||||
let private_attributes = params.n_random_scalars(2);
|
||||
let mut params = Parameters::new(4).unwrap();
|
||||
let public_attributes = params.n_random_scalars(2);
|
||||
|
||||
let (_commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
let private_attributes = params.n_random_scalars(2);
|
||||
let lambda = prepare_blind_sign(
|
||||
&mut params,
|
||||
&elgamal_keypair,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let bytes = lambda.to_bytes();
|
||||
assert_eq!(
|
||||
|
||||
@@ -7,7 +7,7 @@ use core::ops::{Add, Mul};
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use bls12_381::{G1Projective, G2Projective, Scalar};
|
||||
use bls12_381::{G2Projective, Scalar};
|
||||
use group::Curve;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
@@ -17,8 +17,7 @@ use crate::scheme::setup::Parameters;
|
||||
use crate::scheme::SignerIndex;
|
||||
use crate::traits::Bytable;
|
||||
use crate::utils::{
|
||||
try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
|
||||
try_deserialize_scalar_vec, Polynomial,
|
||||
try_deserialize_g2_projective, try_deserialize_scalar, try_deserialize_scalar_vec, Polynomial,
|
||||
};
|
||||
use crate::Base58;
|
||||
|
||||
@@ -33,7 +32,6 @@ impl TryFrom<&[u8]> for SecretKey {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<SecretKey> {
|
||||
// There should be x and at least one y
|
||||
if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 {
|
||||
return Err(CoconutError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
@@ -73,18 +71,16 @@ impl TryFrom<&[u8]> for SecretKey {
|
||||
impl SecretKey {
|
||||
/// Derive verification key using this secret key.
|
||||
pub fn verification_key(&self, params: &Parameters) -> VerificationKey {
|
||||
let g1 = params.gen1();
|
||||
let g2 = params.gen2();
|
||||
VerificationKey {
|
||||
alpha: g2 * self.x,
|
||||
beta_g1: self.ys.iter().map(|y| g1 * y).collect(),
|
||||
beta_g2: self.ys.iter().map(|y| g2 * y).collect(),
|
||||
beta: self.ys.iter().map(|y| g2 * y).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
// x || ys.len() || ys
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let ys_len = self.ys.len();
|
||||
let ys_len = self.ys.len() as u64;
|
||||
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) as usize * 32);
|
||||
|
||||
bytes.extend_from_slice(&self.x.to_bytes());
|
||||
@@ -118,36 +114,33 @@ impl Base58 for SecretKey {}
|
||||
pub struct VerificationKey {
|
||||
// TODO add gen2 as per the paper or imply it from the fact library is using bls381?
|
||||
pub(crate) alpha: G2Projective,
|
||||
pub(crate) beta_g1: Vec<G1Projective>,
|
||||
pub(crate) beta_g2: Vec<G2Projective>,
|
||||
pub(crate) beta: Vec<G2Projective>,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for VerificationKey {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<VerificationKey> {
|
||||
// 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 {
|
||||
if bytes.len() < 96 * 2 + 8 || (bytes.len() - 8) % 96 != 0 {
|
||||
return Err(CoconutError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len() - 8 - 96,
|
||||
target: 96 * 2 + 48 + 8,
|
||||
modulus: 96 + 48,
|
||||
object: "verification key".to_string(),
|
||||
modulus_target: bytes.len() - 8,
|
||||
target: 96 * 2 + 8,
|
||||
modulus: 96,
|
||||
object: "secret key".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// this conversion will not fail as we are taking the same length of data
|
||||
let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
|
||||
let betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
|
||||
let beta_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
|
||||
let actual_beta_len = (bytes.len() - 104) / 96;
|
||||
|
||||
let actual_betas_len = (bytes.len() - 104) / (96 + 48);
|
||||
|
||||
if betas_len as usize != actual_betas_len {
|
||||
if beta_len as usize != actual_beta_len {
|
||||
return Err(
|
||||
CoconutError::Deserialization(
|
||||
format!("Tried to deserialize verification key with inconsistent betas len (expected {}, got {})",
|
||||
betas_len, actual_betas_len
|
||||
format!("Tried to deserialize verification key with inconsistent beta len (expected {}, got {})",
|
||||
beta_len, actual_beta_len
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -158,27 +151,10 @@ impl TryFrom<&[u8]> for VerificationKey {
|
||||
),
|
||||
)?;
|
||||
|
||||
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,
|
||||
CoconutError::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 mut beta = Vec::with_capacity(actual_beta_len);
|
||||
for i in 0..actual_beta_len {
|
||||
let start = 104 + i * 96;
|
||||
let end = start + 96;
|
||||
let beta_i_bytes = bytes[start..end].try_into().unwrap();
|
||||
let beta_i = try_deserialize_g2_projective(
|
||||
&beta_i_bytes,
|
||||
@@ -187,14 +163,10 @@ impl TryFrom<&[u8]> for VerificationKey {
|
||||
),
|
||||
)?;
|
||||
|
||||
beta_g2.push(beta_i)
|
||||
beta.push(beta_i)
|
||||
}
|
||||
|
||||
Ok(VerificationKey {
|
||||
alpha,
|
||||
beta_g1,
|
||||
beta_g2,
|
||||
})
|
||||
Ok(VerificationKey { alpha, beta })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,42 +179,18 @@ impl<'b> Add<&'b VerificationKey> for VerificationKey {
|
||||
// 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"
|
||||
self.beta.len(),
|
||||
rhs.beta.len(),
|
||||
"trying to add verification keys generated for different number of attributes"
|
||||
);
|
||||
|
||||
VerificationKey {
|
||||
alpha: self.alpha + rhs.alpha,
|
||||
beta_g1: self
|
||||
.beta_g1
|
||||
beta: self
|
||||
.beta
|
||||
.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)
|
||||
.zip(rhs.beta.iter())
|
||||
.map(|(self_beta, rhs_beta)| self_beta + rhs_beta)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
@@ -255,8 +203,7 @@ impl<'a> Mul<Scalar> for &'a VerificationKey {
|
||||
fn mul(self, rhs: Scalar) -> Self::Output {
|
||||
VerificationKey {
|
||||
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(),
|
||||
beta: self.beta.iter().map(|b_i| b_i * rhs).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -272,7 +219,7 @@ where
|
||||
{
|
||||
let mut peekable = iter.peekable();
|
||||
let head_attributes = match peekable.peek() {
|
||||
Some(head) => head.borrow().beta_g2.len(),
|
||||
Some(head) => head.borrow().beta.len(),
|
||||
None => {
|
||||
// TODO: this is a really weird edge case. You're trying to sum an EMPTY iterator
|
||||
// of VerificationKey. So should it panic here or just return some nonsense value?
|
||||
@@ -292,8 +239,7 @@ impl VerificationKey {
|
||||
pub(crate) fn identity(beta_size: usize) -> Self {
|
||||
VerificationKey {
|
||||
alpha: G2Projective::identity(),
|
||||
beta_g1: vec![G1Projective::identity(); beta_size],
|
||||
beta_g2: vec![G2Projective::identity(); beta_size],
|
||||
beta: vec![G2Projective::identity(); beta_size],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,31 +251,19 @@ impl VerificationKey {
|
||||
&self.alpha
|
||||
}
|
||||
|
||||
pub fn beta_g1(&self) -> &Vec<G1Projective> {
|
||||
&self.beta_g1
|
||||
}
|
||||
|
||||
pub fn beta_g2(&self) -> &Vec<G2Projective> {
|
||||
&self.beta_g2
|
||||
pub fn beta(&self) -> &Vec<G2Projective> {
|
||||
&self.beta
|
||||
}
|
||||
|
||||
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);
|
||||
let beta_len = self.beta.len() as u64;
|
||||
let mut bytes = Vec::with_capacity(8 + (beta_len + 1) as usize * 96);
|
||||
|
||||
bytes.extend_from_slice(&self.alpha.to_affine().to_compressed());
|
||||
|
||||
bytes.extend_from_slice(&beta_g1_len.to_le_bytes());
|
||||
|
||||
for beta_g1 in self.beta_g1.iter() {
|
||||
bytes.extend_from_slice(&beta_g1.to_affine().to_compressed())
|
||||
bytes.extend_from_slice(&beta_len.to_le_bytes());
|
||||
for beta in self.beta.iter() {
|
||||
bytes.extend_from_slice(&beta.to_affine().to_compressed())
|
||||
}
|
||||
|
||||
for beta_g2 in self.beta_g2.iter() {
|
||||
bytes.extend_from_slice(&beta_g2.to_affine().to_compressed())
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
@@ -554,11 +488,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn keypair_bytes_roundtrip() {
|
||||
let params1 = setup(1).unwrap();
|
||||
let params5 = setup(5).unwrap();
|
||||
let mut params1 = setup(1).unwrap();
|
||||
let mut params5 = setup(5).unwrap();
|
||||
|
||||
let keypair1 = keygen(¶ms1);
|
||||
let keypair5 = keygen(¶ms5);
|
||||
let keypair1 = keygen(&mut params1);
|
||||
let keypair5 = keygen(&mut params5);
|
||||
|
||||
let bytes1 = keypair1.to_bytes();
|
||||
let bytes5 = keypair5.to_bytes();
|
||||
@@ -569,11 +503,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn secret_key_bytes_roundtrip() {
|
||||
let params1 = setup(1).unwrap();
|
||||
let params5 = setup(5).unwrap();
|
||||
let mut params1 = setup(1).unwrap();
|
||||
let mut params5 = setup(5).unwrap();
|
||||
|
||||
let keypair1 = keygen(¶ms1);
|
||||
let keypair5 = keygen(¶ms5);
|
||||
let keypair1 = keygen(&mut params1);
|
||||
let keypair5 = keygen(&mut params5);
|
||||
|
||||
let bytes1 = keypair1.secret_key.to_bytes();
|
||||
let bytes5 = keypair5.secret_key.to_bytes();
|
||||
@@ -584,11 +518,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn verification_key_bytes_roundtrip() {
|
||||
let params1 = setup(1).unwrap();
|
||||
let params5 = setup(5).unwrap();
|
||||
let mut params1 = setup(1).unwrap();
|
||||
let mut params5 = setup(5).unwrap();
|
||||
|
||||
let keypair1 = &keygen(¶ms1);
|
||||
let keypair5 = &keygen(¶ms5);
|
||||
let keypair1 = &keygen(&mut params1);
|
||||
let keypair5 = &keygen(&mut params5);
|
||||
|
||||
let bytes1: Vec<u8> = keypair1.verification_key.to_bytes();
|
||||
let bytes5: Vec<u8> = keypair5.verification_key.to_bytes();
|
||||
|
||||