Compare commits

..

124 Commits

Author SHA1 Message Date
aniampio 5b66701c5d Optimize the identification function 2023-10-17 14:13:23 +01:00
aniampio 3a8b99db10 Add byte calculation for divisible ecash 2023-08-07 21:19:25 +01:00
aniampio 6e7cf42831 Add Wallet, Partial Wallet and Payment to and from bytes converstion 2023-08-07 11:47:38 +01:00
aniampio 0e42f977ee Increase benchmark duration 2023-01-09 14:11:49 +00:00
aniampio ef379cdeb3 Update in divisible ecash benchmark 2022-09-27 13:13:01 +01:00
aniampio 4886312e43 Benchmarks: change the number of public keys 2022-08-31 12:51:04 +03:00
aniampio 3a152366d2 Update benchmarks 2022-08-31 12:39:41 +03:00
aniampio d920bcb2f4 Update compact benchmarks 2022-08-31 11:55:15 +03:00
aniampio 268a1817ab Remove spend verification from the identify function 2022-08-10 18:57:00 +01:00
aniampio f5c493500f Update benchmarks 2022-08-03 19:58:50 +01:00
aniampio 7533f304ff Update benchmarks 2022-08-03 18:58:52 +01:00
aniampio 755d4f2388 Cleaning warnings 2022-08-03 16:03:07 +01:00
aniampio 6884395daa Update for compact e-cash - remove requirement for security tag 2022-08-03 15:46:15 +01:00
aniampio 5299b94f9e Add pairing and exponent benchmarks 2022-07-22 17:11:05 +01:00
aniampio f701787802 Update benchmarks 2022-07-20 14:19:23 +01:00
aniampio 0b58e0ae24 Small updates and code cleaning 2022-07-20 12:19:19 +01:00
aniampio 03226d1487 Add verification of the payments inside identify; speed up the identify checks 2022-07-19 13:18:42 +01:00
aniampio 534f8c6b99 Update the spend function for compact ecash - multi spend 2022-07-15 01:45:58 +01:00
aniampio e544a6d2fa Add all cases for identification 2022-07-15 00:34:27 +01:00
aniampio d696535da5 Add duplicate payinfo check for identify 2022-07-14 23:33:15 +01:00
aniampio 7e444b7c42 Update spend function in the compact ecash 2022-07-14 21:12:26 +01:00
aniampio 0d377d98a7 Remove unused constant 2022-07-06 00:08:42 +01:00
aniampio 5bdbee6dae Fix in the identify function 2022-06-30 18:14:59 +01:00
aniampio 014374e33e Add tests to indetify protocol 2022-06-28 15:35:07 +01:00
aniampio 9adb4fbbbc Add test for identification - case of no double spending 2022-06-23 16:56:59 +01:00
aniampio 5293766856 Add aggregation and e2e test 2022-06-23 14:49:01 +01:00
aniampio a40bb3ae4f Fix the eq 15 for the spend proof 2022-06-09 13:08:21 +01:00
aniampio 226c37cfea Update the verification function for structure preserving signature 2022-06-07 22:17:12 +01:00
aniampio ea8f36dfd2 Add draft of signature verification function 2022-05-26 13:19:41 +01:00
aniampio b466cb6b3d Move the index for signature retrival 2022-05-26 13:12:05 +01:00
aniampio bb0328137a 3 out of 4 pairing tests in the zk proof pass 2022-05-26 13:06:44 +01:00
aniampio e07974ad2b Updates for the assertion and pow function 2022-05-25 17:06:32 +01:00
aniampio e8bb96b7d0 Add assertion check 2022-05-25 14:57:44 +01:00
aniampio 9bd06dbfcc Fix signatures tau_l 2022-05-25 00:28:52 +01:00
aniampio 055b10f7f2 Shifting indices in setup - eq 4 test still passing 2022-05-24 23:53:58 +01:00
aniampio 44ca339ee5 Add eq checks 2022-05-24 23:31:40 +01:00
aniampio b6a43787b3 Add bilinear equations into the zk proof; the last eq passes the tests 2022-05-24 21:16:43 +01:00
aniampio 9cb22d9bde Define spend instance and witness structs 2022-05-12 20:57:16 +01:00
aniampio 68485f8998 Add spend and spend verification functions; fix breaking test for proof of withdrawal 2022-05-10 13:21:53 +01:00
aniampio 9c22c082c0 Draft of the spend function 2022-05-02 17:28:08 +01:00
aniampio 28d7400304 Add issuance and issuance verification - but one of the tests is failing 2022-04-28 13:05:54 +01:00
aniampio 056d189e0e Add key generation functions 2022-04-25 17:12:09 +03:00
aniampio b76eab2363 Update the structure preserving signature to work in the setup function as well 2022-04-25 15:39:10 +03:00
aniampio b8a2ac5719 Rename the package 2022-04-20 13:45:51 +03:00
aniampio c861e40047 Run cargo fmt 2022-04-20 09:58:46 +03:00
aniampio 20426d001e Add explanaition of the benchmark flag 2022-04-20 09:56:28 +03:00
aniampio 2eed9fd247 Add benchmarks 2022-04-19 22:46:58 +03:00
aniampio a1887356dc Run cargo fmt 2022-04-19 19:33:08 +03:00
aniampio ef2420f847 Code cleaning, part 2 2022-04-19 19:32:50 +03:00
aniampio 6f26475055 Code cleaning 2022-04-19 19:11:23 +03:00
aniampio b9bf6e4521 Run cargo fmt 2022-04-19 18:31:50 +03:00
aniampio 7e392ff6c3 Rename capital letter variables 2022-04-19 18:31:21 +03:00
aniampio 9e834ebaef Run cargo fmt 2022-04-19 16:56:27 +03:00
aniampio f96a4ffed3 Update get range proof signature function to return an error 2022-04-19 16:55:57 +03:00
aniampio 8e9c0d2b0d Add into the zk proof the proof that l is within the range 2022-04-19 16:35:16 +03:00
aniampio 33c356b3ec Work in progress: structure preserving signature scheme 2022-04-04 15:24:16 +01:00
aniampio c902e8eaa5 Add divisible ecash crate to the main Cargo 2022-04-01 16:24:56 +01:00
aniampio 1dc8b5894f Add struct for the divisible ecash crate 2022-04-01 16:22:25 +01:00
aniampio ad285af0fa Add test for succesfull identification 2022-04-01 12:45:57 +01:00
aniampio 6621e5c72f Run cargo fmt 2022-04-01 10:57:51 +01:00
aniampio 969a93cf80 Fix the issue with two gamma equations 2022-04-01 10:55:26 +01:00
aniampio f52ee0431c Commit cause need to change branch 2022-03-30 10:08:27 +01:00
aniampio a41cfd47a4 Add S and T into the zk proof for spend 2022-03-28 19:00:42 +01:00
aniampio 0b098687e1 Remove the spend file 2022-03-28 17:47:17 +01:00
aniampio 09709dd8e4 Move signature related structs and functions to utils 2022-03-28 17:45:39 +01:00
aniampio ef0867a7e7 Move spend and spec vfy as functions associated with wallet and payment 2022-03-28 17:36:40 +01:00
aniampio bfa7754ea6 Add identify function 2022-03-28 15:46:03 +01:00
aniampio a4377bf1a6 Add spendVfy function 2022-03-28 13:26:29 +01:00
aniampio 9e30f9a346 Add verification for the spend zkproof and first tests 2022-03-27 14:42:40 +01:00
aniampio 7878895651 Start the spend function and zkproof for spend 2022-03-25 13:25:33 +00:00
aniampio fcc940a4d6 Add new aggregation function and struct for the aggregated wallet 2022-03-24 20:14:10 +00:00
aniampio 036092bb9c Add aggregation into e2e tests 2022-03-23 18:30:19 +00:00
aniampio dcde42c45d Copy and adjust aggregation of verification keys from coconut 2022-03-23 17:34:39 +00:00
aniampio 5b5bbc2a3d Fix bug in the issuance verification 2022-03-23 16:43:59 +00:00
aniampio 0c1cdc4b63 Code cleaning 2022-03-23 16:32:08 +00:00
aniampio 953c1eddd9 Fix a bug in the commitment computation 2022-03-23 15:50:30 +00:00
aniampio 6ae0913aa1 Copy polynomial and ttpcode from Coconut; add first test; add keypair structures 2022-03-22 18:14:13 +00:00
aniampio 92834ff9b8 Add zk proof for withdrawal request 2022-03-22 17:27:01 +00:00
aniampio bfba92de97 Add initial functions 2022-03-21 18:09:05 +00:00
aniampio d37b63f788 Add template for the compact ecash crate 2022-03-21 09:01:12 +00:00
Drazen Urch 63dd26ca1b Add Query endpoints for calculating rewards (#1152) 2022-03-18 15:44:26 +01:00
Drazen Urch f24d6e224d Change accumulated reward to Option, migrate delegations (#1147)
* Change accumulated reward to Option, migrate delegations

* Remove interval from the validator cache

* Add info to db errors

* Remove interval fetch from the contract

* Rework epochs

* Fix undelegation errors

* Emit error event instead of error

* make no-clippy

* Fix clippy lints
2022-03-17 12:03:05 +01:00
Tommy Verrall f9a154b36c Merge pull request #1148 from nymtech/chore/use-urls-types-in-wallet
wallet: use Urls rather than Strings for validator urls
2022-03-16 09:17:04 +00:00
Tommy Verrall 72d994880b Merge pull request #1149 from nymtech/feature/wallet-logging
wallet: add logging
2022-03-16 09:14:45 +00:00
Jon Häggblad fe02bc4631 wallet: add logging 2022-03-15 16:03:39 +01:00
Mark Sinclair 1fb8e1bbaf Add wellknown validators json file 2022-03-15 13:38:27 +00:00
Jon Häggblad 96aa355db3 wallet: use Urls rather than Strings for validator urls 2022-03-15 14:25:22 +01:00
Tommy Verrall 8dfb9c8173 Merge pull request #1134 from nymtech/dependabot/npm_and_yarn/clients/native/examples/js-examples/websocket/url-parse-1.5.10
Bump url-parse from 1.5.7 to 1.5.10 in /clients/native/examples/js-examples/websocket
2022-03-15 11:06:15 +00:00
Jon Häggblad 382d3e130e wallet: remove unused use 2022-03-14 22:18:28 +01:00
Jon Häggblad a8246621e1 wallet: add health query based on plain http 2022-03-14 22:12:34 +01:00
Jon Häggblad 66b6a8aeef wallet: fetch validators url remotely if available (#1146) 2022-03-14 12:19:11 +01:00
Jon Häggblad 93165ad699 nymcoconut: fix clippy 2022-03-14 09:34:32 +01:00
Jon Häggblad 2ebb498589 ci: run clippy with --workspace in build.yml 2022-03-14 09:19:09 +01:00
Drazen Urch 929a780315 Fix delegated free calculation (#1145) 2022-03-11 21:44:28 +01:00
Daniel Filipe Nunes Silva c7cdd1e1b4 feature/pedersen-commitments (#1048)
* add pedersen.rs and first alias type

* replace ciphertexts in BlindSignRequest by commitments and adapt try_from

* update BlindSignRequest.to_byte_vec

* add TODO to issuance.rs

* [WIP] update CmCs proof to use commitments

* complete ProofCmCs construct implementation

* [WIP] ProofCmCs verify

* complete ProofCmCs verify implementation

* complete ProofCmCs to_bytes implementation

* complete ProofCmCs from_bytes implementation

* WIP update ProofCmCs roundtrip tests

* WIP update prepare_blind_sign

* update prepare_blind_sign

* WIP update blind_sign

* update blind_sign

* update BlindedSignature and try_from

* update BlindedSignature unblind

* update BlindedSignature to_bytes

* move elgamal encrypto to elgamal and manage imports

* add verification keys for g1 method

* upadate tests to work with Pedersen commitments

* unused remove pedersen.rs

* update error message

* fix proof_cm_cs_bytes_roundtrip test

* Move generation of commitment openings into prepare blind signature function

* Edits

* Extende the verification key; remove validator's secret key from the unblind function

* Update the unblind function throughout the whole nymcoconut repo

* Fix broken tests

* Run cargo clippy and fmt

* Add benchmark measurements

* Add more detailed printouts

* Change byte printout

* Update benchmarks

* add public attributes as part of the commitment

* update bytes order comment for ProofCmCs

* check proof_cm_cs_bytes_roundtrip test and remove TODO

* remove irrelevant prints in blind signature tests

* remove inappropriate function to get betas_g1

* remove irrelevant prints in verification

* Remove print statement and add additional checks on the verification key

* Run clippy

* Fix coconut call in validator-api

* Update dependend packages

* Update the input to the obtain partial credential function

* Fix the verification key bytes calculation

* Run cargo fmt on keygen.rs

* Run cargo fmt for validator-api

* Run cargo fmt for credentials

* Replace concat with chain

* Remove unneccessary mut

* Run cargo fmt

* fix type conversion

Co-authored-by: aniampio <anna.piotrowska.15@ucl.ac.uk>
2022-03-11 11:44:25 +00:00
Mark Sinclair 8575a72a22 Update Nym wallet dependencies to use ts-packages (#1144)
* Use shared ts-packages in wallet

* Add eslint rules

* Formatting: run eslint --fix on all files

* Formatting: fix linting errors for dependencies

* linting fixes

* fix sign in pages

* fix breaking change

* fix ts errors

fix ts and es errors

* Fix up typings for image and json modules
Add tsconfig for eslint to process webpack config

* Use shared webpack config

* Use shared logo component

* Remove unused images

* Allow html path to be passed as an argument in shared webpack config

* Fix up webpack config for html template

* Build shared ts-packages before starting dev mode

* Fix webpack config

* use shared logo component

Co-authored-by: fmtabbara <fmtabbara@hotmail.co.uk>
Co-authored-by: mmsinclair <mmsinclair@users.noreply.github.com>
2022-03-11 11:41:17 +00:00
Jon Häggblad 9d03bec14b clippy: fix lints from beta toolchain 2022-03-11 10:17:39 +01:00
Mark Sinclair 7c826ef881 Remove GitHub Action for TS type generation
Please generate type manually before committing.
2022-03-10 18:07:08 +00:00
Jon Häggblad ac666b7a1d wallet: fix unused use 2022-03-10 17:18:08 +01:00
Jon Häggblad 83a9b993a1 wallet: add a 3 sec timeout for detecting validator 2022-03-10 17:16:03 +01:00
Jon Häggblad fe4fcc7fdf rustfmt 2022-03-10 11:28:32 +01:00
Jon Häggblad a6e40443a4 ci: add --all-targets to nightly clippy 2022-03-10 11:24:01 +01:00
Jon Häggblad 0a583bd48a clippy: remove unnecessary mut 2022-03-10 11:17:43 +01:00
Jon Häggblad 96ccce1049 clippy: run clippy fix on coconut tests 2022-03-10 11:03:23 +01:00
Jon Häggblad 89d0ff5624 ci: run nightly clippy with --workspace 2022-03-10 10:53:49 +01:00
Jon Häggblad 2391d11758 ci: replace deprecated --all with --workspace
According to the docs `--all` is a deprecated alias for `--workspace`
2022-03-10 10:19:43 +01:00
Jon Häggblad 902c674ff1 makefile: replace deprecated flag 2022-03-10 09:13:34 +01:00
Jon Häggblad 160328a08e wallet: try validators one by one if available (#1143) 2022-03-10 07:38:39 +01:00
Mark Sinclair 9fea869bbc Regenerate icon for nym-wallet 2022-03-10 00:04:05 +00:00
Mark Sinclair c66d7ed489 Update Network Explorer Packages and add mix node identity key copy (#1142)
* Use new eslint rules and apply fixes. Use new logo component and shared theme + webpack config.

* Add shared component to display a copy icon and copy to clipboard with confirmation state

* Organise imports

* Add copy mixnode identity key to list of mixnodes and detail view

* Update nvm node version to 16

* Update GitHub Actions for Network Explorer to use yarn and yarn workspaces

* Switch favicon for smaller N icon

* Update README

* Add error boundary
2022-03-09 19:08:21 +00:00
Mark Sinclair 19d603ba1a Fix package.json 2022-03-09 14:29:45 +00:00
Drazen Urch b30f680549 Refactor to a lazy rewarding system (#1127)
* Remove eager operator and delegator rewarding

* Add mixnodes snapshoting

* Add Intervals map, getter and setter

* Refactor reward params

* Refactor MixnodeToReward

* Persist node reward params and results on chain

* Update cw-storage-plus to 0.12.1

* Refactor delegation storage

* Compound delegator reward command

* Compound delegator reward command

* Add defered delegate and undelegate

* Compound on behalf command

* Scale calculations to epoch

* Rename interval -> epoch where practical

* Store epochs on chain

* Cleanup first pass

* Adapt reporting to lazy rewarding

* make clippy --all
2022-03-09 14:28:16 +01:00
Mark Sinclair 379dd1f02b Merge pull request #1139 from nymtech/feature/ts-packages
Add `ts-packages` for shared Typescript packages
2022-03-09 12:09:40 +00:00
Mark Sinclair 38804279e8 Fix up dependency 2022-03-09 11:52:52 +00:00
Mark Sinclair 3907cd17fe Improve eslint rules and fix up issues 2022-03-09 11:41:04 +00:00
Mark Sinclair 67c69cbec9 Add Network Explorer to yarn workspaces 2022-03-08 18:29:25 +00:00
Mark Sinclair abc5dd8b92 Add theme and palette explorer 2022-03-08 18:29:20 +00:00
Mark Sinclair c791de426a Add GitHub Action to build @nymproject/react storybook and example 2022-03-08 13:09:52 +00:00
Mark Sinclair 4c5d4ac4a4 Add docs 2022-03-08 10:50:41 +00:00
Mark Sinclair 48c1fcaf93 Add network selector component and storybook support for Nym themes 2022-03-08 10:50:41 +00:00
Mark Sinclair 31594c7a79 Add ts-packages for shared Typescript packages using yarn workspaces 2022-03-08 10:50:36 +00:00
Bogdan-Ștefan Neacșu 8ec3c04a39 Use 1.. indices and fix the blind sign request multiple generation 2022-03-08 10:59:57 +02:00
Jon Häggblad ba7fecde6f wallet: use custom validator urls if configured 2022-03-07 13:24:37 +01:00
dependabot[bot] 0575f01f9b Bump url-parse in /clients/native/examples/js-examples/websocket
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.7 to 1.5.10.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.7...1.5.10)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-28 04:45:35 +00:00
452 changed files with 52263 additions and 45262 deletions
Vendored
BIN
View File
Binary file not shown.
+63
View File
@@ -0,0 +1,63 @@
name: CI for ts-packages
on:
push:
paths:
- 'ts-packages/**'
jobs:
build:
runs-on: custom-runner-linux
steps:
- uses: actions/checkout@v2
- name: Install rsync
run: sudo apt-get install rsync
- uses: rlespinasse/github-slug-action@v3.x
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: Setup yarn
run: npm install -g yarn
- name: Build
run: yarn && yarn build && yarn build:ci
- name: Deploy branch to CI www (storybook)
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "ts-packages/dist/storybook/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ts-${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Deploy branch to CI www (example)
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "ts-packages/dist/example/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ts-${{ env.GITHUB_REF_SLUG }}-example
EXCLUDE: "/dist/, /node_modules/"
- name: Keybase - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Keybase - Send Notification
env:
NYM_NOTIFICATION_KIND: ts-packages
NYM_PROJECT_NAME: "ts-packages"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
NYM_CI_WWW_LOCATION: "ts-${{ env.GITHUB_REF_SLUG }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-ts-packages"
IS_SUCCESS: "${{ job.status == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+5 -5
View File
@@ -33,13 +33,13 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --all
args: --workspace
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all --all-features
args: --workspace --all-features
- name: Check formatting
uses: actions-rs/cargo@v1
@@ -57,19 +57,19 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
args: --workspace -- -D warnings
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
args: --all --features=coconut
args: --workspace --features=coconut
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --all --features=coconut
args: --workspace --features=coconut
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
+2 -2
View File
@@ -45,7 +45,7 @@ jobs:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/Cargo.toml --all --target wasm32-unknown-unknown
args: --manifest-path contracts/Cargo.toml --workspace --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
@@ -61,4 +61,4 @@ jobs:
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/Cargo.toml --all -- -D warnings
args: --manifest-path contracts/Cargo.toml --workspace -- -D warnings
+4 -3
View File
@@ -16,8 +16,9 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm install
node-version: '16'
- name: Setup yarn
run: npm install -g yarn
- name: Run ESLint
# GitHub should automatically annotate the PR
run: npm run lint
run: yarn && yarn lint
+8 -5
View File
@@ -19,14 +19,17 @@ jobs:
- uses: rlespinasse/github-slug-action@v3.x
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm install
node-version: '16'
- name: Setup yarn
run: npm install -g yarn
continue-on-error: true
- name: Build shared packages
run: cd .. && yarn && yarn build
- name: Set environment from the example
run: cp .env.prod .env
- run: npm run test
continue-on-error: true
- run: npm run build
# - run: yarn test
# continue-on-error: true
- run: yarn && yarn build
continue-on-error: true
- name: Deploy branch to CI www
continue-on-error: true
+6 -6
View File
@@ -42,13 +42,13 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --all
args: --workspace
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all
args: --workspace
- name: Check formatting
uses: actions-rs/cargo@v1
@@ -73,7 +73,7 @@ jobs:
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: -- -D warnings
args: --workspace --all-targets -- -D warnings
# COCONUT stuff
@@ -81,20 +81,20 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --all --features=coconut
args: --workspace --features=coconut
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --all --features=coconut
args: --workspace --features=coconut
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --features=coconut -- -D warnings
args: --workspace --all-targets --features=coconut -- -D warnings
notification:
needs: build
runs-on: ubuntu-latest
-32
View File
@@ -1,32 +0,0 @@
name: Generate TS types
on:
push:
paths-ignore:
- "explorer/**"
pull_request:
paths-ignore:
- "explorer/**"
jobs:
nym-wallet-types:
runs-on: [ self-hosted, custom-linux-exoscale ]
# Enable sccache
env:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
if: ${{ github.event_name != 'pull_request' }}
steps:
- name: Prepare
run: sudo apt-get update && sudo apt-get install -y libpango1.0-dev libatk1.0-dev libgdk-pixbuf2.0-dev libsoup2.4-dev librust-gdk-dev libwebkit2gtk-4.0-dev
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Generate TS
run: cd nym-wallet/src-tauri && cargo test
- uses: EndBug/add-and-commit@v7.2.1 # https://github.com/marketplace/actions/add-commit
with:
add: '["nym-wallet"]'
message: "[ci skip] Generate TS types"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -3,7 +3,7 @@ require('dotenv').config();
const Bot = require('keybase-bot');
let context = {
kinds: ['network-explorer', 'nightly'],
kinds: ['ts-packages', 'network-explorer', 'nightly'],
};
/**
@@ -0,0 +1,29 @@
const Handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');
async function addToContextAndValidate(context) {
if (!context.env.NYM_CI_WWW_LOCATION) {
throw new Error('Please ensure the env var NYM_CI_WWW_LOCATION is set');
}
if (!context.env.NYM_CI_WWW_BASE) {
throw new Error('Please ensure the env var NYM_CI_WWW_BASE is set');
}
}
async function getMessageBody(context) {
const source = fs
.readFileSync(
context.env.IS_SUCCESS === 'true'
? path.resolve(__dirname, 'templates', 'success')
: path.resolve(__dirname, 'templates', 'failure'),
)
.toString();
const template = Handlebars.compile(source);
return template(context);
}
module.exports = {
addToContextAndValidate,
getMessageBody,
};
@@ -0,0 +1,11 @@
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
> :rocket: {{ env.NYM_PROJECT_NAME }}
> 🔴 **FAILURE** :cry:
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
Commit message:
```
{{ env.GIT_COMMIT_MESSAGE }}
```
@@ -0,0 +1,16 @@
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
> :rocket: {{ env.NYM_PROJECT_NAME }}
> ✅ **SUCCESS**
> ➡️➡️➡️➡️➡️ **View output:**
> `storybook`: https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}
> `example`: https://{{ env.NYM_CI_WWW_LOCATION }}-example.{{ env.NYM_CI_WWW_BASE }}
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}:
```
{{ env.GIT_COMMIT_MESSAGE }}
```
+3 -1
View File
@@ -35,4 +35,6 @@ contracts/mixnet/Justfile
contracts/mixnet/Makefile
validator-config
*.patch
validator-api-config.toml
validator-api-config.toml
dist
storybook-static
+6
View File
@@ -0,0 +1,6 @@
{
"mainnet":[{
"nymd_url":"https://rpc.nyx.nodes.guru/",
"api_url":"https://api.nyx.nodes.guru/"
}]
}
Generated
+181 -84
View File
@@ -69,9 +69,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.55"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27"
[[package]]
name = "arrayref"
@@ -93,9 +93,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "async-stream"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625"
checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e"
dependencies = [
"async-stream-impl",
"futures-core",
@@ -103,9 +103,9 @@ dependencies = [
[[package]]
name = "async-stream-impl"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308"
checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
dependencies = [
"proc-macro2",
"quote",
@@ -218,9 +218,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "base64ct"
version = "1.3.3"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "874f8444adcb4952a8bc51305c8be95c8ec8237bb0d2e78d2e039f771f8828a0"
checksum = "71acf5509fc522cce1b100ac0121c635129bfd4d91cdf036bcc9b9935f97ccf5"
[[package]]
name = "binascii"
@@ -245,7 +245,7 @@ checksum = "873faa4363bfc54c36a48321da034c92a0645a363eed34d948683ffc1706e37f"
dependencies = [
"bs58",
"hmac 0.11.0",
"k256 0.10.2",
"k256 0.10.4",
"once_cell",
"pbkdf2",
"rand_core 0.6.3",
@@ -373,11 +373,24 @@ dependencies = [
"digest 0.9.0",
"ff 0.10.1",
"group 0.10.0",
"pairing",
"pairing 0.20.0",
"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"
@@ -519,9 +532,9 @@ dependencies = [
[[package]]
name = "clap"
version = "3.1.5"
version = "3.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312"
checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123"
dependencies = [
"atty",
"bitflags",
@@ -736,7 +749,7 @@ dependencies = [
"ecdsa 0.13.4",
"eyre",
"getrandom 0.2.5",
"k256 0.10.2",
"k256 0.10.4",
"prost",
"prost-types",
"rand_core 0.6.3",
@@ -750,9 +763,9 @@ dependencies = [
[[package]]
name = "cosmwasm-crypto"
version = "1.0.0-beta5"
version = "1.0.0-beta6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8904127a5b9e325ef5d6b2b3f997dcd74943cd35097139b1a4d15b1b6bccae66"
checksum = "2dddc1443004c6340e55ca66d98e9d2f1a44aadf4ce2bed2c4f29baa8a15e7b7"
dependencies = [
"digest 0.9.0",
"ed25519-zebra",
@@ -763,22 +776,23 @@ dependencies = [
[[package]]
name = "cosmwasm-derive"
version = "1.0.0-beta5"
version = "1.0.0-beta6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a14364ac4d9d085867929d0cf3e94b1d2100121ce02c33c72961406830002613"
checksum = "fe4f0f10f165b8bcc558a13cddb498140960544519aa0581532c766dd80b5598"
dependencies = [
"syn",
]
[[package]]
name = "cosmwasm-std"
version = "1.0.0-beta5"
version = "1.0.0-beta6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2ece12e5bbde434b93937d7b2107e6291f11d69ffa72398c50e8bab41d451d3"
checksum = "2f0f3145097b692b2d95fa5d2c7c6fdd60f193ccc709857e7e1987a608725300"
dependencies = [
"base64",
"cosmwasm-crypto",
"cosmwasm-derive",
"forward_ref",
"schemars",
"serde",
"serde-json-wasm",
@@ -823,6 +837,7 @@ dependencies = [
name = "credentials"
version = "0.1.0"
dependencies = [
"bls12_381 0.5.0",
"coconut-interface",
"crypto",
"network-defaults",
@@ -869,9 +884,9 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
version = "0.5.2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa"
checksum = "fdbfe11fe19ff083c48923cf179540e8cd0535903dc35e178a1fdeeb59aef51f"
dependencies = [
"cfg-if",
"crossbeam-utils",
@@ -890,10 +905,11 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.7"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9"
checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"
dependencies = [
"autocfg 1.1.0",
"cfg-if",
"crossbeam-utils",
"lazy_static",
@@ -903,9 +919,9 @@ dependencies = [
[[package]]
name = "crossbeam-queue"
version = "0.3.4"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dd435b205a4842da59efd07628f921c096bc1cc0a156835b4fa0bcb9a19bcce"
checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2"
dependencies = [
"cfg-if",
"crossbeam-utils",
@@ -913,9 +929,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.7"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
dependencies = [
"cfg-if",
"lazy_static",
@@ -1055,9 +1071,9 @@ dependencies = [
[[package]]
name = "curve25519-dalek"
version = "3.2.1"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0"
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
dependencies = [
"byteorder",
"digest 0.9.0",
@@ -1077,6 +1093,17 @@ 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"
@@ -1255,9 +1282,9 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dyn-clone"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28"
[[package]]
name = "ecdsa"
@@ -1539,9 +1566,9 @@ dependencies = [
[[package]]
name = "fixed"
version = "1.13.0"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d38004fb072c44da88a322343c7c784bd1ff6f2e3f5b332050de60c5eea3e0e"
checksum = "d3342b127378d13cfdbd56de8d7e7feec33a9772d5657b9605e59a6e4a337e36"
dependencies = [
"az",
"bytemuck",
@@ -1592,9 +1619,9 @@ dependencies = [
[[package]]
name = "flume"
version = "0.10.11"
version = "0.10.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b279436a715a9de95dcd26b151db590a71961cc06e54918b24fe0dd5b7d3fc4"
checksum = "843c03199d0c0ca54bc1ea90ac0d507274c28abcc4f691ae8b4eaa375087c76a"
dependencies = [
"futures-core",
"futures-sink",
@@ -1648,6 +1675,12 @@ 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"
@@ -1910,9 +1943,9 @@ dependencies = [
[[package]]
name = "git2"
version = "0.14.1"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e7d3b96ec1fcaa8431cf04a4f1ef5caafe58d5cf7bcc31f09c1626adddb0ffe"
checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c"
dependencies = [
"bitflags",
"libc",
@@ -1957,6 +1990,7 @@ 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",
@@ -1964,9 +1998,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.3.11"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e"
checksum = "62eeb471aa3e3c9197aa4bfeabfe02982f6dc96f750486c0bb0009ac58b26d2b"
dependencies = [
"bytes",
"fnv",
@@ -2182,9 +2216,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "humantime-serde"
version = "1.0.1"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058"
checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c"
dependencies = [
"humantime 2.1.0",
"serde",
@@ -2385,9 +2419,9 @@ dependencies = [
[[package]]
name = "ipnet"
version = "2.3.1"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
checksum = "35e70ee094dc02fd9c13fdad4940090f22dbd6ac7c9e7094a46cf0232a50bc7c"
[[package]]
name = "ipnetwork"
@@ -2482,9 +2516,9 @@ dependencies = [
[[package]]
name = "k256"
version = "0.10.2"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cc5937366afd3b38071f400d1ce5bd8b1d40b5083cc14e6f8dbcc4032a7f5bb"
checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d"
dependencies = [
"cfg-if",
"ecdsa 0.13.4",
@@ -2514,15 +2548,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.119"
version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
checksum = "ad5c14e80759d0939d013e6ca49930e59fc53dd8e5009132f76240c179380c09"
[[package]]
name = "libgit2-sys"
version = "0.13.1+1.4.2"
version = "0.13.2+1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43e598aa7a4faedf1ea1b4608f582b06f0f40211eec551b7ef36019ae3f62def"
checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b"
dependencies = [
"cc",
"libc",
@@ -2549,9 +2583,9 @@ dependencies = [
[[package]]
name = "libz-sys"
version = "1.1.3"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
checksum = "6f35facd4a5673cb5a48822be2be1d4236c1c99cb4113cab7061ac720d5bf859"
dependencies = [
"cc",
"libc",
@@ -2670,14 +2704,15 @@ dependencies = [
[[package]]
name = "mio"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2"
checksum = "7ba42135c6a5917b9db9cd7b293e5409e1c6b041e6f9825e92e55a894c63b6f8"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"wasi 0.11.0+wasi-snapshot-preview1",
"winapi",
]
@@ -2803,13 +2838,12 @@ dependencies = [
[[package]]
name = "nom"
version = "7.1.0"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
dependencies = [
"memchr",
"minimal-lexical",
"version_check",
]
[[package]]
@@ -2873,9 +2907,9 @@ dependencies = [
[[package]]
name = "num_threads"
version = "0.1.3"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15"
checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0"
dependencies = [
"libc",
]
@@ -2923,7 +2957,7 @@ dependencies = [
"bandwidth-claim-contract",
"bip39",
"bs58",
"clap 3.1.5",
"clap 3.1.6",
"coconut-interface",
"colored",
"config",
@@ -2966,7 +3000,7 @@ version = "0.12.1"
dependencies = [
"anyhow",
"bs58",
"clap 3.1.5",
"clap 3.1.6",
"colored",
"config",
"crypto",
@@ -3098,12 +3132,60 @@ 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",
"bls12_381 0.5.0",
"bs58",
"criterion",
"digest 0.9.0",
@@ -3251,9 +3333,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.9.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
[[package]]
name = "oorandom"
@@ -3331,6 +3413,15 @@ 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"
@@ -4106,9 +4197,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.5.4"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [
"aho-corasick",
"memchr",
@@ -4141,9 +4232,9 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.11.9"
version = "0.11.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525"
checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb"
dependencies = [
"base64",
"bytes",
@@ -5196,9 +5287,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.86"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54"
dependencies = [
"proc-macro2",
"quote",
@@ -5249,7 +5340,7 @@ dependencies = [
"ed25519-dalek",
"flex-error",
"futures",
"k256 0.10.2",
"k256 0.10.4",
"num-traits",
"once_cell",
"prost",
@@ -5703,9 +5794,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
[[package]]
name = "tracing"
version = "0.1.31"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f"
checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f"
dependencies = [
"cfg-if",
"log",
@@ -5716,9 +5807,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.19"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716"
checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b"
dependencies = [
"proc-macro2",
"quote",
@@ -5727,9 +5818,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.22"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23"
checksum = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c"
dependencies = [
"lazy_static",
"valuable",
@@ -6046,7 +6137,7 @@ version = "0.1.0"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus",
"cw-storage-plus 0.12.1",
"mixnet-contract-common",
"schemars",
"serde",
@@ -6060,7 +6151,7 @@ version = "0.1.0"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus",
"cw-storage-plus 0.11.1",
"mixnet-contract-common",
"schemars",
"serde",
@@ -6100,6 +6191,12 @@ 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"
@@ -6362,9 +6459,9 @@ checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
[[package]]
name = "winreg"
version = "0.7.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
dependencies = [
"winapi",
]
@@ -6377,9 +6474,9 @@ checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]]
name = "x25519-dalek"
version = "1.2.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077"
checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f"
dependencies = [
"curve25519-dalek",
"rand_core 0.5.1",
@@ -6394,9 +6491,9 @@ checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
[[package]]
name = "zeroize"
version = "1.3.0"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
dependencies = [
"zeroize_derive",
]
+3
View File
@@ -33,6 +33,9 @@ members = [
"common/network-defaults",
"common/nonexhaustive-delayqueue",
"common/nymcoconut",
"common/nym_offline_compact_ecash",
"common/nym_offline_divisible_ecash",
"common/nym_online_divisible_ecash",
"common/nymsphinx",
"common/nymsphinx/acknowledgements",
"common/nymsphinx/addressing",
+11 -10
View File
@@ -1,9 +1,10 @@
test: build clippy-all cargo-test wasm fmt
no-clippy: build cargo-test wasm fmt
happy: fmt clippy-happy test
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet
cargo-test: test-main test-contracts test-wallet
build: build-contracts build-wallet build-main
build: build-contracts build-wallet build-main
fmt: fmt-main fmt-contracts fmt-wallet
clippy-happy-main:
@@ -12,20 +13,20 @@ clippy-happy-main:
clippy-happy-contracts:
cargo clippy --manifest-path contracts/Cargo.toml --target wasm32-unknown-unknown
clippy-happy-wallet:
clippy-happy-wallet:
cargo clippy --manifest-path nym-wallet/Cargo.toml
clippy-all-main:
cargo clippy --all-features -- -D warnings
cargo clippy --workspace --all-features -- -D warnings
clippy-all-contracts:
cargo clippy --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
cargo clippy --workspace --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
clippy-all-wallet:
cargo clippy --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
clippy-all-wallet:
cargo clippy --workspace --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
test-main:
cargo test --all-features --all
cargo test --all-features --workspace
test-contracts:
cargo test --manifest-path contracts/Cargo.toml --all-features
@@ -34,13 +35,13 @@ test-wallet:
cargo test --manifest-path nym-wallet/Cargo.toml --all-features
build-main:
cargo build --all
cargo build --workspace
build-contracts:
cargo build --manifest-path contracts/Cargo.toml --all
cargo build --manifest-path contracts/Cargo.toml --workspace
build-wallet:
cargo build --manifest-path nym-wallet/Cargo.toml --all
cargo build --manifest-path nym-wallet/Cargo.toml --workspace
fmt-main:
cargo fmt --all
+2
View File
@@ -28,6 +28,8 @@ Wallet build instructions are also available on [our docs site](https://nymtech.
There's a `.env.sample-dev` file provided which you can rename to `.env` if you want convenient logging, backtrace, or other environment variables pre-set. The `.env` file is ignored so you don't need to worry about checking it in.
For Typescript components, please see [ts-packages](./ts-packages).
### Developer chat
You can chat to us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes places in the **#dev** channel. Node operators should be in the **#node-operators** channel.
+12
View File
@@ -0,0 +1,12 @@
# Shared assets
This directory contains asset files shared by many projects in this repo.
You will find:
- favicons
- logos
- shared fonts
- shared icon SVGs
See [ts-packages/react-webpack-with-theme-example](../ts-packages/react-webpack-with-theme-example) for examples of usage.
Binary file not shown.

After

Width:  |  Height:  |  Size: 545 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg width="64" height="64" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M40 78.5C61.263 78.5 78.5 61.263 78.5 40C78.5 18.737 61.263 1.5 40 1.5C18.737 1.5 1.5 18.737 1.5 40C1.5 61.263 18.737 78.5 40 78.5Z" fill="#070B15" stroke="url(#paint0_linear_0_1)" stroke-width="3"/>
<path d="M31.4894 27.56L41.8623 56H48.5106H56V24H48.5106V52.4L38.1777 24H31.4894H24V56H31.4894V27.56Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_0_1" x1="0.839161" y1="80" x2="80" y2="80" gradientUnits="userSpaceOnUse">
<stop offset="0.09375" stop-color="#FB6E4E"/>
<stop offset="1" stop-color="#F51473"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 672 B

+6
View File
@@ -0,0 +1,6 @@
@font-face {
font-family: 'Open Sans';
src: url('./OpenSans-VariableFont_wdth,wght.ttf') format('truetype-variations'),
url('./OpenSans-Italic-VariableFont_wdth,wght.ttf') format('truetype-variations');
font-weight: 100 1000;
}
+10
View File
@@ -0,0 +1,10 @@
<svg width="64" height="64" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M40 78.5C61.263 78.5 78.5 61.263 78.5 40C78.5 18.737 61.263 1.5 40 1.5C18.737 1.5 1.5 18.737 1.5 40C1.5 61.263 18.737 78.5 40 78.5Z" fill="#070B15" stroke="url(#paint0_linear_0_1)" stroke-width="3"/>
<path d="M31.4894 27.56L41.8623 56H48.5106H56V24H48.5106V52.4L38.1777 24H31.4894H24V56H31.4894V27.56Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_0_1" x1="0.839161" y1="80" x2="80" y2="80" gradientUnits="userSpaceOnUse">
<stop offset="0.09375" stop-color="#FB6E4E"/>
<stop offset="1" stop-color="#F51473"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 714 B

+13
View File
@@ -0,0 +1,13 @@
<svg width="300" height="300" viewBox="0 0 296 296" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M148 296C229.738 296 296 229.738 296 148C296 66.2619 229.738 0 148 0C66.2619 0 0 66.2619 0 148C0 229.738 66.2619 296 148 296Z" fill="url(#paint0_linear_113_1244)"/>
<path d="M148 285.875C224.147 285.875 285.875 224.146 285.875 148C285.875 71.8536 224.147 10.1248 148 10.1248C71.8538 10.1248 10.125 71.8536 10.125 148C10.125 224.146 71.8538 285.875 148 285.875Z" fill="#121725"/>
<path d="M88.8829 120.143H88.7169V120.281V168.637L68.3289 120.226L68.3012 120.143H68.1905H56.6272H43.653H43.5146V120.281V175.719V175.857H43.653H56.6272H56.7655V175.719V127.28L77.2365 175.774L77.2642 175.857H77.3748H88.8829H101.829H101.968V175.719V120.281V120.143H101.829H88.8829Z" fill="white"/>
<path d="M252.347 120.143H227.616H227.477L227.45 120.253L214.78 168.858L202.082 120.253L202.054 120.143H201.944H177.157H176.991V120.281V175.719V175.857H177.157H190.104H190.242V175.719V127.667L202.774 175.747L202.801 175.857H202.94H226.564H226.675L226.703 175.747L239.234 127.667V175.719V175.857H239.373H252.347H252.485V175.719V120.281V120.143H252.347Z" fill="white"/>
<path d="M155.663 120.143H155.58L155.552 120.198L139.812 147.557L123.988 120.198L123.96 120.143H123.877H108.911H108.635L108.773 120.364L133.145 162.579V175.719V175.857H133.283H146.257H146.396V175.719V162.579L170.767 120.364L170.905 120.143H170.629H155.663Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_113_1244" x1="0" y1="148" x2="296" y2="148" gradientUnits="userSpaceOnUse">
<stop offset="0.09375" stop-color="#FB6E4E"/>
<stop offset="1" stop-color="#FC1D60"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg width="210" height="56" viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M45.8829 0.142822H45.7169V0.28114V48.637L25.3289 0.225818L25.3012 0.142822H25.1905H13.6272H0.652966H0.514648V0.28114V55.7189V55.8572H0.652966H13.6272H13.7655V55.7189V7.28002L34.2365 55.7742L34.2642 55.8572H34.3748H45.8829H58.8294H58.9677V55.7189V0.28114V0.142822H58.8294H45.8829Z"/>
<path d="M209.347 0.142822H184.616H184.477L184.45 0.253483L171.78 48.8583L159.082 0.253483L159.054 0.142822H158.944H134.157H133.991V0.28114V55.7189V55.8572H134.157H147.104H147.242V55.7189V7.66731L159.774 55.7466L159.801 55.8572H159.94H183.564H183.675L183.703 55.7466L196.234 7.66731V55.7189V55.8572H196.373H209.347H209.485V55.7189V0.28114V0.142822H209.347Z"/>
<path d="M112.663 0.142822H112.58L112.552 0.198153L96.8116 27.5574L80.988 0.198153L80.9604 0.142822H80.8774H65.9114H65.6348L65.7731 0.364136L90.1447 42.5787V55.7189V55.8572H90.283H103.257H103.396V55.7189V42.5787L127.767 0.364136L127.905 0.142822H127.629H112.663Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="white"/>
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="#070B15"/>
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="#141521"/>
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="white"/>
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="#141521"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -6085,9 +6085,9 @@
}
},
"node_modules/url-parse": {
"version": "1.5.7",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.7.tgz",
"integrity": "sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==",
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"dev": true,
"dependencies": {
"querystringify": "^2.1.1",
@@ -11733,9 +11733,9 @@
}
},
"url-parse": {
"version": "1.5.7",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.7.tgz",
"integrity": "sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==",
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"dev": true,
"requires": {
"querystringify": "^2.1.1",
@@ -3,7 +3,7 @@
use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, Interval, MixNodeBond};
use network_defaults::DEFAULT_NETWORK;
use url::Url;
use validator_api_requests::models::{
@@ -20,7 +20,7 @@ use mixnet_contract_common::ContractStateParams;
#[cfg(feature = "nymd-client")]
use mixnet_contract_common::{
Delegation, IdentityKey, Interval, MixnetContractVersion, MixnodeRewardingStatusResponse,
Delegation, IdentityKey, MixnetContractVersion, MixnodeRewardingStatusResponse,
RewardedSetNodeStatus, RewardedSetUpdateDetails,
};
#[cfg(feature = "nymd-client")]
@@ -92,7 +92,7 @@ impl Config {
#[cfg(feature = "nymd-client")]
pub struct Client<C> {
network: network_defaults::all::Network,
pub network: network_defaults::all::Network,
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
erc20_bridge_contract_address: Option<cosmrs::AccountId>,
@@ -248,6 +248,35 @@ impl<C> Client<C> {
Ok(self.nymd.get_contract_settings().await?)
}
pub async fn get_operator_rewards(&self, address: String) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_operator_rewards(address).await?.u128())
}
pub async fn get_delegator_rewards(
&self,
address: String,
mix_identity: IdentityKey,
) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_delegator_rewards(address, mix_identity)
.await?
.u128())
}
pub async fn get_current_epoch(&self) -> Result<Option<Interval>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_epoch().await?)
}
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
where
C: CosmWasmClient + Sync,
@@ -276,13 +305,6 @@ impl<C> Client<C> {
Ok(self.nymd.get_reward_pool().await?.u128())
}
pub async fn get_current_interval(&self) -> Result<Interval, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_interval().await?)
}
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
@@ -304,6 +326,13 @@ impl<C> Client<C> {
Ok(self.nymd.get_active_set_work_factor().await?)
}
pub async fn get_epochs_in_interval(&self) -> Result<u64, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_epochs_in_interval().await?)
}
pub async fn get_interval_reward_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
@@ -21,7 +21,7 @@ where
}
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
pub fn connect_with_signer<U>(
pub fn connect_with_signer<U: Clone>(
endpoint: U,
signer: DirectSecp256k1HdWallet,
gas_price: GasPrice,
@@ -641,7 +641,7 @@ pub struct Client {
}
impl Client {
pub fn connect_with_signer<U>(
pub fn connect_with_signer<U: Clone>(
endpoint: U,
signer: DirectSecp256k1HdWallet,
gas_price: GasPrice,
@@ -47,9 +47,12 @@ pub enum Operation {
CreatePeriodicVestingAccount,
AdvanceCurrentInterval,
AdvanceCurrentEpoch,
WriteRewardedSet,
ClearRewardedSet,
UpdateMixnetAddress,
CheckpointMixnodes,
ReconcileDelegations,
}
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
@@ -91,6 +94,9 @@ impl fmt::Display for Operation {
Operation::WriteRewardedSet => f.write_str("WriteRewardedSet"),
Operation::ClearRewardedSet => f.write_str("ClearRewardedSet"),
Operation::UpdateMixnetAddress => f.write_str("UpdateMixnetAddress"),
Operation::CheckpointMixnodes => f.write_str("CheckpointMixnodes"),
Operation::ReconcileDelegations => f.write_str("ReconcileDelegations"),
Operation::AdvanceCurrentEpoch => f.write_str("AdvanceCurrentEpoch"),
}
}
}
@@ -132,6 +138,9 @@ impl Operation {
Operation::WriteRewardedSet => 175_000u64.into(),
Operation::ClearRewardedSet => 175_000u64.into(),
Operation::UpdateMixnetAddress => 80_000u64.into(),
Operation::CheckpointMixnodes => 175_000u64.into(),
Operation::ReconcileDelegations => 500_000u64.into(),
Operation::AdvanceCurrentEpoch => 175_000u64.into(),
}
}
@@ -9,7 +9,8 @@ use crate::nymd::cosmwasm_client::types::{
use crate::nymd::error::NymdError;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
use cosmrs::rpc::Error as TendermintRpcError;
use cosmrs::rpc::HttpClientUrl;
use cosmwasm_std::{Coin, Uint128};
pub use fee::gas_price::GasPrice;
use fee::helpers::Operation;
@@ -82,7 +83,7 @@ impl NymdClient<QueryNymdClient> {
impl NymdClient<SigningNymdClient> {
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
pub fn connect_with_signer<U>(
pub fn connect_with_signer<U: Clone>(
network: config::defaults::all::Network,
endpoint: U,
mixnet_contract_address: Option<AccountId>,
@@ -113,7 +114,7 @@ impl NymdClient<SigningNymdClient> {
})
}
pub fn connect_with_mnemonic<U>(
pub fn connect_with_mnemonic<U: Clone>(
network: config::defaults::all::Network,
endpoint: U,
mixnet_contract_address: Option<AccountId>,
@@ -289,6 +290,43 @@ impl<C> NymdClient<C> {
.await
}
pub async fn get_operator_rewards(&self, address: String) -> Result<Uint128, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::QueryOperatorReward { address };
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
}
pub async fn get_delegator_rewards(
&self,
address: String,
mix_identity: IdentityKey,
) -> Result<Uint128, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::QueryDelegatorReward {
address,
mix_identity,
};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
}
pub async fn get_current_epoch(&self) -> Result<Option<Interval>, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetCurrentEpoch {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
}
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
where
C: CosmWasmClient + Sync,
@@ -368,16 +406,6 @@ impl<C> NymdClient<C> {
.await
}
pub async fn get_current_interval(&self) -> Result<Interval, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetCurrentInterval {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
}
pub async fn get_reward_pool(&self) -> Result<Uint128, NymdError>
where
C: CosmWasmClient + Sync,
@@ -428,6 +456,16 @@ impl<C> NymdClient<C> {
.await
}
pub async fn get_epochs_in_interval(&self) -> Result<u64, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetEpochsInInterval {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
}
/// Checks whether there is a bonded mixnode associated with the provided client's address
pub async fn owns_mixnode(&self, address: &AccountId) -> Result<Option<MixNodeBond>, NymdError>
where
@@ -496,7 +534,7 @@ impl<C> NymdClient<C> {
pub async fn get_mix_delegations_paged(
&self,
mix_identity: IdentityKey,
start_after: Option<String>,
start_after: Option<(String, u64)>,
page_limit: Option<u32>,
) -> Result<PagedMixDelegationsResponse, NymdError>
where
@@ -515,7 +553,7 @@ impl<C> NymdClient<C> {
/// Gets list of all mixnode delegations on particular page.
pub async fn get_all_network_delegations_paged(
&self,
start_after: Option<(IdentityKey, String)>,
start_after: Option<(IdentityKey, Vec<u8>, u64)>,
page_limit: Option<u32>,
) -> Result<PagedAllDelegationsResponse, NymdError>
where
@@ -1175,20 +1213,58 @@ impl<C> NymdClient<C> {
.await
}
pub async fn advance_current_interval(&self) -> Result<ExecuteResult, NymdError>
pub async fn advance_current_epoch(&self) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::AdvanceCurrentInterval);
let fee = self.operation_fee(Operation::AdvanceCurrentEpoch);
let req = ExecuteMsg::AdvanceCurrentInterval {};
let req = ExecuteMsg::AdvanceCurrentEpoch {};
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"Advancing current interval",
"Advance current epoch",
Vec::new(),
)
.await
}
pub async fn reconcile_delegations(&self) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::ReconcileDelegations);
let req = ExecuteMsg::ReconcileDelegations {};
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"Reconciling delegation events",
Vec::new(),
)
.await
}
pub async fn checkpoint_mixnodes(&self) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::CheckpointMixnodes);
let req = ExecuteMsg::CheckpointMixnodes {};
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"Snapshotting mixnodes",
Vec::new(),
)
.await
-4
View File
@@ -83,8 +83,6 @@ impl VerifyCredentialBody {
pub struct BlindSignRequestBody {
#[getset(get = "pub")]
blind_sign_request: BlindSignRequest,
#[getset(get = "pub")]
public_key: nymcoconut::PublicKey,
public_attributes: Vec<String>,
#[getset(get = "pub")]
total_params: u32,
@@ -93,13 +91,11 @@ pub struct BlindSignRequestBody {
impl BlindSignRequestBody {
pub fn new(
blind_sign_request: &BlindSignRequest,
public_key: &nymcoconut::PublicKey,
public_attributes: &[Attribute],
total_params: u32,
) -> BlindSignRequestBody {
BlindSignRequestBody {
blind_sign_request: blind_sign_request.clone(),
public_key: public_key.clone(),
public_attributes: public_attributes
.iter()
.map(|attr| attr.to_bs58())
@@ -37,8 +37,12 @@ impl Delegation {
}
// TODO: change that to use .joined_key() and return Vec<u8>
pub fn storage_key(&self) -> (IdentityKey, Addr) {
(self.node_identity(), self.owner())
pub fn storage_key(&self) -> (IdentityKey, Vec<u8>, u64) {
(
self.node_identity(),
self.owner().as_bytes().to_vec(),
self.block_height(),
)
}
pub fn increment_amount(&mut self, amount: Uint128, at_height: Option<u64>) {
@@ -78,14 +82,14 @@ impl Display for Delegation {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedMixDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<String>,
pub start_next_after: Option<(String, u64)>,
}
impl PagedMixDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<Addr>) -> Self {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<(Addr, u64)>) -> Self {
PagedMixDelegationsResponse {
delegations,
start_next_after: start_next_after.map(|s| s.to_string()),
start_next_after: start_next_after.map(|(s, h)| (s.to_string(), h)),
}
}
}
@@ -108,17 +112,17 @@ impl PagedDelegatorDelegationsResponse {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedAllDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<(IdentityKey, String)>,
pub start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
}
impl PagedAllDelegationsResponse {
pub fn new(
delegations: Vec<Delegation>,
start_next_after: Option<(IdentityKey, Addr)>,
start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
) -> Self {
PagedAllDelegationsResponse {
delegations,
start_next_after: start_next_after.map(|(id, addr)| (id, addr.to_string())),
start_next_after: start_next_after.map(|(id, addr, height)| (id, addr, height)),
}
}
}
@@ -6,4 +6,11 @@ pub enum MixnetContractError {
OverflowError(#[from] cosmwasm_std::OverflowError),
#[error("reward_blockstamp field not set, set_reward_blockstamp must be called before attempting to issue rewards")]
BlockstampNotSet,
#[error("{source}")]
TryFromIntError {
#[from]
source: std::num::TryFromIntError,
},
#[error("Error casting from U128")]
CastError,
}
@@ -1,15 +1,17 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::NodeRewardResult;
use crate::{ContractStateParams, Delegation, IdentityKeyRef, Interval, Layer};
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer};
use cosmwasm_std::{Addr, Coin, Event, Uint128};
pub use contracts_common::events::*;
// FIXME: This should becoma an Enum
// event types
pub const DELEGATION_EVENT_TYPE: &str = "delegation";
pub const PENDING_DELEGATION_EVENT_TYPE: &str = "pending_delegation";
pub const RECONCILE_DELEGATION_EVENT_TYPE: &str = "reconcile_delegation";
pub const UNDELEGATION_EVENT_TYPE: &str = "undelegation";
pub const PENDING_UNDELEGATION_EVENT_TYPE: &str = "pending_undelegation";
pub const GATEWAY_BONDING_EVENT_TYPE: &str = "gateway_bonding";
pub const GATEWAY_UNBONDING_EVENT_TYPE: &str = "gateway_unbonding";
pub const MIXNODE_BONDING_EVENT_TYPE: &str = "mixnode_bonding";
@@ -19,6 +21,10 @@ pub const OPERATOR_REWARDING_EVENT_TYPE: &str = "mix_rewarding";
pub const MIX_DELEGATORS_REWARDING_EVENT_TYPE: &str = "mix_delegators_rewarding";
pub const CHANGE_REWARDED_SET_EVENT_TYPE: &str = "change_rewarded_set";
pub const ADVANCE_INTERVAL_EVENT_TYPE: &str = "advance_interval";
pub const ADVANCE_EPOCH_EVENT_TYPE: &str = "advance_epoch";
pub const COMPOUND_DELEGATOR_REWARD_EVENT_TYPE: &str = "compound_delegator_reward";
pub const COMPOUND_OPERATOR_REWARD_EVENT_TYPE: &str = "compound_operator_reward";
pub const SNAPSHOT_MIXNODES_EVENT: &str = "snapshot_mixnodes";
// attributes that are used in multiple places
pub const OWNER_KEY: &str = "owner";
@@ -70,6 +76,19 @@ pub const NODES_IN_REWARDED_SET_KEY: &str = "nodes_in_rewarded_set";
pub const CURRENT_INTERVAL_ID_KEY: &str = "current_interval";
pub const NEW_CURRENT_INTERVAL_KEY: &str = "new_current_interval";
pub const NEW_CURRENT_EPOCH_KEY: &str = "new_current_epoch";
pub const BLOCK_HEIGHT_KEY: &str = "block_height";
pub const CHECKPOINT_MIXNODES_EVENT: &str = "checkpoint_mixnodes";
pub const RECONCILIATION_ERROR_EVENT: &str = "reconciliation_error";
pub fn new_checkpoint_mixnodes_event(block_height: u64) -> Event {
Event::new(CHECKPOINT_MIXNODES_EVENT)
.add_attribute(BLOCK_HEIGHT_KEY, format!("{}", block_height))
}
pub fn new_error_event(err: String) -> Event {
Event::new(RECONCILIATION_ERROR_EVENT).add_attribute("error", err)
}
pub fn new_delegation_event(
delegator: &Addr,
@@ -89,11 +108,74 @@ pub fn new_delegation_event(
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_pending_delegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(PENDING_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_reconcile_delegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(RECONCILE_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_compound_operator_reward_event(owner: &Addr, amount: Uint128) -> Event {
let event = Event::new(COMPOUND_OPERATOR_REWARD_EVENT_TYPE).add_attribute(OWNER_KEY, owner);
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_compound_delegator_reward_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: Uint128,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(COMPOUND_DELEGATOR_REWARD_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
.add_attribute(DELEGATOR_KEY, delegator)
}
pub fn new_undelegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
old_delegation: &Delegation,
mix_identity: IdentityKeyRef<'_>,
amount: Uint128,
) -> Event {
let mut event = Event::new(UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
@@ -103,14 +185,26 @@ pub fn new_undelegation_event(
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, old_delegation.amount.to_string())
.add_attribute(
DELEGATION_HEIGHT_KEY,
old_delegation.block_height.to_string(),
)
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_pending_undelegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(PENDING_UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_gateway_bonding_event(
owner: &Addr,
proxy: &Option<Addr>,
@@ -280,9 +374,6 @@ pub fn new_mix_operator_rewarding_event(
node_reward_result: NodeRewardResult,
node_pledge: Uint128,
node_delegation: Uint128,
operator_reward: Uint128,
delegation_rewards_distributed: Uint128,
further_delegations: bool,
) -> Event {
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
@@ -295,15 +386,6 @@ pub fn new_mix_operator_rewarding_event(
)
.add_attribute(LAMBDA_KEY, node_reward_result.lambda().to_string())
.add_attribute(SIGMA_KEY, node_reward_result.sigma().to_string())
.add_attribute(OPERATOR_REWARD_KEY, operator_reward)
.add_attribute(
DISTRIBUTED_DELEGATION_REWARDS_KEY,
delegation_rewards_distributed,
)
.add_attribute(
FURTHER_DELEGATIONS_TO_REWARD_KEY,
further_delegations.to_string(),
)
}
pub fn new_mix_delegators_rewarding_event(
@@ -330,16 +412,18 @@ pub fn new_change_rewarded_set_event(
active_set_size: u32,
rewarded_set_size: u32,
nodes_in_rewarded_set: u32,
current_interval_id: u32,
) -> Event {
Event::new(CHANGE_REWARDED_SET_EVENT_TYPE)
.add_attribute(ACTIVE_SET_SIZE_KEY, active_set_size.to_string())
.add_attribute(REWARDED_SET_SIZE_KEY, rewarded_set_size.to_string())
.add_attribute(NODES_IN_REWARDED_SET_KEY, nodes_in_rewarded_set.to_string())
.add_attribute(CURRENT_INTERVAL_ID_KEY, current_interval_id.to_string())
}
pub fn new_advance_interval_event(interval: Interval) -> Event {
Event::new(ADVANCE_INTERVAL_EVENT_TYPE)
.add_attribute(NEW_CURRENT_INTERVAL_KEY, interval.to_string())
}
pub fn new_advance_epoch_event(interval: Interval) -> Event {
Event::new(ADVANCE_EPOCH_EVENT_TYPE).add_attribute(NEW_CURRENT_EPOCH_KEY, interval.to_string())
}
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Env;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::fmt::{Display, Formatter};
@@ -65,14 +66,28 @@ pub struct Interval {
}
impl Interval {
/// Creates new interval instance.
pub const fn new(id: u32, start: OffsetDateTime, length: Duration) -> Self {
Interval { id, start, length }
/// Initialize epoch in the contract with default values.
/// FIXME: THIS unwrap!
pub fn init_epoch(env: Env) -> Self {
Interval {
id: 0,
start: OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap(),
length: Duration::from_secs(3600),
}
}
pub fn is_over(&self, env: Env) -> bool {
self.end_unix_timestamp() <= env.block.time.seconds() as i64
}
pub fn in_progress(&self, env: Env) -> bool {
let block_time = env.block.time.seconds() as i64;
self.start_unix_timestamp() <= block_time && block_time < self.end_unix_timestamp()
}
/// Returns the next interval.
#[must_use]
pub fn next_interval(&self) -> Self {
pub fn next(&self) -> Self {
Interval {
id: self.id + 1,
start: self.end(),
@@ -80,8 +95,19 @@ impl Interval {
}
}
pub fn next_on_chain(&self, env: Env) -> Self {
let start = self
.end()
.max(OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap());
Interval {
id: self.id + 1,
start,
length: self.length,
}
}
/// Returns the last interval.
pub fn previous_interval(&self) -> Option<Self> {
pub fn previous(&self) -> Option<Self> {
if self.id > 0 {
Some(Interval {
id: self.id - 1,
@@ -125,14 +151,14 @@ impl Interval {
if candidate.contains(now) {
return Some(candidate);
}
candidate = candidate.next_interval();
candidate = candidate.next();
}
} else {
loop {
if candidate.contains(now) {
return Some(candidate);
}
candidate = candidate.previous_interval()?;
candidate = candidate.previous()?;
}
}
}
@@ -151,14 +177,14 @@ impl Interval {
if candidate.contains_timestamp(now_unix) {
return Some(candidate);
}
candidate = candidate.next_interval();
candidate = candidate.next();
}
} else {
loop {
if candidate.contains_timestamp(now_unix) {
return Some(candidate);
}
candidate = candidate.previous_interval()?;
candidate = candidate.previous()?;
}
}
}
@@ -239,7 +265,7 @@ mod tests {
use super::*;
#[test]
fn previous_interval() {
fn previous() {
let interval = Interval {
id: 1,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
@@ -250,18 +276,18 @@ mod tests {
start: time::macros::datetime!(2021-08-22 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
};
assert_eq!(expected, interval.previous_interval().unwrap());
assert_eq!(expected, interval.previous().unwrap());
let genesis_interval = Interval {
id: 0,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
};
assert!(genesis_interval.previous_interval().is_none());
assert!(genesis_interval.previous().is_none());
}
#[test]
fn next_interval() {
fn next() {
let interval = Interval {
id: 0,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
@@ -273,7 +299,7 @@ mod tests {
length: Duration::from_secs(24 * 60 * 60),
};
assert_eq!(expected, interval.next_interval())
assert_eq!(expected, interval.next())
}
#[test]
@@ -291,8 +317,8 @@ mod tests {
let in_the_midle = interval.start + Duration::from_secs(interval.length.as_secs() / 2);
assert!(interval.contains(in_the_midle));
assert!(!interval.contains(interval.next_interval().end()));
assert!(!interval.contains(interval.previous_interval().unwrap().start()));
assert!(!interval.contains(interval.next().end()));
assert!(!interval.contains(interval.previous().unwrap().start()));
}
#[test]
@@ -305,10 +331,7 @@ mod tests {
// interval just before
let fake_now = first_interval.start - Duration::from_secs(123);
assert_eq!(
first_interval.previous_interval(),
first_interval.current(fake_now)
);
assert_eq!(first_interval.previous(), first_interval.current(fake_now));
// this interval (start boundary)
assert_eq!(
@@ -329,7 +352,7 @@ mod tests {
// next interval
let fake_now = first_interval.end() + Duration::from_secs(123);
assert_eq!(
first_interval.next_interval(),
first_interval.next(),
first_interval.current(fake_now).unwrap()
);
@@ -340,11 +363,11 @@ mod tests {
- first_interval.length;
assert_eq!(
first_interval
.previous_interval()
.previous()
.unwrap()
.previous_interval()
.previous()
.unwrap()
.previous_interval()
.previous()
.unwrap(),
first_interval.current(fake_now).unwrap()
);
@@ -355,10 +378,7 @@ mod tests {
+ first_interval.length
+ first_interval.length;
assert_eq!(
first_interval
.next_interval()
.next_interval()
.next_interval(),
first_interval.next().next().next(),
first_interval.current(fake_now).unwrap()
);
}
@@ -8,6 +8,7 @@ mod gateway;
mod interval;
pub mod mixnode;
mod msg;
pub mod reward_params;
mod types;
pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250;
@@ -24,3 +25,9 @@ pub use mixnode::{
};
pub use msg::*;
pub use types::*;
pub type U128 = fixed::types::U75F53;
fixed::const_fixed_from_int! {
const ONE: U128 = 1;
}
@@ -1,23 +1,19 @@
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
use crate::{IdentityKey, SphinxKey};
use crate::error::MixnetContractError;
use crate::reward_params::RewardParams;
use crate::{Delegation, IdentityKey, SphinxKey};
use crate::{ONE, U128};
use az::CheckedCast;
use cosmwasm_std::{coin, Addr, Coin, Uint128};
use log::error;
use network_defaults::DEFAULT_OPERATOR_INTERVAL_COST;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::cmp::Ordering;
use std::fmt::Display;
type U128 = fixed::types::U75F53; // u128 with 18 significant digits
fixed::const_fixed_from_int! {
const ONE: U128 = 1;
}
#[cfg_attr(test, derive(ts_rs::TS))]
#[cfg_attr(
test,
@@ -26,7 +22,7 @@ fixed::const_fixed_from_int! {
export_to = "../../../nym-wallet/src/types/rust/rewardedsetnodestatus.ts"
)
)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
pub enum RewardedSetNodeStatus {
Active,
Standby,
@@ -38,6 +34,52 @@ 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,
@@ -87,112 +129,6 @@ impl From<Layer> for String {
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct NodeRewardParams {
period_reward_pool: Uint128,
rewarded_set_size: Uint128,
active_set_size: Uint128,
reward_blockstamp: u64,
circulating_supply: Uint128,
uptime: Uint128,
sybil_resistance_percent: u8,
in_active_set: bool,
active_set_work_factor: u8,
}
impl NodeRewardParams {
#[allow(clippy::too_many_arguments)]
pub fn new(
period_reward_pool: u128,
rewarded_set_size: u128,
active_set_size: u128,
reward_blockstamp: u64,
circulating_supply: u128,
uptime: u128,
sybil_resistance_percent: u8,
in_active_set: bool,
active_set_work_factor: u8,
) -> NodeRewardParams {
NodeRewardParams {
period_reward_pool: Uint128::new(period_reward_pool),
rewarded_set_size: Uint128::new(rewarded_set_size),
active_set_size: Uint128::new(active_set_size),
reward_blockstamp,
circulating_supply: Uint128::new(circulating_supply),
uptime: Uint128::new(uptime),
sybil_resistance_percent,
in_active_set,
active_set_work_factor,
}
}
pub fn omega(&self) -> U128 {
// As per keybase://chat/nymtech#tokeneconomics/1179
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
if self.in_active_set() {
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
self.active_set_work_factor() / denom * self.rewarded_set_size()
} else {
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
ONE / denom * self.rewarded_set_size()
}
}
pub fn idle_nodes(&self) -> Uint128 {
self.rewarded_set_size - self.active_set_size
}
pub fn active_set_work_factor(&self) -> U128 {
U128::from_num(self.active_set_work_factor)
}
pub fn in_active_set(&self) -> bool {
self.in_active_set
}
pub fn performance(&self) -> U128 {
U128::from_num(self.uptime.u128()) / U128::from_num(100)
}
pub fn operator_cost(&self) -> U128 {
U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.reward_blockstamp = blockstamp;
}
pub fn period_reward_pool(&self) -> u128 {
self.period_reward_pool.u128()
}
pub fn rewarded_set_size(&self) -> u128 {
self.rewarded_set_size.u128()
}
pub fn circulating_supply(&self) -> u128 {
self.circulating_supply.u128()
}
pub fn reward_blockstamp(&self) -> u64 {
self.reward_blockstamp
}
pub fn uptime(&self) -> u128 {
self.uptime.u128()
}
pub fn one_over_k(&self) -> U128 {
ONE / U128::from_num(self.rewarded_set_size.u128())
}
pub fn alpha(&self) -> U128 {
U128::from_num(self.sybil_resistance_percent) / U128::from_num(100)
}
}
// cosmwasm's limited serde doesn't work with U128 directly
#[allow(non_snake_case)]
pub mod fixed_U128_as_string {
@@ -226,7 +162,7 @@ pub mod fixed_U128_as_string {
// everything required to reward delegator of given mixnode
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
pub struct DelegatorRewardParams {
node_reward_params: NodeRewardParams,
reward_params: RewardParams,
// to be completely honest I don't understand all consequences of using `#[schemars(with = "String")]`
// for U128 here, but it seems that CosmWasm is using the same attribute for their Uint128
@@ -242,19 +178,24 @@ pub struct DelegatorRewardParams {
}
impl DelegatorRewardParams {
pub fn new(mixnode_bond: &MixNodeBond, node_reward_params: NodeRewardParams) -> Self {
pub fn new(
sigma: U128,
profit_margin: U128,
node_profit: U128,
reward_params: RewardParams,
) -> Self {
DelegatorRewardParams {
sigma: mixnode_bond.sigma(&node_reward_params),
profit_margin: mixnode_bond.profit_margin(),
node_profit: mixnode_bond.node_profit(&node_reward_params),
node_reward_params,
sigma,
profit_margin,
node_profit,
reward_params,
}
}
pub fn determine_delegation_reward(&self, delegation_amount: Uint128) -> u128 {
// change all values into their fixed representations
let delegation_amount = U128::from_num(delegation_amount.u128());
let circulating_supply = U128::from_num(self.node_reward_params.circulating_supply());
let circulating_supply = U128::from_num(self.reward_params.circulating_supply());
let scaled_delegation_amount = delegation_amount / circulating_supply;
let delegator_reward =
@@ -272,8 +213,56 @@ impl DelegatorRewardParams {
}
}
pub fn node_reward_params(&self) -> &NodeRewardParams {
&self.node_reward_params
pub fn node_reward_params(&self) -> RewardParams {
self.reward_params
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct StoredNodeRewardResult {
reward: Uint128,
lambda: Uint128,
sigma: Uint128,
}
impl StoredNodeRewardResult {
pub fn reward(&self) -> Uint128 {
self.reward
}
pub fn lambda(&self) -> Uint128 {
self.lambda
}
pub fn sigma(&self) -> Uint128 {
self.sigma
}
}
impl TryFrom<NodeRewardResult> for StoredNodeRewardResult {
type Error = MixnetContractError;
fn try_from(node_reward_result: NodeRewardResult) -> Result<Self, Self::Error> {
Ok(StoredNodeRewardResult {
reward: Uint128::new(
node_reward_result
.reward()
.checked_cast()
.ok_or(MixnetContractError::CastError)?,
),
lambda: Uint128::new(
node_reward_result
.lambda()
.checked_cast()
.ok_or(MixnetContractError::CastError)?,
),
sigma: Uint128::new(
node_reward_result
.sigma()
.checked_cast()
.ok_or(MixnetContractError::CastError)?,
),
})
}
}
@@ -307,6 +296,7 @@ pub struct MixNodeBond {
pub block_height: u64,
pub mix_node: MixNode,
pub proxy: Option<Addr>,
pub accumulated_rewards: Option<Uint128>,
}
impl MixNodeBond {
@@ -326,9 +316,14 @@ impl MixNodeBond {
block_height,
mix_node,
proxy,
accumulated_rewards: None,
}
}
pub fn accumulated_rewards(&self) -> Uint128 {
self.accumulated_rewards.unwrap_or_else(Uint128::zero)
}
pub fn profit_margin(&self) -> U128 {
U128::from_num(self.mix_node.profit_margin_percent) / U128::from_num(100)
}
@@ -349,11 +344,16 @@ impl MixNodeBond {
&self.mix_node
}
// Takes into account accumulated rewards as well as current pledge and delegation amounts
pub fn total_bond(&self) -> Option<u128> {
if self.pledge_amount.denom != self.total_delegation.denom {
None
} else {
Some(self.pledge_amount.amount.u128() + self.total_delegation.amount.u128())
Some(
self.pledge_amount.amount.u128()
+ self.total_delegation.amount.u128()
+ self.accumulated_rewards().u128(),
)
}
}
@@ -366,6 +366,10 @@ impl MixNodeBond {
* U128::from_num(rewarded_set_size)
}
// TODO: There is an effect here when adding accumulted rewards to the total bond, ie accumulated rewards will not
// affect lambda, but will affect sigma, in turn over time, if left unclaimed operator rewards will not compound, but
// behave similarly to delegations.
// The question is should this be taken into account when calculating operator rewards?
pub fn pledge_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.pledge_amount().amount.u128()) / U128::from_num(circulating_supply)
}
@@ -375,26 +379,46 @@ impl MixNodeBond {
/ U128::from_num(circulating_supply)
}
pub fn lambda(&self, params: &NodeRewardParams) -> U128 {
pub fn lambda(&self, params: &RewardParams) -> U128 {
// Ratio of a bond to the token circulating supply
let pledge_to_circulating_supply_ratio =
self.pledge_to_circulating_supply(params.circulating_supply());
pledge_to_circulating_supply_ratio.min(params.one_over_k())
}
pub fn sigma(&self, params: &NodeRewardParams) -> U128 {
pub fn sigma(&self, params: &RewardParams) -> U128 {
// Ratio of a delegation to the the token circulating supply
let total_bond_to_circulating_supply_ratio =
self.total_bond_to_circulating_supply(params.circulating_supply());
total_bond_to_circulating_supply_ratio.min(params.one_over_k())
}
pub fn reward(&self, params: &NodeRewardParams) -> NodeRewardResult {
pub fn estimate_reward(
&self,
params: &RewardParams,
) -> Result<(u64, u64, u64), MixnetContractError> {
let total_node_reward = self.reward(params);
let operator_reward = self.operator_reward(params);
// TODO: This overestimates the reward by a lot, it should take a Uint128 and return estiamte for that
let delegators_reward = self.reward_delegation(self.total_delegation().amount, params);
Ok((
total_node_reward
.reward()
.checked_to_num::<u128>()
.unwrap_or_default()
.try_into()?,
operator_reward.try_into()?,
delegators_reward.try_into()?,
))
}
pub fn reward(&self, params: &RewardParams) -> NodeRewardResult {
let lambda = self.lambda(params);
let sigma = self.sigma(params);
let reward = params.performance()
* params.period_reward_pool()
* params.epoch_reward_pool()
* (sigma * params.omega()
+ params.alpha() * lambda * sigma * params.rewarded_set_size())
/ (ONE + params.alpha());
@@ -406,22 +430,22 @@ impl MixNodeBond {
}
}
pub fn node_profit(&self, params: &NodeRewardParams) -> U128 {
if self.reward(params).reward() < params.operator_cost() {
U128::from_num(0)
pub fn node_profit(&self, params: &RewardParams) -> U128 {
if self.reward(params).reward() < params.node.operator_cost() {
U128::from_num(0u128)
} else {
self.reward(params).reward() - params.operator_cost()
self.reward(params).reward() - params.node.operator_cost()
}
}
pub fn operator_reward(&self, params: &NodeRewardParams) -> u128 {
pub fn operator_reward(&self, params: &RewardParams) -> u128 {
let reward = self.reward(params);
let profit = if reward.reward < params.operator_cost() {
U128::from_num(0)
let profit = if reward.reward < params.node.operator_cost() {
U128::from_num(0u128)
} else {
reward.reward - params.operator_cost()
reward.reward - params.node.operator_cost()
};
let operator_base_reward = reward.reward.min(params.operator_cost());
let operator_base_reward = reward.reward.min(params.node.operator_cost());
let operator_reward = (self.profit_margin()
+ (ONE - self.profit_margin()) * reward.lambda / reward.sigma)
* profit;
@@ -440,7 +464,7 @@ impl MixNodeBond {
}
}
pub fn sigma_ratio(&self, params: &NodeRewardParams) -> U128 {
pub fn sigma_ratio(&self, params: &RewardParams) -> U128 {
if self.total_bond_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
{
self.total_bond_to_circulating_supply(params.circulating_supply())
@@ -449,8 +473,13 @@ impl MixNodeBond {
}
}
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &NodeRewardParams) -> u128 {
let reward_params = DelegatorRewardParams::new(self, *params);
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &RewardParams) -> u128 {
let reward_params = DelegatorRewardParams::new(
self.sigma(params),
self.profit_margin(),
self.node_profit(params),
params.to_owned(),
);
reward_params.determine_delegation_reward(delegation_amount)
}
}
@@ -589,6 +618,7 @@ mod tests {
block_height: 100,
mix_node: mixnode_fixture(),
proxy: None,
accumulated_rewards: Some(Uint128::zero()),
};
let mix2 = MixNodeBond {
@@ -599,6 +629,7 @@ mod tests {
block_height: 120,
mix_node: mixnode_fixture(),
proxy: None,
accumulated_rewards: Some(Uint128::zero()),
};
let mix3 = MixNodeBond {
@@ -609,6 +640,7 @@ mod tests {
block_height: 120,
mix_node: mixnode_fixture(),
proxy: None,
accumulated_rewards: Some(Uint128::zero()),
};
let mix4 = MixNodeBond {
@@ -619,6 +651,7 @@ mod tests {
block_height: 120,
mix_node: mixnode_fixture(),
proxy: None,
accumulated_rewards: Some(Uint128::zero()),
};
let mix5 = MixNodeBond {
@@ -629,6 +662,7 @@ mod tests {
block_height: 120,
mix_node: mixnode_fixture(),
proxy: None,
accumulated_rewards: Some(Uint128::zero()),
};
// summary:
@@ -1,12 +1,15 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::NodeRewardParams;
use crate::reward_params::NodeRewardParams;
use crate::ContractStateParams;
use crate::{Gateway, IdentityKey, MixNode};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
type BlockHeight = u64;
type DelegateAddress = Vec<u8>;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub rewarding_validator_address: String,
@@ -15,6 +18,19 @@ pub struct InstantiateMsg {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
ReconcileDelegations {},
CheckpointMixnodes {},
CompoundOperatorRewardOnBehalf {
owner: String,
},
CompoundDelegatorRewardOnBehalf {
owner: String,
mix_identity: IdentityKey,
},
CompoundOperatorReward {},
CompoundDelegatorReward {
mix_identity: IdentityKey,
},
BondMixnode {
mix_node: MixNode,
owner_signature: String,
@@ -50,11 +66,11 @@ pub enum ExecuteMsg {
// id of the current rewarding interval
interval_id: u32,
},
RewardNextMixDelegators {
mix_identity: IdentityKey,
// id of the current rewarding interval
interval_id: u32,
},
// RewardNextMixDelegators {
// mix_identity: IdentityKey,
// // id of the current rewarding interval
// interval_id: u32,
// },
DelegateToMixnodeOnBehalf {
mix_identity: IdentityKey,
delegate: String,
@@ -83,7 +99,8 @@ pub enum ExecuteMsg {
rewarded_set: Vec<IdentityKey>,
expected_active_set_size: u32,
},
AdvanceCurrentInterval {},
// AdvanceCurrentInterval {},
AdvanceCurrentEpoch {},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -108,7 +125,7 @@ pub enum QueryMsg {
// gets all [paged] delegations in the entire network
// TODO: do we even want that?
GetAllNetworkDelegations {
start_after: Option<(IdentityKey, String)>,
start_after: Option<(IdentityKey, DelegateAddress, BlockHeight)>,
limit: Option<u32>,
},
// gets all [paged] delegations associated with particular mixnode
@@ -116,7 +133,7 @@ pub enum QueryMsg {
mix_identity: IdentityKey,
// since `start_after` is user-provided input, we can't use `Addr` as we
// can't guarantee it's validated.
start_after: Option<String>,
start_after: Option<(String, u64)>,
limit: Option<u32>,
},
// gets all [paged] delegations associated with particular delegator
@@ -147,13 +164,18 @@ pub enum QueryMsg {
start_after: Option<IdentityKey>,
limit: Option<u32>,
},
GetRewardedSetHeightsForInterval {
interval_id: u32,
},
GetRewardedSetUpdateDetails {},
GetCurrentRewardedSetHeight {},
GetCurrentInterval {},
GetRewardedSetRefreshBlocks {},
GetCurrentEpoch {},
GetEpochsInInterval {},
QueryOperatorReward {
address: String,
},
QueryDelegatorReward {
address: String,
mix_identity: IdentityKey,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -0,0 +1,260 @@
use crate::{error::MixnetContractError, mixnode::StoredNodeRewardResult, ONE, U128};
use az::CheckedCast;
use cosmwasm_std::Uint128;
use network_defaults::DEFAULT_OPERATOR_INTERVAL_COST;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct NodeEpochRewards {
params: NodeRewardParams,
result: StoredNodeRewardResult,
epoch_id: u32,
}
impl NodeEpochRewards {
pub fn new(params: NodeRewardParams, result: StoredNodeRewardResult, epoch_id: u32) -> Self {
Self {
params,
result,
epoch_id,
}
}
pub fn epoch_id(&self) -> u32 {
self.epoch_id
}
pub fn sigma(&self) -> Uint128 {
self.result.sigma()
}
pub fn lambda(&self) -> Uint128 {
self.result.lambda()
}
pub fn params(&self) -> NodeRewardParams {
self.params
}
pub fn reward(&self) -> Uint128 {
self.result.reward()
}
pub fn operator_cost(&self) -> U128 {
U128::from_num(self.params.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
}
pub fn node_profit(&self) -> U128 {
let reward = U128::from_num(self.reward().u128());
if reward < self.operator_cost() {
U128::from_num(0u128)
} else {
reward - self.operator_cost()
}
}
pub fn operator_reward(&self, profit_margin: U128) -> Result<Uint128, MixnetContractError> {
let reward = self.node_profit();
let operator_base_reward = reward.min(self.operator_cost());
let operator_reward = (profit_margin
+ (ONE - profit_margin) * U128::from_num(self.lambda().u128())
/ U128::from_num(self.sigma().u128()))
* reward;
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0u128));
if let Some(int_reward) = reward.checked_cast() {
Ok(Uint128::new(int_reward))
} else {
Err(MixnetContractError::CastError)
}
}
pub fn delegation_reward(
&self,
delegation_amount: Uint128,
profit_margin: U128,
epoch_reward_params: EpochRewardParams,
) -> Result<Uint128, MixnetContractError> {
// change all values into their fixed representations
let delegation_amount = U128::from_num(delegation_amount.u128());
let circulating_supply = U128::from_num(epoch_reward_params.circulating_supply());
let scaled_delegation_amount = delegation_amount / circulating_supply;
let delegator_reward = (ONE - profit_margin) * scaled_delegation_amount
/ U128::from_num(self.sigma().u128())
* self.node_profit();
let reward = delegator_reward.max(U128::ZERO);
if let Some(int_reward) = reward.checked_cast() {
Ok(Uint128::new(int_reward))
} else {
Err(MixnetContractError::CastError)
}
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct EpochRewardParams {
epoch_reward_pool: Uint128,
rewarded_set_size: Uint128,
active_set_size: Uint128,
circulating_supply: Uint128,
sybil_resistance_percent: u8,
active_set_work_factor: u8,
}
impl EpochRewardParams {
pub fn new(
epoch_reward_pool: u128,
rewarded_set_size: u128,
active_set_size: u128,
circulating_supply: u128,
sybil_resistance_percent: u8,
active_set_work_factor: u8,
) -> EpochRewardParams {
EpochRewardParams {
epoch_reward_pool: Uint128::new(epoch_reward_pool),
rewarded_set_size: Uint128::new(rewarded_set_size),
active_set_size: Uint128::new(active_set_size),
circulating_supply: Uint128::new(circulating_supply),
sybil_resistance_percent,
active_set_work_factor,
}
}
// technically it's identical to what would have been derived with a Default implementation,
// however, I prefer to be explicit about it, as a `Default::default` value makes no sense
// apart from the `ValidatorCacheInner` context, where this value is not going to be touched anyway
// (it's guarded behind an `initialised` flag)
pub fn new_empty() -> Self {
EpochRewardParams {
epoch_reward_pool: Uint128::new(0),
circulating_supply: Uint128::new(0),
sybil_resistance_percent: 0,
rewarded_set_size: Uint128::new(0),
active_set_size: Uint128::new(0),
active_set_work_factor: 0,
}
}
pub fn rewarded_set_size(&self) -> u128 {
self.rewarded_set_size.u128()
}
pub fn active_set_size(&self) -> u128 {
self.active_set_size.u128()
}
pub fn circulating_supply(&self) -> u128 {
self.circulating_supply.u128()
}
pub fn epoch_reward_pool(&self) -> u128 {
self.epoch_reward_pool.u128()
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct NodeRewardParams {
reward_blockstamp: u64,
uptime: Uint128,
in_active_set: bool,
}
impl NodeRewardParams {
pub fn new(reward_blockstamp: u64, uptime: u128, in_active_set: bool) -> NodeRewardParams {
NodeRewardParams {
reward_blockstamp,
uptime: Uint128::new(uptime),
in_active_set,
}
}
pub fn operator_cost(&self) -> U128 {
U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
}
pub fn uptime(&self) -> u128 {
self.uptime.u128()
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.reward_blockstamp = blockstamp;
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct RewardParams {
pub epoch: EpochRewardParams,
pub node: NodeRewardParams,
}
impl RewardParams {
pub fn new(epoch: EpochRewardParams, node: NodeRewardParams) -> RewardParams {
RewardParams { epoch, node }
}
pub fn omega(&self) -> U128 {
// As per keybase://chat/nymtech#tokeneconomics/1179
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
if self.in_active_set() {
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
self.active_set_work_factor() / denom * self.rewarded_set_size()
} else {
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
ONE / denom * self.rewarded_set_size()
}
}
pub fn idle_nodes(&self) -> Uint128 {
self.epoch.rewarded_set_size - self.epoch.active_set_size
}
pub fn active_set_work_factor(&self) -> U128 {
U128::from_num(self.epoch.active_set_work_factor)
}
pub fn in_active_set(&self) -> bool {
self.node.in_active_set
}
pub fn performance(&self) -> U128 {
U128::from_num(self.node.uptime.u128()) / U128::from_num(100)
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.node.reward_blockstamp = blockstamp;
}
pub fn epoch_reward_pool(&self) -> u128 {
self.epoch.epoch_reward_pool.u128()
}
pub fn rewarded_set_size(&self) -> u128 {
self.epoch.rewarded_set_size.u128()
}
pub fn circulating_supply(&self) -> u128 {
self.epoch.circulating_supply.u128()
}
pub fn reward_blockstamp(&self) -> u64 {
self.node.reward_blockstamp
}
pub fn uptime(&self) -> u128 {
self.node.uptime.u128()
}
pub fn one_over_k(&self) -> U128 {
ONE / U128::from_num(self.epoch.rewarded_set_size.u128())
}
pub fn alpha(&self) -> U128 {
U128::from_num(self.epoch.sybil_resistance_percent) / U128::from_num(100)
}
}
@@ -73,8 +73,7 @@ impl Display for ContractStateParams {
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct RewardingResult {
pub operator_reward: Uint128,
pub total_delegator_reward: Uint128,
pub node_reward: Uint128,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -124,7 +123,7 @@ pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
pub type SphinxKey = String;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
pub struct PagedRewardedSetResponse {
pub identities: Vec<(IdentityKey, RewardedSetNodeStatus)>,
pub start_next_after: Option<IdentityKey>,
+1
View File
@@ -6,6 +6,7 @@ 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"
+11 -16
View File
@@ -1,10 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bls12_381::Scalar;
use coconut_interface::{
aggregate_signature_shares, aggregate_verification_keys, prepare_blind_sign,
prove_bandwidth_credential, Attribute, BlindSignRequest, BlindSignRequestBody, Credential,
ElGamalKeyPair, Parameters, Signature, SignatureShare, VerificationKey,
Parameters, Signature, SignatureShare, VerificationKey,
};
use url::Url;
@@ -64,14 +65,13 @@ async fn obtain_partial_credential(
params: &Parameters,
public_attributes: &[Attribute],
private_attributes: &[Attribute],
pedersen_commitments_openings: &[Scalar],
blind_sign_request: &BlindSignRequest,
client: &validator_client::ApiClient,
validator_vk: &VerificationKey,
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,22 +110,17 @@ 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 elgamal_keypair = coconut_interface::elgamal_keygen(params);
let blind_sign_request = prepare_blind_sign(
params,
&elgamal_keypair,
private_attributes,
public_attributes,
)?;
let (pedersen_commitments_openings, blind_sign_request) =
prepare_blind_sign(params, private_attributes, public_attributes)?;
let first = obtain_partial_credential(
params,
public_attributes,
private_attributes,
&pedersen_commitments_openings,
&blind_sign_request,
&client,
&validator_partial_vk.key,
&blind_sign_request,
&elgamal_keypair,
)
.await?;
shares.push(SignatureShare::new(first, 1));
@@ -138,10 +133,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);
+10 -16
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt, ops::Deref, str::FromStr};
use std::{collections::HashMap, fmt, 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,20 +118,12 @@ pub struct SupportedNetworks {
impl SupportedNetworks {
pub fn new(support: Vec<Network>) -> Self {
let mut networks = HashMap::new();
for network in support {
match network {
Network::MAINNET => {
networks.insert(Network::MAINNET, 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: support
.into_iter()
.map(|n| (n, n.details().into()))
.collect(),
}
SupportedNetworks { networks }
}
pub fn bech32_prefix(&self, network: Network) -> Option<&str> {
@@ -170,9 +162,11 @@ impl SupportedNetworks {
.map(|network_details| network_details.rewarding_validator_address.as_str())
}
pub fn validators(&self, network: Network) -> Option<&Vec<ValidatorDetails>> {
pub fn validators(&self, network: Network) -> impl Iterator<Item = &ValidatorDetails> {
self.networks
.get(&network)
.map(|network_details| &network_details.validators)
.into_iter()
.flatten()
}
}
+5 -5
View File
@@ -95,10 +95,9 @@ pub struct ValidatorDetails {
impl ValidatorDetails {
pub fn new(nymd_url: &str, api_url: Option<&str>) -> Self {
let api_url = api_url.map(|api_url_str| api_url_str.to_string());
ValidatorDetails {
nymd_url: nymd_url.to_string(),
api_url,
api_url: api_url.map(ToString::to_string),
}
}
@@ -118,14 +117,14 @@ impl ValidatorDetails {
pub fn default_nymd_endpoints() -> Vec<Url> {
DEFAULT_NETWORK
.validators()
.map(|validator| validator.nymd_url())
.map(ValidatorDetails::nymd_url)
.collect()
}
pub fn default_api_endpoints() -> Vec<Url> {
DEFAULT_NETWORK
.validators()
.filter_map(|validator| validator.api_url())
.filter_map(ValidatorDetails::api_url)
.collect()
}
@@ -179,7 +178,8 @@ pub const VALIDATOR_API_VERSION: &str = "v1";
// REWARDING
/// We'll be assuming a few more things, profit margin and cost function. Since we don't have relialable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate interval costs to Nyms. We'll also assume a cost of 40$ per interval(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms
pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$
// pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$
pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 55_556; // 40$/1hr at 1 Nym == 1$
// TODO: is there a way to get this from the chain
pub const TOTAL_SUPPLY: u128 = 1_000_000_000_000_000;
@@ -0,0 +1,32 @@
[package]
name = "nym_compact_ecash"
version = "0.1.0"
authors = ["Ania Piotrowska <ania@nymtech.net>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
#bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
bls12_381 = { path = "/Users/ania/Documents/Git/andrew_bls12_381", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
itertools = "0.10"
digest = "0.9"
rand = "0.8"
thiserror = "1.0"
sha2 = "0.9"
bs58 = "0.4.0"
[dev-dependencies]
criterion = { version = "0.3", features = ["html_reports"] }
[dependencies.ff]
version = "0.11"
default-features = false
[dependencies.group]
version = "0.11"
default-features = false
[[bench]]
name = "benchmarks"
harness = false
@@ -0,0 +1,360 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::ops::Neg;
use std::time::Duration;
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, multi_miller_loop, Scalar};
use criterion::{Criterion, criterion_group, criterion_main};
use ff::Field;
use group::{Curve, Group};
use itertools::izip;
use rand::seq::SliceRandom;
use nym_compact_ecash::{
aggregate_verification_keys, aggregate_wallets, generate_keypair_user,
issue_verify, issue_wallet, PartialWallet,
PayInfo, PublicKeyUser, SecretKeyUser, ttp_keygen, VerificationKeyAuth, withdrawal_request,
};
use nym_compact_ecash::identify::{identify, IdentifyResult};
use nym_compact_ecash::setup::setup;
#[allow(unused)]
fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
let gt1 = bls12_381::pairing(g11, g21);
let gt2 = bls12_381::pairing(g12, g22);
assert_eq!(gt1, gt2)
}
#[allow(unused)]
fn single_pairing(g11: &G1Affine, g21: &G2Affine) {
let gt1 = bls12_381::pairing(g11, g21);
}
#[allow(unused)]
fn exponent_in_g1(g1: G1Projective, r: Scalar) {
let g11 = (g1 * r);
}
#[allow(unused)]
fn exponent_in_g2(g2: G2Projective, r: Scalar) {
let g22 = (g2 * r);
}
#[allow(unused)]
fn exponent_in_gt(gt: Gt, r: Scalar) {
let gtt = (gt * r);
}
#[allow(unused)]
fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
let miller_loop_result = multi_miller_loop(&[
(g11, &G2Prepared::from(*g21)),
(&g12.neg(), &G2Prepared::from(*g22)),
]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
#[allow(unused)]
fn multi_miller_pairing_with_prepared(
g11: &G1Affine,
g21: &G2Prepared,
g12: &G1Affine,
g22: &G2Prepared,
) {
let miller_loop_result = multi_miller_loop(&[(g11, g21), (&g12.neg(), g22)]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
// the case of being able to prepare G2 generator
#[allow(unused)]
fn multi_miller_pairing_with_semi_prepared(
g11: &G1Affine,
g21: &G2Affine,
g12: &G1Affine,
g22: &G2Prepared,
) {
let miller_loop_result =
multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), g22)]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
#[allow(unused)]
fn bench_pairings(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-pairings");
group.measurement_time(Duration::from_secs(200));
let mut rng = rand::thread_rng();
let g1 = G1Affine::generator();
let g2 = G2Affine::generator();
let r = Scalar::random(&mut rng);
let s = Scalar::random(&mut rng);
let g11 = (g1 * r).to_affine();
let g21 = (g2 * s).to_affine();
let g21_prep = G2Prepared::from(g21);
let g12 = (g1 * s).to_affine();
let g22 = (g2 * r).to_affine();
let g22_prep = G2Prepared::from(g22);
let gt = bls12_381::pairing(&g11, &g21);
let gen1 = G1Projective::generator();
let gen2 = G2Projective::generator();
group.bench_function("exponent operation in G1", |b| {
b.iter(|| exponent_in_g1(gen1, r))
});
group.bench_function("exponent operation in G2", |b| {
b.iter(|| exponent_in_g2(gen2, r))
});
group.bench_function("exponent operation in Gt", |b| {
b.iter(|| exponent_in_gt(gt, r))
});
group.bench_function("single pairing", |b| {
b.iter(|| single_pairing(&g11, &g21))
});
group.bench_function("double pairing", |b| {
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
});
group.bench_function("multi miller in affine", |b| {
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
});
group.bench_function("multi miller with prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
});
group.bench_function("multi miller with semi-prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
});
}
struct BenchCase {
num_authorities: u64,
threshold_p: f32,
L: u64,
spend_vv: u64,
case_nr_pub_keys: u64,
}
fn bench_compact_ecash(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-compact-ecash");
// group.sample_size(300);
// group.measurement_time(Duration::from_secs(1500));
let case = BenchCase {
num_authorities: 100,
threshold_p: 0.7,
L: 100,
spend_vv: 1,
case_nr_pub_keys: 99,
};
let params = setup(case.L);
let grp = params.grp();
let user_keypair = generate_keypair_user(&grp);
let threshold = (case.threshold_p * case.num_authorities as f32).round() as u64;
let authorities_keypairs = ttp_keygen(&grp, threshold, case.num_authorities).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let indices: Vec<u64> = (1..case.num_authorities + 1).collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
// ISSUANCE PHASE
let (req, req_info) = withdrawal_request(grp, &user_keypair.secret_key()).unwrap();
// CLIENT BENCHMARK: prepare a single withdrawal request
// group.bench_function(
// &format!(
// "[Client] withdrawal_request_{}_authorities_{}_L_{}_threshold",
// case.num_authorities, case.L, case.threshold_p,
// ),
// |b| b.iter(|| withdrawal_request(grp, &user_keypair.secret_key()).unwrap()),
// );
// ISSUING AUTHRORITY BENCHMARK: Benchmark the issue_wallet function
// called by an authority to issue a blind signature on a partial wallet
let mut rng = rand::thread_rng();
let keypair = authorities_keypairs.choose(&mut rng).unwrap();
// group.bench_function(
// &format!("[Issuing Authority] issue_partial_wallet_with_L_{}", case.L, ),
// |b| {
// b.iter(|| {
// issue_wallet(
// &grp,
// keypair.secret_key(),
// user_keypair.public_key(),
// &req,
// ).unwrap()
// })
// },
// );
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue_wallet(
&grp,
auth_keypair.secret_key(),
user_keypair.public_key(),
&req,
);
wallet_blinded_signatures.push(blind_signature.unwrap());
}
// CLIENT BENCHMARK: verify the issued partial wallet
let w = wallet_blinded_signatures.get(0).clone().unwrap();
let vk = verification_keys_auth.get(0).clone().unwrap();
// group.bench_function(
// &format!("[Client] issue_verify_a_partial_wallet_with_L_{}", case.L, ),
// |b| b.iter(|| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap()),
// );
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
wallet_blinded_signatures.iter(),
verification_keys_auth.iter()
)
.map(|(w, vk)| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
.collect();
// CLIENT BENCHMARK: aggregating all partial wallets
// group.bench_function(
// &format!(
// "[Client] aggregate_wallets_with_L_{}_threshold_{}",
// case.L, case.threshold_p,
// ),
// |b| {
// b.iter(|| {
// aggregate_wallets(
// &grp,
// &verification_key,
// &user_keypair.secret_key(),
// &unblinded_wallet_shares,
// &req_info,
// )
// .unwrap()
// })
// },
// );
// Aggregate partial wallets
let aggr_wallet = aggregate_wallets(
&grp,
&verification_key,
&user_keypair.secret_key(),
&unblinded_wallet_shares,
&req_info,
)
.unwrap();
// SPENDING PHASE
let pay_info = PayInfo { info: [6u8; 32] };
// CLIENT BENCHMARK: spend a single coin from the wallet
// group.bench_function(
// &format!(
// "[Client] spend_a_single_coin_L_{}_threshold_{}",
// case.L, case.threshold_p,
// ),
// |b| {
// b.iter(|| {
// aggr_wallet
// .spend(
// &params,
// &verification_key,
// &user_keypair.secret_key(),
// &pay_info,
// true,
// case.spend_vv,
// )
// .unwrap()
// })
// },
// );
let (payment, upd_wallet) = aggr_wallet
.spend(
&params,
&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(&params, &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(
&params,
&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(&params, &verification_key, payment.clone(), payment2.clone(), pay_info.clone(), pay_info2.clone()).unwrap()
})
},
);
let identify_result = identify(&params, &verification_key, payment, payment2, pay_info.clone(), pay_info2.clone()).unwrap();
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key()));
}
criterion_group!(benches, bench_compact_ecash);
criterion_main!(benches);
@@ -0,0 +1,49 @@
use thiserror::Error;
pub type Result<T> = std::result::Result<T, CompactEcashError>;
#[derive(Error, Debug)]
pub enum CompactEcashError {
#[error("Setup error: {0}")]
Setup(String),
#[error("Aggregation error: {0}")]
Aggregation(String),
#[error("Withdrawal Request Verification related error: {0}")]
WithdrawalRequestVerification(String),
#[error("Deserialization error: {0}")]
Deserialization(String),
#[error("Interpolation error: {0}")]
Interpolation(String),
#[error("Issuance Verification related error: {0}")]
IssuanceVfy(String),
#[error("Spend Verification related error: {0}")]
Spend(String),
#[error("ZKP Proof related error: {0}")]
RangeProofOutOfBound(String),
#[error("Identify Verification related error: {0}")]
Identify(String),
#[error(
"Deserailization error, expected at least {} bytes, got {}",
min,
actual
)]
DeserializationMinLength { min: usize, actual: usize },
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {} or {modulus_target} % {modulus} == 0")]
DeserializationInvalidLength {
actual: usize,
target: usize,
modulus_target: usize,
modulus: usize,
object: String,
},
}
@@ -0,0 +1,40 @@
use std::convert::TryInto;
use bls12_381::Scalar;
pub use scheme::aggregation::aggregate_verification_keys;
pub use scheme::aggregation::aggregate_wallets;
pub use scheme::identify;
pub use scheme::keygen::{PublicKeyUser, SecretKeyUser, VerificationKeyAuth};
pub use scheme::keygen::generate_keypair_user;
pub use scheme::keygen::ttp_keygen;
pub use scheme::PartialWallet;
pub use scheme::PayInfo;
pub use scheme::setup;
pub use scheme::withdrawal::issue_verify;
pub use scheme::withdrawal::issue_wallet;
pub use scheme::withdrawal::withdrawal_request;
pub use traits::Base58;
use crate::error::CompactEcashError;
use crate::traits::Bytable;
mod error;
mod proofs;
mod scheme;
#[cfg(test)]
mod tests;
mod traits;
mod utils;
pub type Attribute = Scalar;
impl Bytable for Attribute {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CompactEcashError> {
Ok(Attribute::from_bytes(slice.try_into().unwrap()).unwrap())
}
}
@@ -0,0 +1,56 @@
use std::borrow::Borrow;
use bls12_381::Scalar;
use digest::Digest;
use digest::generic_array::typenum::Unsigned;
use sha2::Sha256;
pub mod proof_spend;
pub mod proof_withdrawal;
type ChallengeDigest = Sha256;
/// Generates a Scalar [or Fp] challenge by hashing a number of elliptic curve points.
fn compute_challenge<D, I, B>(iter: I) -> Scalar
where
D: Digest,
I: Iterator<Item=B>,
B: AsRef<[u8]>,
{
let mut h = D::new();
for point_representation in iter {
h.update(point_representation);
}
let digest = h.finalize();
// TODO: I don't like the 0 padding here (though it's what we've been using before,
// but we never had a security audit anyway...)
// instead we could maybe use the `from_bytes` variant and adding some suffix
// when computing the digest until we produce a valid scalar.
let mut bytes = [0u8; 64];
let pad_size = 64usize
.checked_sub(D::OutputSize::to_usize())
.unwrap_or_default();
bytes[pad_size..].copy_from_slice(&digest);
Scalar::from_bytes_wide(&bytes)
}
fn produce_response(witness_replacement: &Scalar, challenge: &Scalar, secret: &Scalar) -> Scalar {
witness_replacement - challenge * secret
}
// note: it's caller's responsibility to ensure witnesses.len() = secrets.len()
fn produce_responses<S>(witnesses: &[Scalar], challenge: &Scalar, secrets: &[S]) -> Vec<Scalar>
where
S: Borrow<Scalar>,
{
debug_assert_eq!(witnesses.len(), secrets.len());
witnesses
.iter()
.zip(secrets.iter())
.map(|(w, x)| produce_response(w, challenge, x.borrow()))
.collect()
}
@@ -0,0 +1,777 @@
use std::convert::{TryFrom, TryInto};
use bls12_381::{G1Projective, G2Projective, Scalar};
use group::{Curve, GroupEncoding};
use crate::error::{CompactEcashError, Result};
use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produce_responses};
use crate::scheme::keygen::VerificationKeyAuth;
use crate::scheme::setup::Parameters;
use crate::utils::{try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar_vec, try_deserialize_scalar};
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct SpendInstance {
pub kappa: G2Projective,
pub cc: G1Projective,
pub aa: Vec<G1Projective>,
pub ss: Vec<G1Projective>,
pub tt: Vec<G1Projective>,
pub kappa_k: Vec<G2Projective>,
}
impl TryFrom<&[u8]> for SpendInstance {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<SpendInstance> {
if bytes.len() < 48 * 5 + 2 * 96 || (bytes.len()) % 48 != 0 {
return Err(CompactEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len(),
target: 48 * 5 + 2 * 96,
modulus: 48,
object: "spend instance".to_string(),
});
}
let mut j = 0;
let kappa_bytes = bytes[j..j + 96].try_into().unwrap();
let kappa = try_deserialize_g2_projective(
&kappa_bytes,
CompactEcashError::Deserialization("Failed to deserialize kappa".to_string()),
)?;
j += 96;
let a_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < a_len as usize * 48 {
return Err(CompactEcashError::DeserializationMinLength {
min: a_len as usize * 48,
actual: bytes[j..].len(),
});
}
let mut aa = Vec::with_capacity(a_len as usize);
for i in 0..a_len as usize {
let start = j + i * 48;
let end = start + 48;
let aa_elem_bytes = bytes[start..end].try_into().unwrap();
let aa_elem = try_deserialize_g1_projective(
&aa_elem_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed A values".to_string(),
),
)?;
aa.push(aa_elem)
}
j += j + a_len as usize * 48;
let cc_bytes = bytes[j..j + 48].try_into().unwrap();
let cc = try_deserialize_g1_projective(
&cc_bytes,
CompactEcashError::Deserialization("Failed to deserialize C".to_string()),
)?;
j += 48;
let s_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < s_len as usize * 48 {
return Err(CompactEcashError::DeserializationMinLength {
min: s_len as usize * 48,
actual: bytes[j..].len(),
});
}
let mut ss = Vec::with_capacity(s_len as usize);
for i in 0..s_len as usize {
let start = j + i * 48;
let end = start + 48;
let ss_elem_bytes = bytes[start..end].try_into().unwrap();
let ss_elem = try_deserialize_g1_projective(
&ss_elem_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed S values".to_string(),
),
)?;
ss.push(ss_elem)
}
j += j + s_len as usize * 48;
let t_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < t_len as usize * 48 {
return Err(CompactEcashError::DeserializationMinLength {
min: t_len as usize * 48,
actual: bytes[j..].len(),
});
}
let mut tt = Vec::with_capacity(t_len as usize);
for i in 0..t_len as usize {
let start = j + i * 48;
let end = start + 48;
let tt_elem_bytes = bytes[start..end].try_into().unwrap();
let tt_elem = try_deserialize_g1_projective(
&tt_elem_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed T values".to_string(),
),
)?;
tt.push(tt_elem)
}
j += j + t_len as usize * 48;
let kappa_k_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < kappa_k_len as usize * 96 {
return Err(CompactEcashError::DeserializationMinLength {
min: kappa_k_len as usize * 96,
actual: bytes[j..].len(),
});
}
let mut kappa_k = Vec::with_capacity(kappa_k_len as usize);
for i in 0..kappa_k_len as usize {
let start = j + i * 48;
let end = start + 48;
let kappa_k_elem_bytes = bytes[start..end].try_into().unwrap();
let kappa_k_elem = try_deserialize_g2_projective(
&kappa_k_elem_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed kappa_k values".to_string(),
),
)?;
kappa_k.push(kappa_k_elem)
}
Ok(SpendInstance {
kappa,
aa,
cc,
ss,
tt,
kappa_k,
})
}
}
impl SpendInstance {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes: Vec<u8> = Default::default();
bytes.extend_from_slice(self.kappa.to_bytes().as_ref());
bytes.extend_from_slice(&self.aa.len().to_le_bytes());
for a in &self.aa {
bytes.extend_from_slice(&a.to_affine().to_compressed());
}
bytes.extend_from_slice(self.cc.to_bytes().as_ref());
bytes.extend_from_slice(&self.ss.len().to_le_bytes());
for s in &self.ss {
bytes.extend_from_slice(&s.to_affine().to_compressed());
}
bytes.extend_from_slice(&self.tt.len().to_le_bytes());
for t in &self.tt {
bytes.extend_from_slice(&t.to_affine().to_compressed());
}
bytes.extend_from_slice(&self.kappa_k.len().to_le_bytes());
for k in &self.kappa_k {
bytes.extend_from_slice(&k.to_affine().to_compressed());
}
bytes
}
}
pub struct SpendWitness {
// includes skUser, v, t
pub attributes: Vec<Scalar>,
// signature randomizing element
pub r: Scalar,
pub o_c: Scalar,
pub lk: Vec<Scalar>,
pub o_a: Vec<Scalar>,
pub mu: Vec<Scalar>,
pub o_mu: Vec<Scalar>,
pub r_k: Vec<Scalar>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SpendProof {
challenge: Scalar,
response_r: Scalar,
response_r_l: Vec<Scalar>,
response_l: Vec<Scalar>,
response_o_a: Vec<Scalar>,
response_o_c: Scalar,
response_mu: Vec<Scalar>,
response_o_mu: Vec<Scalar>,
response_attributes: Vec<Scalar>,
}
impl SpendProof {
pub fn construct(
params: &Parameters,
instance: &SpendInstance,
witness: &SpendWitness,
verification_key: &VerificationKeyAuth,
rr: &[Scalar],
) -> Self {
let grparams = params.grp();
// generate random values to replace each witness
let r_attributes = grparams.n_random_scalars(witness.attributes.len());
let r_sk = r_attributes[0];
let r_v = r_attributes[1];
let r_r = grparams.random_scalar();
let r_o_c = grparams.random_scalar();
let r_r_lk = grparams.n_random_scalars(witness.r_k.len());
let r_lk = grparams.n_random_scalars(witness.lk.len());
let r_o_a = grparams.n_random_scalars(witness.o_a.len());
let r_mu = grparams.n_random_scalars(witness.mu.len());
let r_o_mu = grparams.n_random_scalars(witness.o_mu.len());
let g1 = *grparams.gen1();
let gamma1 = *grparams.gamma1();
let beta2_bytes = verification_key
.beta_g2
.iter()
.map(|beta_i| beta_i.to_bytes())
.collect::<Vec<_>>();
// compute zkp commitment for each instance
let zkcm_kappa = grparams.gen2() * r_r
+ verification_key.alpha
+ r_attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
let zkcm_cc = g1 * r_o_c + gamma1 * r_v;
let zkcm_aa: Vec<G1Projective> =
r_o_a
.iter()
.zip(r_lk.iter()).map(|(r_o_a_k, r_l_k)| g1 * r_o_a_k + gamma1 * r_l_k)
.collect::<Vec<_>>();
let zkcm_aa_bytes = zkcm_aa
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
let zkcm_ss = r_mu.iter().map(|r_mu_k| grparams.delta() * r_mu_k).collect::<Vec<_>>();
let zkcm_ss_bytes = zkcm_ss
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
let zkcm_tt = rr
.iter()
.zip(r_mu.iter()).map(|(rr_k, r_mu_k)| g1 * r_sk + (g1 * rr_k) * r_mu_k).collect::<Vec<_>>();
let zkcm_tt_bytes = zkcm_tt
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
let zkcm_gamma11 = instance.aa
.iter()
.zip(r_mu.iter())
.zip(r_o_mu.iter())
.map(|((aa_k, r_mu_k), r_o_mu_k)| (aa_k + instance.cc + gamma1) * r_mu_k + g1 * r_o_mu_k)
.collect::<Vec<_>>();
let zkcm_gamma11_bytes = zkcm_gamma11
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
let zkcm_kappa_k = r_lk.iter()
.zip(r_r_lk.iter())
.map(|(r_k, r_r_k)| params.pk_rp().alpha + params.pk_rp().beta * r_k + grparams.gen2() * r_r_k)
.collect::<Vec<_>>();
let zkcm_kappa_k_bytes = zkcm_kappa_k
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
// compute the challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(grparams.gen1().to_bytes().as_ref())
.chain(std::iter::once(gamma1.to_bytes().as_ref()))
.chain(std::iter::once(verification_key.alpha.to_bytes().as_ref()))
.chain(beta2_bytes.iter().map(|b| b.as_ref()))
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_kappa.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_cc.to_bytes().as_ref()))
.chain(zkcm_aa_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_ss_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_kappa_k_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_tt_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_gamma11_bytes.iter().map(|x| x.as_ref()))
);
// compute response for each witness
let response_attributes = produce_responses(
&r_attributes,
&challenge,
&witness.attributes.iter().collect::<Vec<_>>(),
);
let response_r = produce_response(&r_r, &challenge, &witness.r);
let response_r_l = produce_responses(&r_r_lk, &challenge, &witness.r_k);
let response_l = produce_responses(&r_lk, &challenge, &witness.lk);
let response_o_a = produce_responses(&r_o_a, &challenge, &witness.o_a);
let response_o_c = produce_response(&r_o_c, &challenge, &witness.o_c);
let response_mu = produce_responses(&r_mu, &challenge, &witness.mu);
let response_o_mu = produce_responses(&r_o_mu, &challenge, &witness.o_mu);
SpendProof {
challenge,
response_r,
response_r_l,
response_l,
response_o_a,
response_o_c,
response_mu,
response_o_mu,
response_attributes,
}
}
pub fn verify(
&self,
params: &Parameters,
instance: &SpendInstance,
verification_key: &VerificationKeyAuth,
rr: &[Scalar],
) -> bool {
let grparams = params.grp();
let g1 = *grparams.gen1();
let gamma1 = *grparams.gamma1();
let beta2_bytes = verification_key
.beta_g2
.iter()
.map(|beta_i| beta_i.to_bytes())
.collect::<Vec<_>>();
// re-compute each zkp commitment
let zkcm_kappa = instance.kappa * self.challenge
+ grparams.gen2() * self.response_r
+ verification_key.alpha * (Scalar::one() - self.challenge)
+ self
.response_attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
let zkcm_aa = self.response_o_a
.iter()
.zip(self.response_l.iter())
.zip(instance.aa.iter())
.map(|((resp_o_a_k, resp_l_k), aa_k)| g1 * resp_o_a_k + gamma1 * resp_l_k + aa_k * self.challenge)
.collect::<Vec<_>>();
let zkcm_aa_bytes = zkcm_aa
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
let zkcm_cc = g1 * self.response_o_c
+ gamma1 * self.response_attributes[1]
+ instance.cc * self.challenge;
let zkcm_ss = self.response_mu
.iter()
.zip(instance.ss.iter())
.map(|(resp_mu_k, ss_k)| grparams.delta() * resp_mu_k + ss_k * self.challenge)
.collect::<Vec<_>>();
let zkcm_ss_bytes = zkcm_ss
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
let zkcm_tt = self.response_mu
.iter()
.zip(rr.iter())
.zip(instance.tt.iter())
.map(|((resp_mu_k, rr_k), tt_k)| g1 * self.response_attributes[0] + (g1 * rr_k) * resp_mu_k + tt_k * self.challenge)
.collect::<Vec<_>>();
let zkcm_tt_bytes = zkcm_tt
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
let zkcm_gamma11 = instance.aa
.iter()
.zip(self.response_mu.iter())
.zip(self.response_o_mu.iter())
.map(|((aa_k, resp_mu_k), resp_o_mu_k)| (aa_k + instance.cc + gamma1) * resp_mu_k
+ g1 * resp_o_mu_k + gamma1 * self.challenge)
.collect::<Vec<_>>();
let zkcm_gamma11_bytes = zkcm_gamma11
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
let zkcm_kappa_k = instance.kappa_k
.iter()
.zip(self.response_r_l.iter())
.zip(self.response_l.iter())
.map(|((kappa_k, resp_r_k), resp_r_l_k)| kappa_k * self.challenge + grparams.gen2() * resp_r_k + params.pk_rp().alpha * (Scalar::one() - self.challenge) + params.pk_rp().beta * resp_r_l_k)
.collect::<Vec<_>>();
let zkcm_kappa_k_bytes = zkcm_kappa_k
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
// re-compute the challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(grparams.gen1().to_bytes().as_ref())
.chain(std::iter::once(gamma1.to_bytes().as_ref()))
.chain(std::iter::once(verification_key.alpha.to_bytes().as_ref()))
.chain(beta2_bytes.iter().map(|b| b.as_ref()))
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_kappa.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_cc.to_bytes().as_ref()))
.chain(zkcm_aa_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_ss_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_kappa_k_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_tt_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_gamma11_bytes.iter().map(|x| x.as_ref()))
);
challenge == self.challenge
}
pub fn to_bytes(&self) -> Vec<u8> {
let challenge_bytes = self.challenge.to_bytes();
let response_r_bytes = self.response_r.to_bytes();
let rrl_len = self.response_r_l.len();
let rrl_len_bytes = rrl_len.to_le_bytes();
let rl_len = self.response_l.len();
let rl_len_bytes = rl_len.to_le_bytes();
let roa_len = self.response_o_a.len();
let roa_len_bytes = roa_len.to_le_bytes();
let roc_bytes = self.response_o_c.to_bytes();
let rmu_len = self.response_mu.len();
let rmu_len_bytes = rmu_len.to_le_bytes();
let romu_len = self.response_o_mu.len();
let romu_len_bytes = romu_len.to_le_bytes();
let rattributes_len = self.response_attributes.len();
let rattributes_len_bytes = rattributes_len.to_le_bytes();
let mut bytes: Vec<u8> = Vec::with_capacity(
96 + (rrl_len + rl_len + roa_len + rmu_len + romu_len + rattributes_len) * 8
+ (rrl_len + rl_len + roa_len + rmu_len + romu_len + rattributes_len) * 32);
bytes.extend_from_slice(&challenge_bytes);
bytes.extend_from_slice(&response_r_bytes);
bytes.extend_from_slice(&roc_bytes);
bytes.extend_from_slice(&rrl_len_bytes);
for rrl in &self.response_r_l {
bytes.extend_from_slice(&rrl.to_bytes());
}
bytes.extend_from_slice(&rl_len_bytes);
for rl in &self.response_l {
bytes.extend_from_slice(&rl.to_bytes());
}
bytes.extend_from_slice(&roa_len_bytes);
for roa in &self.response_o_a {
bytes.extend_from_slice(&roa.to_bytes());
}
bytes.extend_from_slice(&rmu_len_bytes);
for rmu in &self.response_mu {
bytes.extend_from_slice(&rmu.to_bytes());
}
bytes.extend_from_slice(&romu_len_bytes);
for romu in &self.response_o_mu {
bytes.extend_from_slice(&romu.to_bytes());
}
bytes.extend_from_slice(&rattributes_len_bytes);
for rattr in &self.response_attributes {
bytes.extend_from_slice(&rattr.to_bytes());
}
bytes
}
}
impl TryFrom<&[u8]> for SpendProof {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<SpendProof> {
if bytes.len() < 336 || (bytes.len() - 96 - 48) % 32 != 0 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize proof of spending with bytes of invalid length"
.to_string(),
));
}
let mut idx = 0;
let challenge_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let response_r_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let response_o_c_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let challenge = try_deserialize_scalar(
&challenge_bytes,
CompactEcashError::Deserialization("Failed to deserialize challenge".to_string()),
)?;
let response_r = try_deserialize_scalar(
&response_r_bytes,
CompactEcashError::Deserialization("Failed to deserialize response_r".to_string()),
)?;
let response_o_c = try_deserialize_scalar(
&response_o_c_bytes,
CompactEcashError::Deserialization("Failed to deserialize response_o_c".to_string()),
)?;
let rrl_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
idx += 8;
if bytes[idx..].len() < rrl_len as usize * 32 {
return Err(
CompactEcashError::Deserialization(
"tried to deserialize response_r_l".to_string()),
);
}
let rrl_end = idx + rrl_len as usize * 32;
let response_r_l = try_deserialize_scalar_vec(
rrl_len,
&bytes[idx..rrl_end],
CompactEcashError::Deserialization("Failed to deserialize response_r_l".to_string()),
)?;
let rl_len = u64::from_le_bytes(bytes[rrl_end..rrl_end + 8].try_into().unwrap());
let response_l_start = rrl_end + 8;
if bytes[response_l_start..].len() < rl_len as usize * 32 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize response_l".to_string(),
));
}
let rl_end = response_l_start + rl_len as usize * 32;
let response_l = try_deserialize_scalar_vec(
rl_len,
&bytes[response_l_start..rl_end],
CompactEcashError::Deserialization("Failed to deserialize response_l".to_string()),
)?;
let roa_len = u64::from_le_bytes(bytes[rl_end..rl_end + 8].try_into().unwrap());
let roa_end = rl_end + 8;
if bytes[roa_end..].len() < roa_len as usize * 32 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize response_o_a".to_string(),
));
}
let roa_end = roa_end + roa_len as usize * 32;
let response_o_a = try_deserialize_scalar_vec(
roa_len,
&bytes[rl_end + 8..roa_end],
CompactEcashError::Deserialization("Failed to deserialize response_o_a".to_string()),
)?;
let response_mu_len = u64::from_le_bytes(bytes[roa_end..roa_end + 8].try_into().unwrap());
let response_mu_end = roa_end + 8;
if bytes[response_mu_end..].len() < response_mu_len as usize * 32 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize response_mu".to_string(),
));
}
let response_mu_end = response_mu_end + response_mu_len as usize * 32;
let response_mu = try_deserialize_scalar_vec(
response_mu_len,
&bytes[roa_end + 8..response_mu_end],
CompactEcashError::Deserialization("Failed to deserialize response_mu".to_string()),
)?;
let response_o_mu_len = u64::from_le_bytes(bytes[response_mu_end..response_mu_end + 8].try_into().unwrap());
let response_o_mu_end = response_mu_end + 8;
if bytes[response_o_mu_end..].len() < response_o_mu_len as usize * 32 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize response_o_mu".to_string(),
));
}
let response_o_mu_end = response_o_mu_end + response_o_mu_len as usize * 32;
let response_o_mu = try_deserialize_scalar_vec(
response_o_mu_len,
&bytes[response_mu_end + 8..response_o_mu_end],
CompactEcashError::Deserialization("Failed to deserialize response_o_mu".to_string()),
)?;
let response_attributes_len = u64::from_le_bytes(bytes[response_o_mu_end..response_o_mu_end + 8].try_into().unwrap());
let response_attributes_end = response_o_mu_end + 8;
if bytes[response_attributes_end..].len() < response_attributes_len as usize * 32 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize response_attributes".to_string(),
));
}
let response_attributes_end = response_attributes_end + response_attributes_len as usize * 32;
let response_attributes = try_deserialize_scalar_vec(
response_attributes_len,
&bytes[response_o_mu_end + 8..response_attributes_end],
CompactEcashError::Deserialization("Failed to deserialize response_attributes".to_string()),
)?;
// Construct the SpendProof struct from the deserialized data
let spend_proof = SpendProof {
challenge,
response_r,
response_o_c,
response_r_l,
response_l,
response_o_a,
response_mu,
response_o_mu,
response_attributes,
};
Ok(spend_proof)
}
}
#[cfg(test)]
mod tests {
use bls12_381::{G2Projective, Scalar};
use rand::thread_rng;
use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
use crate::scheme::{pseudorandom_f_delta_v, pseudorandom_f_g_v};
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::keygen::{PublicKeyUser, ttp_keygen, VerificationKeyAuth};
use crate::scheme::PayInfo;
use crate::scheme::setup::setup;
use crate::utils::hash_to_scalar;
#[test]
fn spend_proof_construct_and_verify() {
let _rng = thread_rng();
let L = 32;
let params = setup(L);
let grparams = params.grp();
let sk = grparams.random_scalar();
let _pk_user = PublicKeyUser {
pk: grparams.gen1() * sk,
};
let authorities_keypairs = ttp_keygen(&grparams, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
let v = grparams.random_scalar();
let t = grparams.random_scalar();
let attributes = vec![sk, v, t];
// the below value must be from range 0 to params.L()
let l = 5;
let gamma1 = *grparams.gamma1();
let g1 = *grparams.gen1();
let r = grparams.random_scalar();
let kappa = grparams.gen2() * r
+ verification_key.alpha
+ attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
.sum::<G2Projective>();
let o_a = grparams.random_scalar();
let o_c = grparams.random_scalar();
// compute commitments A, C, D
let aa = g1 * o_a + gamma1 * Scalar::from(l);
let cc = g1 * o_c + gamma1 * v;
// compute hash of the payment info
let pay_info = PayInfo { info: [37u8; 32] };
let rr = hash_to_scalar(pay_info.info);
// evaluate the pseudorandom functions
let ss = pseudorandom_f_delta_v(&grparams, v, l);
let tt = g1 * sk + pseudorandom_f_g_v(&grparams, v, l) * rr;
// compute values mu, o_mu, lambda, o_lambda
let mu: Scalar = (v + Scalar::from(l) + Scalar::from(1)).invert().unwrap();
let o_mu = ((o_a + o_c) * mu).neg();
// parse the signature associated with value l
let sign_l = params.get_sign_by_idx(l).unwrap();
// randomise the signature associated with value l
let (_sign_l_prime, r_l) = sign_l.randomise(grparams);
// compute kappa_l
let kappa_k =
grparams.gen2() * r_l + params.pk_rp().alpha + params.pk_rp().beta * Scalar::from(l);
let instance = SpendInstance {
kappa,
aa: vec![aa],
cc,
ss: vec![ss],
tt: vec![tt],
kappa_k: vec![kappa_k],
};
let witness = SpendWitness {
attributes,
r,
o_c,
lk: vec![Scalar::from(l)],
o_a: vec![o_a],
mu: vec![mu],
o_mu: vec![o_mu],
r_k: vec![r_l],
};
let zk_proof = SpendProof::construct(&params, &instance, &witness, &verification_key, &[rr]);
assert!(zk_proof.verify(&params, &instance, &verification_key, &[rr]));
let zk_proof_bytes = zk_proof.to_bytes();
let zk_proof2 = SpendProof::try_from(zk_proof_bytes.as_slice()).unwrap();
assert_eq!(zk_proof, zk_proof2);
}
}
@@ -0,0 +1,405 @@
use std::convert::{TryFrom, TryInto};
use bls12_381::{G1Projective, Scalar};
use group::GroupEncoding;
use itertools::izip;
use crate::error::{CompactEcashError, Result};
use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produce_responses};
use crate::scheme::keygen::PublicKeyUser;
use crate::scheme::setup::GroupParameters;
use crate::utils::{try_deserialize_g1_projective, try_deserialize_scalar_vec, try_deserialize_scalar};
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
// instance: g, gamma1, gamma2, gamma3, com, h, com1, com2, com3, pkUser
pub struct WithdrawalReqInstance {
// Joined commitment to all attributes
pub com: G1Projective,
// Hash of the joined commitment com
pub h: G1Projective,
// Pedersen commitments to each attribute
pub pc_coms: Vec<G1Projective>,
// Public key of a user
pub pk_user: PublicKeyUser,
}
impl TryFrom<&[u8]> for WithdrawalReqInstance {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<WithdrawalReqInstance> {
if bytes.len() < 48 * 4 + 8 || (bytes.len() - 8) % 48 != 0 {
return Err(CompactEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8,
target: 48 * 4 + 8,
modulus: 48,
object: "withdrawal request zkp instance".to_string(),
});
}
let com_bytes: [u8; 48] = bytes[..48].try_into().unwrap();
let com = try_deserialize_g1_projective(
&com_bytes,
CompactEcashError::Deserialization("Failed to deserialize com".to_string()),
)?;
let h_bytes: [u8; 48] = bytes[48..96].try_into().unwrap();
let h = try_deserialize_g1_projective(
&h_bytes,
CompactEcashError::Deserialization("Failed to deserialize h".to_string()),
)?;
let pc_coms_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
let actual_pc_coms_len = (bytes.len() - 152) / 48;
if pc_coms_len as usize != actual_pc_coms_len {
return Err(CompactEcashError::Deserialization(format!(
"Tried to deserialize pedersen commitments with inconsistent pc_coms_len (expected {}, got {})",
pc_coms_len, actual_pc_coms_len
)));
}
let mut pc_coms = Vec::new();
let mut pc_coms_end: usize = 0;
for i in 0..pc_coms_len {
let start = (104 + i * 48) as usize;
let end = (start + 48) as usize;
let pc_i_bytes = bytes[start..end].try_into().unwrap();
let pc_i = try_deserialize_g1_projective(
&pc_i_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize pedersen commitment".to_string(),
),
)?;
pc_coms_end = end;
pc_coms.push(pc_i);
}
let pk_bytes = bytes[pc_coms_end..].try_into().unwrap();
let pk = try_deserialize_g1_projective(
&pk_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize user's public key".to_string(),
),
)?;
Ok(WithdrawalReqInstance {
com,
h,
pc_coms,
pk_user: PublicKeyUser { pk },
})
}
}
impl WithdrawalReqInstance {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let pc_coms_len = self.pc_coms.len();
let mut bytes = Vec::with_capacity(8 + (pc_coms_len + 3) as usize * 48);
bytes.extend_from_slice(self.com.to_bytes().as_ref());
bytes.extend_from_slice(self.h.to_bytes().as_ref());
bytes.extend_from_slice(&pc_coms_len.to_le_bytes());
for pc in self.pc_coms.iter() {
bytes.extend_from_slice((pc.to_bytes()).as_ref());
}
bytes.extend_from_slice(self.pk_user.pk.to_bytes().as_ref());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<WithdrawalReqInstance> {
WithdrawalReqInstance::try_from(bytes)
}
}
// witness: m1, m2, m3, o, o1, o2, o3,
pub struct WithdrawalReqWitness {
pub attributes: Vec<Scalar>,
// Opening for the joined commitment com
pub com_opening: Scalar,
// Openings for the pedersen commitments
pub pc_coms_openings: Vec<Scalar>,
}
#[derive(Debug, PartialEq)]
pub struct WithdrawalReqProof {
challenge: Scalar,
response_opening: Scalar,
response_openings: Vec<Scalar>,
response_attributes: Vec<Scalar>,
}
impl WithdrawalReqProof {
pub(crate) fn construct(
params: &GroupParameters,
instance: &WithdrawalReqInstance,
witness: &WithdrawalReqWitness,
) -> Self {
// generate random values to replace the witnesses
let r_com_opening = params.random_scalar();
let r_pedcom_openings = params.n_random_scalars(witness.pc_coms_openings.len());
let r_attributes = params.n_random_scalars(witness.attributes.len());
// compute zkp commitments for each instance
let zkcm_com = params.gen1() * r_com_opening
+ r_attributes
.iter()
.zip(params.gammas().iter())
.map(|(rm_i, gamma_i)| gamma_i * rm_i)
.sum::<G1Projective>();
let zkcm_pedcom = r_pedcom_openings
.iter()
.zip(r_attributes.iter())
.map(|(o_j, m_j)| params.gen1() * o_j + instance.h * m_j)
.collect::<Vec<_>>();
let zkcm_user_sk = params.gen1() * r_attributes[0];
// covert to bytes
let gammas_bytes = params
.gammas()
.iter()
.map(|gamma| gamma.to_bytes())
.collect::<Vec<_>>();
let zkcm_pedcom_bytes = zkcm_pedcom
.iter()
.map(|cm| cm.to_bytes())
.collect::<Vec<_>>();
// compute zkp challenge using g1, gammas, c, h, c1, c2, c3, zk commitments
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(params.gen1().to_bytes().as_ref())
.chain(gammas_bytes.iter().map(|gamma| gamma.as_ref()))
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_com.to_bytes().as_ref()))
.chain(zkcm_pedcom_bytes.iter().map(|c| c.as_ref()))
.chain(std::iter::once(zkcm_user_sk.to_bytes().as_ref())),
);
// compute response
let response_opening = produce_response(&r_com_opening, &challenge, &witness.com_opening);
let response_openings = produce_responses(
&r_pedcom_openings,
&challenge,
&witness.pc_coms_openings.iter().collect::<Vec<_>>(),
);
let response_attributes = produce_responses(
&r_attributes,
&challenge,
&witness.attributes.iter().collect::<Vec<_>>(),
);
WithdrawalReqProof {
challenge,
response_opening,
response_openings,
response_attributes,
}
}
pub(crate) fn verify(
&self,
params: &GroupParameters,
instance: &WithdrawalReqInstance,
) -> bool {
// recompute zk commitments for each instance
let zkcm_com = instance.com * self.challenge
+ params.gen1() * self.response_opening
+ self
.response_attributes
.iter()
.zip(params.gammas().iter())
.map(|(m_i, gamma_i)| gamma_i * m_i)
.sum::<G1Projective>();
let zkcm_pedcom = izip!(
instance.pc_coms.iter(),
self.response_openings.iter(),
self.response_attributes.iter()
)
.map(|(cm_j, resp_o_j, resp_m_j)| {
cm_j * self.challenge + params.gen1() * resp_o_j + instance.h * resp_m_j
})
.collect::<Vec<_>>();
let zk_commitment_user_sk =
instance.pk_user.pk * self.challenge + params.gen1() * self.response_attributes[0];
// covert to bytes
let gammas_bytes = params
.gammas()
.iter()
.map(|gamma| gamma.to_bytes())
.collect::<Vec<_>>();
let zkcm_pedcom_bytes = zkcm_pedcom
.iter()
.map(|cm| cm.to_bytes())
.collect::<Vec<_>>();
// recompute zkp challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(params.gen1().to_bytes().as_ref())
.chain(gammas_bytes.iter().map(|hs| hs.as_ref()))
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_com.to_bytes().as_ref()))
.chain(zkcm_pedcom_bytes.iter().map(|c| c.as_ref()))
.chain(std::iter::once(zk_commitment_user_sk.to_bytes().as_ref())),
);
challenge == self.challenge
}
pub fn to_bytes(&self) -> Vec<u8>{
let challenge_bytes = self.challenge.to_bytes();
let response_opening_bytes = self.response_opening.to_bytes();
let ro_len = self.response_openings.len() as u64;
let ra_len = self.response_attributes.len() as u64;
let mut bytes = Vec::with_capacity(32 + 32 + 8 + ro_len as usize * 32 + 8 + ra_len as usize * 32);
bytes.extend_from_slice(&challenge_bytes);
bytes.extend_from_slice(&response_opening_bytes);
bytes.extend_from_slice(&ro_len.to_le_bytes());
for ro in &self.response_openings {
bytes.extend_from_slice(&ro.to_bytes());
}
bytes.extend_from_slice(&ra_len.to_le_bytes());
for ra in &self.response_attributes {
bytes.extend_from_slice(&ra.to_bytes());
}
bytes
}
}
impl TryFrom<&[u8]> for WithdrawalReqProof {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<WithdrawalReqProof> {
if bytes.len() < 32 + 32 + 16 + 32 + 32 || (bytes.len() - 16) % 32 != 0 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize proof of withdrawal with bytes of invalid length"
.to_string(),
));
}
let mut idx = 0;
let challenge_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let response_opening_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let challenge = try_deserialize_scalar(
&challenge_bytes,
CompactEcashError::Deserialization("Failed to deserialize challenge".to_string()),
)?;
let response_opening = try_deserialize_scalar(
&response_opening_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize the response to the random".to_string(),
),
)?;
let ro_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
idx += 8;
if bytes[idx..].len() < ro_len as usize * 32 + 8 {
return Err(
CompactEcashError::Deserialization(
"tried to deserialize response openings".to_string()),
);
}
let ro_end = idx + ro_len as usize * 32;
let response_openings = try_deserialize_scalar_vec(
ro_len,
&bytes[idx..ro_end],
CompactEcashError::Deserialization("Failed to deserialize openings response".to_string()),
)?;
let ra_len = u64::from_le_bytes(bytes[ro_end..ro_end + 8].try_into().unwrap());
let response_attributes = try_deserialize_scalar_vec(
ra_len,
&bytes[ro_end + 8..],
CompactEcashError::Deserialization("Failed to deserialize attributes response".to_string()),
)?;
Ok(WithdrawalReqProof{
challenge,
response_opening,
response_openings,
response_attributes,
})
}
}
#[cfg(test)]
mod tests {
use group::Group;
use rand::thread_rng;
use crate::utils::hash_g1;
use super::*;
#[test]
fn withdrawal_request_instance_roundtrip() {
let mut rng = thread_rng();
let params = GroupParameters::new().unwrap();
let instance = WithdrawalReqInstance {
com: G1Projective::random(&mut rng),
h: G1Projective::random(&mut rng),
pc_coms: vec![
G1Projective::random(&mut rng),
G1Projective::random(&mut rng),
G1Projective::random(&mut rng),
],
pk_user: PublicKeyUser {
pk: params.gen1() * params.random_scalar(),
},
};
let instance_bytes = instance.to_bytes();
let instance_p = WithdrawalReqInstance::from_bytes(&instance_bytes).unwrap();
assert_eq!(instance, instance_p)
}
#[test]
fn withdrawal_proof_construct_and_verify() {
let _rng = thread_rng();
let params = GroupParameters::new().unwrap();
let sk = params.random_scalar();
let pk_user = PublicKeyUser {
pk: params.gen1() * sk,
};
let v = params.random_scalar();
let t = params.random_scalar();
let attr = vec![sk, v, t];
let com_opening = params.random_scalar();
let com = params.gen1() * com_opening
+ attr
.iter()
.zip(params.gammas())
.map(|(&m, gamma)| gamma * m)
.sum::<G1Projective>();
let h = hash_g1(com.to_bytes());
let pc_openings = params.n_random_scalars(attr.len());
let pc_coms = pc_openings
.iter()
.zip(attr.iter())
.map(|(o_j, m_j)| params.gen1() * o_j + h * m_j)
.collect::<Vec<_>>();
let instance = WithdrawalReqInstance {
com,
h,
pc_coms,
pk_user,
};
let witness = WithdrawalReqWitness {
attributes: attr,
com_opening,
pc_coms_openings: pc_openings,
};
let zk_proof = WithdrawalReqProof::construct(&params, &instance, &witness);
assert!(zk_proof.verify(&params, &instance))
}
}
@@ -0,0 +1,168 @@
use core::iter::Sum;
use core::ops::Mul;
use std::cell::Cell;
use bls12_381::{G2Prepared, G2Projective, Scalar};
use group::Curve;
use itertools::Itertools;
use crate::Attribute;
use crate::error::{CompactEcashError, Result};
use crate::scheme::{PartialWallet, Wallet};
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::GroupParameters;
use crate::scheme::withdrawal::RequestInfo;
use crate::utils::{
check_bilinear_pairing, PartialSignature, perform_lagrangian_interpolation_at_origin,
Signature, SignatureShare, SignerIndex,
};
pub(crate) trait Aggregatable: Sized {
fn aggregate(aggregatable: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self>;
fn check_unique_indices(indices: &[SignerIndex]) -> bool {
// if aggregation is a threshold one, all indices should be unique
indices.iter().unique_by(|&index| index).count() == indices.len()
}
}
impl<T> Aggregatable for T
where
T: Sum,
for<'a> T: Sum<&'a T>,
for<'a> &'a T: Mul<Scalar, Output=T>,
{
fn aggregate(aggregatable: &[T], indices: Option<&[u64]>) -> Result<T> {
if aggregatable.is_empty() {
return Err(CompactEcashError::Aggregation(
"Empty set of values".to_string(),
));
}
if let Some(indices) = indices {
if !Self::check_unique_indices(indices) {
return Err(CompactEcashError::Aggregation(
"Non-unique indices".to_string(),
));
}
perform_lagrangian_interpolation_at_origin(indices, aggregatable)
} else {
// non-threshold
Ok(aggregatable.iter().sum())
}
}
}
impl Aggregatable for PartialSignature {
fn aggregate(sigs: &[PartialSignature], indices: Option<&[u64]>) -> Result<Signature> {
let h = sigs
.get(0)
.ok_or_else(|| CompactEcashError::Aggregation("Empty set of signatures".to_string()))?
.sig1();
// TODO: is it possible to avoid this allocation?
let sigmas = sigs.iter().map(|sig| *sig.sig2()).collect::<Vec<_>>();
let aggr_sigma = Aggregatable::aggregate(&sigmas, indices)?;
Ok(Signature(*h, aggr_sigma))
}
}
/// Ensures all provided verification keys were generated to verify the same number of attributes.
fn check_same_key_size(keys: &[VerificationKeyAuth]) -> bool {
keys.iter().map(|vk| vk.beta_g1.len()).all_equal()
&& keys.iter().map(|vk| vk.beta_g2.len()).all_equal()
}
pub fn aggregate_verification_keys(
keys: &[VerificationKeyAuth],
indices: Option<&[SignerIndex]>,
) -> Result<VerificationKeyAuth> {
if !check_same_key_size(keys) {
return Err(CompactEcashError::Aggregation(
"Verification keys are of different sizes".to_string(),
));
}
Aggregatable::aggregate(keys, indices)
}
pub fn aggregate_signature_shares(
params: &GroupParameters,
verification_key: &VerificationKeyAuth,
attributes: &[Attribute],
shares: &[SignatureShare],
) -> Result<Signature> {
let (signatures, indices): (Vec<_>, Vec<_>) = shares
.iter()
.map(|share| (*share.signature(), share.index()))
.unzip();
aggregate_signatures(
params,
verification_key,
attributes,
&signatures,
Some(&indices),
)
}
pub fn aggregate_signatures(
params: &GroupParameters,
verification_key: &VerificationKeyAuth,
attributes: &[Attribute],
signatures: &[PartialSignature],
indices: Option<&[SignerIndex]>,
) -> Result<Signature> {
// aggregate the signature
let signature = match Aggregatable::aggregate(signatures, indices) {
Ok(res) => res,
Err(err) => return Err(err),
};
// Verify the signature
let alpha = verification_key.alpha;
let tmp = attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
if !check_bilinear_pairing(
&signature.0.to_affine(),
&G2Prepared::from((alpha + tmp).to_affine()),
&signature.1.to_affine(),
params.prepared_miller_g2(),
) {
return Err(CompactEcashError::Aggregation(
"Verification of the aggregated signature failed".to_string(),
));
}
Ok(signature)
}
pub fn aggregate_wallets(
params: &GroupParameters,
verification_key: &VerificationKeyAuth,
sk_user: &SecretKeyUser,
wallets: &[PartialWallet],
req_info: &RequestInfo,
) -> Result<Wallet> {
// Aggregate partial wallets
let signature_shares: Vec<SignatureShare> = wallets
.iter()
.enumerate()
.map(|(idx, wallet)| SignatureShare::new(*wallet.signature(), (idx + 1) as u64))
.collect();
let attributes = vec![sk_user.sk, req_info.get_v()];
let aggregated_signature =
aggregate_signature_shares(&params, &verification_key, &attributes, &signature_shares)?;
Ok(Wallet {
sig: aggregated_signature,
v: req_info.get_v(),
l: Cell::new(0),
})
}
@@ -0,0 +1,384 @@
use crate::{PayInfo, VerificationKeyAuth};
use crate::error::{CompactEcashError, Result};
use crate::scheme::keygen::PublicKeyUser;
use crate::scheme::Payment;
use crate::scheme::setup::Parameters;
#[derive(Debug, Eq, PartialEq)]
pub enum IdentifyResult {
NotADuplicatePayment,
DuplicatePayInfo(PayInfo),
DoubleSpendingPublicKeys(PublicKeyUser),
}
pub fn identify(params: &Parameters, verification_key: &VerificationKeyAuth, payment1: Payment, payment2: Payment, pay_info1: PayInfo, pay_info2: PayInfo) -> Result<IdentifyResult> {
let mut k = 0;
let mut j = 0;
for (id1, pay1_ss) in payment1.ss.iter().enumerate() {
for (id2, pay2_ss) in payment2.ss.iter().enumerate() {
if pay1_ss == pay2_ss {
k = id1;
j = id2;
break;
}
}
}
if payment1.ss.iter().any(|pay1_ss| payment2.ss.contains(pay1_ss)) {
if pay_info1 == pay_info2 {
Ok(IdentifyResult::DuplicatePayInfo(pay_info1))
} else {
let rr_diff = payment1.rr[k] - payment2.rr[j];
let pk = (payment2.tt[j] * payment1.rr[k] - payment1.tt[k] * payment2.rr[j]) * rr_diff.invert().unwrap();
let pk_user = PublicKeyUser { pk };
Ok(IdentifyResult::DoubleSpendingPublicKeys(pk_user))
}
} else {
Ok(IdentifyResult::NotADuplicatePayment)
}
}
#[cfg(test)]
mod tests {
use itertools::izip;
use crate::{aggregate_verification_keys, aggregate_wallets, generate_keypair_user, issue_verify, issue_wallet, PartialWallet, PayInfo, ttp_keygen, VerificationKeyAuth, withdrawal_request};
use crate::scheme::identify::{identify, IdentifyResult};
use crate::scheme::keygen::{PublicKeyUser, SecretKeyUser};
use crate::scheme::setup::setup;
#[test]
fn duplicate_payments_with_the_same_pay_info() {
let L = 32;
let params = setup(L);
let grparams = params.grp();
let user_keypair = generate_keypair_user(&grparams);
let (req, req_info) = withdrawal_request(grparams, &user_keypair.secret_key()).unwrap();
let authorities_keypairs = ttp_keygen(&grparams, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key = aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue_wallet(
&grparams,
auth_keypair.secret_key(),
user_keypair.public_key(),
&req,
);
wallet_blinded_signatures.push(blind_signature.unwrap());
}
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
wallet_blinded_signatures.iter(),
verification_keys_auth.iter()
)
.map(|(w, vk)| issue_verify(&grparams, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
.collect();
// Aggregate partial wallets
let aggr_wallet = aggregate_wallets(
&grparams,
&verification_key,
&user_keypair.secret_key(),
&unblinded_wallet_shares,
&req_info,
).unwrap();
// Let's try to spend some coins
let pay_info1 = PayInfo { info: [6u8; 32] };
let spend_vv = 1;
let (payment1, _upd_wallet) = aggr_wallet.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info1,
false,
spend_vv,
).unwrap();
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1)
.unwrap());
let payment2 = payment1.clone();
assert!(payment2
.spend_verify(&params, &verification_key, &pay_info1)
.unwrap());
let pay_info2 = pay_info1.clone();
let identify_result = identify(&params, &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(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info1,
false,
spend_vv,
).unwrap();
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1)
.unwrap());
let pay_info2 = PayInfo { info: [7u8; 32] };
let (payment2, _) = upd_wallet.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info2,
false,
spend_vv,
).unwrap();
assert!(payment2
.spend_verify(&params, &verification_key, &pay_info2)
.unwrap());
let identify_result = identify(&params, &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(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info1,
false,
spend_vv,
).unwrap();
assert!(payment1
.spend_verify(&params, &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(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info2,
false,
spend_vv,
).unwrap();
assert!(payment2
.spend_verify(&params, &verification_key, &pay_info2)
.unwrap());
let identify_result = identify(&params, &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(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info1,
false,
spend_vv,
).unwrap();
assert!(payment1
.spend_verify(&params, &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(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info2,
false,
spend_vv,
).unwrap();
let identify_result = identify(&params, &verification_key, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key()));
}
}
@@ -0,0 +1,442 @@
use core::borrow::Borrow;
use core::iter::Sum;
use core::ops::{Add, Mul};
use std::convert::TryFrom;
use std::convert::TryInto;
use bls12_381::{G1Projective, G2Projective, Scalar};
use group::Curve;
use crate::error::{CompactEcashError, Result};
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::setup::GroupParameters;
use crate::scheme::SignerIndex;
use crate::utils::{
try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
try_deserialize_scalar_vec,
};
use crate::utils::Polynomial;
#[derive(Debug, PartialEq, Clone)]
pub struct SecretKeyAuth {
pub(crate) x: Scalar,
pub(crate) ys: Vec<Scalar>,
}
impl TryFrom<&[u8]> for SecretKeyAuth {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<SecretKeyAuth> {
// There should be x and at least one y
if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 {
return Err(CompactEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8,
target: 32 * 2 + 8,
modulus: 32,
object: "secret key".to_string(),
});
}
// this conversion will not fail as we are taking the same length of data
let x_bytes: [u8; 32] = bytes[..32].try_into().unwrap();
let ys_len = u64::from_le_bytes(bytes[32..40].try_into().unwrap());
let actual_ys_len = (bytes.len() - 40) / 32;
if ys_len as usize != actual_ys_len {
return Err(CompactEcashError::Deserialization(format!(
"Tried to deserialize secret key with inconsistent ys len (expected {}, got {})",
ys_len, actual_ys_len
)));
}
let x = try_deserialize_scalar(
&x_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize secret key scalar".to_string(),
),
)?;
let ys = try_deserialize_scalar_vec(
ys_len,
&bytes[40..],
CompactEcashError::Deserialization(
"Failed to deserialize secret key scalars".to_string(),
),
)?;
Ok(SecretKeyAuth { x, ys })
}
}
impl SecretKeyAuth {
pub fn verification_key(&self, params: &GroupParameters) -> VerificationKeyAuth {
let g1 = params.gen1();
let g2 = params.gen2();
VerificationKeyAuth {
alpha: g2 * self.x,
beta_g1: self.ys.iter().map(|y| g1 * y).collect(),
beta_g2: self.ys.iter().map(|y| g2 * y).collect(),
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let ys_len = self.ys.len();
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) as usize * 32);
bytes.extend_from_slice(&self.x.to_bytes());
bytes.extend_from_slice(&ys_len.to_le_bytes());
for y in self.ys.iter() {
bytes.extend_from_slice(&y.to_bytes())
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<SecretKeyAuth> {
SecretKeyAuth::try_from(bytes)
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct VerificationKeyAuth {
pub(crate) alpha: G2Projective,
pub(crate) beta_g1: Vec<G1Projective>,
pub(crate) beta_g2: Vec<G2Projective>,
}
impl TryFrom<&[u8]> for VerificationKeyAuth {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<VerificationKeyAuth> {
// There should be at least alpha, one betaG1 and one betaG2 and their length
if bytes.len() < 96 * 2 + 48 + 8 || (bytes.len() - 8 - 96) % (96 + 48) != 0 {
return Err(CompactEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8 - 96,
target: 96 * 2 + 48 + 8,
modulus: 96 + 48,
object: "verification key".to_string(),
});
}
// this conversion will not fail as we are taking the same length of data
let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
let betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
let actual_betas_len = (bytes.len() - 104) / (96 + 48);
if betas_len as usize != actual_betas_len {
return Err(
CompactEcashError::Deserialization(
format!("Tried to deserialize verification key with inconsistent betas len (expected {}, got {})",
betas_len, actual_betas_len
)));
}
let alpha = try_deserialize_g2_projective(
&alpha_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize verification key G2 point (alpha)".to_string(),
),
)?;
let mut beta_g1 = Vec::with_capacity(betas_len as usize);
let mut beta_g1_end: u64 = 0;
for i in 0..betas_len {
let start = (104 + i * 48) as usize;
let end = (start + 48) as usize;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g1_projective(
&beta_i_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize verification key G1 point (beta)".to_string(),
),
)?;
beta_g1_end = end as u64;
beta_g1.push(beta_i)
}
let mut beta_g2 = Vec::with_capacity(betas_len as usize);
for i in 0..betas_len {
let start = (beta_g1_end + i * 96) as usize;
let end = (start + 96) as usize;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g2_projective(
&beta_i_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize verification key G2 point (beta)".to_string(),
),
)?;
beta_g2.push(beta_i)
}
Ok(VerificationKeyAuth {
alpha,
beta_g1,
beta_g2,
})
}
}
impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth {
type Output = VerificationKeyAuth;
#[inline]
fn add(self, rhs: &'b VerificationKeyAuth) -> VerificationKeyAuth {
// If you're trying to add two keys together that were created
// for different number of attributes, just panic as it's a
// nonsense operation.
assert_eq!(
self.beta_g1.len(),
rhs.beta_g1.len(),
"trying to add verification keys generated for different number of attributes [G1]"
);
assert_eq!(
self.beta_g2.len(),
rhs.beta_g2.len(),
"trying to add verification keys generated for different number of attributes [G2]"
);
assert_eq!(
self.beta_g1.len(),
self.beta_g2.len(),
"this key is incorrect - the number of elements G1 and G2 does not match"
);
assert_eq!(
rhs.beta_g1.len(),
rhs.beta_g2.len(),
"they key you want to add is incorrect - the number of elements G1 and G2 does not match"
);
VerificationKeyAuth {
alpha: self.alpha + rhs.alpha,
beta_g1: self
.beta_g1
.iter()
.zip(rhs.beta_g1.iter())
.map(|(self_beta_g1, rhs_beta_g1)| self_beta_g1 + rhs_beta_g1)
.collect(),
beta_g2: self
.beta_g2
.iter()
.zip(rhs.beta_g2.iter())
.map(|(self_beta_g2, rhs_beta_g2)| self_beta_g2 + rhs_beta_g2)
.collect(),
}
}
}
impl<'a> Mul<Scalar> for &'a VerificationKeyAuth {
type Output = VerificationKeyAuth;
#[inline]
fn mul(self, rhs: Scalar) -> Self::Output {
VerificationKeyAuth {
alpha: self.alpha * rhs,
beta_g1: self.beta_g1.iter().map(|b_i| b_i * rhs).collect(),
beta_g2: self.beta_g2.iter().map(|b_i| b_i * rhs).collect(),
}
}
}
impl<T> Sum<T> for VerificationKeyAuth
where
T: Borrow<VerificationKeyAuth>,
{
#[inline]
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item=T>,
{
let mut peekable = iter.peekable();
let head_attributes = match peekable.peek() {
Some(head) => head.borrow().beta_g2.len(),
None => {
// TODO: this is a really weird edge case. You're trying to sum an EMPTY iterator
// of VerificationKey. So should it panic here or just return some nonsense value?
return VerificationKeyAuth::identity(0);
}
};
peekable.fold(
VerificationKeyAuth::identity(head_attributes),
|acc, item| acc + item.borrow(),
)
}
}
impl VerificationKeyAuth {
/// Create a (kinda) identity verification key using specified
/// number of 'beta' elements
pub(crate) fn identity(beta_size: usize) -> Self {
VerificationKeyAuth {
alpha: G2Projective::identity(),
beta_g1: vec![G1Projective::identity(); beta_size],
beta_g2: vec![G2Projective::identity(); beta_size],
}
}
pub fn aggregate(sigs: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self> {
aggregate_verification_keys(sigs, indices)
}
pub fn alpha(&self) -> &G2Projective {
&self.alpha
}
pub fn beta_g1(&self) -> &Vec<G1Projective> {
&self.beta_g1
}
pub fn beta_g2(&self) -> &Vec<G2Projective> {
&self.beta_g2
}
pub fn to_bytes(&self) -> Vec<u8> {
let beta_g1_len = self.beta_g1.len();
let beta_g2_len = self.beta_g2.len();
let mut bytes = Vec::with_capacity(96 + 8 + beta_g1_len * 48 + beta_g2_len * 96);
bytes.extend_from_slice(&self.alpha.to_affine().to_compressed());
bytes.extend_from_slice(&beta_g1_len.to_le_bytes());
for beta_g1 in self.beta_g1.iter() {
bytes.extend_from_slice(&beta_g1.to_affine().to_compressed())
}
for beta_g2 in self.beta_g2.iter() {
bytes.extend_from_slice(&beta_g2.to_affine().to_compressed())
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<VerificationKeyAuth> {
VerificationKeyAuth::try_from(bytes)
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct SecretKeyUser {
pub sk: Scalar,
}
impl SecretKeyUser {
pub fn public_key(&self, params: &GroupParameters) -> PublicKeyUser {
PublicKeyUser {
pk: params.gen1() * self.sk,
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct PublicKeyUser {
pub(crate) pk: G1Projective,
}
pub struct KeyPairAuth {
secret_key: SecretKeyAuth,
verification_key: VerificationKeyAuth,
/// Optional index value specifying polynomial point used during threshold key generation.
pub index: Option<SignerIndex>,
}
impl KeyPairAuth {
pub fn secret_key(&self) -> SecretKeyAuth {
self.secret_key.clone()
}
pub fn verification_key(&self) -> VerificationKeyAuth {
self.verification_key.clone()
}
}
pub struct KeyPairUser {
secret_key: SecretKeyUser,
public_key: PublicKeyUser,
}
impl KeyPairUser {
pub fn secret_key(&self) -> SecretKeyUser {
self.secret_key.clone()
}
pub fn public_key(&self) -> PublicKeyUser {
self.public_key.clone()
}
}
pub fn generate_keypair_user(params: &GroupParameters) -> KeyPairUser {
let sk_user = SecretKeyUser {
sk: params.random_scalar(),
};
let pk_user = PublicKeyUser {
pk: params.gen1() * sk_user.sk,
};
KeyPairUser {
secret_key: sk_user,
public_key: pk_user,
}
}
pub fn ttp_keygen(
params: &GroupParameters,
threshold: u64,
num_authorities: u64,
) -> Result<Vec<KeyPairAuth>> {
if threshold == 0 {
return Err(CompactEcashError::Setup(
"Tried to generate threshold keys with a 0 threshold value".to_string(),
));
}
if threshold > num_authorities {
return Err(
CompactEcashError::Setup(
"Tried to generate threshold keys for threshold value being higher than number of the signing authorities".to_string(),
));
}
let attributes = params.gammas().len();
// generate polynomials
let v = Polynomial::new_random(params, threshold - 1);
let ws = (0..attributes)
.map(|_| Polynomial::new_random(params, threshold - 1))
.collect::<Vec<_>>();
// TODO: potentially if we had some known authority identifier we could use that instead
// of the increasing (1,2,3,...) sequence
let polynomial_indices = (1..=num_authorities).collect::<Vec<_>>();
// generate polynomial shares
let x = polynomial_indices
.iter()
.map(|&id| v.evaluate(&Scalar::from(id)));
let ys = polynomial_indices.iter().map(|&id| {
ws.iter()
.map(|w| w.evaluate(&Scalar::from(id)))
.collect::<Vec<_>>()
});
// finally set the keys
let secret_keys = x.zip(ys).map(|(x, ys)| SecretKeyAuth { x, ys });
let keypairs = secret_keys
.zip(polynomial_indices.iter())
.map(|(secret_key, index)| {
let verification_key = secret_key.verification_key(params);
KeyPairAuth {
secret_key,
verification_key,
index: Some(*index),
}
})
.collect();
Ok(keypairs)
}
@@ -0,0 +1,593 @@
use std::cell::Cell;
use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar};
use group::Curve;
use crate::Attribute;
use crate::error::{CompactEcashError, Result};
use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::utils::{
check_bilinear_pairing, hash_to_scalar, Signature, SignerIndex,
try_deserialize_g1_projective, try_deserialize_scalar, try_deserialize_g2_projective,
};
pub mod aggregation;
pub mod identify;
pub mod keygen;
pub mod setup;
pub mod withdrawal;
#[derive(Debug, Clone, PartialEq)]
pub struct PartialWallet {
sig: Signature,
v: Scalar,
idx: Option<SignerIndex>,
}
impl PartialWallet {
pub fn signature(&self) -> &Signature {
&self.sig
}
pub fn v(&self) -> Scalar {
self.v
}
pub fn index(&self) -> Option<SignerIndex> {
self.idx
}
pub fn to_bytes(&self) -> [u8; 136]{
let mut bytes = [0u8; 136];
bytes[0..96].copy_from_slice(&self.sig.to_bytes());
bytes[96..128].copy_from_slice(&self.v.to_bytes());
// Check if idx is Some and copy its bytes if it exists
if let Some(idx) = &self.idx {
bytes[128..136].copy_from_slice(&idx.to_le_bytes());
}
bytes
}
}
impl TryFrom<&[u8]> for PartialWallet {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<PartialWallet> {
if bytes.len() != 136 {
return Err(CompactEcashError::Deserialization(format!(
"PartialWallet should be exactly 136 bytes, got {}",
bytes.len()
)));
}
let sig_bytes: &[u8; 96] = &bytes[..96].try_into().expect("Slice size != 96");
let v_bytes: &[u8; 32] = &bytes[96..128].try_into().expect("Slice size != 32");
let idx_bytes: &[u8; 8] = &bytes[128..136].try_into().expect("Slice size != 8");
let sig = Signature::try_from(sig_bytes.as_slice()).unwrap();
let v = Scalar::from_bytes(&v_bytes).unwrap();
let idx = None;
if !idx_bytes.iter().all(|&x| x == 0){
let idx = Some(u64::from_le_bytes(*idx_bytes));
}
Ok(PartialWallet{
sig,
v,
idx,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Wallet {
sig: Signature,
v: Scalar,
pub l: Cell<u64>,
}
impl Wallet {
pub fn signature(&self) -> &Signature {
&self.sig
}
pub fn v(&self) -> Scalar {
self.v
}
pub fn l(&self) -> u64 {
self.l.get()
}
pub fn to_bytes(&self) -> [u8; 136]{
let mut bytes = [0u8; 136];
bytes[0..96].copy_from_slice(&self.sig.to_bytes());
bytes[96..128].copy_from_slice(&self.v.to_bytes());
bytes[128..136].copy_from_slice(&self.l.get().to_le_bytes());
bytes
}
fn up(&self) {
self.l.set(self.l.get() + 1);
}
pub fn spend(
&self,
params: &Parameters,
verification_key: &VerificationKeyAuth,
sk_user: &SecretKeyUser,
pay_info: &PayInfo,
bench_flag: bool,
spend_vv: u64,
) -> Result<(Payment, &Self)> {
if self.l() + spend_vv > params.L() {
return Err(CompactEcashError::Spend(
"The counter l is higher than max L".to_string(),
));
}
let grparams = params.grp();
// randomize signature in the wallet
let (signature_prime, sign_blinding_factor) = self.signature().randomise(grparams);
// construct kappa i.e., blinded attributes for show
let attributes = vec![sk_user.sk, self.v()];
// compute kappa
let kappa = compute_kappa(
&grparams,
&verification_key,
&attributes,
sign_blinding_factor,
);
// pick random openings o_c
let o_c = grparams.random_scalar();
// compute commitments C
let cc = grparams.gen1() * o_c + grparams.gamma1() * self.v();
let mut aa: Vec<G1Projective> = Default::default();
let mut ss: Vec<G1Projective> = Default::default();
let mut tt: Vec<G1Projective> = Default::default();
let mut rr: Vec<Scalar> = Default::default();
let mut o_a: Vec<Scalar> = Default::default();
let mut o_mu: Vec<Scalar> = Default::default();
let mut mu: Vec<Scalar> = Default::default();
let mut r_k_vec: Vec<Scalar> = Default::default();
let mut kappa_k_vec: Vec<G2Projective> = Default::default();
let mut sign_lk_prime_vec: Vec<Signature> = Default::default();
let mut lk: Vec<Scalar> = Default::default();
for k in 0..spend_vv {
lk.push(Scalar::from(self.l() + k));
// compute hashes R_k of the payment info
let rr_k = hash_to_scalar(pay_info.info);
rr.push(rr_k);
let o_a_k = grparams.random_scalar();
o_a.push(o_a_k);
let aa_k = grparams.gen1() * o_a_k + grparams.gamma1() * Scalar::from(self.l() + k);
aa.push(aa_k);
// evaluate the pseudorandom functions
let ss_k = pseudorandom_f_delta_v(&grparams, self.v(), self.l() + k);
ss.push(ss_k);
let tt_k =
grparams.gen1() * sk_user.sk + pseudorandom_f_g_v(&grparams, self.v(), self.l() + k) * rr_k;
tt.push(tt_k);
// compute values mu, o_mu, lambda, o_lambda
let mu_k: Scalar = (self.v() + Scalar::from(self.l() + k) + Scalar::from(1))
.invert()
.unwrap();
mu.push(mu_k);
let o_mu_k = ((o_a_k + o_c) * mu_k).neg();
o_mu.push(o_mu_k);
// parse the signature associated with value l+k
let sign_lk = params.get_sign_by_idx(self.l() + k)?;
// randomise the signature associated with value l+k
let (sign_lk_prime, r_k) = sign_lk.randomise(grparams);
sign_lk_prime_vec.push(sign_lk_prime);
r_k_vec.push(r_k);
// compute kappa_k
let kappa_k = grparams.gen2() * r_k
+ params.pk_rp().alpha
+ params.pk_rp().beta * Scalar::from(self.l() + k);
kappa_k_vec.push(kappa_k);
}
// construct the zkp proof
let spend_instance = SpendInstance {
kappa,
cc,
aa: aa.clone(),
ss: ss.clone(),
tt: tt.clone(),
kappa_k: kappa_k_vec.clone(),
};
let spend_witness = SpendWitness {
attributes,
r: sign_blinding_factor,
o_c,
lk,
o_a,
mu,
o_mu,
r_k: r_k_vec,
};
let zk_proof = SpendProof::construct(
&params,
&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(&params, &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)
}
}
@@ -0,0 +1,162 @@
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,
}
}
@@ -0,0 +1,289 @@
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(&params, &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(&params, &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,
})
}
@@ -0,0 +1,94 @@
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(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info,
false,
spend_vv,
)?;
assert!(payment
.spend_verify(&params, &verification_key, &pay_info)
.unwrap());
let payment_bytes = payment.to_bytes();
let payment2 = Payment::try_from(&payment_bytes[..]).unwrap();
assert_eq!(payment, payment2);
Ok(())
}
@@ -0,0 +1 @@
mod e2e;
@@ -0,0 +1,22 @@
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()
}
}
@@ -0,0 +1,432 @@
// 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));
}
}
@@ -0,0 +1,32 @@
[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
@@ -0,0 +1,329 @@
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(&params, 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(&params, &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(&params, &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(
&params,
&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(
&params,
&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(&params, &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(&params, &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(&params, &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(&params, &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(&params, &verification_key, &pk_all_users, payment.clone(), payment2.clone(), pay_info, pay_info2).unwrap()
})
},
);
let identify_result = identify(&params, &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);
@@ -0,0 +1,2 @@
/// Max value of wallet
pub(crate) const L: u64 = 100;
@@ -0,0 +1,39 @@
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,
},
}
@@ -0,0 +1,29 @@
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;
@@ -0,0 +1,64 @@
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()
}
@@ -0,0 +1,613 @@
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(&params, 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(), &params_a.get_ith_delta((vv - 1) as usize).to_affine());
let pg_psi0_delta = pairing(&psi_g1.to_affine(), &params_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(), &params_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(), &params_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(&params, &instance, &witness, &verification_key, vv);
assert!(zk_proof.verify(&params, &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);
}
}
@@ -0,0 +1,335 @@
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(&params, &instance, &witness);
assert!(zk_proof.verify(&params, &instance))
}
}
@@ -0,0 +1,167 @@
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),
})
}
@@ -0,0 +1,381 @@
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(), &params_a.get_ith_delta(k as usize).to_affine())
+ pairing(&payment1.phi.0.to_affine(), &params_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(), &params_a.get_ith_delta(j as usize).to_affine())
+ pairing(&payment2.phi.0.to_affine(), &params_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(), &params_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(), &params_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(&params, 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(&params, &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(
&params,
&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(&params, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
// SPEND VERIFICATION for USER1
assert!(payment1.spend_verify(&params, &verification_key, &pay_info1).unwrap());
let payment2 = payment1.clone();
// SPEND VERIFICATION for the duplicate payment
assert!(payment1.spend_verify(&params, &verification_key, &pay_info1).unwrap());
let pay_info2 = pay_info1.clone();
let public_keys = HashSet::from([pk_user1, pk_user2]);
let identify_result = identify(&params, &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(&params, 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(&params, &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(
&params,
&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(&params, &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(&params, &verification_key, &sk_user1, &pay_info2, 10, false).unwrap();
let identify_result = identify(&params, &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(&params, 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(&params, &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(
&params,
&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(&params, &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(&params, &verification_key, &sk_user1, &pay_info2, 10, false).unwrap();
let identify_result = identify(&params, &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(&params, 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(&params, &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(
&params,
&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(&params, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
// SPEND VERIFICATION for USER1
assert!(payment1.spend_verify(&params, &verification_key, &pay_info1).unwrap());
// WITHDRAWAL REQUEST FOR USER2
let (withdrawal_req2, req_info2) = withdrawal_request(&params, &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(
&params,
&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(&params, &verification_key, &sk_user2, &pay_info2, 10, false).unwrap();
// SPEND VERIFICATION for USER2
assert!(payment2.spend_verify(&params, &verification_key, &pay_info2).unwrap());
let public_keys = HashSet::from([pk_user1, pk_user2]);
let identify_result = identify(&params, &verification_key, &public_keys, payment1, payment2, pay_info1, pay_info2).unwrap();
assert_eq!(identify_result, IdentifyResult::NotADuplicatePayment);
}
}
@@ -0,0 +1,432 @@
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,
}
}
@@ -0,0 +1,660 @@
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(), &params_a.get_ith_delta((vv - 1) as usize).to_affine());
let pg_psi0_delta = pairing(&psi_g1.to_affine(), &params_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(), &params_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(), &params_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(), &params_a.get_ith_delta((self.vv - 1) as usize).to_affine());
let pg_psi0_delta = pairing(&psi_g1.to_affine(), &params_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(), &params_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(), &params_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(&params, &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(&params, 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();
}
}
@@ -0,0 +1,212 @@
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() }
}
@@ -0,0 +1,219 @@
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));
}
}
@@ -0,0 +1,166 @@
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(&params.get_grp()),
};
let witness = WithdrawalReqWitness {
attributes,
com_opening,
pc_coms_openings: pc_coms_openings.clone(),
};
let zk_proof = WithdrawalReqProof::construct(&params, &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(&params, &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,
})
}
@@ -0,0 +1,63 @@
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(&params, 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(&params, &sk_user)?;
// ISSUE PARTIAL WALLETS
let mut partial_wallets = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue(
&params,
&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(&params, &verification_key, &sk_user, &pay_info, 10, false)?;
// SPEND VERIFICATION
assert!(payment.spend_verify(&params, &verification_key, &pay_info).unwrap());
let payment_bytes = payment.to_bytes();
let payment2 = Payment::try_from(&payment_bytes[..]).unwrap();
assert_eq!(payment, payment2);
Ok(())
}
@@ -0,0 +1 @@
mod e2e;
@@ -0,0 +1,22 @@
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()
}
}
@@ -0,0 +1,442 @@
// 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));
}
}
@@ -0,0 +1,30 @@
[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
@@ -0,0 +1,4 @@
mod error;
mod scheme;
mod traits;
mod utils;
@@ -0,0 +1 @@
mod e2e;
+3 -3
View File
@@ -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 = []
+340
View File
@@ -0,0 +1,340 @@
// 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(&params);
// The prepare blind sign is performed by the user
let (pedersen_commitments_openings, blind_sign_request) =
prepare_blind_sign(&params, &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(&params, &private_attributes, &public_attributes).unwrap())
},
);
// keys for the validators
let coconut_keypairs = ttp_keygen(&params, 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(
&params,
&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(
&params,
&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(
&params,
&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(
&params,
&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(
&params,
&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(
&params,
&aggr_verification_key,
&aggregated_signature,
serial_number,
binding_number,
)
.unwrap()
})
},
);
// VERIFIER OPERATION
// Verify credentials
verify_credential(&params, &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(&params, &aggr_verification_key, &theta, &public_attributes)
})
},
);
}
criterion_group!(benches, bench_coconut);
criterion_main!(benches);
+13
View File
@@ -13,6 +13,7 @@ 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;
@@ -222,6 +223,18 @@ 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::*;
+1 -1
View File
@@ -38,7 +38,7 @@ mod scheme;
#[cfg(test)]
mod tests;
mod traits;
mod utils;
pub mod utils;
pub type Attribute = Scalar;
pub type PrivateAttribute = Attribute;
+101 -157
View File
@@ -13,12 +13,11 @@ use group::GroupEncoding;
use itertools::izip;
use sha2::Sha256;
use crate::elgamal::Ciphertext;
use crate::error::{CoconutError, Result};
use crate::scheme::setup::Parameters;
use crate::scheme::VerificationKey;
use crate::utils::{hash_g1, try_deserialize_scalar, try_deserialize_scalar_vec};
use crate::{elgamal, Attribute, ElGamalKeyPair};
use crate::Attribute;
// as per the reference python implementation
type ChallengeDigest = Sha256;
@@ -28,8 +27,7 @@ type ChallengeDigest = Sha256;
pub struct ProofCmCs {
challenge: Scalar,
response_opening: Scalar,
response_private_elgamal_key: Scalar,
response_keys: Vec<Scalar>,
response_openings: Vec<Scalar>,
response_attributes: Vec<Scalar>,
}
@@ -88,12 +86,11 @@ 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
@@ -101,10 +98,9 @@ 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_private_elgamal_key = params.random_scalar();
let witness_keys = params.n_random_scalars(ephemeral_keys.len());
let witness_pedersen_commitments_openings =
params.n_random_scalars(pedersen_commitments_openings.len());
let witness_attributes = params.n_random_scalars(private_attributes.len());
// recompute h
@@ -118,22 +114,6 @@ 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])
@@ -144,9 +124,21 @@ impl ProofCmCs {
.map(|(wm_i, hs_i)| hs_i * wm_i)
.sum::<G1Projective>();
let ciphertexts_bytes = priv_attributes_ciphertexts
// zkp commitments for the individual attributes
let commitments_attributes = witness_pedersen_commitments_openings
.iter()
.map(|c| c.to_bytes())
.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())
.collect::<Vec<_>>();
// compute challenge
@@ -154,28 +146,20 @@ 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(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())),
.chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())),
);
// Responses
let response_opening =
produce_response(&witness_commitment_opening, &challenge, commitment_opening);
let response_private_elgamal_key = produce_response(
&witness_private_elgamal_key,
let response_openings = produce_responses(
&witness_pedersen_commitments_openings,
&challenge,
&elgamal_keypair.private_key().0,
&pedersen_commitments_openings.iter().collect::<Vec<_>>(),
);
let response_keys = produce_responses(&witness_keys, &challenge, ephemeral_keys);
let response_attributes = produce_responses(
&witness_attributes,
&challenge,
@@ -185,8 +169,7 @@ impl ProofCmCs {
ProofCmCs {
challenge,
response_opening,
response_private_elgamal_key,
response_keys,
response_openings,
response_attributes,
}
}
@@ -194,11 +177,11 @@ impl ProofCmCs {
pub(crate) fn verify(
&self,
params: &Parameters,
pub_key: &elgamal::PublicKey,
commitment: &G1Projective,
attributes_ciphertexts: &[elgamal::Ciphertext],
commitments: &[G1Projective],
public_attributes: &[Attribute],
) -> bool {
if self.response_keys.len() != attributes_ciphertexts.len() {
if self.response_attributes.len() != commitments.len() {
return false;
}
@@ -213,32 +196,14 @@ impl ProofCmCs {
.collect::<Vec<_>>();
// recompute witnesses commitments
let commitment_private_key_elgamal =
pub_key * &self.challenge + g1 * self.response_private_elgamal_key;
// Aw[i] = (c * c1[i]) + (rk[i] * g1)
let commitment_keys1_bytes = attributes_ciphertexts
.iter()
.map(|ciphertext| ciphertext.c1())
.zip(self.response_keys.iter())
.map(|(c1, res_k)| c1 * self.challenge + g1 * res_k)
.map(|witness| witness.to_bytes())
.collect::<Vec<_>>();
// Bw[i] = (c * c2[i]) + (rk[i] * gamma) + (rm[i] * h)
let commitment_keys2_bytes = izip!(
attributes_ciphertexts
.iter()
.map(|ciphertext| ciphertext.c2()),
self.response_keys.iter(),
self.response_attributes.iter()
)
.map(|(c2, res_key, res_attr)| c2 * self.challenge + pub_key * res_key + h * res_attr)
.map(|witness| witness.to_bytes())
.collect::<Vec<_>>();
// Cw = (cm * c) + (rr * g1) + (rm[0] * hs[0]) + ... + (rm[n] * hs[n])
let commitment_attributes = commitment * self.challenge
let commitment_attributes = (commitment
- public_attributes
.iter()
.zip(params.gen_hs().iter().skip(self.response_attributes.len()))
.map(|(pub_attr, hs)| hs * pub_attr)
.sum::<G1Projective>())
* self.challenge
+ g1 * self.response_opening
+ self
.response_attributes
@@ -247,9 +212,22 @@ impl ProofCmCs {
.map(|(res_attr, hs)| hs * res_attr)
.sum::<G1Projective>();
let ciphertexts_bytes = attributes_ciphertexts
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
.iter()
.map(|c| c.to_bytes())
.map(|cm| cm.to_bytes())
.collect::<Vec<_>>();
let commitments_attributes_bytes = commitments_attributes
.iter()
.map(|cm| cm.to_bytes())
.collect::<Vec<_>>();
// re-compute the challenge
@@ -257,38 +235,32 @@ 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(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())),
.chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())),
);
challenge == self.challenge
}
// challenge || response opening || response private elgamal key || keys len || response keys || attributes len || response attributes
// challenge || response opening || openings len || response openings || attributes len ||
// response attributes
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let keys_len = self.response_keys.len() as u64;
let openings_len = self.response_openings.len() as u64;
let attributes_len = self.response_attributes.len() as u64;
let mut bytes = Vec::with_capacity(16 + (keys_len + attributes_len + 3) as usize * 32);
let mut bytes = Vec::with_capacity(16 + (2 + openings_len + attributes_len) as usize * 32);
bytes.extend_from_slice(&self.challenge.to_bytes());
bytes.extend_from_slice(&self.response_opening.to_bytes());
bytes.extend_from_slice(&self.response_private_elgamal_key.to_bytes());
bytes.extend_from_slice(&keys_len.to_le_bytes());
for rk in &self.response_keys {
bytes.extend_from_slice(&rk.to_bytes());
bytes.extend_from_slice(&openings_len.to_le_bytes());
for ro in &self.response_openings {
bytes.extend_from_slice(&ro.to_bytes());
}
bytes.extend_from_slice(&attributes_len.to_le_bytes());
for rm in &self.response_attributes {
bytes.extend_from_slice(&rm.to_bytes());
}
@@ -298,11 +270,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 * 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())
);
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(),
));
}
let mut idx = 0;
@@ -310,54 +282,46 @@ 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 rk_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
let ro_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
idx += 8;
if bytes[idx..].len() < rk_len as usize * 32 + 8 {
if bytes[idx..].len() < ro_len as usize * 32 + 8 {
return Err(
CoconutError::Deserialization(
"tried to deserialize proof of ciphertexts and commitment with insufficient number of bytes provided".to_string()),
);
}
let rk_end = idx + rk_len as usize * 32;
let response_keys = try_deserialize_scalar_vec(
rk_len,
&bytes[idx..rk_end],
CoconutError::Deserialization("Failed to deserialize keys response".to_string()),
let 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 rm_len = u64::from_le_bytes(bytes[rk_end..rk_end + 8].try_into().unwrap());
let rm_len = u64::from_le_bytes(bytes[ro_end..ro_end + 8].try_into().unwrap());
let response_attributes = try_deserialize_scalar_vec(
rm_len,
&bytes[rk_end + 8..],
&bytes[ro_end + 8..],
CoconutError::Deserialization("Failed to deserialize attributes response".to_string()),
)?;
Ok(ProofCmCs {
challenge,
response_opening,
response_private_elgamal_key,
response_keys,
response_openings,
response_attributes,
})
}
@@ -392,7 +356,7 @@ impl ProofKappaZeta {
let witness_attributes = vec![witness_serial_number, witness_binding_number];
let beta_bytes = verification_key
.beta
.beta_g2
.iter()
.map(|beta_i| beta_i.to_bytes())
.collect::<Vec<_>>();
@@ -403,7 +367,7 @@ impl ProofKappaZeta {
+ verification_key.alpha
+ witness_attributes
.iter()
.zip(verification_key.beta.iter())
.zip(verification_key.beta_g2.iter())
.map(|(wm_i, beta_i)| beta_i * wm_i)
.sum::<G2Projective>();
@@ -447,7 +411,7 @@ impl ProofKappaZeta {
zeta: &G2Projective,
) -> bool {
let beta_bytes = verification_key
.beta
.beta_g2
.iter()
.map(|beta_i| beta_i.to_bytes())
.collect::<Vec<_>>();
@@ -460,7 +424,7 @@ impl ProofKappaZeta {
+ verification_key.alpha * (Scalar::one() - self.challenge)
+ response_attributes
.iter()
.zip(verification_key.beta.iter())
.zip(verification_key.beta_g2.iter())
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
.sum::<G2Projective>();
@@ -550,7 +514,6 @@ 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};
@@ -560,51 +523,32 @@ mod tests {
#[test]
fn proof_cm_cs_bytes_roundtrip() {
let mut rng = thread_rng();
let mut params = setup(1).unwrap();
let elgamal_keypair = elgamal::elgamal_keygen(&params);
let private_attributes = params.n_random_scalars(1);
// we don't care about 'correctness' of the proof. only whether we can correctly recover it from bytes
let params = setup(1).unwrap();
let cm = G1Projective::random(&mut rng);
let r = params.random_scalar();
let commitment_hash = compute_commitment_hash(cm);
let (attributes_ciphertexts, _): (Vec<_>, Vec<_>) = compute_attribute_encryption(
&params,
private_attributes.as_ref(),
elgamal_keypair.public_key(),
commitment_hash,
);
let ephemeral_keys = params.n_random_scalars(1);
let cms: [G1Projective; 1] = [G1Projective::random(&mut rng)];
let rs = params.n_random_scalars(1);
let private_attributes = params.n_random_scalars(1);
// 0 public 1 private
let pi_s = ProofCmCs::construct(
&mut params,
&elgamal_keypair,
&ephemeral_keys,
&cm,
&r,
&private_attributes,
&*attributes_ciphertexts,
);
let pi_s = ProofCmCs::construct(&params, &cm, &r, &cms, &rs, &private_attributes);
let bytes = pi_s.to_bytes();
assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s);
// 2 private
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);
let private_attributes = params.n_random_scalars(2);
let ephemeral_keys = params.n_random_scalars(2);
let pi_s = ProofCmCs::construct(
&mut params,
&elgamal_keypair,
&ephemeral_keys,
&cm,
&r,
&private_attributes,
&*attributes_ciphertexts,
);
// 0 public 2 privates
let pi_s = ProofCmCs::construct(&params, &cm, &r, &cms, &rs, &private_attributes);
let bytes = pi_s.to_bytes();
assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s);
@@ -612,9 +556,9 @@ mod tests {
#[test]
fn proof_kappa_zeta_bytes_roundtrip() {
let mut params = setup(4).unwrap();
let params = setup(4).unwrap();
let keypair = keygen(&mut params);
let keypair = keygen(&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();
@@ -627,7 +571,7 @@ mod tests {
// 0 public 2 private
let pi_v = ProofKappaZeta::construct(
&mut params,
&params,
&keypair.verification_key(),
&serial_number,
&binding_number,
@@ -642,11 +586,11 @@ mod tests {
assert_eq!(proof_from_bytes, pi_v);
// 2 public 2 private
let mut params = setup(4).unwrap();
let keypair = keygen(&mut params);
let params = setup(4).unwrap();
let keypair = keygen(&params);
let pi_v = ProofKappaZeta::construct(
&mut params,
&params,
&keypair.verification_key(),
&serial_number,
&binding_number,
+12 -11
View File
@@ -64,7 +64,8 @@ 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.len()).all_equal()
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(
@@ -98,7 +99,7 @@ pub fn aggregate_signatures(
let tmp = attributes
.iter()
.zip(verification_key.beta.iter())
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
@@ -149,8 +150,8 @@ mod tests {
#[test]
fn key_aggregation_works_for_any_subset_of_keys() {
let mut params = Parameters::new(2).unwrap();
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let params = Parameters::new(2).unwrap();
let keypairs = ttp_keygen(&params, 3, 5).unwrap();
let vks = keypairs
.into_iter()
@@ -213,7 +214,7 @@ mod tests {
let mut params = Parameters::new(2).unwrap();
let attributes = params.n_random_scalars(2);
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let keypairs = ttp_keygen(&params, 3, 5).unwrap();
let (sks, vks): (Vec<_>, Vec<_>) = keypairs
.into_iter()
@@ -310,9 +311,9 @@ mod tests {
#[test]
fn signature_aggregation_doesnt_work_for_empty_set_of_signatures() {
let signatures: Vec<Signature> = vec![];
let mut params = Parameters::new(2).unwrap();
let params = Parameters::new(2).unwrap();
let attributes = params.n_random_scalars(2);
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let keypairs = ttp_keygen(&params, 3, 5).unwrap();
let (_, vks): (Vec<_>, Vec<_>) = keypairs
.into_iter()
@@ -328,9 +329,9 @@ mod tests {
#[test]
fn signature_aggregation_doesnt_work_if_indices_have_invalid_length() {
let signatures = vec![random_signature()];
let mut params = Parameters::new(2).unwrap();
let params = Parameters::new(2).unwrap();
let attributes = params.n_random_scalars(2);
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let keypairs = ttp_keygen(&params, 3, 5).unwrap();
let (_, vks): (Vec<_>, Vec<_>) = keypairs
.into_iter()
.map(|keypair| (keypair.secret_key(), keypair.verification_key()))
@@ -354,9 +355,9 @@ mod tests {
#[test]
fn signature_aggregation_doesnt_work_for_non_unique_indices() {
let signatures = vec![random_signature(), random_signature()];
let mut params = Parameters::new(2).unwrap();
let params = Parameters::new(2).unwrap();
let attributes = params.n_random_scalars(2);
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let keypairs = ttp_keygen(&params, 3, 5).unwrap();
let (_, vks): (Vec<_>, Vec<_>) = keypairs
.into_iter()
.map(|keypair| (keypair.secret_key(), keypair.verification_key()))

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