Compare commits

...

169 Commits

Author SHA1 Message Date
durch ce8718ae96 Rebase, clippy, fmt 2023-12-07 10:43:03 +01:00
Simon Wicky 07a86afb54 generate ecash key on client upgrade 2023-12-07 09:49:27 +01:00
Simon Wicky 3098eb8544 add config migration for clients 2023-12-07 09:49:27 +01:00
Simon Wicky f2ab3e0972 add small note on nym-api 2023-12-07 09:49:27 +01:00
Simon Wicky 785634b824 fix sql migrations 2023-12-07 09:49:27 +01:00
Simon Wicky f6d00ba196 increase api timeout for ecash 2023-12-07 09:49:27 +01:00
Simon Wicky 1dd208dd4c prevent shutdown to be dropped in online mode 2023-12-07 09:49:15 +01:00
Simon Wicky 49d22ab791 add serial number to payments 2023-12-07 09:49:15 +01:00
Simon Wicky d97244fb27 add value to payment 2023-12-07 09:49:13 +01:00
Simon Wicky 8093932c7b online mode on gateway 2023-12-07 09:48:29 +01:00
Simon Wicky e0f36537db add value to ecash wallets 2023-12-07 09:48:29 +01:00
Simon Wicky 2bb130c204 prepare new api endpoint 2023-12-07 09:48:29 +01:00
Simon Wicky 338613362d offline/online toggle for gateway 2023-12-07 09:48:29 +01:00
Simon Wicky 770324cf95 name change in preparation for online setup 2023-12-07 09:48:29 +01:00
Simon Wicky 9c6555663a persist pending credentials on reboot 2023-12-07 09:48:27 +01:00
Simon Wicky 9255e32d08 set ticker missed behavior 2023-12-07 09:48:12 +01:00
Simon Wicky f35f2649ef tweak to not try_send every time 2023-12-07 09:48:12 +01:00
Simon Wicky 7514fa3ca6 async credential sending 2023-12-07 09:48:12 +01:00
Simon Wicky 976b6d9ada bump up the bandwidth a bit 2023-12-07 09:46:00 +01:00
Simon Wicky 9d030bef94 fix sql migration 2023-12-07 09:46:00 +01:00
Simon Wicky ddfd503962 change parameters serialization so it's not random 2023-12-07 09:46:00 +01:00
Simon Wicky b8803376ff conversion to keypairauth elsewhere 2023-12-07 09:46:00 +01:00
Simon Wicky 3afb4c5041 use api ecash key for issuance 2023-12-07 09:46:00 +01:00
Simon Wicky f8a881af59 use stored ecash keypair for the api 2023-12-07 09:46:00 +01:00
Simon Wicky 14b974d2e1 add ecash key to api 2023-12-07 09:46:00 +01:00
Simon Wicky 3bf105301a fix route for coconut api 2023-12-07 09:46:00 +01:00
Simon Wicky 61cf4ea0c7 distribute parameters to all components 2023-12-07 09:46:00 +01:00
Simon Wicky d559d9a113 hard code ecash parameters 2023-12-07 09:45:43 +01:00
Simon Wicky 575282fe32 add consumed update to wallet 2023-12-07 09:45:39 +01:00
Simon Wicky 3e3c8a5467 add ecash params to api 2023-12-07 09:45:15 +01:00
Simon Wicky 0603273a49 remove secret key from credential storage 2023-12-07 09:45:15 +01:00
Simon Wicky dcfae92bab use stored ecash key when using credentials 2023-12-07 09:45:13 +01:00
Simon Wicky 9e925fd4ce config file template update 2023-12-07 09:45:00 +01:00
Simon Wicky 5792b89917 use ecash key for credential issuance 2023-12-07 09:45:00 +01:00
Simon Wicky d5bfa732c6 add ecash keypair to client 2023-12-07 09:45:00 +01:00
Simon Wicky 89644e5476 use identity key as provider pk 2023-12-07 09:44:57 +01:00
aniampio 7057170381 Move generate payinfo as implementation of the struct 2023-12-07 09:43:36 +01:00
aniampio ada02fe631 Improve function performance and readability 2023-12-07 09:43:07 +01:00
aniampio 7836f5771b Add function generating the payinfo 2023-12-07 09:42:23 +01:00
Simon Wicky 46c53eb1a5 credentials storage on gateway and api 2023-12-07 09:41:55 +01:00
Simon Wicky 506693a8a5 wip double spending detection 2023-12-07 09:41:53 +01:00
Simon Wicky cece0bb80e add traits to ecashcredential 2023-12-07 09:41:14 +01:00
Simon Wicky 56d36974fa add params in ecashcredential 2023-12-07 09:41:12 +01:00
Simon Wicky c0713a5a13 add payinfo to credential 2023-12-07 09:40:26 +01:00
Simon Wicky 74536467b6 impl payment flow 2023-12-07 09:40:23 +01:00
Simon Wicky c1c58a7476 update ecash credential 2023-12-07 09:40:00 +01:00
Simon Wicky bc9d4b4682 add secret-key for ecash credential storage 2023-12-07 09:40:00 +01:00
Simon Wicky e41796b157 WIP spending ecash 2023-12-07 09:40:00 +01:00
Simon Wicky 5a831b6482 swap VerificationKey for VerificationKeyAuth 2023-12-07 09:40:00 +01:00
Simon Wicky 4de642315d add get_next_ecash_cred for storage 2023-12-07 09:40:00 +01:00
Simon Wicky 1091f1341c issuance cli side 2023-12-07 09:39:58 +01:00
Simon Wicky ea0dbfea03 ecash issuance api side 2023-12-07 09:39:45 +01:00
Simon Wicky dd323ce493 update bls12_381 dependency 2023-12-07 09:39:45 +01:00
aniampio 954f76e241 Optimize the identification function 2023-12-07 09:39:19 +01:00
aniampio a09dc8e462 Add byte calculation for divisible ecash 2023-12-07 09:39:19 +01:00
aniampio f623bfed4c Add Wallet, Partial Wallet and Payment to and from bytes converstion 2023-12-07 09:39:19 +01:00
aniampio e6bcfb697c Increase benchmark duration 2023-12-07 09:39:19 +01:00
aniampio a70843b940 Update in divisible ecash benchmark 2023-12-07 09:39:19 +01:00
aniampio 8a21e4ae25 Benchmarks: change the number of public keys 2023-12-07 09:39:19 +01:00
aniampio d7f4590239 Update compact benchmarks 2023-12-07 09:39:19 +01:00
aniampio cd2f7a30d2 Remove spend verification from the identify function 2023-12-07 09:39:19 +01:00
aniampio 23b9e4d48a Update benchmarks 2023-12-07 09:39:19 +01:00
aniampio fecb67ded7 Update for compact e-cash - remove requirement for security tag 2023-12-07 09:39:19 +01:00
aniampio 3ffa367de1 Update benchmarks 2023-12-07 09:39:19 +01:00
aniampio 3691110db8 Add verification of the payments inside identify; speed up the identify checks 2023-12-07 09:39:17 +01:00
aniampio dd527e4295 Update the spend function for compact ecash - multi spend 2023-12-07 09:39:06 +01:00
aniampio 778f5bf5e5 Add duplicate payinfo check for identify 2023-12-07 09:39:06 +01:00
aniampio 761d09534f Update spend function in the compact ecash 2023-12-07 09:39:06 +01:00
aniampio 4a7e44b9c7 Add test for identification - case of no double spending 2023-12-07 09:39:06 +01:00
aniampio db69158b4b Add aggregation and e2e test 2023-12-07 09:39:06 +01:00
aniampio 40af8ebf47 Fix the eq 15 for the spend proof 2023-12-07 09:39:06 +01:00
aniampio f8e78c7fcc Update the verification function for structure preserving signature 2023-12-07 09:39:06 +01:00
aniampio 05ddbf2fba Add draft of signature verification function 2023-12-07 09:39:06 +01:00
aniampio 31e9c0004a Move the index for signature retrival 2023-12-07 09:39:06 +01:00
aniampio e4cfdc9888 3 out of 4 pairing tests in the zk proof pass 2023-12-07 09:39:06 +01:00
aniampio 0df62df780 Fix signatures tau_l 2023-12-07 09:39:06 +01:00
aniampio 3687600587 Shifting indices in setup - eq 4 test still passing 2023-12-07 09:39:06 +01:00
aniampio 64dd6c2b8c Add eq checks 2023-12-07 09:39:06 +01:00
aniampio aa018769c2 Add bilinear equations into the zk proof; the last eq passes the tests 2023-12-07 09:39:04 +01:00
aniampio 2ec2613a89 Define spend instance and witness structs 2023-12-07 09:38:54 +01:00
aniampio 15d10ab027 Add spend and spend verification functions; fix breaking test for proof of withdrawal 2023-12-07 09:38:54 +01:00
aniampio 0fc5b97dfb Add issuance and issuance verification - but one of the tests is failing 2023-12-07 09:38:54 +01:00
aniampio 7632e524c0 Add key generation functions 2023-12-07 09:38:54 +01:00
aniampio 49721177fc Update the structure preserving signature to work in the setup function as well 2023-12-07 09:38:50 +01:00
aniampio 7f0f2b056f Add benchmarks 2023-12-07 09:38:21 +01:00
aniampio cec35ee4d0 Update get range proof signature function to return an error 2023-12-07 09:37:43 +01:00
aniampio 5f545675e1 Add into the zk proof the proof that l is within the range 2023-12-07 09:36:20 +01:00
aniampio 49ac369ce2 Add struct for the divisible ecash crate 2023-12-07 09:36:17 +01:00
aniampio dfcc87167b Add test for succesfull identification 2023-12-07 09:35:54 +01:00
aniampio ee95596abf Fix the issue with two gamma equations 2023-12-07 09:35:54 +01:00
aniampio 23ce9de873 Add S and T into the zk proof for spend 2023-12-07 09:35:54 +01:00
aniampio 28081bd72c Move signature related structs and functions to utils 2023-12-07 09:35:54 +01:00
aniampio 5e146aa432 Move spend and spec vfy as functions associated with wallet and payment 2023-12-07 09:35:54 +01:00
aniampio 53a9d43c87 Add identify function 2023-12-07 09:35:54 +01:00
aniampio f487234007 Add spendVfy function 2023-12-07 09:35:54 +01:00
aniampio d2a7d26e5a Add verification for the spend zkproof and first tests 2023-12-07 09:35:54 +01:00
aniampio 7fcd246089 Start the spend function and zkproof for spend 2023-12-07 09:35:54 +01:00
aniampio f384338f0f Add new aggregation function and struct for the aggregated wallet 2023-12-07 09:35:54 +01:00
aniampio 0114743db9 Add aggregation into e2e tests 2023-12-07 09:35:54 +01:00
aniampio 9776bfb0b0 Copy and adjust aggregation of verification keys from coconut 2023-12-07 09:35:52 +01:00
aniampio 0526b3e6b4 Fix bug in the issuance verification 2023-12-07 09:35:37 +01:00
aniampio d00bef2ddf Fix a bug in the commitment computation 2023-12-07 09:35:37 +01:00
aniampio a0c81c982c Copy polynomial and ttpcode from Coconut; add first test; add keypair structures 2023-12-07 09:35:37 +01:00
aniampio 32ba1c7c18 Add zk proof for withdrawal request 2023-12-07 09:35:35 +01:00
aniampio bea8e4a881 Add initial functions 2023-12-07 09:34:41 +01:00
aniampio 9558c3deb2 Add template for the compact ecash crate 2023-12-07 09:33:00 +01:00
Pierre Dommerc 26a8dec707 refactor(vpnapp): node location logic (#4223)
* refactor node location logic

fontend:
remove app local data from state
remove node config from state
use only country location state

backend:
add node location in state
add set_node_location command

* call backend to update node location

* clean code
2023-12-06 15:39:53 +01:00
mx 74481003e6 made taskclient shutdown log more verbose (#4134)
* made taskclient shutdown log more verbose

* fmt

* changed 'disconnect' to 'shutdown' in 'task client is being dropped' log
2023-12-06 10:25:00 +01:00
dependabot[bot] 6d6eb186c0 Bump @adobe/css-tools from 4.3.1 to 4.3.2 (#4203)
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.3.1 to 4.3.2.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-06 09:59:56 +01:00
dependabot[bot] 6a4f8d502d Bump vite from 5.0.0 to 5.0.5 in /nym-vpn/ui (#4222)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.0 to 5.0.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-06 09:57:23 +01:00
Jon Häggblad 755fd1d765 Merge pull request #4216 from nymtech/jon/per-client-mix-hops-in-ipr
IPR: handle client specific number of mix hops
2023-12-05 21:11:18 +01:00
Jon Häggblad ac14382a08 Change to Option in new_regular_with_custom_hops 2023-12-05 20:30:20 +01:00
Jon Häggblad c8017db6c4 Extract out ConmnectedClientsListener 2023-12-05 20:30:20 +01:00
Jon Häggblad 49aaf860a8 Extract out ConnectedClients type 2023-12-05 20:30:20 +01:00
Jon Häggblad 66e36a7ed5 Use mix hops for handling responses 2023-12-05 20:30:20 +01:00
Jon Häggblad 34be9dc60f Handle mixhops in tun_listener 2023-12-05 20:30:20 +01:00
Jon Häggblad 0e26a6efdf Register num_hops 2023-12-05 20:30:20 +01:00
Jon Häggblad a190506b41 Rename IpPacketRouterBuilder to IpPacketRouter 2023-12-05 20:30:20 +01:00
Jon Häggblad 8be372acff Extract out mixnet_listener.rs 2023-12-05 20:30:20 +01:00
Jon Häggblad c2321c20eb Rename to MixnetListener 2023-12-05 20:30:20 +01:00
Zane Schepke 8b5dc867cd feat(vpn-desktop): add exit node select location (#4220) 2023-12-05 19:28:09 +01:00
Jon Häggblad a2219323d1 Overide number of mix hops separately per packet (#4205)
* Try passing mix_hops all the way down the call chain

* Set zero mix_hops manually

* fix

* also set zero mix hops in tun listener

* fix

* mix hops for surbs

* Another case covered

* Remove mix_hops config from Traffic

* clippy

* Add comment about why we added new functions

* Update comment

* Add surb_mix_hops to wasm config

* Remove temporary added mix_hops = 0

* Remove another temporary added mix_hops = 0

* Add comment about the limitation of num_mix_hops
2023-12-05 16:09:15 +01:00
Jędrzej Stuczyński 0f844aba38 Merge pull request #4158 from nymtech/feature/nymvisor
Feature/nymvisor
2023-12-05 09:46:31 +00:00
Jędrzej Stuczyński cf794b63a7 review comments 2023-12-04 15:35:42 +00:00
Pierre Dommerc 145b702f41 feat(vpn-desktop-ui): add theme switch into settings (#4217) 2023-12-04 16:08:44 +01:00
Jędrzej Stuczyński bb9b3cdb64 updated config load logic 2023-12-04 12:13:28 +00:00
Jędrzej Stuczyński b3927b9d0d update lock files 2023-12-04 12:13:26 +00:00
Jędrzej Stuczyński 66f8ce46bf fixes in paths + better error reporting 2023-12-04 12:12:57 +00:00
Jędrzej Stuczyński 1a2cf6b523 fixed upstream poller 2023-12-04 12:12:56 +00:00
Jędrzej Stuczyński f0ae49b18e adding binary info to generated hashes 2023-12-04 12:12:56 +00:00
Jędrzej Stuczyński abe6a16896 changed base64 encoding to hex 2023-12-04 12:12:56 +00:00
Jędrzej Stuczyński 7d6dde5148 cargo fmt 2023-12-04 12:12:56 +00:00
Jędrzej Stuczyński b10da899a8 clippy and final missing features 2023-12-04 12:12:56 +00:00
Jędrzej Stuczyński 9b5714b897 current upgrade info logic 2023-12-04 12:12:56 +00:00
Jędrzej Stuczyński 6b133750d4 adjusted restart conditions 2023-12-04 12:12:55 +00:00
Jędrzej Stuczyński 70c9348c30 checksum verification 2023-12-04 12:12:55 +00:00
Jędrzej Stuczyński 0bf0b10c5c daemon-build-info command 2023-12-04 12:12:55 +00:00
Jędrzej Stuczyński 8d774cf6a0 initial 'add-upgrade' command 2023-12-04 12:12:55 +00:00
Jędrzej Stuczyński e5c2280a1c main run loop 2023-12-04 12:12:55 +00:00
Jędrzej Stuczyński c04b617a55 moved backup to separate module + standalone file support 2023-12-04 12:12:54 +00:00
Jędrzej Stuczyński 56ecfa7e38 fixed backups 2023-12-04 12:12:54 +00:00
Jędrzej Stuczyński 1be60922c2 binary upgrade logic 2023-12-04 12:12:54 +00:00
Jędrzej Stuczyński 22da01ccd4 initial run loop 2023-12-04 12:12:54 +00:00
Jędrzej Stuczyński 2e077ca946 basic draft of all tasks 2023-12-04 12:12:54 +00:00
Jędrzej Stuczyński 70d3b784f4 logic for updating upgrade plan 2023-12-04 12:12:54 +00:00
Jędrzej Stuczyński f6e88b610b setting up initial upgrade plan file 2023-12-04 12:12:54 +00:00
Jędrzej Stuczyński 822dac8ee3 setting up genesis upgrade-info.json file 2023-12-04 12:12:53 +00:00
Jędrzej Stuczyński 95e9a96ae1 wip 2023-12-04 12:12:52 +00:00
Jędrzej Stuczyński e853e8ffc1 added upstream url to config 2023-12-04 12:10:47 +00:00
Jędrzej Stuczyński aaeb6a7cbf attaching file watcher to upgrade-plan.json 2023-12-04 12:10:47 +00:00
Jędrzej Stuczyński 4a98631e93 wrapping subprocess and being able to send signals 2023-12-04 12:10:46 +00:00
Jędrzej Stuczyński ce4c6de1e9 config command 2023-12-04 12:10:46 +00:00
Jędrzej Stuczyński 29b41da1bb further template fixes 2023-12-04 12:10:46 +00:00
Jędrzej Stuczyński 94c4fd2af5 overriding config with env on load 2023-12-04 12:10:46 +00:00
Jędrzej Stuczyński 12497f3222 not errorring out if genesis or current already exist (and match up) 2023-12-04 12:10:46 +00:00
Jędrzej Stuczyński 4a5a6d366c conditionally enabling logging 2023-12-04 12:10:46 +00:00
Jędrzej Stuczyński b4ed20487d copying the genesis binary + symlink creation 2023-12-04 12:10:45 +00:00
Jędrzej Stuczyński b8036031ba first part of nymvisor initialisation 2023-12-04 12:10:45 +00:00
Jędrzej Stuczyński 3117ed45b4 fixed config template rendering 2023-12-04 12:10:45 +00:00
Jędrzej Stuczyński 8b8e8a8282 config template 2023-12-04 12:10:45 +00:00
Jędrzej Stuczyński 29d2ab4a7a defined env 2023-12-04 12:10:43 +00:00
Jędrzej Stuczyński ea834a60a5 defined nymvisor config structure 2023-12-04 12:10:17 +00:00
Jędrzej Stuczyński a6c627df33 executing the dummy cmd in async context 2023-12-04 12:10:17 +00:00
Jędrzej Stuczyński 52b8703028 passing through the args 2023-12-04 12:10:17 +00:00
Jędrzej Stuczyński b40736d46b init binary + initial clap 2023-12-04 12:10:14 +00:00
Jon Häggblad caf055efc1 nym-wallet: update Cargo.lock post release (#4210) 2023-12-04 12:20:10 +01:00
serinko 0f6c2293bf [DOCs]: hotfix - syntax amd flow unification (#4215)
* add cargo install mdbook-cmdrun

* unify <NODE> syntax and smooth the flow
2023-12-04 10:52:53 +00:00
serinko 3e374e4c91 add gpl-3.0 licence (#4211) 2023-12-04 10:17:22 +00:00
mx 2a7ed0faa8 fix cmdrun rendering (#4213)
* compile with all features

* add plugin again
2023-12-04 10:16:33 +00:00
484 changed files with 19228 additions and 2257 deletions
Vendored
BIN
View File
Binary file not shown.
@@ -3,8 +3,27 @@ import fetch from "node-fetch";
import { Octokit } from "@octokit/rest";
import fs from "fs";
import path from "path";
import { execSync } from "child_process";
function getBinInfo(path) {
// let's be super naive about it. add a+x bits on the file and try to run the command
try {
let mode = fs.statSync(path).mode
fs.chmodSync(path, mode | 0o111)
const raw = execSync(`${path} build-info --output=json`, { stdio: 'pipe', encoding: "utf8" });
const parsed = JSON.parse(raw)
return parsed
} catch (_) {
return undefined
}
}
async function run(assets, algorithm, filename, cache) {
if (!cache) {
console.warn("cache is set to 'false', but we we no longer support it")
}
try {
fs.mkdirSync('.tmp');
} catch(e) {
@@ -19,26 +38,25 @@ async function run(assets, algorithm, filename, cache) {
let buffer = null;
let sig = null;
if(cache) {
// cache in `${WORKING_DIR}/.tmp/`
const cacheFilename = path.resolve(`.tmp/${asset.name}`);
if(!fs.existsSync(cacheFilename)) {
console.log(`Downloading ${asset.browser_download_url}... to ${cacheFilename}`);
buffer = Buffer.from(await fetch(asset.browser_download_url).then(res => res.arrayBuffer()));
fs.writeFileSync(cacheFilename, buffer);
} else {
console.log(`Loading from ${cacheFilename}`);
buffer = Buffer.from(fs.readFileSync(cacheFilename));
// console.log('Reading signature from content');
// if(asset.name.endsWith('.sig')) {
// sig = fs.readFileSync(cacheFilename).toString();
// }
}
} else {
// fetch always
// cache in `${WORKING_DIR}/.tmp/`
const cacheFilename = path.resolve(`.tmp/${asset.name}`);
if(!fs.existsSync(cacheFilename)) {
console.log(`Downloading ${asset.browser_download_url}... to ${cacheFilename}`);
buffer = Buffer.from(await fetch(asset.browser_download_url).then(res => res.arrayBuffer()));
fs.writeFileSync(cacheFilename, buffer);
} else {
console.log(`Loading from ${cacheFilename}`);
buffer = Buffer.from(fs.readFileSync(cacheFilename));
// console.log('Reading signature from content');
// if(asset.name.endsWith('.sig')) {
// sig = fs.readFileSync(cacheFilename).toString();
// }
}
const binInfo = getBinInfo(cacheFilename)
if(!hashes[asset.name]) {
hashes[asset.name] = {};
}
@@ -99,6 +117,9 @@ async function run(assets, algorithm, filename, cache) {
if(kind) {
hashes[asset.name].kind = kind;
}
if(binInfo) {
hashes[asset.name].details = binInfo;
}
// process Tauri signature files
if(asset.name.endsWith('.sig')) {
@@ -225,6 +246,8 @@ export async function createHashesFromReleaseTagOrNameOrId({ releaseTagOrNameOrI
assets: hashes,
};
console.log(output)
if(upload) {
console.log(`🚚 Uploading ${filename} to release name="${release.name}" id=${release.id} (${release.upload_url})...`);
Generated
+881 -582
View File
File diff suppressed because it is too large Load Diff
+25 -2
View File
@@ -56,6 +56,9 @@ members = [
"common/node-tester-utils",
"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",
@@ -105,9 +108,10 @@ members = [
"tools/internal/sdk-version-bump",
"tools/nym-cli",
"tools/nym-nr-query",
"tools/nymvisor",
"tools/ts-rs-cli",
"wasm/client",
# "wasm/full-nym-wasm",
# "wasm/full-nym-wasm",
"wasm/mix-fetch",
"wasm/node-tester",
]
@@ -120,10 +124,19 @@ default-members = [
"service-providers/network-statistics",
"mixnode",
"nym-api",
"tools/nymvisor",
"explorer-api",
]
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-vpn/ui/src-tauri", "cpu-cycles"]
exclude = [
"explorer",
"contracts",
"nym-wallet",
"nym-connect/mobile/src-tauri",
"nym-connect/desktop",
"nym-vpn/ui/src-tauri",
"cpu-cycles",
]
[workspace.package]
authors = ["Nym Technologies SA"]
@@ -158,6 +171,7 @@ schemars = "0.8.1"
serde = "1.0.152"
serde_json = "1.0.91"
tap = "1.0.1"
time = "0.3.30"
thiserror = "1.0.48"
tokio = "1.24.1"
tokio-tungstenite = "0.20.1"
@@ -201,6 +215,15 @@ wasm-bindgen-futures = "0.4.37"
wasmtimer = "0.2.0"
web-sys = "0.3.63"
bls12_381 = { path = "/Users/drazen/nym/bls12_381", default-features = false, features = [
"alloc",
"pairings",
"experimental",
"zeroize",
] }
ff = { version = "0.13", default-features = false }
group = { version = "0.13", default-features = false }
# Profile settings for individual crates
[profile.release.package.nym-socks5-listener]
+675
View File
@@ -0,0 +1,675 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means tocopy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
+1
View File
@@ -38,6 +38,7 @@ nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "cli"] }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" }
nym-config = { path = "../../common/config" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-credentials = { path = "../../common/credentials" }
@@ -5,8 +5,9 @@ use crate::client::config::old_config_v1_1_20_2::{
ClientPathsV1_1_20_2, ConfigV1_1_20_2, SocketTypeV1_1_20_2, SocketV1_1_20_2,
};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::{
ClientKeysPathsV1_1_20_2, CommonClientPathsV1_1_20_2,
};
use nym_client_core::config::old_config_v1_1_20::ConfigV1_1_20 as BaseConfigV1_1_20;
use nym_client_core::config::old_config_v1_1_20_2::{
ClientV1_1_20_2, ConfigV1_1_20_2 as BaseConfigV1_1_20_2,
@@ -60,7 +61,7 @@ impl From<ConfigV1_1_20> for ConfigV1_1_20_2 {
socket: value.socket.into(),
storage_paths: ClientPathsV1_1_20_2 {
common_paths: CommonClientPathsV1_1_20_2 {
keys: ClientKeysPaths {
keys: ClientKeysPathsV1_1_20_2 {
private_identity_key_file: value.base.client.private_identity_key_file,
public_identity_key_file: value.base.client.public_identity_key_file,
private_encryption_key_file: value.base.client.private_encryption_key_file,
@@ -50,6 +50,12 @@ keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key
# Path to file containing public encryption key.
keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}'
# Path to file containing private ecash key.
keys.private_ecash_key_file = '{{ storage_paths.keys.private_ecash_key_file }}'
# Path to file containing public ecash key.
keys.public_ecash_key_file = '{{ storage_paths.keys.public_ecash_key_file }}'
# A gateway specific, optional, base58 stringified shared key used for
# communication with particular gateway.
keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}'
+26
View File
@@ -18,6 +18,7 @@ use nym_client_core::client::base_client::storage::gateway_details::{
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::error::ClientCoreError;
use nym_compact_ecash::{generate_keypair_user, setup::GroupParameters};
use nym_config::OptionalSet;
use std::error::Error;
use std::net::IpAddr;
@@ -121,6 +122,28 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
)
}
fn init_ecash_keypair(config: &Config) -> Result<(), ClientError> {
let kp = generate_keypair_user(&GroupParameters::new().unwrap());
nym_pemstore::store_keypair(
&kp,
&nym_pemstore::KeyPairPath::new(
config
.storage_paths
.common_paths
.keys
.private_ecash_key_file
.clone(),
config
.storage_paths
.common_paths
.keys
.public_ecash_key_file
.clone(),
),
)?;
Ok(())
}
fn persist_gateway_details(
config: &Config,
details: GatewayEndpointConfig,
@@ -159,6 +182,7 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
let (updated, gateway_config) = updated_step2.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
init_ecash_keypair(&updated)?; //SW does that belong here?
updated.save_to_default_location()?;
Ok(true)
@@ -179,6 +203,7 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, ClientError> {
let updated_step1: ConfigV1_1_20_2 = old_config.into();
let (updated, gateway_config) = updated_step1.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
init_ecash_keypair(&updated)?; //SW does that belong here?
updated.save_to_default_location()?;
Ok(true)
@@ -196,6 +221,7 @@ fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, ClientError> {
let (updated, gateway_config) = old_config.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
init_ecash_keypair(&updated)?; //SW does that belong here?
updated.save_to_default_location()?;
Ok(true)
@@ -5,8 +5,9 @@ use crate::config::old_config_v1_1_20_2::{
ConfigV1_1_20_2, CoreConfigV1_1_20_2, SocksClientPathsV1_1_20_2,
};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::{
ClientKeysPathsV1_1_20_2, CommonClientPathsV1_1_20_2,
};
use nym_client_core::config::old_config_v1_1_20::ConfigV1_1_20 as BaseConfigV1_1_20;
use nym_client_core::config::old_config_v1_1_20_2::ClientV1_1_20_2;
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
@@ -50,7 +51,7 @@ impl From<ConfigV1_1_20> for ConfigV1_1_20_2 {
},
storage_paths: SocksClientPathsV1_1_20_2 {
common_paths: CommonClientPathsV1_1_20_2 {
keys: ClientKeysPaths {
keys: ClientKeysPathsV1_1_20_2 {
private_identity_key_file: value.base.client.private_identity_key_file,
public_identity_key_file: value.base.client.public_identity_key_file,
private_encryption_key_file: value.base.client.private_encryption_key_file,
+6
View File
@@ -50,6 +50,12 @@ keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key
# Path to file containing public encryption key.
keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}'
# Path to file containing private ecash key.
keys.private_ecash_key_file = '{{ storage_paths.keys.private_ecash_key_file }}'
# Path to file containing public ecash key.
keys.public_ecash_key_file = '{{ storage_paths.keys.public_ecash_key_file }}'
# A gateway specific, optional, base58 stringified shared key used for
# communication with particular gateway.
keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}'
+8 -6
View File
@@ -10,6 +10,8 @@ use std::path::{Path, PathBuf};
use std::time::Duration;
use tokio::time::Instant;
pub use notify::{Error as NotifyError, Result as NotifyResult};
pub type FileWatcherEventSender = mpsc::UnboundedSender<Event>;
pub type FileWatcherEventReceiver = mpsc::UnboundedReceiver<Event>;
@@ -22,7 +24,7 @@ pub struct AsyncFileWatcher {
last_received: HashMap<EventKind, Instant>,
tick_duration: Duration,
inner_rx: mpsc::UnboundedReceiver<notify::Result<Event>>,
inner_rx: mpsc::UnboundedReceiver<NotifyResult<Event>>,
event_sender: FileWatcherEventSender,
}
@@ -30,7 +32,7 @@ impl AsyncFileWatcher {
pub fn new_file_changes_watcher<P: AsRef<Path>>(
path: P,
event_sender: FileWatcherEventSender,
) -> notify::Result<Self> {
) -> NotifyResult<Self> {
Self::new(
path,
event_sender,
@@ -48,7 +50,7 @@ impl AsyncFileWatcher {
event_sender: FileWatcherEventSender,
filters: Option<Vec<EventKind>>,
tick_duration: Option<Duration>,
) -> notify::Result<Self> {
) -> NotifyResult<Self> {
let watcher_config = Config::default();
let (inner_tx, inner_rx) = mpsc::unbounded();
let watcher = RecommendedWatcher::new(
@@ -112,17 +114,17 @@ impl AsyncFileWatcher {
false
}
fn start_watching(&mut self) -> notify::Result<()> {
fn start_watching(&mut self) -> NotifyResult<()> {
self.is_watching = true;
self.watcher.watch(&self.path, RecursiveMode::NonRecursive)
}
fn stop_watching(&mut self) -> notify::Result<()> {
fn stop_watching(&mut self) -> NotifyResult<()> {
self.is_watching = false;
self.watcher.unwatch(&self.path)
}
pub async fn watch(&mut self) -> notify::Result<()> {
pub async fn watch(&mut self) -> NotifyResult<()> {
self.start_watching()?;
while let Some(event) = self.inner_rx.next().await {
+1
View File
@@ -12,6 +12,7 @@ thiserror = { workspace = true }
url = { workspace = true }
nym-coconut-interface = { path = "../coconut-interface" }
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
nym-credential-storage = { path = "../credential-storage" }
nym-credentials = { path = "../credentials" }
nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
+22 -21
View File
@@ -2,13 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::BandwidthControllerError;
use nym_coconut_interface::{Base58, Parameters};
use nym_compact_ecash::scheme::keygen::KeyPairUser;
use nym_compact_ecash::setup::GroupParameters;
use nym_compact_ecash::Base58;
use nym_credential_storage::storage::Storage;
use nym_credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use nym_credentials::coconut::utils::obtain_aggregate_signature;
use nym_crypto::asymmetric::{encryption, identity};
use nym_network_defaults::VOUCHER_INFO;
use nym_validator_client::coconut::all_coconut_api_clients;
use nym_network_defaults::ECASH_INFO;
use nym_validator_client::coconut::all_ecash_api_clients;
use nym_validator_client::nyxd::contract_traits::CoconutBandwidthSigningClient;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use nym_validator_client::nyxd::Coin;
@@ -19,20 +21,24 @@ use std::str::FromStr;
pub mod state;
pub async fn deposit<C>(client: &C, amount: Coin) -> Result<State, BandwidthControllerError>
pub async fn deposit<C>(
client: &C,
amount: Coin,
ecash_keypair: KeyPairUser,
) -> Result<State, BandwidthControllerError>
where
C: CoconutBandwidthSigningClient + Sync,
{
let mut rng = OsRng;
let signing_keypair = KeyPair::from(identity::KeyPair::new(&mut rng));
let encryption_keypair = KeyPair::from(encryption::KeyPair::new(&mut rng));
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
let params = GroupParameters::new().unwrap();
let voucher_value = amount.amount.to_string();
let tx_hash = client
.deposit(
amount,
String::from(VOUCHER_INFO),
String::from(ECASH_INFO),
signing_keypair.public_key.clone(),
encryption_keypair.public_key.clone(),
None,
@@ -44,10 +50,11 @@ where
let voucher = BandwidthVoucher::new(
&params,
voucher_value,
VOUCHER_INFO.to_string(),
ECASH_INFO.to_string(),
Hash::from_str(&tx_hash).map_err(|_| BandwidthControllerError::InvalidTxHash)?,
identity::PrivateKey::from_base58_string(&signing_keypair.private_key)?,
encryption::PrivateKey::from_base58_string(&encryption_keypair.private_key)?,
ecash_keypair,
);
let state = State { voucher, params };
@@ -71,22 +78,16 @@ where
.await?
.ok_or(BandwidthControllerError::NoThreshold)?;
let coconut_api_clients = all_coconut_api_clients(client, epoch_id).await?;
let ecash_api_clients = all_ecash_api_clients(client, epoch_id).await?;
let signature = obtain_aggregate_signature(
&state.params,
&state.voucher,
&coconut_api_clients,
threshold,
)
.await?;
let wallet =
obtain_aggregate_signature(&state.params, &state.voucher, &ecash_api_clients, threshold)
.await?;
storage
.insert_coconut_credential(
.insert_ecash_wallet(
ECASH_INFO.to_string(),
wallet.to_bs58(),
state.voucher.get_voucher_value(),
VOUCHER_INFO.to_string(),
state.voucher.get_private_attributes()[0].to_bs58(),
state.voucher.get_private_attributes()[1].to_bs58(),
signature.to_bs58(),
epoch_id.to_string(),
)
.await
@@ -1,8 +1,8 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_coconut_interface::Parameters;
use nym_credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
use nym_compact_ecash::setup::GroupParameters;
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use nym_crypto::asymmetric::{encryption, identity};
@@ -31,14 +31,14 @@ impl From<encryption::KeyPair> for KeyPair {
pub struct State {
pub voucher: BandwidthVoucher,
pub params: Parameters,
pub params: GroupParameters,
}
impl State {
pub fn new(voucher: BandwidthVoucher) -> Self {
State {
voucher,
params: Parameters::new(TOTAL_ATTRIBUTES).unwrap(),
params: GroupParameters::new().unwrap(),
}
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_coconut_interface::CoconutError;
use nym_compact_ecash::error::CompactEcashError;
use nym_credential_storage::error::StorageError;
use nym_credentials::error::Error as CredentialsError;
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
@@ -25,8 +25,8 @@ pub enum BandwidthControllerError {
#[error(transparent)]
StorageError(#[from] StorageError),
#[error("Coconut error - {0}")]
CoconutError(#[from] CoconutError),
#[error("Ecash error - {0}")]
EcashError(#[from] CompactEcashError),
#[error("Validator client error - {0}")]
ValidatorError(#[from] ValidatorClientError),
+62 -40
View File
@@ -2,17 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::BandwidthControllerError;
use nym_compact_ecash::scheme::keygen::KeyPairUser;
use nym_compact_ecash::scheme::{EcashCredential, Wallet};
use nym_compact_ecash::setup::{setup, Parameters};
use nym_compact_ecash::{Base58, PayInfo};
use nym_credential_storage::error::StorageError;
use nym_credential_storage::storage::Storage;
use nym_validator_client::coconut::all_coconut_api_clients;
use nym_credentials::obtain_aggregate_verification_key;
use nym_validator_client::coconut::all_ecash_api_clients;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use std::str::FromStr;
use {
nym_coconut_interface::Base58,
nym_credentials::coconut::{
bandwidth::prepare_for_spending, utils::obtain_aggregate_verification_key,
},
};
pub mod acquire;
pub mod error;
@@ -20,68 +19,89 @@ pub mod error;
pub struct BandwidthController<C, St> {
storage: St,
client: C,
ecash_keypair: KeyPairUser,
ecash_params: Parameters,
}
impl<C, St: Storage> BandwidthController<C, St> {
pub fn new(storage: St, client: C) -> Self {
BandwidthController { storage, client }
pub fn new(
storage: St,
client: C,
ecash_keypair: KeyPairUser,
ecash_params: Parameters,
) -> Self {
BandwidthController {
storage,
client,
ecash_keypair,
ecash_params,
}
}
pub fn storage(&self) -> &St {
&self.storage
}
pub async fn prepare_coconut_credential(
pub async fn prepare_ecash_credential(
&self,
) -> Result<(nym_coconut_interface::Credential, i64), BandwidthControllerError>
provider_pk: [u8; 32],
) -> Result<(EcashCredential, Wallet, i64), BandwidthControllerError>
where
C: DkgQueryClient + Sync + Send,
<St as Storage>::StorageError: Send + Sync + 'static,
{
let bandwidth_credential = self
let ecash_wallet = self
.storage
.get_next_coconut_credential()
.get_next_ecash_wallet()
.await
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?;
let voucher_value = u64::from_str(&bandwidth_credential.voucher_value)
.map_err(|_| StorageError::InconsistentData)?;
let voucher_info = bandwidth_credential.voucher_info.clone();
let serial_number =
nym_coconut_interface::Attribute::try_from_bs58(bandwidth_credential.serial_number)?;
let binding_number =
nym_coconut_interface::Attribute::try_from_bs58(bandwidth_credential.binding_number)?;
let signature =
nym_coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?;
let epoch_id = u64::from_str(&bandwidth_credential.epoch_id)
.map_err(|_| StorageError::InconsistentData)?;
let coconut_api_clients = all_coconut_api_clients(&self.client, epoch_id).await?;
let wallet = Wallet::try_from_bs58(ecash_wallet.wallet)?;
let epoch_id =
u64::from_str(&ecash_wallet.epoch_id).map_err(|_| StorageError::InconsistentData)?;
let verification_key = obtain_aggregate_verification_key(&coconut_api_clients).await?;
let ecash_api_clients = all_ecash_api_clients(&self.client, epoch_id).await?;
let verification_key = obtain_aggregate_verification_key(&ecash_api_clients).await?;
let sk_user = self.ecash_keypair.secret_key();
let pay_info = PayInfo::generate_payinfo(provider_pk);
let nb_tickets = 1u64; //SW: TEMPORARY VALUE, what should we put there?
let wallet_value = u64::from_str(&ecash_wallet.value)
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?;
let credential_value = nb_tickets * wallet_value / (self.ecash_params.ll());
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
Ok((
prepare_for_spending(
voucher_value,
voucher_info,
serial_number,
binding_number,
epoch_id,
&signature,
&verification_key,
)?,
bandwidth_credential.id,
))
let (payment, _) = wallet.spend(
&self.ecash_params,
&verification_key,
&sk_user,
&pay_info,
false,
nb_tickets,
)?;
let credential = EcashCredential::new(payment, credential_value, pay_info, epoch_id);
Ok((credential, wallet, ecash_wallet.id))
}
pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError>
pub async fn update_ecash_wallet(
&self,
wallet: Wallet,
id: i64,
) -> Result<(), BandwidthControllerError>
where
<St as Storage>::StorageError: Send + Sync + 'static,
{
// JS: shouldn't we send some contract/validator/gateway message here to actually, you know,
// consume it?
let consumed = wallet.l() >= setup(100).ll(); //temporary, depends on parameters distribution
let wallet_string = wallet.to_bs58();
self.storage
.consume_coconut_credential(id)
.update_ecash_wallet(wallet_string, id, consumed)
.await
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
}
@@ -96,6 +116,8 @@ where
BandwidthController {
storage: self.storage.clone(),
client: self.client.clone(),
ecash_keypair: self.ecash_keypair.clone(),
ecash_params: self.ecash_params.clone(),
}
}
}
+2 -1
View File
@@ -47,8 +47,9 @@ default = []
openapi = ["utoipa"]
output_format = ["serde_json"]
bin_info_schema = ["schemars"]
basic_tracing = ["tracing-subscriber"]
tracing = [
"tracing-subscriber",
"basic_tracing",
"tracing-tree",
"opentelemetry-jaeger",
"tracing-opentelemetry",
@@ -80,7 +80,7 @@ impl BinaryBuildInformation {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "bin_info_schema", derive(schemars::JsonSchema))]
pub struct BinaryBuildInformationOwned {
+24
View File
@@ -43,6 +43,30 @@ pub fn setup_logging() {
.init();
}
#[cfg(feature = "basic_tracing")]
pub fn setup_tracing_logger() {
let log_builder = tracing_subscriber::fmt()
// Use a more compact, abbreviated log format
.compact()
// Display source code file paths
.with_file(true)
// Display source code line numbers
.with_line_number(true)
// Don't display the event's target (module path)
.with_target(false);
if ::std::env::var("RUST_LOG").is_ok() {
log_builder
.with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env())
.init()
} else {
// default to 'Info
log_builder
.with_max_level(tracing_subscriber::filter::LevelFilter::INFO)
.init()
}
}
// TODO: This has to be a macro, running it as a function does not work for the file_appender for some reason
#[cfg(feature = "tracing")]
#[macro_export]
+1
View File
@@ -34,6 +34,7 @@ zeroize = { workspace = true }
nym-bandwidth-controller = { path = "../bandwidth-controller" }
nym-config = { path = "../config" }
nym-crypto = { path = "../crypto" }
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
nym-gateway-client = { path = "../client-libs/gateway-client" }
nym-gateway-requests = { path = "../../gateway/gateway-requests" }
@@ -34,6 +34,7 @@ use crate::{config, spawn_future};
use futures::channel::mpsc;
use log::{debug, error, info};
use nym_bandwidth_controller::BandwidthController;
use nym_compact_ecash::setup::Parameters;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_crypto::asymmetric::encryption;
use nym_gateway_client::{
@@ -49,7 +50,10 @@ use nym_task::{TaskClient, TaskHandle};
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::HardcodedTopologyProvider;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::fmt::Debug;
use std::ops::Deref;
use std::path::Path;
use std::sync::Arc;
use url::Url;
@@ -556,6 +560,23 @@ where
setup_gateway(setup_method, key_store, details_store).await
}
async fn get_ecash_parameters(nym_api_urls: Vec<Url>) -> Result<Parameters, ClientCoreError> {
let nym_api = nym_api_urls
.choose(&mut thread_rng())
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
let validator_client = nym_validator_client::NymApiClient::new(nym_api.clone());
match validator_client.ecash_parameters().await {
Err(err) => {
error!(
"Failed to grab ecash parameters - {err}\n Plesae try again in a few minutes"
);
Err(ClientCoreError::ValidatorClientError(err))
}
Ok(response) => Ok(response.params),
}
}
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
where
S::ReplyStore: Send + Sync,
@@ -612,9 +633,16 @@ where
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
let bandwidth_controller = self
.dkg_query_client
.map(|client| BandwidthController::new(credential_store, client));
let ecash_parameters =
Self::get_ecash_parameters(self.config.get_nym_api_endpoints()).await?;
let bandwidth_controller = self.dkg_query_client.map(|client| {
BandwidthController::new(
credential_store,
client,
init_res.managed_keys.ecash_keypair().deref().clone(),
ecash_parameters,
)
});
let topology_provider = Self::setup_topology_provider(
self.custom_topology_provider.take(),
@@ -9,6 +9,8 @@ use crate::config::Config;
use crate::error::ClientCoreError;
use log::{error, info};
use nym_bandwidth_controller::BandwidthController;
use nym_compact_ecash::scheme::keygen::KeyPairUser;
use nym_compact_ecash::setup::Parameters;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_validator_client::nyxd;
use nym_validator_client::QueryHttpRpcNyxdClient;
@@ -101,25 +103,30 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
}
}
//SW Is this used anywhere?
pub fn create_bandwidth_controller<St: CredentialStorage>(
config: &Config,
storage: St,
ecash_keypair: KeyPairUser,
ecash_params: Parameters,
) -> BandwidthController<QueryHttpRpcNyxdClient, St> {
let nyxd_url = config
.get_validator_endpoints()
.pop()
.expect("No nyxd validator endpoint provided");
create_bandwidth_controller_with_urls(nyxd_url, storage)
create_bandwidth_controller_with_urls(nyxd_url, storage, ecash_keypair, ecash_params)
}
pub fn create_bandwidth_controller_with_urls<St: CredentialStorage>(
nyxd_url: Url,
storage: St,
ecash_keypair: KeyPairUser,
ecash_params: Parameters,
) -> BandwidthController<QueryHttpRpcNyxdClient, St> {
let client = default_query_dkg_client(nyxd_url);
BandwidthController::new(storage, client)
BandwidthController::new(storage, client, ecash_keypair, ecash_params)
}
pub fn default_query_dkg_client_from_config(config: &Config) -> QueryHttpRpcNyxdClient {
@@ -28,6 +28,7 @@ pub enum InputMessage {
recipient: Recipient,
data: Vec<u8>,
lane: TransmissionLane,
mix_hops: Option<u8>,
},
/// Creates a message used for a duplex anonymous communication where the recipient
@@ -43,6 +44,7 @@ pub enum InputMessage {
data: Vec<u8>,
reply_surbs: u32,
lane: TransmissionLane,
mix_hops: Option<u8>,
},
/// Attempt to use our internally received and stored `ReplySurb` to send the message back
@@ -92,6 +94,29 @@ impl InputMessage {
recipient,
data,
lane,
mix_hops: None,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
// IMHO `new_regular` should take `mix_hops: Option<u8>` as an argument instead of creating
// this function, but that would potentially break backwards compatibility with the current API
pub fn new_regular_with_custom_hops(
recipient: Recipient,
data: Vec<u8>,
lane: TransmissionLane,
packet_type: Option<PacketType>,
mix_hops: Option<u8>,
) -> Self {
let message = InputMessage::Regular {
recipient,
data,
lane,
mix_hops,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
@@ -112,6 +137,31 @@ impl InputMessage {
data,
reply_surbs,
lane,
mix_hops: None,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
// IMHO `new_anonymous` should take `mix_hops: Option<u8>` as an argument instead of creating
// this function, but that would potentially break backwards compatibility with the current API
pub fn new_anonymous_with_custom_hops(
recipient: Recipient,
data: Vec<u8>,
reply_surbs: u32,
lane: TransmissionLane,
packet_type: Option<PacketType>,
mix_hops: Option<u8>,
) -> Self {
let message = InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
mix_hops,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
@@ -2,6 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::key_manager::persistence::KeyStore;
use nym_compact_ecash::scheme::keygen::KeyPairUser;
use nym_compact_ecash::setup::GroupParameters;
use nym_compact_ecash::{generate_keypair_user, PublicKeyUser};
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
@@ -87,6 +90,14 @@ impl ManagedKeys {
}
}
pub fn ecash_keypair(&self) -> Arc<KeyPairUser> {
match self {
ManagedKeys::Initial(keys) => keys.ecash_keypair(),
ManagedKeys::FullyDerived(keys) => keys.ecash_keypair(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn ack_key(&self) -> Arc<AckKey> {
match self {
ManagedKeys::Initial(keys) => keys.ack_key(),
@@ -124,6 +135,14 @@ impl ManagedKeys {
}
}
pub fn ecash_public_key(&self) -> PublicKeyUser {
match self {
ManagedKeys::Initial(keys) => keys.ecash_keypair().public_key(),
ManagedKeys::FullyDerived(keys) => keys.ecash_keypair().public_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn ensure_gateway_key(&self, gateway_shared_key: Option<Arc<SharedKeys>>) {
if let ManagedKeys::FullyDerived(key_manager) = &self {
if self.gateway_shared_key().is_none() && gateway_shared_key.is_none() {
@@ -185,6 +204,9 @@ pub struct KeyManagerBuilder {
/// key used for producing and processing acknowledgement packets.
ack_key: Arc<AckKey>,
/// key used for ecash wallet
ecash_keypair: Arc<KeyPairUser>,
}
impl KeyManagerBuilder {
@@ -197,6 +219,7 @@ impl KeyManagerBuilder {
identity_keypair: Arc::new(identity::KeyPair::new(rng)),
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
ack_key: Arc::new(AckKey::new(rng)),
ecash_keypair: Arc::new(generate_keypair_user(&GroupParameters::new().unwrap())),
}
}
@@ -207,6 +230,7 @@ impl KeyManagerBuilder {
KeyManager {
identity_keypair: self.identity_keypair,
encryption_keypair: self.encryption_keypair,
ecash_keypair: self.ecash_keypair,
gateway_shared_key,
ack_key: self.ack_key,
}
@@ -220,6 +244,10 @@ impl KeyManagerBuilder {
Arc::clone(&self.encryption_keypair)
}
pub fn ecash_keypair(&self) -> Arc<KeyPairUser> {
Arc::clone(&self.ecash_keypair)
}
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
}
@@ -240,6 +268,9 @@ pub struct KeyManager {
/// encryption key associated with the client instance.
encryption_keypair: Arc<encryption::KeyPair>,
/// ecash key associated with the client instance
ecash_keypair: Arc<KeyPairUser>,
/// shared key derived with the gateway during "registration handshake"
// I'm not a fan of how we broke the nice transition of `KeyManagerBuilder` -> `KeyManager`
// by making this field optional.
@@ -255,12 +286,14 @@ impl KeyManager {
pub fn from_keys(
id_keypair: identity::KeyPair,
enc_keypair: encryption::KeyPair,
ecash_keypair: KeyPairUser,
gateway_shared_key: Option<SharedKeys>,
ack_key: AckKey,
) -> Self {
Self {
identity_keypair: Arc::new(id_keypair),
encryption_keypair: Arc::new(enc_keypair),
ecash_keypair: Arc::new(ecash_keypair),
gateway_shared_key: gateway_shared_key.map(Arc::new),
ack_key: Arc::new(ack_key),
}
@@ -283,6 +316,12 @@ impl KeyManager {
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
Arc::clone(&self.encryption_keypair)
}
/// Gets an atomically reference counted pointer to [`encryption::KeyPair`].
pub fn ecash_keypair(&self) -> Arc<KeyPairUser> {
Arc::clone(&self.ecash_keypair)
}
/// Gets an atomically reference counted pointer to [`AckKey`].
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
@@ -310,6 +349,7 @@ impl KeyManager {
KeyManagerBuilder {
identity_keypair: self.identity_keypair,
encryption_keypair: self.encryption_keypair,
ecash_keypair: self.ecash_keypair,
ack_key: self.ack_key,
}
}
@@ -320,6 +360,7 @@ fn _assert_keys_zeroize_on_drop() {
_assert_zeroize_on_drop::<identity::KeyPair>();
_assert_zeroize_on_drop::<encryption::KeyPair>();
_assert_zeroize_on_drop::<KeyPairUser>();
_assert_zeroize_on_drop::<AckKey>();
_assert_zeroize_on_drop::<SharedKeys>();
}
@@ -3,6 +3,7 @@
use crate::client::key_manager::KeyManager;
use async_trait::async_trait;
use nym_compact_ecash::scheme::keygen::KeyPairUser;
use std::error::Error;
use tokio::sync::Mutex;
@@ -104,6 +105,12 @@ impl OnDiskKeys {
self.load_keypair(identity_paths, "identity")
}
#[doc(hidden)]
pub fn load_ecash_keypair(&self) -> Result<KeyPairUser, OnDiskKeysError> {
let ecash_paths = self.paths.ecash_key_pair_path();
self.load_keypair(ecash_paths, "ecash")
}
fn load_key<T: PemStorableKey>(
&self,
path: &std::path::Path,
@@ -159,6 +166,7 @@ impl OnDiskKeys {
fn load_keys(&self) -> Result<KeyManager, OnDiskKeysError> {
let identity_keypair = self.load_identity_keypair()?;
let encryption_keypair = self.load_encryption_keypair()?;
let ecash_keypair = self.load_ecash_keypair()?;
let ack_key: AckKey = self.load_key(self.paths.ack_key(), "ack key")?;
let gateway_shared_key: Option<SharedKeys> = self
@@ -168,6 +176,7 @@ impl OnDiskKeys {
Ok(KeyManager::from_keys(
identity_keypair,
encryption_keypair,
ecash_keypair,
gateway_shared_key,
ack_key,
))
@@ -178,6 +187,7 @@ impl OnDiskKeys {
let identity_paths = self.paths.identity_key_pair_path();
let encryption_paths = self.paths.encryption_key_pair_path();
let ecash_paths = self.paths.ecash_key_pair_path();
self.store_keypair(
keys.identity_keypair.as_ref(),
@@ -189,6 +199,7 @@ impl OnDiskKeys {
encryption_paths,
"encryption keys",
)?;
self.store_keypair(keys.ecash_keypair.as_ref(), ecash_paths, "ecash keys")?;
self.store_key(keys.ack_key.as_ref(), self.paths.ack_key(), "ack key")?;
@@ -73,10 +73,11 @@ where
content: Vec<u8>,
lane: TransmissionLane,
packet_type: PacketType,
mix_hops: Option<u8>,
) {
if let Err(err) = self
.message_handler
.try_send_plain_message(recipient, content, lane, packet_type)
.try_send_plain_message(recipient, content, lane, packet_type, mix_hops)
.await
{
warn!("failed to send a plain message - {err}")
@@ -90,10 +91,18 @@ where
reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
mix_hops: Option<u8>,
) {
if let Err(err) = self
.message_handler
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane, packet_type)
.try_send_message_with_reply_surbs(
recipient,
content,
reply_surbs,
lane,
packet_type,
mix_hops,
)
.await
{
warn!("failed to send a repliable message - {err}")
@@ -106,8 +115,9 @@ where
recipient,
data,
lane,
mix_hops,
} => {
self.handle_plain_message(recipient, data, lane, PacketType::Mix)
self.handle_plain_message(recipient, data, lane, PacketType::Mix, mix_hops)
.await
}
InputMessage::Anonymous {
@@ -115,9 +125,17 @@ where
data,
reply_surbs,
lane,
mix_hops,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane, PacketType::Mix)
.await
self.handle_repliable_message(
recipient,
data,
reply_surbs,
lane,
PacketType::Mix,
mix_hops,
)
.await
}
InputMessage::Reply {
recipient_tag,
@@ -135,8 +153,9 @@ where
recipient,
data,
lane,
mix_hops,
} => {
self.handle_plain_message(recipient, data, lane, packet_type)
self.handle_plain_message(recipient, data, lane, packet_type, mix_hops)
.await
}
InputMessage::Anonymous {
@@ -144,9 +163,17 @@ where
data,
reply_surbs,
lane,
mix_hops,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane, packet_type)
.await
self.handle_repliable_message(
recipient,
data,
reply_surbs,
lane,
packet_type,
mix_hops,
)
.await
}
InputMessage::Reply {
recipient_tag,
@@ -69,6 +69,7 @@ pub(crate) struct PendingAcknowledgement {
message_chunk: Fragment,
delay: SphinxDelay,
destination: PacketDestination,
mix_hops: Option<u8>,
}
impl PendingAcknowledgement {
@@ -77,11 +78,13 @@ impl PendingAcknowledgement {
message_chunk: Fragment,
delay: SphinxDelay,
recipient: Recipient,
mix_hops: Option<u8>,
) -> Self {
PendingAcknowledgement {
message_chunk,
delay,
destination: PacketDestination::KnownRecipient(recipient.into()),
mix_hops,
}
}
@@ -98,6 +101,9 @@ impl PendingAcknowledgement {
recipient_tag,
extra_surb_request,
},
// Messages sent using SURBs are using the number of mix hops set by the recipient when
// they provided the SURBs, so it doesn't make sense to include it here.
mix_hops: None,
}
}
@@ -49,12 +49,18 @@ where
packet_recipient: Recipient,
chunk_data: Fragment,
packet_type: PacketType,
mix_hops: Option<u8>,
) -> Result<PreparedFragment, PreparationError> {
debug!("retransmitting normal packet...");
// TODO: Figure out retransmission packet type signaling
self.message_handler
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data, packet_type)
.try_prepare_single_chunk_for_sending(
packet_recipient,
chunk_data,
packet_type,
mix_hops,
)
.await
}
@@ -89,6 +95,7 @@ where
**recipient,
timed_out_ack.message_chunk.clone(),
packet_type,
timed_out_ack.mix_hops,
)
.await
}
@@ -418,9 +418,10 @@ where
message: Vec<u8>,
lane: TransmissionLane,
packet_type: PacketType,
mix_hops: Option<u8>,
) -> Result<(), PreparationError> {
let message = NymMessage::new_plain(message);
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type, mix_hops)
.await
}
@@ -430,6 +431,7 @@ where
recipient: Recipient,
lane: TransmissionLane,
packet_type: PacketType,
mix_hops: Option<u8>,
) -> Result<(), PreparationError> {
debug!("Sending non-reply message with packet type {packet_type}");
// TODO: I really dislike existence of this assertion, it implies code has to be re-organised
@@ -461,6 +463,7 @@ where
&self.config.ack_key,
&recipient,
packet_type,
mix_hops,
)?;
let real_message = RealMessage::new(
@@ -468,7 +471,8 @@ where
Some(fragment.fragment_identifier()),
);
let delay = prepared_fragment.total_delay;
let pending_ack = PendingAcknowledgement::new_known(fragment, delay, recipient);
let pending_ack =
PendingAcknowledgement::new_known(fragment, delay, recipient, mix_hops);
real_messages.push(real_message);
pending_acks.push(pending_ack);
@@ -485,6 +489,7 @@ where
recipient: Recipient,
amount: u32,
packet_type: PacketType,
mix_hops: Option<u8>,
) -> Result<(), PreparationError> {
debug!("Sending additional reply SURBs with packet type {packet_type}");
let sender_tag = self.get_or_create_sender_tag(&recipient);
@@ -501,6 +506,7 @@ where
recipient,
TransmissionLane::AdditionalReplySurbs,
packet_type,
mix_hops,
)
.await?;
@@ -517,6 +523,7 @@ where
num_reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
mix_hops: Option<u8>,
) -> Result<(), SurbWrappedPreparationError> {
debug!("Sending message with reply SURBs with packet type {packet_type}");
let sender_tag = self.get_or_create_sender_tag(&recipient);
@@ -527,7 +534,7 @@ where
let message =
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type, mix_hops)
.await?;
log::trace!("storing {} reply keys", reply_keys.len());
@@ -541,6 +548,7 @@ where
recipient: Recipient,
chunk: Fragment,
packet_type: PacketType,
mix_hops: Option<u8>,
) -> Result<PreparedFragment, PreparationError> {
debug!("Sending single chunk with packet type {packet_type}");
let topology_permit = self.topology_access.get_read_permit().await;
@@ -554,6 +562,7 @@ where
&self.config.ack_key,
&recipient,
packet_type,
mix_hops,
)
.unwrap();
@@ -516,6 +516,7 @@ where
recipient,
to_send,
nym_sphinx::params::PacketType::Mix,
self.config.reply_surbs.surb_mix_hops,
)
.await
{
@@ -8,6 +8,8 @@ pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem";
pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem";
pub const DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME: &str = "private_encryption.pem";
pub const DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME: &str = "public_encryption.pem";
pub const DEFAULT_PRIVATE_ECASH_KEY_FILENAME: &str = "private_ecash.pem";
pub const DEFAULT_PUBLIC_ECASH_KEY_FILENAME: &str = "public_ecash.pem";
pub const DEFAULT_GATEWAY_SHARED_KEY_FILENAME: &str = "gateway_shared.pem";
pub const DEFAULT_ACK_KEY_FILENAME: &str = "ack_key.pem";
@@ -25,6 +27,12 @@ pub struct ClientKeysPaths {
/// Path to file containing public encryption key.
pub public_encryption_key_file: PathBuf,
/// Path to file containing private ecash key.
pub private_ecash_key_file: PathBuf,
/// Path to file containing public ecash key.
pub public_ecash_key_file: PathBuf,
/// Path to file containing shared key derived with the specified gateway that is used
/// for all communication with it.
pub gateway_shared_key_file: PathBuf,
@@ -43,6 +51,8 @@ impl ClientKeysPaths {
public_identity_key_file: base_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME),
private_encryption_key_file: base_dir.join(DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME),
public_encryption_key_file: base_dir.join(DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME),
private_ecash_key_file: base_dir.join(DEFAULT_PRIVATE_ECASH_KEY_FILENAME),
public_ecash_key_file: base_dir.join(DEFAULT_PUBLIC_ECASH_KEY_FILENAME),
gateway_shared_key_file: base_dir.join(DEFAULT_GATEWAY_SHARED_KEY_FILENAME),
ack_key_file: base_dir.join(DEFAULT_ACK_KEY_FILENAME),
}
@@ -62,11 +72,20 @@ impl ClientKeysPaths {
)
}
pub fn ecash_key_pair_path(&self) -> nym_pemstore::KeyPairPath {
nym_pemstore::KeyPairPath::new(
self.private_ecash_key().to_path_buf(),
self.public_ecash_key().to_path_buf(),
)
}
pub fn any_file_exists(&self) -> bool {
matches!(self.public_identity_key_file.try_exists(), Ok(true))
|| matches!(self.private_identity_key_file.try_exists(), Ok(true))
|| matches!(self.public_encryption_key_file.try_exists(), Ok(true))
|| matches!(self.private_encryption_key_file.try_exists(), Ok(true))
|| matches!(self.public_ecash_key_file.try_exists(), Ok(true))
|| matches!(self.private_ecash_key_file.try_exists(), Ok(true))
|| matches!(self.gateway_shared_key_file.try_exists(), Ok(true))
|| matches!(self.ack_key_file.try_exists(), Ok(true))
}
@@ -76,6 +95,8 @@ impl ClientKeysPaths {
.or_else(|| file_exists(&self.private_identity_key_file))
.or_else(|| file_exists(&self.public_encryption_key_file))
.or_else(|| file_exists(&self.private_encryption_key_file))
.or_else(|| file_exists(&self.public_ecash_key_file))
.or_else(|| file_exists(&self.private_ecash_key_file))
.or_else(|| file_exists(&self.gateway_shared_key_file))
.or_else(|| file_exists(&self.ack_key_file))
}
@@ -100,6 +121,14 @@ impl ClientKeysPaths {
&self.public_encryption_key_file
}
pub fn private_ecash_key(&self) -> &Path {
&self.private_ecash_key_file
}
pub fn public_ecash_key(&self) -> &Path {
&self.public_ecash_key_file
}
pub fn gateway_shared_key(&self) -> &Path {
&self.gateway_shared_key_file
}
@@ -1,7 +1,9 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::disk_persistence::keys_paths::ClientKeysPaths;
use crate::config::disk_persistence::keys_paths::{
ClientKeysPaths, DEFAULT_PRIVATE_ECASH_KEY_FILENAME, DEFAULT_PUBLIC_ECASH_KEY_FILENAME,
};
use crate::config::disk_persistence::{CommonClientPaths, DEFAULT_GATEWAY_DETAILS_FILENAME};
use crate::error::ClientCoreError;
use serde::{Deserialize, Serialize};
@@ -10,7 +12,7 @@ use std::path::PathBuf;
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CommonClientPathsV1_1_20_2 {
pub keys: ClientKeysPaths,
pub keys: ClientKeysPathsV1_1_20_2,
pub credentials_database: PathBuf,
pub reply_surb_database: PathBuf,
}
@@ -23,10 +25,53 @@ impl CommonClientPathsV1_1_20_2 {
}
})?;
Ok(CommonClientPaths {
keys: self.keys,
keys: self.keys.upgrade_default()?,
gateway_details: data_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME),
credentials_database: self.credentials_database,
reply_surb_database: self.reply_surb_database,
})
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ClientKeysPathsV1_1_20_2 {
/// Path to file containing private identity key.
pub private_identity_key_file: PathBuf,
/// Path to file containing public identity key.
pub public_identity_key_file: PathBuf,
/// Path to file containing private encryption key.
pub private_encryption_key_file: PathBuf,
/// Path to file containing public encryption key.
pub public_encryption_key_file: PathBuf,
/// Path to file containing shared key derived with the specified gateway that is used
/// for all communication with it.
pub gateway_shared_key_file: PathBuf,
/// Path to file containing key used for encrypting and decrypting the content of an
/// acknowledgement so that nobody besides the client knows which packet it refers to.
pub ack_key_file: PathBuf,
}
impl ClientKeysPathsV1_1_20_2 {
pub fn upgrade_default(self) -> Result<ClientKeysPaths, ClientCoreError> {
let data_dir = self.gateway_shared_key_file.parent().ok_or_else(|| {
ClientCoreError::UnableToUpgradeConfigFile {
new_version: "1.1.20-2".to_string(),
}
})?;
Ok(ClientKeysPaths {
private_identity_key_file: self.private_identity_key_file,
public_identity_key_file: self.public_identity_key_file,
private_encryption_key_file: self.private_encryption_key_file,
public_encryption_key_file: self.public_encryption_key_file,
private_ecash_key_file: data_dir.join(DEFAULT_PRIVATE_ECASH_KEY_FILENAME),
public_ecash_key_file: data_dir.join(DEFAULT_PUBLIC_ECASH_KEY_FILENAME),
gateway_shared_key_file: self.gateway_shared_key_file,
ack_key_file: self.ack_key_file,
})
}
}
+5
View File
@@ -607,6 +607,10 @@ pub struct ReplySurbs {
/// This is going to be superseded by key rotation once implemented.
#[serde(with = "humantime_serde")]
pub maximum_reply_key_age: Duration,
/// Specifies the number of mixnet hops the packet should go through. If not specified, then
/// the default value is used.
pub surb_mix_hops: Option<u8>,
}
impl Default for ReplySurbs {
@@ -622,6 +626,7 @@ impl Default for ReplySurbs {
maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD,
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
surb_mix_hops: None,
}
}
}
@@ -155,6 +155,7 @@ impl From<ConfigV1_1_30> for Config {
.maximum_reply_surb_drop_waiting_period,
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
surb_mix_hops: None,
},
},
}
@@ -19,6 +19,7 @@ tokio = { version = "1.24.1", features = ["macros"] }
# internal
nym-bandwidth-controller = { path = "../../bandwidth-controller" }
nym-coconut-interface = { path = "../../coconut-interface" }
nym-compact-ecash = { path = "../../nym_offline_compact_ecash" }
nym-credential-storage = { path = "../../credential-storage" }
nym-crypto = { path = "../../crypto" }
nym-gateway-requests = { path = "../../../gateway/gateway-requests" }
@@ -12,7 +12,7 @@ use crate::{cleanup_socket_message, try_decrypt_binary_message};
use futures::{SinkExt, StreamExt};
use log::*;
use nym_bandwidth_controller::BandwidthController;
use nym_coconut_interface::Credential;
use nym_compact_ecash::scheme::EcashCredential;
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_crypto::asymmetric::identity;
@@ -513,14 +513,14 @@ impl<C, St> GatewayClient<C, St> {
}
}
async fn claim_coconut_bandwidth(
async fn claim_ecash_bandwidth(
&mut self,
credential: Credential,
credential: EcashCredential,
) -> Result<(), GatewayClientError> {
let mut rng = OsRng;
let iv = IV::new_random(&mut rng);
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential(
let msg = ClientControlRequest::new_enc_ecash_credential(
&credential,
self.shared_key.as_ref().unwrap(),
iv,
@@ -567,18 +567,18 @@ impl<C, St> GatewayClient<C, St> {
return self.try_claim_testnet_bandwidth().await;
}
let (credential, credential_id) = self
let (credential, new_wallet, wallet_id) = self
.bandwidth_controller
.as_ref()
.unwrap()
.prepare_coconut_credential()
.prepare_ecash_credential(self.gateway_identity.to_bytes())
.await?;
self.claim_coconut_bandwidth(credential).await?;
self.claim_ecash_bandwidth(credential).await?;
self.bandwidth_controller
.as_ref()
.unwrap()
.consume_credential(credential_id)
.update_ecash_wallet(new_wallet, wallet_id)
.await?;
Ok(())
@@ -671,6 +671,7 @@ impl<C, St> GatewayClient<C, St> {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
//SW NOTE : Logic to stop sending packet is there already. We only need to update bandwidth_remaining
if (mix_packet.packet().len() as i64) > self.bandwidth_remaining {
return Err(GatewayClientError::NotEnoughBandwidth(
mix_packet.packet().len() as i64,
@@ -33,6 +33,7 @@ futures = { workspace = true }
openssl = { version = "^0.10.55", features = ["vendored"], optional = true }
nym-coconut-interface = { path = "../../coconut-interface" }
nym-compact-ecash = { path = "../../nym_offline_compact_ecash" }
nym-network-defaults = { path = "../../network-defaults" }
nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
@@ -9,7 +9,8 @@ use crate::{
ReqwestRpcClient, ValidatorClientError,
};
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
BlindSignRequestBody, BlindedSignatureResponse, EcashParametersResponse,
OfflineVerifyCredentialBody, OnlineVerifyCredentialBody, VerifyCredentialResponse,
};
use nym_api_requests::models::{DescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::models::{
@@ -331,13 +332,21 @@ impl NymApiClient {
Ok(self.nym_api.blind_sign(request_body).await?)
}
pub async fn verify_bandwidth_credential(
pub async fn verify_offline_credential(
&self,
request_body: &VerifyCredentialBody,
request_body: &OfflineVerifyCredentialBody,
) -> Result<VerifyCredentialResponse, ValidatorClientError> {
Ok(self
.nym_api
.verify_bandwidth_credential(request_body)
.await?)
Ok(self.nym_api.verify_offline_credential(request_body).await?)
}
pub async fn verify_online_credential(
&self,
request_body: &OnlineVerifyCredentialBody,
) -> Result<VerifyCredentialResponse, ValidatorClientError> {
Ok(self.nym_api.verify_online_credential(request_body).await?)
}
pub async fn ecash_parameters(&self) -> Result<EcashParametersResponse, ValidatorClientError> {
Ok(self.nym_api.ecash_parameters().await?)
}
}
@@ -6,7 +6,8 @@ use crate::nyxd::error::NyxdError;
use crate::NymApiClient;
use nym_coconut_dkg_common::types::{EpochId, NodeIndex};
use nym_coconut_dkg_common::verification_key::ContractVKShare;
use nym_coconut_interface::{Base58, CoconutError, VerificationKey};
use nym_compact_ecash::error::CompactEcashError;
use nym_compact_ecash::{Base58, VerificationKeyAuth};
use thiserror::Error;
use url::Url;
@@ -14,7 +15,7 @@ use url::Url;
#[derive(Clone)]
pub struct CoconutApiClient {
pub api_client: NymApiClient,
pub verification_key: VerificationKey,
pub verification_key: VerificationKeyAuth,
pub node_id: NodeIndex,
pub cosmos_address: cosmrs::AccountId,
}
@@ -43,7 +44,7 @@ pub enum CoconutApiError {
#[error("the provided verification key is malformed: {source}")]
MalformedVerificationKey {
#[from]
source: CoconutError,
source: CompactEcashError,
},
#[error("the provided account address is malformed: {source}")]
@@ -65,14 +66,14 @@ impl TryFrom<ContractVKShare> for CoconutApiClient {
Ok(CoconutApiClient {
api_client: NymApiClient::new(url_address),
verification_key: VerificationKey::try_from_bs58(&share.share)?,
verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
node_id: share.node_index,
cosmos_address: share.owner.as_str().parse()?,
})
}
}
pub async fn all_coconut_api_clients<C>(
pub async fn all_ecash_api_clients<C>(
client: &C,
epoch_id: EpochId,
) -> Result<Vec<CoconutApiClient>, CoconutApiError>
@@ -6,7 +6,8 @@ use crate::nym_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
use async_trait::async_trait;
use http_api_client::{ApiClient, NO_PARAMS};
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
BlindSignRequestBody, BlindedSignatureResponse, EcashParametersResponse,
OfflineVerifyCredentialBody, OnlineVerifyCredentialBody, VerifyCredentialResponse,
};
use nym_api_requests::models::{
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
@@ -382,16 +383,16 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn verify_bandwidth_credential(
async fn verify_offline_credential(
&self,
request_body: &VerifyCredentialBody,
request_body: &OfflineVerifyCredentialBody,
) -> Result<VerifyCredentialResponse, NymAPIError> {
self.post_json(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::COCONUT_VERIFY_BANDWIDTH_CREDENTIAL,
routes::ECASH_VERIFY_OFFLINE_CREDENTIAL,
],
NO_PARAMS,
request_body,
@@ -399,6 +400,36 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn verify_online_credential(
&self,
request_body: &OnlineVerifyCredentialBody,
) -> Result<VerifyCredentialResponse, NymAPIError> {
self.post_json(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::ECASH_VERIFY_ONLINE_CREDENTIAL,
],
NO_PARAMS,
request_body,
)
.await
}
async fn ecash_parameters(&self) -> Result<EcashParametersResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::ECASH_PARAMETERS,
],
NO_PARAMS,
)
.await
}
async fn get_service_providers(&self) -> Result<ServicesListResponse, NymAPIError> {
log::trace!("Getting service providers");
self.get_json(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS)
@@ -16,7 +16,9 @@ pub const COCONUT_ROUTES: &str = "coconut";
pub const BANDWIDTH: &str = "bandwidth";
pub const COCONUT_BLIND_SIGN: &str = "blind-sign";
pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential";
pub const ECASH_VERIFY_OFFLINE_CREDENTIAL: &str = "verify-offline-credential";
pub const ECASH_VERIFY_ONLINE_CREDENTIAL: &str = "verify-online-credential";
pub const ECASH_PARAMETERS: &str = "ecash-parameters";
pub const STATUS_ROUTES: &str = "status";
pub const MIXNODE: &str = "mixnode";
+1 -1
View File
@@ -21,7 +21,7 @@ rand = {version = "0.6", features = ["std"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = { workspace = true }
thiserror = { workspace = true }
time = { version = "0.3.6", features = ["parsing", "formatting"] }
time = { workspace = true, features = ["parsing", "formatting"] }
toml = "0.5.6"
url = { workspace = true }
tap = "1"
@@ -36,6 +36,14 @@ pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
bail!("the loaded config does not have a credentials store information")
};
let Ok(ecash_key_path) = loaded.try_get_ecash_key() else {
bail!("the loaded config does not have an ecash key path information")
};
let Ok(ecash_keypair) = nym_pemstore::load_keypair(&ecash_key_path) else {
bail!("invalid secret key in the config path")
};
println!(
"using credentials store at '{}'",
credentials_store.display()
@@ -45,7 +53,14 @@ pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
let coin = Coin::new(args.amount as u128, denom);
let persistent_storage = initialise_persistent_storage(credentials_store).await;
utils::issue_credential(&client, coin, &persistent_storage, args.recovery_dir).await?;
utils::issue_credential(
&client,
coin,
ecash_keypair,
&persistent_storage,
args.recovery_dir,
)
.await?;
Ok(())
}
+27
View File
@@ -123,6 +123,18 @@ impl CommonConfigsWrapper {
}
}
pub(crate) fn try_get_ecash_key(&self) -> anyhow::Result<nym_pemstore::KeyPairPath> {
match self {
CommonConfigsWrapper::NymClients(cfg) => {
Ok(cfg.storage_paths.inner.keys.ecash_key_pair_path())
}
CommonConfigsWrapper::NymApi(cfg) => {
Ok(cfg.network_monitor.storage_paths.ecash_keypair_path())
}
CommonConfigsWrapper::Unknown(cfg) => cfg.try_get_ecash_key(),
}
}
pub(crate) fn try_get_credentials_store(&self) -> anyhow::Result<PathBuf> {
match self {
CommonConfigsWrapper::NymClients(cfg) => {
@@ -159,6 +171,17 @@ struct NymApiConfigNetworkMonitorLight {
#[derive(Deserialize, Debug)]
struct NetworkMonitorPaths {
credentials_database_path: PathBuf,
ecash_private_key_path: PathBuf,
ecash_public_key_path: PathBuf,
}
impl NetworkMonitorPaths {
fn ecash_keypair_path(&self) -> nym_pemstore::KeyPairPath {
nym_pemstore::KeyPairPath::new(
self.ecash_private_key_path.clone(),
self.ecash_public_key_path.clone(),
)
}
}
// a hacky way of reading common data from client configs (native, socks5, etc.)
@@ -215,6 +238,10 @@ impl UnknownConfigWrapper {
}
}
pub(crate) fn try_get_ecash_key(&self) -> anyhow::Result<nym_pemstore::KeyPairPath> {
todo!()
}
pub(crate) fn try_get_credentials_store(&self) -> anyhow::Result<PathBuf> {
let id_val = self
.find_value("credentials_database_path")
+11 -5
View File
@@ -4,7 +4,7 @@
use handlebars::{Handlebars, TemplateRenderError};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::fs::File;
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::{fs, io};
@@ -72,16 +72,22 @@ where
C: NymConfigTemplate,
P: AsRef<Path>,
{
log::debug!("trying to save config file to {}", path.as_ref().display());
let file = File::create(path.as_ref())?;
let path = path.as_ref();
log::debug!("trying to save config file to {}", path.display());
// TODO: check for whether any of our configs stores anything sensitive
if let Some(parent) = path.parent() {
create_dir_all(parent)?;
}
let file = File::create(path)?;
// TODO: check for whether any of our configs store anything sensitive
// and change that to 0o644 instead
#[cfg(target_family = "unix")]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(path.as_ref())?.permissions();
let mut perms = fs::metadata(path)?.permissions();
perms.set_mode(0o600);
fs::set_permissions(path, perms)?;
}
@@ -25,12 +25,12 @@ humantime-serde = "1.1.1"
# TO CHECK WHETHER STILL NEEDED:
log = { workspace = true }
time = { version = "0.3.6", features = ["parsing", "formatting"] }
time = { workspace = true, features = ["parsing", "formatting"] }
ts-rs = { workspace = true, optional = true }
[dev-dependencies]
rand_chacha = "0.3"
time = { version = "0.3.5", features = ["serde", "macros"] }
time = { workspace = true, features = ["serde", "macros"] }
[features]
default = []
@@ -0,0 +1,14 @@
/*
* Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
CREATE TABLE ecash_wallets
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
voucher_info TEXT NOT NULL,
wallet TEXT NOT NULL UNIQUE,
value TEXT NOT NULL,
epoch_id TEXT NOT NULL,
consumed BOOLEAN NOT NULL
);
@@ -1,13 +1,14 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::CoconutCredential;
use crate::models::{CoconutCredential, EcashWallet};
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Clone)]
pub struct CoconutCredentialManager {
inner: Arc<RwLock<Vec<CoconutCredential>>>,
ecash: Arc<RwLock<Vec<EcashWallet>>>,
}
impl CoconutCredentialManager {
@@ -15,6 +16,7 @@ impl CoconutCredentialManager {
pub fn new() -> Self {
CoconutCredentialManager {
inner: Arc::new(RwLock::new(Vec::new())),
ecash: Arc::new(RwLock::new(Vec::new())),
}
}
@@ -50,6 +52,39 @@ impl CoconutCredentialManager {
});
}
/// Inserts provided signature into the database.
///
/// # Arguments
///
/// * `voucher_info`: What type of credential it is.
/// * `signature`: Ecash wallet credential in the form of a wallet.
/// * `value` : The value of the ecash wallet
/// * `epoch_id`: The epoch when it was signed.
pub async fn insert_ecash_wallet(
&self,
voucher_info: String,
wallet: String,
value: String,
epoch_id: String,
) {
let mut creds = self.ecash.write().await;
let id = creds.len() as i64;
creds.push(EcashWallet {
id,
voucher_info,
wallet,
value,
epoch_id,
consumed: false,
});
}
/// Tries to retrieve one of the stored, unused credentials.
pub async fn get_next_ecash_wallet(&self) -> Option<EcashWallet> {
let creds = self.ecash.read().await;
creds.iter().find(|c| !c.consumed).cloned()
}
/// Tries to retrieve one of the stored, unused credentials.
pub async fn get_next_coconut_credential(&self) -> Option<CoconutCredential> {
let creds = self.inner.read().await;
@@ -67,4 +102,12 @@ impl CoconutCredentialManager {
cred.consumed = true;
}
}
pub async fn update_ecash_wallet(&self, wallet: String, id: i64, consumed: bool) {
let mut creds = self.ecash.write().await;
if let Some(cred) = creds.get_mut(id as usize) {
cred.wallet = wallet;
cred.consumed = consumed;
}
}
}
@@ -1,7 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::CoconutCredential;
use crate::models::{CoconutCredential, EcashWallet};
#[derive(Clone)]
pub struct CoconutCredentialManager {
@@ -45,6 +45,41 @@ impl CoconutCredentialManager {
Ok(())
}
/// Inserts provided signature into the database.
///
/// # Arguments
///
/// * `voucher_info`: What type of credential it is.
/// * `signature`: Ecash wallet credential in the form of a wallet.
/// * `value` : The value of the ecash wallet
/// * `epoch_id`: The epoch when it was signed.
pub async fn insert_ecash_wallet(
&self,
voucher_info: String,
wallet: String,
value: String,
epoch_id: String,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"INSERT INTO ecash_wallets(voucher_info, wallet, value, epoch_id, consumed) VALUES (?, ?, ?, ?, ?)",
voucher_info, wallet, value, epoch_id, false
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
/// Tries to retrieve one of the stored, unused credentials.
pub async fn get_next_ecash_wallet(&self) -> Result<Option<EcashWallet>, sqlx::Error> {
sqlx::query_as!(
EcashWallet,
"SELECT * FROM ecash_wallets WHERE NOT consumed"
)
.fetch_optional(&self.connection_pool)
.await
}
/// Tries to retrieve one of the stored, unused credentials.
pub async fn get_next_coconut_credential(
&self,
@@ -71,4 +106,29 @@ impl CoconutCredentialManager {
.await?;
Ok(())
}
/// Consumes in the database the specified credential.
///
/// # Arguments
///
/// * `wallet` : New wallet string to update with
/// * `id`: Database id.
/// * `consumed` : If the wallet is entirely consumed
///
pub async fn update_ecash_wallet(
&self,
wallet: String,
id: i64,
consumed: bool,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"UPDATE ecash_wallets SET wallet = ?, consumed = ? WHERE id = ?",
wallet,
consumed,
id
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
}
@@ -3,7 +3,7 @@
use crate::backends::memory::CoconutCredentialManager;
use crate::error::StorageError;
use crate::models::CoconutCredential;
use crate::models::{CoconutCredential, EcashWallet};
use crate::storage::Storage;
use async_trait::async_trait;
@@ -50,6 +50,30 @@ impl Storage for EphemeralStorage {
Ok(())
}
async fn insert_ecash_wallet(
&self,
voucher_info: String,
wallet: String,
value: String,
epoch_id: String,
) -> Result<(), StorageError> {
self.coconut_credential_manager
.insert_ecash_wallet(voucher_info, wallet, value, epoch_id)
.await;
Ok(())
}
async fn get_next_ecash_wallet(&self) -> Result<EcashWallet, StorageError> {
let credential = self
.coconut_credential_manager
.get_next_ecash_wallet()
.await
.ok_or(StorageError::NoCredential)?;
Ok(credential)
}
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError> {
let credential = self
.coconut_credential_manager
@@ -67,4 +91,16 @@ impl Storage for EphemeralStorage {
Ok(())
}
async fn update_ecash_wallet(
&self,
wallet: String,
id: i64,
consumed: bool,
) -> Result<(), StorageError> {
self.coconut_credential_manager
.update_ecash_wallet(wallet, id, consumed)
.await;
Ok(())
}
}
+11
View File
@@ -13,3 +13,14 @@ pub struct CoconutCredential {
pub epoch_id: String,
pub consumed: bool,
}
#[derive(Clone)]
pub struct EcashWallet {
#[allow(dead_code)]
pub id: i64,
pub voucher_info: String,
pub wallet: String,
pub value: String,
pub epoch_id: String,
pub consumed: bool,
}
@@ -1,9 +1,9 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::backends::sqlite::CoconutCredentialManager;
use crate::error::StorageError;
use crate::storage::Storage;
use crate::{backends::sqlite::CoconutCredentialManager, models::EcashWallet};
use crate::models::CoconutCredential;
use async_trait::async_trait;
@@ -81,6 +81,30 @@ impl Storage for PersistentStorage {
Ok(())
}
async fn insert_ecash_wallet(
&self,
voucher_info: String,
wallet: String,
value: String,
epoch_id: String,
) -> Result<(), StorageError> {
self.coconut_credential_manager
.insert_ecash_wallet(voucher_info, wallet, value, epoch_id)
.await?;
Ok(())
}
async fn get_next_ecash_wallet(&self) -> Result<EcashWallet, StorageError> {
let credential = self
.coconut_credential_manager
.get_next_ecash_wallet()
.await?
.ok_or(StorageError::NoCredential)?;
Ok(credential)
}
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError> {
let credential = self
.coconut_credential_manager
@@ -98,4 +122,16 @@ impl Storage for PersistentStorage {
Ok(())
}
async fn update_ecash_wallet(
&self,
wallet: String,
id: i64,
consumed: bool,
) -> Result<(), StorageError> {
self.coconut_credential_manager
.update_ecash_wallet(wallet, id, consumed)
.await?;
Ok(())
}
}
+35 -1
View File
@@ -1,7 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::CoconutCredential;
use crate::models::{CoconutCredential, EcashWallet};
use async_trait::async_trait;
use std::error::Error;
@@ -29,6 +29,25 @@ pub trait Storage: Send + Sync {
epoch_id: String,
) -> Result<(), Self::StorageError>;
/// Inserts provided wallet into the database.
///
/// # Arguments
///
/// * `voucher_info`: What type of credential it is.
/// * `signature`: Ecash wallet credential in the form of a wallet.
/// * `value` : The value of the ecash wallet
/// * `epoch_id`: The epoch when it was signed.
async fn insert_ecash_wallet(
&self,
voucher_info: String,
signature: String,
value: String,
epoch_id: String,
) -> Result<(), Self::StorageError>;
/// Tries to retrieve one of the stored, unused credentials.
async fn get_next_ecash_wallet(&self) -> Result<EcashWallet, Self::StorageError>;
/// Tries to retrieve one of the stored, unused credentials.
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, Self::StorageError>;
@@ -38,4 +57,19 @@ pub trait Storage: Send + Sync {
///
/// * `id`: Id of the credential to be consumed.
async fn consume_coconut_credential(&self, id: i64) -> Result<(), Self::StorageError>;
/// Update in the database the specified credential.
///
/// # Arguments
///
/// * `wallet` : New Ecash wallet credential
/// * `id`: Id of the credential to be updated.
/// * `consumed`: if the credential is consumed or not
///
async fn update_ecash_wallet(
&self,
wallet: String,
id: i64,
consumed: bool,
) -> Result<(), Self::StorageError>;
}
+1
View File
@@ -16,3 +16,4 @@ nym-credential-storage = { path = "../../common/credential-storage" }
nym-validator-client = { path = "../../common/client-libs/validator-client" }
nym-config = { path = "../../common/config" }
nym-client-core = { path = "../../common/client-core" }
nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" }
+4 -1
View File
@@ -3,6 +3,7 @@ use crate::recovery_storage::RecoveryStorage;
use log::*;
use nym_bandwidth_controller::acquire::state::State;
use nym_client_core::config::disk_persistence::CommonClientPaths;
use nym_compact_ecash::scheme::keygen::KeyPairUser;
use nym_config::DEFAULT_DATA_DIR;
use nym_credential_storage::persistent_storage::PersistentStorage;
use nym_validator_client::nyxd::contract_traits::{CoconutBandwidthSigningClient, DkgQueryClient};
@@ -16,6 +17,7 @@ const SAFETY_BUFFER_SECS: u64 = 60; // 1 minute
pub async fn issue_credential<C>(
client: &C,
amount: Coin,
ecash_keypair: KeyPairUser,
persistent_storage: &PersistentStorage,
recovery_storage_path: PathBuf,
) -> Result<()>
@@ -39,7 +41,8 @@ where
}
};
let state = nym_bandwidth_controller::acquire::deposit(client, amount.clone()).await?;
let state =
nym_bandwidth_controller::acquire::deposit(client, amount.clone(), ecash_keypair).await?;
if nym_bandwidth_controller::acquire::get_credential(&state, client, persistent_storage)
.await
+1
View File
@@ -13,6 +13,7 @@ log = { workspace = true }
# I guess temporarily until we get serde support in coconut up and running
nym-coconut-interface = { path = "../coconut-interface" }
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "hashing"] }
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
+78 -180
View File
@@ -8,13 +8,16 @@
use cosmrs::tendermint::hash::Algorithm;
use cosmrs::tendermint::Hash;
use nym_coconut_interface::{
hash_to_scalar, prepare_blind_sign, Attribute, BlindSignRequest, Credential, Parameters,
PrivateAttribute, PublicAttribute, Signature, VerificationKey,
use nym_compact_ecash::{
scheme::{
keygen::KeyPairUser,
withdrawal::{RequestInfo, WithdrawalRequest},
},
setup::GroupParameters,
withdrawal_request,
};
use nym_crypto::asymmetric::{encryption, identity};
use super::utils::prepare_credential_for_spending;
use crate::error::Error;
pub const PUBLIC_ATTRIBUTES: u32 = 2;
@@ -22,16 +25,8 @@ pub const PRIVATE_ATTRIBUTES: u32 = 2;
pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES;
pub struct BandwidthVoucher {
// a random secret value generated by the client used for double-spending detection
serial_number: PrivateAttribute,
// a random secret value generated by the client used to bind multiple credentials together
binding_number: PrivateAttribute,
// the value (e.g., bandwidth) encoded in this voucher
voucher_value: PublicAttribute,
// the plain text value (e.g., bandwidth) encoded in this voucher
voucher_value_plain: String,
// a field with public information, e.g., type of voucher, interval etc.
voucher_info: PublicAttribute,
// the plain text information
voucher_info_plain: String,
// the hash of the deposit transaction
@@ -40,121 +35,109 @@ pub struct BandwidthVoucher {
signing_key: identity::PrivateKey,
// base58 encoded private key ensuring only this client receives the signature share
encryption_key: encryption::PrivateKey,
pedersen_commitments_openings: Vec<Attribute>,
blind_sign_request: BlindSignRequest,
ecash_keypair: KeyPairUser,
withdrawal_request_info: RequestInfo,
withdrawal_request: WithdrawalRequest,
}
impl BandwidthVoucher {
pub fn new(
params: &Parameters,
params: &GroupParameters,
voucher_value: String,
voucher_info: String,
tx_hash: Hash,
signing_key: identity::PrivateKey,
encryption_key: encryption::PrivateKey,
ecash_keypair: KeyPairUser,
) -> Self {
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let voucher_value_plain = voucher_value.clone();
let voucher_info_plain = voucher_info.clone();
let voucher_value = hash_to_scalar(voucher_value.as_bytes());
let voucher_info = hash_to_scalar(voucher_info.as_bytes());
let (pedersen_commitments_openings, blind_sign_request) = prepare_blind_sign(
params,
&[serial_number, binding_number],
&[voucher_value, voucher_info],
)
.unwrap();
let (withdrawal_request, withdrawal_request_info) =
withdrawal_request(params, &ecash_keypair.secret_key()).unwrap();
BandwidthVoucher {
serial_number,
binding_number,
voucher_value,
voucher_value_plain,
voucher_info,
voucher_info_plain,
tx_hash,
signing_key,
encryption_key,
pedersen_commitments_openings,
blind_sign_request,
ecash_keypair,
withdrawal_request_info,
withdrawal_request,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let serial_number_b = self.serial_number.to_bytes();
let binding_number_b = self.binding_number.to_bytes();
let voucher_value_plain_b = self.voucher_value_plain.as_bytes();
let voucher_info_plain_b = self.voucher_info_plain.as_bytes();
let tx_hash_b = self.tx_hash.as_bytes();
let signing_key_b = self.signing_key.to_bytes();
let encryption_key_b = self.encryption_key.to_bytes();
let blind_sign_request_b = self.blind_sign_request.to_bytes();
let ecash_key_b = self.ecash_keypair.to_bytes();
let withdrawal_request_b = self.withdrawal_request.to_bytes();
let withdrawal_request_info_b = self.withdrawal_request_info.to_bytes();
let mut ret = Vec::new();
ret.extend_from_slice(&serial_number_b);
ret.extend_from_slice(&binding_number_b);
ret.extend_from_slice(tx_hash_b);
ret.extend_from_slice(&signing_key_b);
ret.extend_from_slice(&encryption_key_b);
ret.extend_from_slice(&ecash_key_b);
ret.extend_from_slice(&(voucher_value_plain_b.len() as u64).to_be_bytes());
ret.extend_from_slice(&(voucher_info_plain_b.len() as u64).to_be_bytes());
ret.extend_from_slice(&(blind_sign_request_b.len() as u64).to_be_bytes());
ret.extend_from_slice(&(self.pedersen_commitments_openings.len() as u64).to_be_bytes());
ret.extend_from_slice(&(withdrawal_request_b.len() as u64).to_be_bytes());
ret.extend_from_slice(&(withdrawal_request_info_b.len() as u64).to_be_bytes());
ret.extend_from_slice(voucher_value_plain_b);
ret.extend_from_slice(voucher_info_plain_b);
ret.extend_from_slice(&blind_sign_request_b);
for commitment in self.pedersen_commitments_openings.iter() {
ret.extend_from_slice(&commitment.to_bytes());
}
ret.extend_from_slice(&withdrawal_request_b);
ret.extend_from_slice(&withdrawal_request_info_b);
ret
}
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 32 * 5 + 4 * 8 {
if bytes.len() < 32 * 3 + 80 + 4 * 8 {
return Err(Error::BandwidthVoucherDeserializationError(format!(
"Less then {} bytes needed",
32 * 5 + 4 * 8
32 * 3 + 80 + 4 * 8
)));
}
let mut buff = [0u8; 32];
let mut small_buff = [0u8; 8];
let scalar_err =
|| Error::BandwidthVoucherDeserializationError(String::from("Invalid Scalar"));
buff.copy_from_slice(&bytes[..32]);
let serial_number = Option::<PrivateAttribute>::from(PrivateAttribute::from_bytes(&buff))
.ok_or_else(scalar_err)?;
buff.copy_from_slice(&bytes[32..2 * 32]);
let binding_number = Option::<PrivateAttribute>::from(PrivateAttribute::from_bytes(&buff))
.ok_or_else(scalar_err)?;
buff.copy_from_slice(&bytes[2 * 32..3 * 32]);
buff.copy_from_slice(&bytes[0..32]);
let tx_hash = Hash::from_bytes(Algorithm::Sha256, &buff).map_err(|_| {
Error::BandwidthVoucherDeserializationError(String::from("Invalid transaction Hash"))
})?;
buff.copy_from_slice(&bytes[3 * 32..4 * 32]);
buff.copy_from_slice(&bytes[32..2 * 32]);
let signing_key = identity::PrivateKey::from_bytes(&buff).map_err(|_| {
Error::BandwidthVoucherDeserializationError(String::from("Invalid key"))
})?;
buff.copy_from_slice(&bytes[4 * 32..5 * 32]);
buff.copy_from_slice(&bytes[2 * 32..3 * 32]);
let encryption_key = encryption::PrivateKey::from_bytes(&buff).map_err(|_| {
Error::BandwidthVoucherDeserializationError(String::from("Invalid key"))
})?;
small_buff.copy_from_slice(&bytes[5 * 32..5 * 32 + 8]);
let voucher_value_plain_no = u64::from_be_bytes(small_buff) as usize;
small_buff.copy_from_slice(&bytes[5 * 32 + 8..5 * 32 + 2 * 8]);
let voucher_info_plain_no = u64::from_be_bytes(small_buff) as usize;
small_buff.copy_from_slice(&bytes[5 * 32 + 2 * 8..5 * 32 + 3 * 8]);
let blind_sign_request_no = u64::from_be_bytes(small_buff) as usize;
small_buff.copy_from_slice(&bytes[5 * 32 + 3 * 8..5 * 32 + 4 * 8]);
let pedersen_commitments_openings_no = u64::from_be_bytes(small_buff) as usize;
let total_length = 32 * 5
//ecash key
let ecash_keypair = KeyPairUser::from_bytes(&bytes[3 * 32..3 * 32 + 80])?;
small_buff.copy_from_slice(&bytes[3 * 32 + 80..3 * 32 + 80 + 8]);
let voucher_value_plain_no = u64::from_be_bytes(small_buff) as usize;
small_buff.copy_from_slice(&bytes[3 * 32 + 80 + 8..3 * 32 + 80 + 2 * 8]);
let voucher_info_plain_no = u64::from_be_bytes(small_buff) as usize;
small_buff.copy_from_slice(&bytes[3 * 32 + 80 + 2 * 8..3 * 32 + 80 + 3 * 8]);
let withdrawal_request_no = u64::from_be_bytes(small_buff) as usize;
small_buff.copy_from_slice(&bytes[3 * 32 + 80 + 3 * 8..3 * 32 + 80 + 4 * 8]);
let withdrawal_request_info_no = u64::from_be_bytes(small_buff) as usize;
let total_length = 32 * 3
+ 80
+ 4 * 8
+ voucher_value_plain_no
+ voucher_info_plain_no
+ blind_sign_request_no
+ pedersen_commitments_openings_no * 32;
+ withdrawal_request_no
+ withdrawal_request_info_no;
if bytes.len() != total_length {
return Err(Error::BandwidthVoucherDeserializationError(format!(
"Expected {total_length} bytes",
@@ -166,74 +149,58 @@ impl BandwidthVoucher {
"Invalid UTF8 string",
)))
};
let mut var_length_pointer = 5 * 32 + 4 * 8;
let mut var_length_pointer = 32 * 3 + 80 + 4 * 8;
let voucher_value_plain = String::from_utf8(
bytes[var_length_pointer..var_length_pointer + voucher_value_plain_no].to_vec(),
)
.or_else(utf_err)?;
let voucher_value = hash_to_scalar(&voucher_value_plain);
var_length_pointer += voucher_value_plain_no;
let voucher_info_plain = String::from_utf8(
bytes[var_length_pointer..var_length_pointer + voucher_info_plain_no].to_vec(),
)
.or_else(utf_err)?;
let voucher_info = hash_to_scalar(&voucher_info_plain);
var_length_pointer += voucher_info_plain_no;
let blind_sign_request = BlindSignRequest::from_bytes(
&bytes[var_length_pointer..var_length_pointer + blind_sign_request_no],
)?;
var_length_pointer += blind_sign_request_no;
let mut pedersen_commitments_openings = Vec::new();
for _ in 0..pedersen_commitments_openings_no {
buff.copy_from_slice(&bytes[var_length_pointer..var_length_pointer + 32]);
let commitment =
Option::<Attribute>::from(Attribute::from_bytes(&buff)).ok_or_else(scalar_err)?;
var_length_pointer += 32;
pedersen_commitments_openings.push(commitment);
}
let withdrawal_request = WithdrawalRequest::try_from(
&bytes[var_length_pointer..var_length_pointer + withdrawal_request_no],
)?;
var_length_pointer += withdrawal_request_no;
let withdrawal_request_info = RequestInfo::try_from(
&bytes[var_length_pointer..var_length_pointer + withdrawal_request_info_no],
)?;
Ok(Self {
serial_number,
binding_number,
voucher_value,
voucher_value_plain,
voucher_info,
voucher_info_plain,
tx_hash,
signing_key,
encryption_key,
pedersen_commitments_openings,
blind_sign_request,
ecash_keypair,
withdrawal_request,
withdrawal_request_info,
})
}
/// Check if the plain values correspond to the PublicAttributes
pub fn verify_against_plain(values: &[PublicAttribute], plain_values: &[String]) -> bool {
values.len() == 2
&& plain_values.len() == 2
&& values[0] == hash_to_scalar(&plain_values[0])
&& values[1] == hash_to_scalar(&plain_values[1])
}
pub fn tx_hash(&self) -> &Hash {
&self.tx_hash
}
pub fn get_public_attributes(&self) -> Vec<PublicAttribute> {
vec![self.voucher_value, self.voucher_info]
}
pub fn encryption_key(&self) -> &encryption::PrivateKey {
&self.encryption_key
}
pub fn pedersen_commitments_openings(&self) -> &Vec<Attribute> {
&self.pedersen_commitments_openings
pub fn ecash_keypair(&self) -> &KeyPairUser {
&self.ecash_keypair
}
pub fn blind_sign_request(&self) -> &BlindSignRequest {
&self.blind_sign_request
pub fn withdrawal_request(&self) -> &WithdrawalRequest {
&self.withdrawal_request
}
pub fn withdrawal_request_info(&self) -> &RequestInfo {
&self.withdrawal_request_info
}
pub fn get_voucher_value(&self) -> String {
@@ -247,49 +214,22 @@ impl BandwidthVoucher {
]
}
pub fn get_private_attributes(&self) -> Vec<PrivateAttribute> {
vec![self.serial_number, self.binding_number]
}
pub fn sign(&self, request: &BlindSignRequest) -> identity::Signature {
pub fn sign(&self, request: &WithdrawalRequest) -> identity::Signature {
let mut message = request.to_bytes();
message.extend_from_slice(self.tx_hash.to_string().as_bytes());
self.signing_key.sign(&message)
}
}
pub fn prepare_for_spending(
voucher_value: u64,
voucher_info: String,
serial_number: PrivateAttribute,
binding_number: PrivateAttribute,
epoch_id: u64,
signature: &Signature,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
let params = Parameters::new(TOTAL_ATTRIBUTES)?;
prepare_credential_for_spending(
&params,
voucher_value,
voucher_info,
serial_number,
binding_number,
epoch_id,
signature,
verification_key,
)
}
#[cfg(test)]
mod test {
use super::*;
use cosmrs::tendermint::hash::Algorithm;
use nym_coconut_interface::Base58;
use nym_compact_ecash::{generate_keypair_user, Base58};
use rand::rngs::OsRng;
fn voucher_fixture() -> BandwidthVoucher {
let params = Parameters::new(4).unwrap();
let params = GroupParameters::new().unwrap();
let mut rng = OsRng;
BandwidthVoucher::new(
&params,
@@ -306,6 +246,7 @@ mod test {
&encryption::KeyPair::new(&mut rng).private_key().to_bytes(),
)
.unwrap(),
generate_keypair_user(&params),
)
}
@@ -314,14 +255,10 @@ mod test {
let voucher = voucher_fixture();
let bytes = voucher.to_bytes();
let deserialized_voucher = BandwidthVoucher::try_from_bytes(&bytes).unwrap();
assert_eq!(voucher.serial_number, deserialized_voucher.serial_number);
assert_eq!(voucher.binding_number, deserialized_voucher.binding_number);
assert_eq!(voucher.voucher_value, deserialized_voucher.voucher_value);
assert_eq!(
voucher.voucher_value_plain,
deserialized_voucher.voucher_value_plain
);
assert_eq!(voucher.voucher_info, deserialized_voucher.voucher_info);
assert_eq!(
voucher.voucher_info_plain,
deserialized_voucher.voucher_info_plain
@@ -336,51 +273,12 @@ mod test {
deserialized_voucher.encryption_key.to_string()
);
assert_eq!(
voucher.pedersen_commitments_openings,
deserialized_voucher.pedersen_commitments_openings
voucher.withdrawal_request_info.to_bytes(),
deserialized_voucher.withdrawal_request_info.to_bytes()
);
assert_eq!(
voucher.blind_sign_request.to_bs58(),
deserialized_voucher.blind_sign_request.to_bs58()
voucher.withdrawal_request.to_bs58(),
deserialized_voucher.withdrawal_request.to_bs58()
);
}
#[test]
fn voucher_consistency() {
let voucher = voucher_fixture();
assert!(!BandwidthVoucher::verify_against_plain(
&[],
&voucher.get_public_attributes_plain()
));
assert!(!BandwidthVoucher::verify_against_plain(
&voucher.get_public_attributes(),
&[],
));
assert!(!BandwidthVoucher::verify_against_plain(
&voucher.get_public_attributes(),
&[
voucher.get_public_attributes_plain()[0].clone(),
String::new()
]
));
assert!(!BandwidthVoucher::verify_against_plain(
&voucher.get_public_attributes(),
&[
String::new(),
voucher.get_public_attributes_plain()[1].clone()
]
));
assert!(!BandwidthVoucher::verify_against_plain(
&[voucher.get_public_attributes()[0], Attribute::one()],
&voucher.get_public_attributes_plain()
));
assert!(!BandwidthVoucher::verify_against_plain(
&[Attribute::one(), voucher.get_public_attributes()[1]],
&voucher.get_public_attributes_plain()
));
assert!(BandwidthVoucher::verify_against_plain(
&voucher.get_public_attributes(),
&voucher.get_public_attributes_plain()
));
}
}
+73 -67
View File
@@ -1,14 +1,17 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES};
use crate::coconut::bandwidth::BandwidthVoucher;
use crate::coconut::params::{NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm};
use crate::error::Error;
use log::{debug, warn};
use nym_api_requests::coconut::BlindSignRequestBody;
use nym_coconut_interface::{
aggregate_signature_shares, aggregate_verification_keys, prove_bandwidth_credential, Attribute,
BlindedSignature, Credential, Parameters, Signature, SignatureShare, VerificationKey,
use nym_compact_ecash::scheme::Wallet;
use nym_compact_ecash::setup::GroupParameters;
use nym_compact_ecash::utils::BlindedSignature;
use nym_compact_ecash::{
aggregate_verification_keys, aggregate_wallets, issue_verify, PartialWallet,
VerificationKeyAuth,
};
use nym_crypto::asymmetric::encryption::PublicKey;
use nym_crypto::shared_key::recompute_shared_key;
@@ -17,7 +20,7 @@ use nym_validator_client::client::CoconutApiClient;
pub async fn obtain_aggregate_verification_key(
api_clients: &[CoconutApiClient],
) -> Result<VerificationKey, Error> {
) -> Result<VerificationKeyAuth, Error> {
if api_clients.is_empty() {
return Err(Error::NoValidatorsAvailable);
}
@@ -35,23 +38,24 @@ pub async fn obtain_aggregate_verification_key(
}
async fn obtain_partial_credential(
params: &Parameters,
params: &GroupParameters,
attributes: &BandwidthVoucher,
client: &nym_validator_client::client::NymApiClient,
validator_vk: &VerificationKey,
) -> Result<Signature, Error> {
let public_attributes = attributes.get_public_attributes();
validator_vk: &VerificationKeyAuth,
) -> Result<PartialWallet, Error> {
//let public_attributes = attributes.get_public_attributes();
let public_attributes_plain = attributes.get_public_attributes_plain();
let private_attributes = attributes.get_private_attributes();
let blind_sign_request = attributes.blind_sign_request();
//let private_attributes = attributes.get_private_attributes();
let withdrawal_request = attributes.withdrawal_request();
let blind_sign_request_body = BlindSignRequestBody::new(
blind_sign_request,
withdrawal_request,
attributes.tx_hash().to_string(),
attributes.sign(blind_sign_request).to_base58_string(),
&public_attributes,
public_attributes_plain,
(public_attributes.len() + private_attributes.len()) as u32,
attributes.sign(withdrawal_request).to_base58_string(),
attributes.ecash_keypair().public_key().to_base58_string(),
// &public_attributes,
public_attributes_plain.clone(),
(public_attributes_plain.len()) as u32,
);
let response = client.blind_sign(&blind_sign_request_body).await?;
let encrypted_signature = response.encrypted_signature;
@@ -70,43 +74,42 @@ async fn obtain_partial_credential(
let blinded_signature = BlindedSignature::from_bytes(&blinded_signature_bytes)?;
let unblinded_signature = blinded_signature.unblind(
let unblinded_signature = issue_verify(
params,
validator_vk,
&private_attributes,
&public_attributes,
&blind_sign_request.get_commitment_hash(),
attributes.pedersen_commitments_openings(),
&attributes.ecash_keypair().secret_key(),
&blinded_signature,
attributes.withdrawal_request_info(),
)?;
Ok(unblinded_signature)
}
pub async fn obtain_aggregate_signature(
params: &Parameters,
params: &GroupParameters,
attributes: &BandwidthVoucher,
coconut_api_clients: &[CoconutApiClient],
ecash_api_clients: &[CoconutApiClient],
threshold: u64,
) -> Result<Signature, Error> {
if coconut_api_clients.is_empty() {
) -> Result<Wallet, Error> {
if ecash_api_clients.is_empty() {
return Err(Error::NoValidatorsAvailable);
}
let public_attributes = attributes.get_public_attributes();
let private_attributes = attributes.get_private_attributes();
//let public_attributes = attributes.get_public_attributes();
//let private_attributes = attributes.get_private_attributes();
let mut shares = Vec::with_capacity(coconut_api_clients.len());
let validators_partial_vks: Vec<_> = coconut_api_clients
let mut wallets = Vec::with_capacity(ecash_api_clients.len());
let validators_partial_vks: Vec<_> = ecash_api_clients
.iter()
.map(|api_client| api_client.verification_key.clone())
.collect();
let indices: Vec<_> = coconut_api_clients
let indices: Vec<_> = ecash_api_clients
.iter()
.map(|api_client| api_client.node_id)
.collect();
let verification_key =
aggregate_verification_keys(&validators_partial_vks, Some(indices.as_ref()))?;
for coconut_api_client in coconut_api_clients.iter() {
for coconut_api_client in ecash_api_clients.iter() {
debug!(
"attempting to obtain partial credential from {}",
coconut_api_client.api_client.api_url()
@@ -120,10 +123,7 @@ pub async fn obtain_aggregate_signature(
)
.await
{
Ok(signature) => {
let share = SignatureShare::new(signature, coconut_api_client.node_id);
shares.push(share)
}
Ok(wallet) => wallets.push(wallet),
Err(err) => {
warn!(
"failed to obtain partial credential from {}: {err}",
@@ -132,43 +132,49 @@ pub async fn obtain_aggregate_signature(
}
};
}
if shares.len() < threshold as usize {
if wallets.len() < threshold as usize {
return Err(Error::NotEnoughShares);
}
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
attributes.extend_from_slice(&private_attributes);
attributes.extend_from_slice(&public_attributes);
// let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
// attributes.extend_from_slice(&private_attributes);
// attributes.extend_from_slice(&public_attributes);
aggregate_signature_shares(params, &verification_key, &attributes, &shares)
.map_err(Error::SignatureAggregationError)
aggregate_wallets(
params,
&verification_key,
&attributes.ecash_keypair().secret_key(),
&wallets,
attributes.withdrawal_request_info(),
)
.map_err(Error::CompactEcashError)
}
// TODO: better type flow
#[allow(clippy::too_many_arguments)]
pub fn prepare_credential_for_spending(
params: &Parameters,
voucher_value: u64,
voucher_info: String,
serial_number: Attribute,
binding_number: Attribute,
epoch_id: u64,
signature: &Signature,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
let theta = prove_bandwidth_credential(
params,
verification_key,
signature,
serial_number,
binding_number,
)?;
// #[allow(clippy::too_many_arguments)]
// pub fn prepare_credential_for_spending(
// params: &nym_coconut_interface::Parameters,
// voucher_value: u64,
// voucher_info: String,
// serial_number: nym_coconut_interface::Attribute,
// binding_number: nym_coconut_interface::Attribute,
// epoch_id: u64,
// signature: &nym_coconut_interface::Signature,
// verification_key: &nym_coconut_interface::VerificationKey,
// ) -> Result<nym_coconut_interface::Credential, Error> {
// let theta = nym_coconut_interface::prove_bandwidth_credential(
// params,
// verification_key,
// signature,
// serial_number,
// binding_number,
// )?;
Ok(Credential::new(
PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES,
theta,
voucher_value,
voucher_info,
epoch_id,
))
}
// Ok(nym_coconut_interface::Credential::new(
// PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES,
// theta,
// voucher_value,
// voucher_info,
// epoch_id,
// ))
// }
+4
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use nym_coconut_interface::CoconutError;
use nym_compact_ecash::error::CompactEcashError;
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
use nym_validator_client::ValidatorClientError;
@@ -21,6 +22,9 @@ pub enum Error {
#[error("Ran into a coconut error - {0}")]
CoconutError(#[from] CoconutError),
#[error("Ran into a Compact ecash error - {0}")]
CompactEcashError(#[from] CompactEcashError),
#[error("Ran into a validator client error - {0}")]
ValidatorClientError(#[from] ValidatorClientError),
+1 -1
View File
@@ -11,7 +11,7 @@ use thiserror::Error;
use tracing::warn;
use url::Url;
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(6);
pub type PathSegments<'a> = &'a [&'a str];
pub type Params<'a, K, V> = &'a [(K, V)];
+10
View File
@@ -108,16 +108,26 @@ pub enum IpPacketRequestData {
pub struct StaticConnectRequest {
pub request_id: u64,
pub ip: IpAddr,
// The nym-address the response should be sent back to
pub reply_to: Recipient,
// The number of mix node hops that responses should take, in addition to the entry and exit
// node. Zero means only client -> entry -> exit -> client.
pub reply_to_hops: Option<u8>,
// The average delay at each mix node, in milliseconds. Currently this is not supported by the
// ip packet router.
pub reply_to_avg_mix_delays: Option<f64>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct DynamicConnectRequest {
pub request_id: u64,
// The nym-address the response should be sent back to
pub reply_to: Recipient,
// The number of mix node hops that responses should take, in addition to the entry and exit
// node. Zero means only client -> entry -> exit -> client.
pub reply_to_hops: Option<u8>,
// The average delay at each mix node, in milliseconds. Currently this is not supported by the
// ip packet router.
pub reply_to_avg_mix_delays: Option<f64>,
}
+2
View File
@@ -434,6 +434,8 @@ pub const BANDWIDTH_VALUE: u64 = UTOKENS_TO_BURN * BYTES_PER_UTOKEN;
pub const VOUCHER_INFO: &str = "BandwidthVoucher";
pub const ECASH_INFO: &str = "TicketBook";
pub const ETH_MIN_BLOCK_DEPTH: usize = 7;
/// Defaults Cosmos Hub/ATOM path
+1
View File
@@ -258,6 +258,7 @@ where
&address,
&address,
PacketType::Mix,
None,
)?)
}
@@ -0,0 +1,34 @@
[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 = { workspace = true }
bs58 = "0.4.0"
chrono = "0.4.19"
digest = "0.9"
ff = { workspace = true }
getset = "0.1.1"
group = { workspace = true }
itertools = "0.10"
rand = "0.8"
serde = "1.0.189"
sha2 = "0.9"
thiserror = "1.0"
zeroize = { workspace = true }
# internal
nym-pemstore = { path = "../pemstore", version = "0.3.0" }
[dev-dependencies]
criterion = { version = "0.3", features = ["html_reports"] }
[[bench]]
name = "benchmarks"
harness = false
@@ -0,0 +1,374 @@
// 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::{
multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, Scalar,
};
use criterion::{criterion_group, criterion_main, Criterion};
use ff::Field;
use group::{Curve, Group};
use itertools::izip;
use rand::seq::SliceRandom;
use nym_compact_ecash::identify::{identify, IdentifyResult};
use nym_compact_ecash::setup::setup;
use nym_compact_ecash::{
aggregate_verification_keys, aggregate_wallets, generate_keypair_user, issue_verify,
issue_wallet, ttp_keygen, withdrawal_request, PartialWallet, PayInfo, PublicKeyUser,
SecretKeyUser, VerificationKeyAuth,
};
#[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 { payinfo: [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 { payinfo: [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(
payment.clone(),
payment2.clone(),
pay_info.clone(),
pay_info2.clone(),
)
.unwrap()
})
},
);
let identify_result = identify(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,55 @@
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("Could not decode base 58 string - {0}")]
MalformedString(#[from] bs58::decode::Error),
#[error("Payment did not verify")]
PaymentVerification,
#[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} < {target} or {modulus_target} % {modulus} == 0")]
DeserializationInvalidLength {
actual: usize,
target: usize,
modulus_target: usize,
modulus: usize,
object: String,
},
}
@@ -0,0 +1,18 @@
use crate::scheme::withdrawal::WithdrawalRequest;
use crate::scheme::EcashCredential;
use crate::setup::Parameters;
use crate::traits::Bytable;
macro_rules! impl_clone {
($struct:ident) => {
impl Clone for $struct {
fn clone(&self) -> Self {
Self::try_from_byte_slice(&self.to_byte_vec()).unwrap()
}
}
};
}
impl_clone!(WithdrawalRequest);
impl_clone!(EcashCredential);
impl_clone!(Parameters);
@@ -0,0 +1,2 @@
mod clone;
mod serde;
@@ -0,0 +1,53 @@
use crate::scheme::EcashCredential;
use crate::setup::Parameters;
use crate::traits::Base58;
use crate::VerificationKeyAuth;
use serde::de::Unexpected;
use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use crate::scheme::withdrawal::WithdrawalRequest;
macro_rules! impl_serde {
($struct:ident, $visitor:ident) => {
pub struct $visitor {}
impl Serialize for $struct {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_bs58())
}
}
impl<'de> Visitor<'de> for $visitor {
type Value = $struct;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "A base58 encoded struct")
}
fn visit_str<E: Error>(self, s: &str) -> Result<Self::Value, E> {
match $struct::try_from_bs58(s) {
Ok(x) => Ok(x),
Err(_) => Err(Error::invalid_value(Unexpected::Str(s), &self)),
}
}
}
impl<'de> Deserialize<'de> for $struct {
fn deserialize<D>(deserializer: D) -> Result<$struct, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str($visitor {})
}
}
};
}
impl_serde!(WithdrawalRequest, V1);
impl_serde!(EcashCredential, V2);
impl_serde!(VerificationKeyAuth, V3);
impl_serde!(Parameters, V4);
@@ -0,0 +1,41 @@
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::generate_keypair_user;
pub use scheme::keygen::ttp_keygen;
pub use scheme::keygen::{PublicKeyUser, SecretKeyUser, VerificationKeyAuth};
pub use scheme::setup;
pub use scheme::withdrawal::issue_verify;
pub use scheme::withdrawal::issue_wallet;
pub use scheme::withdrawal::withdrawal_request;
pub use scheme::PartialWallet;
pub use scheme::PayInfo;
pub use traits::Base58;
use crate::error::CompactEcashError;
use crate::traits::Bytable;
pub mod error;
mod impls;
mod proofs;
pub mod scheme;
#[cfg(test)]
mod tests;
mod traits;
pub 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::generic_array::typenum::Unsigned;
use digest::Digest;
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,796 @@
use std::convert::{TryFrom, TryInto};
use bls12_381::{G1Projective, G2Projective, Scalar};
use group::{Curve, GroupEncoding};
use crate::error::{CompactEcashError, Result};
use crate::proofs::{compute_challenge, produce_response, produce_responses, ChallengeDigest};
use crate::scheme::keygen::VerificationKeyAuth;
use crate::scheme::setup::Parameters;
use crate::utils::{
try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
try_deserialize_scalar_vec,
};
#[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 += 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 += 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 += 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::aggregation::aggregate_verification_keys;
use crate::scheme::keygen::{ttp_keygen, PublicKeyUser, VerificationKeyAuth};
use crate::scheme::setup::setup;
use crate::scheme::PayInfo;
use crate::scheme::{pseudorandom_f_delta_v, pseudorandom_f_g_v};
use crate::utils::hash_to_scalar;
#[test]
fn spend_proof_construct_and_verify() {
let _rng = thread_rng();
let ll = 32;
let params = setup(ll);
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 {
payinfo: [37u8; 72],
};
let rr = hash_to_scalar(pay_info.payinfo);
// 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,410 @@
use std::convert::{TryFrom, TryInto};
use bls12_381::{G1Projective, Scalar};
use group::GroupEncoding;
use itertools::izip;
use crate::error::{CompactEcashError, Result};
use crate::proofs::{compute_challenge, produce_response, produce_responses, ChallengeDigest};
use crate::scheme::keygen::PublicKeyUser;
use crate::scheme::setup::GroupParameters;
use crate::utils::{
try_deserialize_g1_projective, try_deserialize_scalar, try_deserialize_scalar_vec,
};
#[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;
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) * 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
}
#[allow(dead_code)]
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::error::{CompactEcashError, Result};
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::GroupParameters;
use crate::scheme::withdrawal::RequestInfo;
use crate::scheme::{PartialWallet, Wallet};
use crate::utils::{
check_bilinear_pairing, perform_lagrangian_interpolation_at_origin, PartialSignature,
Signature, SignatureShare, SignerIndex,
};
use crate::Attribute;
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,464 @@
use crate::error::Result;
use crate::scheme::keygen::PublicKeyUser;
use crate::scheme::Payment;
use crate::PayInfo;
#[derive(Debug, Eq, PartialEq)]
pub enum IdentifyResult {
NotADuplicatePayment,
DuplicatePayInfo(PayInfo),
DoubleSpendingPublicKeys(PublicKeyUser),
}
pub fn identify(
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::scheme::identify::{identify, IdentifyResult};
use crate::scheme::keygen::{PublicKeyUser, SecretKeyUser};
use crate::scheme::setup::setup;
use crate::{
aggregate_verification_keys, aggregate_wallets, generate_keypair_user, issue_verify,
issue_wallet, ttp_keygen, withdrawal_request, PartialWallet, PayInfo, VerificationKeyAuth,
};
#[test]
fn duplicate_payments_with_the_same_pay_info() {
let ll = 32;
let params = setup(ll);
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 provider_keypair = generate_keypair_user(&grparams);
let pay_info1 = PayInfo::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
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(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 ll = 32;
let params = setup(ll);
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 provider_keypair = generate_keypair_user(&grparams);
let pay_info1 = PayInfo::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
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::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
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(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 ll = 32;
let params = setup(ll);
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 provider_keypair = generate_keypair_user(&grp);
let pay_info1 = PayInfo::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
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::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
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(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 ll = 32;
let params = setup(ll);
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 _ 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 provider_keypair = generate_keypair_user(&grp);
let pay_info1 = PayInfo::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
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::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
let (payment2, _) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info2,
false,
spend_vv,
)
.unwrap();
let identify_result =
identify(payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
assert_eq!(
identify_result,
IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key())
);
}
}
@@ -0,0 +1,595 @@
use core::borrow::Borrow;
use core::iter::Sum;
use core::ops::{Add, Mul};
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
use std::convert::TryFrom;
use std::convert::TryInto;
use bls12_381::{G1Projective, G2Projective, Scalar};
use group::{Curve, GroupEncoding};
use crate::error::{CompactEcashError, Result};
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::setup::GroupParameters;
use crate::scheme::SignerIndex;
use crate::traits::Bytable;
use crate::utils::Polynomial;
use crate::utils::{
try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
try_deserialize_scalar_vec,
};
use crate::Base58;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[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) * 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;
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;
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)
}
}
impl Bytable for VerificationKeyAuth {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> std::result::Result<Self, CompactEcashError> {
Self::from_bytes(slice)
}
}
impl Base58 for VerificationKeyAuth {}
#[derive(Debug, PartialEq, Clone, Zeroize, ZeroizeOnDrop)]
pub struct SecretKeyUser {
pub sk: Scalar,
}
impl SecretKeyUser {
pub fn public_key(&self, params: &GroupParameters) -> PublicKeyUser {
PublicKeyUser {
pk: params.gen1() * self.sk,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
self.sk.to_bytes().to_vec()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
let sk = Scalar::try_from_byte_slice(bytes)?;
Ok(SecretKeyUser { sk })
}
}
impl Bytable for SecretKeyUser {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> std::result::Result<Self, CompactEcashError> {
Self::from_bytes(slice)
}
}
impl Base58 for SecretKeyUser {}
impl PemStorableKey for SecretKeyUser {
type Error = CompactEcashError;
fn pem_type() -> &'static str {
"ECASH PRIVATE KEY"
}
fn to_bytes(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn from_bytes(bytes: &[u8]) -> Result<Self> {
Self::from_bytes(bytes)
}
}
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct PublicKeyUser {
pub(crate) pk: G1Projective,
}
impl PublicKeyUser {
pub fn to_base58_string(&self) -> String {
bs58::encode(&self.pk.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self> {
let bytes = bs58::decode(val)
.into_vec()
.map_err(|source| CompactEcashError::Deserialization(source.to_string()))?;
Self::from_bytes(&bytes)
}
pub fn to_bytes(&self) -> Vec<u8> {
self.pk.to_affine().to_compressed().to_vec()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() != 48 {
return Err(CompactEcashError::Deserialization(
"Failed to deserialize : Invalid length".to_string(),
));
}
let pk_bytes: &[u8; 48] = bytes[..48].try_into().unwrap();
let pk = try_deserialize_g1_projective(
pk_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize verification key G1 point".to_string(),
),
)?;
Ok(PublicKeyUser { pk })
}
}
impl PemStorableKey for PublicKeyUser {
type Error = CompactEcashError;
fn pem_type() -> &'static str {
"ECASH PUBLIC KEY"
}
fn to_bytes(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn from_bytes(bytes: &[u8]) -> Result<Self> {
Self::from_bytes(bytes)
}
}
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 new(
sk: SecretKeyAuth,
vk: VerificationKeyAuth,
index: Option<SignerIndex>,
) -> KeyPairAuth {
KeyPairAuth {
secret_key: sk,
verification_key: vk,
index,
}
}
pub fn secret_key(&self) -> SecretKeyAuth {
self.secret_key.clone()
}
pub fn verification_key(&self) -> VerificationKeyAuth {
self.verification_key.clone()
}
}
#[derive(Zeroize, ZeroizeOnDrop, Debug, Clone, PartialEq)]
pub struct KeyPairUser {
secret_key: SecretKeyUser,
#[zeroize(skip)]
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 to_bytes(&self) -> Vec<u8> {
[self.secret_key.to_bytes(), self.public_key.to_bytes()].concat()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() != 32 + 48 {
return Err(CompactEcashError::Deserialization(
"Failed to deserialize keypair : Invalid length".to_string(),
));
}
let sk = SecretKeyUser::from_bytes(&bytes[..32])?;
let pk = PublicKeyUser::from_bytes(&bytes[32..32 + 48])?;
Ok(KeyPairUser {
secret_key: sk,
public_key: pk,
})
}
}
impl PemStorableKeyPair for KeyPairUser {
type PrivatePemKey = SecretKeyUser;
type PublicPemKey = PublicKeyUser;
fn private_key(&self) -> &Self::PrivatePemKey {
&self.secret_key
}
fn public_key(&self) -> &Self::PublicPemKey {
&self.public_key
}
fn from_keys(private_key: Self::PrivatePemKey, public_key: Self::PublicPemKey) -> Self {
KeyPairUser {
secret_key: private_key,
public_key,
}
}
}
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,810 @@
use std::cell::Cell;
use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar};
use group::Curve;
use getset::{CopyGetters, Getters};
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::traits::Bytable;
use crate::utils::{
check_bilinear_pairing, hash_to_scalar, try_deserialize_g1_projective,
try_deserialize_g2_projective, try_deserialize_scalar, Signature, SignerIndex,
};
use crate::{Attribute, Base58};
use chrono::Utc;
use rand::{thread_rng, Rng};
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 mut idx = None;
if !idx_bytes.iter().all(|&x| x == 0) {
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
}
#[allow(dead_code)]
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.ll() {
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.payinfo);
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 })
}
}
impl Bytable for Wallet {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> std::result::Result<Self, CompactEcashError> {
Wallet::try_from(slice)
}
}
impl Base58 for Wallet {}
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 payinfo: [u8; 72],
}
impl PayInfo {
pub fn generate_payinfo(provider_pk: [u8; 32]) -> PayInfo {
let mut payinfo = [0u8; 72];
// Generating random bytes
thread_rng().fill(&mut payinfo[..32]);
// Adding timestamp bytes
let timestamp = Utc::now().timestamp();
payinfo[32..40].copy_from_slice(&timestamp.to_be_bytes());
// Adding provider public key bytes
payinfo[40..].copy_from_slice(&provider_pk);
PayInfo { payinfo }
}
pub fn timestamp(&self) -> i64 {
i64::from_be_bytes(self.payinfo[32..40].try_into().unwrap())
}
pub fn pk(&self) -> [u8; 32] {
self.payinfo[40..].try_into().unwrap()
}
}
#[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.payinfo)) {
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 serial_number_bs58(&self) -> String {
SerialNumber {
inner: self.ss.clone(),
}
.to_bs58()
}
pub fn has_serial_number(&self, serial_number_bs58: &str) -> Result<bool> {
let serial_number = SerialNumber::try_from_bs58(serial_number_bs58)?;
let ret = self.ss.eq(&serial_number.inner);
Ok(ret)
}
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)
}
}
pub struct SerialNumber {
pub(crate) inner: Vec<G1Projective>,
}
impl SerialNumber {
pub fn to_bytes(&self) -> Vec<u8> {
let ss_len = self.inner.len();
let mut bytes: Vec<u8> = Vec::with_capacity(ss_len * 48);
for s in &self.inner {
bytes.extend_from_slice(&s.to_affine().to_compressed());
}
bytes
}
}
impl TryFrom<&[u8]> for SerialNumber {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<Self> {
if bytes.len() % 48 != 0 {
return Err(
CompactEcashError::Deserialization(
format!("Tried to deserialize blinded serial number with incorrect number of bytes, expected a multiple of 48, got {}", bytes.len()),
));
}
let inner_len = bytes.len() / 48;
let mut inner = Vec::with_capacity(inner_len);
let mut idx = 0;
for _ in 0..inner_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()),
)?;
inner.push(ss_elem);
idx += 48;
}
Ok(SerialNumber { inner })
}
}
impl Bytable for SerialNumber {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
Self::try_from(slice)
}
}
impl Base58 for SerialNumber {}
#[derive(Getters, CopyGetters)]
pub struct EcashCredential {
#[getset(get = "pub")]
payment: Payment,
value: u64,
#[getset(get = "pub")]
pay_info: PayInfo,
#[getset(get = "pub")]
epoch_id: u64,
}
impl EcashCredential {
pub fn new(payment: Payment, value: u64, pay_info: PayInfo, epoch_id: u64) -> Self {
EcashCredential {
payment,
value,
pay_info,
epoch_id,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let payment_bytes = self.payment.to_bytes();
let mut bytes = Vec::with_capacity(payment_bytes.len() + 72 + 8 + 8 + 8);
bytes.extend_from_slice(&(payment_bytes.len() as u64).to_be_bytes());
bytes.extend_from_slice(&self.payment.to_bytes());
bytes.extend_from_slice(&self.value.to_be_bytes());
bytes.extend_from_slice(&self.pay_info.payinfo);
bytes.extend_from_slice(&self.epoch_id.to_be_bytes());
bytes
}
pub fn value(&self) -> u64 {
self.value
}
pub fn serial_number(&self) -> String {
self.payment.serial_number_bs58()
}
pub fn has_serial_number(&self, serial_number_bs58: &str) -> Result<bool> {
self.payment.has_serial_number(serial_number_bs58)
}
}
impl TryFrom<&[u8]> for EcashCredential {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 72 + 8 + 8 + 8 {
return Err(CompactEcashError::Deserialization(
"Invalid byte array for EcashCredential deserialization".to_string(),
));
}
let mut index = 0;
let payment_len = u64::from_be_bytes(bytes[index..index + 8].try_into().unwrap()) as usize;
index += 8;
if bytes[index..].len() < payment_len {
return Err(CompactEcashError::Deserialization(
"Invalid byte array for EcashCredential deserialization".to_string(),
));
}
let payment = Payment::try_from(&bytes[index..index + payment_len])?;
index += payment_len;
if bytes[index..].len() != 72 + 8 + 8 {
return Err(CompactEcashError::Deserialization(
"Invalid byte array for EcashCredential deserialization".to_string(),
));
}
let value = u64::from_be_bytes(bytes[index..index + 8].try_into().unwrap());
index += 8;
let pay_info = PayInfo {
payinfo: bytes[index..index + 72].try_into().unwrap(),
};
index += 72;
let epoch_id = u64::from_be_bytes(bytes[index..index + 8].try_into().unwrap());
Ok(EcashCredential {
payment,
value,
pay_info,
epoch_id,
})
}
}
impl Bytable for EcashCredential {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> std::result::Result<Self, CompactEcashError> {
Self::try_from(slice)
}
}
impl Base58 for EcashCredential {}
@@ -0,0 +1,249 @@
use std::collections::HashMap;
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar};
use ff::Field;
use group::Curve;
use rand::thread_rng;
use crate::error::{CompactEcashError, Result};
use crate::traits::Bytable;
use crate::utils::{hash_g1, try_deserialize_g2_projective, Signature};
use crate::Base58;
const ATTRIBUTES_LEN: usize = 3;
#[derive(Debug)]
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]
}
#[allow(dead_code)]
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,
}
#[derive(Debug)]
pub struct Parameters {
/// group parameters
grp: GroupParameters,
/// Public Key for range proof verification
pk_rp: PublicKeyRP,
/// Max value of wallet
ll: 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 ll(&self) -> u64 {
self.ll
}
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) => Ok(val),
None => 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 to_bytes(&self) -> Vec<u8> {
//we omit grp as it is fixed
let pk_rp_alpha_bytes = self.pk_rp.alpha.to_affine().to_compressed();
let pk_rp_beta_bytes = self.pk_rp.beta.to_affine().to_compressed();
let l_bytes = self.ll.to_be_bytes();
let mut signs_bytes: Vec<u8> = Vec::with_capacity((self.ll * 96) as usize);
for l in 0..self.ll {
let sign = self.signs[&l];
signs_bytes.extend_from_slice(&sign.to_bytes());
}
let mut bytes = Vec::with_capacity(96 + 96 + 8 + signs_bytes.len());
bytes.extend_from_slice(&pk_rp_alpha_bytes);
bytes.extend_from_slice(&pk_rp_beta_bytes);
bytes.extend_from_slice(&l_bytes);
bytes.extend_from_slice(&signs_bytes);
bytes
}
}
impl TryFrom<&[u8]> for Parameters {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 96 + 96 + 8 {
return Err(CompactEcashError::Deserialization(
"Invalid byte array for Parameters deserialization".to_string(),
));
}
let pk_rp_alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
let pk_rp_beta_bytes: [u8; 96] = bytes[96..192].try_into().unwrap();
let ll = u64::from_be_bytes(bytes[192..200].try_into().unwrap());
let mut index = 200;
if bytes[index..].len() != ll as usize * 96 {
return Err(CompactEcashError::Deserialization(
"Invalid byte array for Parameters signatures deserialization".to_string(),
));
}
let pk_rp_alpha = try_deserialize_g2_projective(
&pk_rp_alpha_bytes,
CompactEcashError::Deserialization("Failed to deserialize pk_rp_alpha".to_string()),
)?;
let pk_rp_beta = try_deserialize_g2_projective(
&pk_rp_beta_bytes,
CompactEcashError::Deserialization("Failed to deserialize pk_rp_beta".to_string()),
)?;
let grp_params = GroupParameters::new()?;
let pk_rp = PublicKeyRP {
alpha: pk_rp_alpha,
beta: pk_rp_beta,
};
let mut signs = HashMap::new();
for l in 0..ll {
let sign = Signature::try_from(&bytes[index..index + 96])?;
signs.insert(l, sign);
index += 96;
}
Ok(Self {
grp: grp_params,
pk_rp,
ll,
signs,
})
}
}
impl Bytable for Parameters {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> std::result::Result<Self, CompactEcashError> {
Self::try_from(slice)
}
}
impl Base58 for Parameters {}
pub fn setup(ll: 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..ll {
let r = grp.random_scalar();
let h = grp.gen1() * r;
signs.insert(l, Signature(h, h * (x + y * Scalar::from(l))));
}
Parameters {
grp,
pk_rp,
ll,
signs,
}
}
@@ -0,0 +1,402 @@
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::setup::GroupParameters;
use crate::scheme::PartialWallet;
use crate::traits::Bytable;
use crate::utils::{
check_bilinear_pairing, hash_g1, try_deserialize_g1_projective, try_deserialize_scalar,
};
use crate::utils::{BlindedSignature, Signature};
use crate::Base58;
#[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,
})
}
}
impl Bytable for WithdrawalRequest {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
WithdrawalRequest::try_from(slice)
}
}
impl Base58 for WithdrawalRequest {}
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 to_bytes(&self) -> Vec<u8> {
let com_hash_bytes = self.com_hash.to_affine().to_compressed();
let com_opening_bytes = self.com_opening.to_bytes();
let pr_coms_openings_len = self.pc_coms_openings.len() as u64;
let v_bytes = self.v.to_bytes();
let mut bytes = Vec::with_capacity(48 + 32 + 8 + pr_coms_openings_len as usize * 32 + 32);
bytes.extend_from_slice(&com_hash_bytes);
bytes.extend_from_slice(&com_opening_bytes);
bytes.extend_from_slice(&pr_coms_openings_len.to_le_bytes());
for c in &self.pc_coms_openings {
bytes.extend_from_slice(&c.to_bytes());
}
bytes.extend_from_slice(&v_bytes);
bytes
}
}
impl TryFrom<&[u8]> for RequestInfo {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<RequestInfo> {
if bytes.len() < 48 + 32 + 8 + 32 {
return Err(CompactEcashError::DeserializationMinLength {
min: 48 + 32 + 8 + 32,
actual: bytes.len(),
});
}
let mut j = 0;
let commitment_hash_bytes_len = 48;
let com_hash_bytes = bytes[j..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_opening_bytes_len = 32;
let com_opening_bytes = bytes[j..j + com_opening_bytes_len].try_into().unwrap();
let com_opening = try_deserialize_scalar(
&com_opening_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize commitment opening".to_string(),
),
)?;
j += com_opening_bytes_len;
let pc_coms_openings_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < pc_coms_openings_len as usize * 32 {
return Err(CompactEcashError::DeserializationMinLength {
min: pc_coms_openings_len as usize * 32,
actual: bytes[j..].len(),
});
}
let mut pc_coms_openings = Vec::with_capacity(pc_coms_openings_len as usize);
for i in 0..pc_coms_openings_len as usize {
let start = j + i * 32;
let end = start + 32;
let pc_com_opening_bytes = bytes[start..end].try_into().unwrap();
let pc_com_opening = try_deserialize_scalar(
&pc_com_opening_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed Pedersen commitment opening".to_string(),
),
)?;
pc_coms_openings.push(pc_com_opening)
}
j += pc_coms_openings_len as usize * 32;
let v_len = 32;
if bytes[j..].len() != v_len {
return Err(CompactEcashError::DeserializationMinLength {
min: v_len,
actual: bytes[j..].len(),
});
}
let v_bytes = bytes[j..j + v_len].try_into().unwrap();
let v = try_deserialize_scalar(
v_bytes,
CompactEcashError::Deserialization("Failed to deserialize v".to_string()),
)?;
Ok(RequestInfo {
com_hash,
com_opening,
pc_coms_openings,
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 = [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,97 @@
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::setup::setup;
use crate::scheme::withdrawal::{
issue_verify, issue_wallet, withdrawal_request, WithdrawalRequest,
};
use crate::scheme::PayInfo;
use crate::scheme::{PartialWallet, Payment, Wallet};
#[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 provider_keypair = generate_keypair_user(&grparams);
let payinfo = PayInfo::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
let spend_vv = 1;
let (payment, _) = aggr_wallet.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&payinfo,
false,
spend_vv,
)?;
assert!(payment
.spend_verify(&params, &verification_key, &payinfo)
.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,477 @@
// 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::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField};
use bls12_381::{
multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar,
};
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);
impl TryFrom<&[u8]> for BlindedSignature {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<BlindedSignature> {
if bytes.len() != 96 {
return Err(CompactEcashError::Deserialization(format!(
"BlindedSignature must be exactly 96 bytes, got {}",
bytes.len()
)));
}
let bsig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
let bsig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
let bsig1 = try_deserialize_g1_projective(
bsig1_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed bsig1".to_string(),
),
)?;
let bsig2 = try_deserialize_g1_projective(
bsig2_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed bsig2".to_string(),
),
)?;
Ok(BlindedSignature(bsig1, bsig2))
}
}
impl BlindedSignature {
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<BlindedSignature> {
BlindedSignature::try_from(bytes)
}
}
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,26 @@
[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 = { workspace = true }
ff = { workspace = true }
group = { workspace = true }
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"] }
[[bench]]
name = "benchmarks"
harness = false
@@ -0,0 +1,375 @@
use std::collections::HashSet;
use std::ops::Neg;
use std::time::Duration;
use bls12_381::{
multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, Scalar,
};
use criterion::{criterion_group, criterion_main, Criterion};
use ff::Field;
use group::{Curve, Group};
use itertools::izip;
use rand::seq::SliceRandom;
use rand::thread_rng;
use nym_offline_divisible_ecash::identification::{identify, IdentifyResult};
use nym_offline_divisible_ecash::setup::{GroupParameters, Parameters};
use nym_offline_divisible_ecash::{
aggregate_verification_keys, aggregate_wallets, issue, issue_verify, ttp_keygen_authorities,
ttp_keygen_users, withdrawal_request, PartialWallet, PayInfo, PublicKeyUser, SecretKeyUser,
VerificationKeyAuth,
};
#[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::{pairing, G1Projective, G2Prepared, G2Projective, Scalar};
pub use scheme::aggregation::aggregate_verification_keys;
pub use scheme::aggregation::aggregate_wallets;
pub use scheme::identification;
pub use scheme::keygen::ttp_keygen_authorities;
pub use scheme::keygen::ttp_keygen_users;
pub use scheme::keygen::{PublicKeyUser, SecretKeyUser, VerificationKeyAuth};
pub use scheme::setup;
pub use scheme::withdrawal::issue;
pub use scheme::withdrawal::issue_verify;
pub use scheme::withdrawal::withdrawal_request;
pub use scheme::PartialWallet;
pub use scheme::PayInfo;
pub use traits::Base58;
use crate::error::DivisibleEcashError;
use crate::traits::Bytable;
mod constants;
mod error;
mod proofs;
mod scheme;
#[cfg(test)]
mod tests;
mod traits;
mod utils;
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::generic_array::typenum::Unsigned;
use digest::Digest;
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_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,655 @@
use std::convert::TryFrom;
use std::ops::Neg;
use bls12_381::{G1Projective, G2Projective, Gt, Scalar};
use group::GroupEncoding;
use crate::error::{DivisibleEcashError, Result};
use crate::proofs::{compute_challenge, produce_response, produce_responses, ChallengeDigest};
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::Parameters;
use crate::scheme::{Phi, VarPhi, Wallet};
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::{pairing, G2Projective};
use group::Curve;
use rand::thread_rng;
use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::keygen::{
ttp_keygen_authorities, PublicKeyUser, SecretKeyUser, VerificationKeyAuth,
};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::scheme::{PayInfo, Phi, VarPhi};
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,330 @@
use std::convert::{TryFrom, TryInto};
use bls12_381::{G1Projective, Scalar};
use group::GroupEncoding;
use itertools::izip;
use crate::error::{DivisibleEcashError, Result};
use crate::proofs::{compute_challenge, produce_response, produce_responses, ChallengeDigest};
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,166 @@
use core::iter::Sum;
use core::ops::Mul;
use std::cell::Cell;
use bls12_381::{pairing, G2Prepared, G2Projective, Scalar};
use group::Curve;
use itertools::Itertools;
use crate::error::{DivisibleEcashError, Result};
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::GroupParameters;
use crate::scheme::{PartialWallet, Wallet};
use crate::utils::{
check_bilinear_pairing, perform_lagrangian_interpolation_at_origin, PartialSignature,
Signature, SignatureShare, SignerIndex,
};
use crate::Attribute;
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,514 @@
use std::collections::{HashMap, HashSet};
use std::ops::Neg;
use bls12_381::{pairing, Gt, Scalar};
use group::Curve;
use crate::error::{DivisibleEcashError, Result};
use crate::scheme::identification::IdentifyResult::DoubleSpendingPublicKeys;
use crate::scheme::keygen::{PublicKeyUser, VerificationKeyAuth};
use crate::scheme::setup::Parameters;
use crate::scheme::{PayInfo, Payment};
#[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::aggregation::{aggregate_verification_keys, aggregate_wallets};
use crate::scheme::identification::{identify, IdentifyResult};
use crate::scheme::keygen::{
ttp_keygen_authorities, PublicKeyUser, SecretKeyUser, VerificationKeyAuth,
};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::scheme::withdrawal::{issue, issue_verify, withdrawal_request};
use crate::scheme::{PayInfo, Payment};
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,438 @@
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::{
try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
try_deserialize_scalar_vec, Polynomial, SignerIndex,
};
#[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,729 @@
use std::cell::Cell;
use std::convert::{TryFrom, TryInto};
use std::ops::Neg;
use bls12_381::{pairing, G1Projective, G2Prepared, G2Projective, Scalar};
use group::{Curve, GroupEncoding};
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, try_deserialize_g1_projective,
try_deserialize_g2_projective, try_deserialize_scalar, Signature, SignerIndex,
};
use crate::Attribute;
pub mod aggregation;
pub mod identification;
pub mod keygen;
pub mod setup;
pub mod structure_preserving_signature;
pub mod withdrawal;
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::aggregation::aggregate_verification_keys;
use crate::scheme::keygen::{ttp_keygen_authorities, PublicKeyUser, VerificationKeyAuth};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::scheme::{PayInfo, Phi, VarPhi, Wallet};
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,258 @@
use std::convert::TryFrom;
use std::net::ToSocketAddrs;
use bls12_381::{pairing, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, 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,245 @@
use std::fmt::Debug;
use std::ops::Neg;
use bls12_381::{pairing, G1Projective, G2Projective, Gt, 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,175 @@
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::setup::{GroupParameters, Parameters};
use crate::scheme::PartialWallet;
use crate::utils::{check_bilinear_pairing, hash_g1, BlindedSignature, 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,76 @@
use rand::thread_rng;
use crate::error::DivisibleEcashError;
use crate::scheme::aggregation::{
aggregate_signatures, aggregate_verification_keys, aggregate_wallets,
};
use crate::scheme::identification::identify;
use crate::scheme::keygen::{
ttp_keygen_authorities, PublicKeyUser, SecretKeyUser, VerificationKeyAuth,
};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::scheme::withdrawal::{issue, issue_verify, withdrawal_request};
use crate::scheme::{PayInfo, Payment};
#[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(())
}

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