Compare commits
169 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ce8718ae96 | |||
| 07a86afb54 | |||
| 3098eb8544 | |||
| f2ab3e0972 | |||
| 785634b824 | |||
| f6d00ba196 | |||
| 1dd208dd4c | |||
| 49d22ab791 | |||
| d97244fb27 | |||
| 8093932c7b | |||
| e0f36537db | |||
| 2bb130c204 | |||
| 338613362d | |||
| 770324cf95 | |||
| 9c6555663a | |||
| 9255e32d08 | |||
| f35f2649ef | |||
| 7514fa3ca6 | |||
| 976b6d9ada | |||
| 9d030bef94 | |||
| ddfd503962 | |||
| b8803376ff | |||
| 3afb4c5041 | |||
| f8a881af59 | |||
| 14b974d2e1 | |||
| 3bf105301a | |||
| 61cf4ea0c7 | |||
| d559d9a113 | |||
| 575282fe32 | |||
| 3e3c8a5467 | |||
| 0603273a49 | |||
| dcfae92bab | |||
| 9e925fd4ce | |||
| 5792b89917 | |||
| d5bfa732c6 | |||
| 89644e5476 | |||
| 7057170381 | |||
| ada02fe631 | |||
| 7836f5771b | |||
| 46c53eb1a5 | |||
| 506693a8a5 | |||
| cece0bb80e | |||
| 56d36974fa | |||
| c0713a5a13 | |||
| 74536467b6 | |||
| c1c58a7476 | |||
| bc9d4b4682 | |||
| e41796b157 | |||
| 5a831b6482 | |||
| 4de642315d | |||
| 1091f1341c | |||
| ea0dbfea03 | |||
| dd323ce493 | |||
| 954f76e241 | |||
| a09dc8e462 | |||
| f623bfed4c | |||
| e6bcfb697c | |||
| a70843b940 | |||
| 8a21e4ae25 | |||
| d7f4590239 | |||
| cd2f7a30d2 | |||
| 23b9e4d48a | |||
| fecb67ded7 | |||
| 3ffa367de1 | |||
| 3691110db8 | |||
| dd527e4295 | |||
| 778f5bf5e5 | |||
| 761d09534f | |||
| 4a7e44b9c7 | |||
| db69158b4b | |||
| 40af8ebf47 | |||
| f8e78c7fcc | |||
| 05ddbf2fba | |||
| 31e9c0004a | |||
| e4cfdc9888 | |||
| 0df62df780 | |||
| 3687600587 | |||
| 64dd6c2b8c | |||
| aa018769c2 | |||
| 2ec2613a89 | |||
| 15d10ab027 | |||
| 0fc5b97dfb | |||
| 7632e524c0 | |||
| 49721177fc | |||
| 7f0f2b056f | |||
| cec35ee4d0 | |||
| 5f545675e1 | |||
| 49ac369ce2 | |||
| dfcc87167b | |||
| ee95596abf | |||
| 23ce9de873 | |||
| 28081bd72c | |||
| 5e146aa432 | |||
| 53a9d43c87 | |||
| f487234007 | |||
| d2a7d26e5a | |||
| 7fcd246089 | |||
| f384338f0f | |||
| 0114743db9 | |||
| 9776bfb0b0 | |||
| 0526b3e6b4 | |||
| d00bef2ddf | |||
| a0c81c982c | |||
| 32ba1c7c18 | |||
| bea8e4a881 | |||
| 9558c3deb2 | |||
| 26a8dec707 | |||
| 74481003e6 | |||
| 6d6eb186c0 | |||
| 6a4f8d502d | |||
| 755fd1d765 | |||
| ac14382a08 | |||
| c8017db6c4 | |||
| 49aaf860a8 | |||
| 66e36a7ed5 | |||
| 34be9dc60f | |||
| 0e26a6efdf | |||
| a190506b41 | |||
| 8be372acff | |||
| c2321c20eb | |||
| 8b5dc867cd | |||
| a2219323d1 | |||
| 0f844aba38 | |||
| cf794b63a7 | |||
| 145b702f41 | |||
| bb9b3cdb64 | |||
| b3927b9d0d | |||
| 66f8ce46bf | |||
| 1a2cf6b523 | |||
| f0ae49b18e | |||
| abe6a16896 | |||
| 7d6dde5148 | |||
| b10da899a8 | |||
| 9b5714b897 | |||
| 6b133750d4 | |||
| 70c9348c30 | |||
| 0bf0b10c5c | |||
| 8d774cf6a0 | |||
| e5c2280a1c | |||
| c04b617a55 | |||
| 56ecfa7e38 | |||
| 1be60922c2 | |||
| 22da01ccd4 | |||
| 2e077ca946 | |||
| 70d3b784f4 | |||
| f6e88b610b | |||
| 822dac8ee3 | |||
| 95e9a96ae1 | |||
| e853e8ffc1 | |||
| aaeb6a7cbf | |||
| 4a98631e93 | |||
| ce4c6de1e9 | |||
| 29b41da1bb | |||
| 94c4fd2af5 | |||
| 12497f3222 | |||
| 4a5a6d366c | |||
| b4ed20487d | |||
| b8036031ba | |||
| 3117ed45b4 | |||
| 8b8e8a8282 | |||
| 29d2ab4a7a | |||
| ea834a60a5 | |||
| a6c627df33 | |||
| 52b8703028 | |||
| b40736d46b | |||
| caf055efc1 | |||
| 0f6c2293bf | |||
| 3e374e4c91 | |||
| 2a7ed0faa8 |
@@ -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
File diff suppressed because it is too large
Load Diff
+25
-2
@@ -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]
|
||||
|
||||
@@ -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>.
|
||||
|
||||
@@ -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 }}'
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }}'
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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(
|
||||
¶ms,
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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")?;
|
||||
|
||||
|
||||
+35
-8
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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(
|
||||
¶ms,
|
||||
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(
|
||||
¶ms,
|
||||
@@ -306,6 +246,7 @@ mod test {
|
||||
&encryption::KeyPair::new(&mut rng).private_key().to_bytes(),
|
||||
)
|
||||
.unwrap(),
|
||||
generate_keypair_user(¶ms),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
// ))
|
||||
// }
|
||||
|
||||
@@ -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),
|
||||
|
||||
|
||||
@@ -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)];
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
// ¶ms,
|
||||
// &verification_key,
|
||||
// &user_keypair.secret_key(),
|
||||
// &pay_info,
|
||||
// true,
|
||||
// case.spend_vv,
|
||||
// )
|
||||
// .unwrap()
|
||||
// })
|
||||
// },
|
||||
// );
|
||||
|
||||
let (payment, upd_wallet) = aggr_wallet
|
||||
.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info,
|
||||
false,
|
||||
case.spend_vv,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// MERCHANT BENCHMARK: verify whether the submitted payment is legit
|
||||
// group.bench_function(
|
||||
// &format!(
|
||||
// "[Merchant] spend_verify_of_a_single_payment_L_{}_threshold_{}",
|
||||
// case.L, case.threshold_p,
|
||||
// ),
|
||||
// |b| {
|
||||
// b.iter(|| {
|
||||
// payment
|
||||
// .spend_verify(¶ms, &verification_key, &pay_info)
|
||||
// .unwrap()
|
||||
// })
|
||||
// },
|
||||
// );
|
||||
|
||||
// BENCHMARK IDENTIFICATION
|
||||
// Let's generate a double spending payment
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = aggr_wallet.l.get();
|
||||
aggr_wallet.l.set(current_l - case.spend_vv);
|
||||
|
||||
let pay_info2 = PayInfo { payinfo: [7u8; 32] };
|
||||
let (payment2, _) = aggr_wallet
|
||||
.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info2,
|
||||
true,
|
||||
case.spend_vv,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut public_keys: Vec<PublicKeyUser> = Default::default();
|
||||
for i in 0..case.case_nr_pub_keys {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
public_keys.push(pk_user);
|
||||
}
|
||||
public_keys.push(user_keypair.public_key());
|
||||
|
||||
// MERCHANT BENCHMARK: identify double spending
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Merchant] identify_L_{}_threshold_{}_spend_vv_{}_pks_{}",
|
||||
case.L,
|
||||
case.threshold_p,
|
||||
case.spend_vv,
|
||||
public_keys.len()
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
identify(
|
||||
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(¶ms, &instance, &witness, &verification_key, &[rr]);
|
||||
assert!(zk_proof.verify(¶ms, &instance, &verification_key, &[rr]));
|
||||
|
||||
let zk_proof_bytes = zk_proof.to_bytes();
|
||||
let zk_proof2 = SpendProof::try_from(zk_proof_bytes.as_slice()).unwrap();
|
||||
assert_eq!(zk_proof, zk_proof2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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(¶ms, &instance, &witness);
|
||||
assert!(zk_proof.verify(¶ms, &instance))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
use core::iter::Sum;
|
||||
use core::ops::Mul;
|
||||
use std::cell::Cell;
|
||||
|
||||
use bls12_381::{G2Prepared, G2Projective, Scalar};
|
||||
use group::Curve;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::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(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info1,
|
||||
false,
|
||||
spend_vv,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
let payment2 = payment1.clone();
|
||||
assert!(payment2
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
let pay_info2 = pay_info1.clone();
|
||||
let identify_result =
|
||||
identify(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(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info1,
|
||||
false,
|
||||
spend_vv,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
let pay_info2 = PayInfo::generate_payinfo(
|
||||
provider_keypair.public_key().to_bytes()[..32]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let (payment2, _) = upd_wallet
|
||||
.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info2,
|
||||
false,
|
||||
spend_vv,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(payment2
|
||||
.spend_verify(¶ms, &verification_key, &pay_info2)
|
||||
.unwrap());
|
||||
|
||||
let identify_result =
|
||||
identify(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(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info1,
|
||||
false,
|
||||
spend_vv,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = aggr_wallet.l.get();
|
||||
aggr_wallet.l.set(current_l - 1);
|
||||
|
||||
let pay_info2 = PayInfo::generate_payinfo(
|
||||
provider_keypair.public_key().to_bytes()[..32]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let (payment2, _) = aggr_wallet
|
||||
.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info2,
|
||||
false,
|
||||
spend_vv,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(payment2
|
||||
.spend_verify(¶ms, &verification_key, &pay_info2)
|
||||
.unwrap());
|
||||
|
||||
let identify_result =
|
||||
identify(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(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info1,
|
||||
false,
|
||||
spend_vv,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = aggr_wallet.l.get();
|
||||
aggr_wallet.l.set(current_l - 10);
|
||||
|
||||
let pay_info2 = PayInfo::generate_payinfo(
|
||||
provider_keypair.public_key().to_bytes()[..32]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let (payment2, _) = aggr_wallet
|
||||
.spend(
|
||||
¶ms,
|
||||
&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(×tamp.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(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&payinfo,
|
||||
false,
|
||||
spend_vv,
|
||||
)?;
|
||||
|
||||
assert!(payment
|
||||
.spend_verify(¶ms, &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(¶ms, threshold, case.num_authorities).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let indices: Vec<u64> = (1..case.num_authorities + 1).collect();
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
|
||||
|
||||
// KEY GENERATION FOR THE USER
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = SecretKeyUser::public_key(&sk_user, &grp);
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut pk_all_users = HashSet::new();
|
||||
for i in 0..50 {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
pk_all_users.insert(pk_user);
|
||||
}
|
||||
pk_all_users.insert(pk_user.clone());
|
||||
|
||||
// WITHDRAWAL REQUEST
|
||||
let (withdrawal_req, req_info) = withdrawal_request(¶ms, &sk_user).unwrap();
|
||||
|
||||
// CLIENT BENCHMARK: prepare a single withdrawal request
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Client] withdrawal_request_{}_authorities_{}_L_{}_threshold",
|
||||
case.num_authorities, case.L, case.threshold_p,
|
||||
),
|
||||
|b| b.iter(|| withdrawal_request(¶ms, &sk_user).unwrap()),
|
||||
);
|
||||
|
||||
// ISSUE PARTIAL WALLETS
|
||||
// first one meaningful one just for benchmark
|
||||
let mut rng = rand::thread_rng();
|
||||
let keypair = authorities_keypairs.choose(&mut rng).unwrap();
|
||||
group.bench_function(
|
||||
&format!("[Issuing Authority] issue_partial_wallet_with_L_{}", case.L,),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
issue(
|
||||
¶ms,
|
||||
&withdrawal_req,
|
||||
pk_user.clone(),
|
||||
&keypair.secret_key(),
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
let mut wallet_blinded_signatures = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req,
|
||||
pk_user.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
)
|
||||
.unwrap();
|
||||
wallet_blinded_signatures.push(blind_signature);
|
||||
}
|
||||
|
||||
// CLIENT BENCHMARK: verify the issued partial wallet
|
||||
let w = wallet_blinded_signatures.get(0).clone().unwrap();
|
||||
let vk = verification_keys_auth.get(0).clone().unwrap();
|
||||
group.bench_function(
|
||||
&format!("[Client] issue_verify_a_partial_wallet_with_L_{}", case.L,),
|
||||
|b| b.iter(|| issue_verify(&grp, vk, &sk_user, w, &req_info).unwrap()),
|
||||
);
|
||||
|
||||
let partial_wallets: Vec<PartialWallet> = izip!(
|
||||
wallet_blinded_signatures.iter(),
|
||||
verification_keys_auth.iter()
|
||||
)
|
||||
.map(|(w, vk)| issue_verify(&grp, &vk, &sk_user, &w, &req_info).unwrap())
|
||||
.collect();
|
||||
|
||||
// AGGREGATE WALLET
|
||||
let mut wallet =
|
||||
aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets).unwrap();
|
||||
|
||||
// CLIENT BENCHMARK: aggregating all partial wallets
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Client] aggregate_wallets_with_L_{}_threshold_{}",
|
||||
case.L, case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets).unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
let pay_info = PayInfo { info: [67u8; 32] };
|
||||
let (payment, wallet) = wallet
|
||||
.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&sk_user,
|
||||
&pay_info,
|
||||
case.spend_vv,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// CLIENT BENCHMARK: spend a single coin from the wallet
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Client] spend_a_single_coin_L_{}_threshold_{}",
|
||||
case.L, case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
wallet
|
||||
.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&sk_user,
|
||||
&pay_info,
|
||||
case.spend_vv,
|
||||
true,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// MERCHANT BENCHMARK: verify whether the submitted payment is legit
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Merchant] spend_verify_of_a_single_payment_L_{}_threshold_{}",
|
||||
case.L, case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
payment
|
||||
.spend_verify(¶ms, &verification_key, &pay_info)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// BENCHMARK IDENTIFICATION
|
||||
// Let's generate a double spending payment
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = wallet.l();
|
||||
wallet.l.set(current_l - 1);
|
||||
|
||||
let pay_info2 = PayInfo { info: [52u8; 32] };
|
||||
let (payment2, wallet) = wallet
|
||||
.spend(¶ms, &verification_key, &sk_user, &pay_info2, 10, false)
|
||||
.unwrap();
|
||||
|
||||
// MERCHANT BENCHMARK: identify double spending
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Merchant] identify_L_{}_threshold_{}_spend_vv_{}_pks_{}",
|
||||
case.L,
|
||||
case.threshold_p,
|
||||
case.spend_vv,
|
||||
pk_all_users.len()
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
identify(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&pk_all_users,
|
||||
payment.clone(),
|
||||
payment2.clone(),
|
||||
pay_info,
|
||||
pay_info2,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
let identify_result = identify(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&pk_all_users,
|
||||
payment.clone(),
|
||||
payment2.clone(),
|
||||
pay_info,
|
||||
pay_info2,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
identify_result,
|
||||
IdentifyResult::DoubleSpendingPublicKeys(pk_user)
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_divisible_ecash);
|
||||
criterion_main!(benches);
|
||||
@@ -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(¶ms, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
let r = grp.random_scalar();
|
||||
let kappa = grp.gen2() * r
|
||||
+ verification_key.alpha
|
||||
+ attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
let r1 = grp.random_scalar();
|
||||
let r2 = grp.random_scalar();
|
||||
let phi = Phi(
|
||||
grp.gen1() * r1,
|
||||
params_u.get_ith_sigma(l as usize) * v + params_u.get_ith_eta(vv as usize) * r1,
|
||||
);
|
||||
|
||||
let pay_info = PayInfo { info: [78u8; 32] };
|
||||
let rr = hash_to_scalar(pay_info.info);
|
||||
let varphi = VarPhi(
|
||||
grp.gen1() * r2,
|
||||
(grp.gen1() * rr) * sk
|
||||
+ params_u.get_ith_theta(l as usize) * v
|
||||
+ params_u.get_ith_eta(vv as usize) * r2,
|
||||
);
|
||||
|
||||
// random value used to compute blinded bases
|
||||
let r_varsig1 = grp.random_scalar();
|
||||
let r_theta1 = grp.random_scalar();
|
||||
let r_varsig2 = grp.random_scalar();
|
||||
let r_theta2 = grp.random_scalar();
|
||||
let r_rr = grp.random_scalar();
|
||||
let r_ss = grp.random_scalar();
|
||||
let r_tt = grp.random_scalar();
|
||||
|
||||
// compute blinded bases
|
||||
let psi_g1 = params_u.get_psi_g1();
|
||||
let psi_g2 = params_u.get_psi_g2();
|
||||
let varsig_prime1 = params_u.get_ith_sigma(l as usize) + (psi_g1 * r_varsig1);
|
||||
let theta_prime1 = params_u.get_ith_theta(l as usize) + (psi_g1 * r_theta1);
|
||||
let varsig_prime2 =
|
||||
params_u.get_ith_sigma(l as usize + vv as usize - 1) + (psi_g1 * r_varsig2);
|
||||
let theta_prime2 =
|
||||
params_u.get_ith_theta(l as usize + vv as usize - 1) + (psi_g1 * r_theta2);
|
||||
let rr_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).rr + (psi_g1 * r_rr);
|
||||
let ss_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).ss + (psi_g1 * r_ss);
|
||||
let tt_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).tt + (psi_g2 * r_tt);
|
||||
|
||||
let rho1 = v.neg() * r_varsig1;
|
||||
let rho2 = v.neg() * r_theta1;
|
||||
let rho3 = r_rr * r_tt;
|
||||
|
||||
let pg_varsigpr1_delta = pairing(
|
||||
&varsig_prime1.to_affine(),
|
||||
¶ms_a.get_ith_delta((vv - 1) as usize).to_affine(),
|
||||
);
|
||||
let pg_psi0_delta = pairing(
|
||||
&psi_g1.to_affine(),
|
||||
¶ms_a.get_ith_delta((vv - 1) as usize).to_affine(),
|
||||
);
|
||||
let pg_varsigpr2_gen2 = pairing(&varsig_prime2.to_affine(), grp.gen2());
|
||||
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
|
||||
let pg_thetapr1_delta = pairing(
|
||||
&theta_prime1.to_affine(),
|
||||
¶ms_a.get_ith_delta((vv - 1) as usize).to_affine(),
|
||||
);
|
||||
let pg_thetapr2_gen2 = pairing(&theta_prime2.to_affine(), grp.gen2());
|
||||
let yy = params_u.get_sps_pk().get_yy();
|
||||
let pg_rrprime_yy = pairing(&rr_prime.to_affine(), &yy.to_affine());
|
||||
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
|
||||
let pg_ssprime_gen2 = pairing(&ss_prime.to_affine(), grp.gen2());
|
||||
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
|
||||
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
|
||||
let pg_varsigpr2_ww1 = pairing(&varsig_prime2.to_affine(), &ww1.to_affine());
|
||||
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
|
||||
let pg_thetapr2_ww2 = pairing(&theta_prime2.to_affine(), &ww2.to_affine());
|
||||
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
|
||||
let pg_gen1_zz = pairing(grp.gen1(), ¶ms_u.get_sps_pk().get_zz().to_affine());
|
||||
let pg_rr_tt = pairing(&rr_prime.to_affine(), &tt_prime.to_affine());
|
||||
let pg_rr_psi1 = pairing(&rr_prime.to_affine(), &psi_g2.to_affine());
|
||||
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &tt_prime.to_affine());
|
||||
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
|
||||
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
|
||||
|
||||
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
|
||||
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
|
||||
let pg_eq3 =
|
||||
pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 - pg_gen1_zz;
|
||||
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
|
||||
|
||||
let instance = SpendInstance {
|
||||
kappa,
|
||||
phi,
|
||||
varphi,
|
||||
rr,
|
||||
rr_prime,
|
||||
ss_prime: ss_prime,
|
||||
tt_prime: tt_prime,
|
||||
varsig_prime1,
|
||||
theta_prime1,
|
||||
pg_eq1,
|
||||
pg_eq2,
|
||||
pg_eq3,
|
||||
pg_eq4,
|
||||
psi_g1: *psi_g1,
|
||||
psi_g2: *psi_g2,
|
||||
pg_psi0_delta,
|
||||
pg_psi0_gen2,
|
||||
pg_psi0_yy,
|
||||
pg_psi0_ww1,
|
||||
pg_psi0_ww2,
|
||||
pg_rr_psi1,
|
||||
pg_psi0_tt,
|
||||
pg_psi0_psi1,
|
||||
};
|
||||
|
||||
let witness = SpendWitness {
|
||||
sk_u: SecretKeyUser { sk },
|
||||
v,
|
||||
r,
|
||||
r1,
|
||||
r2,
|
||||
r_varsig1,
|
||||
r_theta1,
|
||||
r_varsig2,
|
||||
r_theta2,
|
||||
r_rr,
|
||||
r_ss,
|
||||
r_tt,
|
||||
rho1,
|
||||
rho2,
|
||||
rho3,
|
||||
};
|
||||
|
||||
// compute the zk proof
|
||||
let zk_proof = SpendProof::construct(¶ms, &instance, &witness, &verification_key, vv);
|
||||
assert!(zk_proof.verify(¶ms, &instance, &verification_key, vv));
|
||||
|
||||
// do a to and from bytes check
|
||||
let zk_proof_bytes = zk_proof.to_bytes();
|
||||
let zk_proof2 = SpendProof::try_from(&zk_proof_bytes[..]).unwrap();
|
||||
assert_eq!(zk_proof, zk_proof2);
|
||||
}
|
||||
}
|
||||
@@ -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(¶ms, &instance, &witness);
|
||||
assert!(zk_proof.verify(¶ms, &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(),
|
||||
¶ms_a.get_ith_delta(k as usize).to_affine(),
|
||||
) + pairing(
|
||||
&payment1.phi.0.to_affine(),
|
||||
¶ms_a
|
||||
.get_etas_ith_jth_elem(payment1.vv as usize, k as usize)
|
||||
.to_affine(),
|
||||
);
|
||||
serial_numbers.insert(sn, k);
|
||||
}
|
||||
|
||||
// compute the serial numbers fo k2 in [0, V2-1]
|
||||
let mut k1 = 0;
|
||||
let mut k2 = 0;
|
||||
let mut duplicate_serial_numbers: Vec<(Gt, u64, u64)> = Default::default();
|
||||
for j in 0..payment2.vv {
|
||||
let sn = pairing(
|
||||
&payment2.phi.1.to_affine(),
|
||||
¶ms_a.get_ith_delta(j as usize).to_affine(),
|
||||
) + pairing(
|
||||
&payment2.phi.0.to_affine(),
|
||||
¶ms_a
|
||||
.get_etas_ith_jth_elem(payment2.vv as usize, j as usize)
|
||||
.to_affine(),
|
||||
);
|
||||
if !serial_numbers.contains_key(&sn) {
|
||||
serial_numbers.insert(sn, j);
|
||||
} else {
|
||||
k1 = *serial_numbers.get(&sn).unwrap() as u64;
|
||||
k2 = j.clone();
|
||||
break;
|
||||
}
|
||||
return Ok(IdentifyResult::NotADuplicatePayment);
|
||||
}
|
||||
|
||||
if pay_info1 == pay_info2 {
|
||||
Ok(IdentifyResult::DuplicatePayInfo(pay_info1))
|
||||
} else {
|
||||
let delta_k1 = params_a.get_ith_delta(k1 as usize);
|
||||
let delta_k2 = params_a.get_ith_delta(k2 as usize);
|
||||
let tt1 = pairing(&payment1.varphi.1.to_affine(), &delta_k1.to_affine())
|
||||
+ pairing(
|
||||
&payment1.varphi.0.to_affine(),
|
||||
¶ms_a
|
||||
.get_etas_ith_jth_elem(payment1.vv as usize, k1 as usize)
|
||||
.to_affine(),
|
||||
);
|
||||
let tt2 = pairing(&payment2.varphi.1.to_affine(), &delta_k2.to_affine())
|
||||
+ pairing(
|
||||
&payment2.varphi.0.to_affine(),
|
||||
¶ms_a
|
||||
.get_etas_ith_jth_elem(payment2.vv as usize, k2 as usize)
|
||||
.to_affine(),
|
||||
);
|
||||
|
||||
for pk_u in public_keys_u.iter() {
|
||||
let pg_pku_deltas = pairing(
|
||||
&pk_u.pk.to_affine(),
|
||||
&(delta_k1 * payment1.rr + delta_k2 * payment2.rr.neg()).to_affine(),
|
||||
);
|
||||
if tt1 - tt2 == pg_pku_deltas {
|
||||
return Ok(IdentifyResult::DoubleSpendingPublicKeys(pk_u.clone()));
|
||||
}
|
||||
}
|
||||
return Err(DivisibleEcashError::Identify(
|
||||
"A duplicate serial number was detected, the payinfo1 and payinfo2 are different, but we failed to identify the double-spending public key".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use bls12_381::pairing;
|
||||
use group::Curve;
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::scheme::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(¶ms, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
// KEY GENERATION FOR THE USER1
|
||||
let sk1 = grp.random_scalar();
|
||||
let sk_user1 = SecretKeyUser { sk: sk1 };
|
||||
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
|
||||
|
||||
// KEY GENERATION FOR THE USER2
|
||||
let sk2 = grp.random_scalar();
|
||||
let sk_user2 = SecretKeyUser { sk: sk2 };
|
||||
let pk_user2 = SecretKeyUser::public_key(&sk_user2, &grp);
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER1
|
||||
let (withdrawal_req1, req_info1) = withdrawal_request(¶ms, &sk_user1).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER1
|
||||
let mut partial_wallets1 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req1,
|
||||
pk_user1.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
)
|
||||
.unwrap();
|
||||
let partial_wallet1 = issue_verify(
|
||||
&grp,
|
||||
&auth_keypair.verification_key(),
|
||||
&sk_user1,
|
||||
&blind_signature,
|
||||
&req_info1,
|
||||
)
|
||||
.unwrap();
|
||||
partial_wallets1.push(partial_wallet1);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER1
|
||||
let mut wallet1 =
|
||||
aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
|
||||
|
||||
let pay_info1 = PayInfo { info: [67u8; 32] };
|
||||
let (payment1, wallet1) = wallet1
|
||||
.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false)
|
||||
.unwrap();
|
||||
|
||||
// SPEND VERIFICATION for USER1
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
let payment2 = payment1.clone();
|
||||
// SPEND VERIFICATION for the duplicate payment
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
let pay_info2 = pay_info1.clone();
|
||||
|
||||
let public_keys = HashSet::from([pk_user1, pk_user2]);
|
||||
let identify_result = identify(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&public_keys,
|
||||
payment1,
|
||||
payment2,
|
||||
pay_info1,
|
||||
pay_info2,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::DuplicatePayInfo(pay_info1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_payments_with_one_repeating_serial_number_but_different_pay_info() {
|
||||
let rng = thread_rng();
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
// KEY GENERATION FOR THE AUTHORITIES
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
// KEY GENERATION FOR THE USER1
|
||||
let sk1 = grp.random_scalar();
|
||||
let sk_user1 = SecretKeyUser { sk: sk1 };
|
||||
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut pk_all_users = HashSet::new();
|
||||
for i in 0..50 {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
pk_all_users.insert(pk_user);
|
||||
}
|
||||
pk_all_users.insert(pk_user1.clone());
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER1
|
||||
let (withdrawal_req1, req_info1) = withdrawal_request(¶ms, &sk_user1).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER1
|
||||
let mut partial_wallets1 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req1,
|
||||
pk_user1.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
)
|
||||
.unwrap();
|
||||
let partial_wallet1 = issue_verify(
|
||||
&grp,
|
||||
&auth_keypair.verification_key(),
|
||||
&sk_user1,
|
||||
&blind_signature,
|
||||
&req_info1,
|
||||
)
|
||||
.unwrap();
|
||||
partial_wallets1.push(partial_wallet1);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER1
|
||||
let mut wallet1 =
|
||||
aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
|
||||
|
||||
let pay_info1 = PayInfo { info: [67u8; 32] };
|
||||
let (payment1, new_wallet1) = wallet1
|
||||
.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false)
|
||||
.unwrap();
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = wallet1.l.get();
|
||||
wallet1.l.set(current_l - 1);
|
||||
|
||||
let pay_info2 = PayInfo { info: [52u8; 32] };
|
||||
let (payment2, wallet1) = wallet1
|
||||
.spend(¶ms, &verification_key, &sk_user1, &pay_info2, 10, false)
|
||||
.unwrap();
|
||||
|
||||
let identify_result = identify(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&pk_all_users,
|
||||
payment1,
|
||||
payment2,
|
||||
pay_info1,
|
||||
pay_info2,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
identify_result,
|
||||
IdentifyResult::DoubleSpendingPublicKeys(pk_user1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_payments_with_multiple_repeating_serial_number_but_different_pay_info() {
|
||||
let rng = thread_rng();
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
// KEY GENERATION FOR THE AUTHORITIES
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
// KEY GENERATION FOR THE USER1
|
||||
let sk1 = grp.random_scalar();
|
||||
let sk_user1 = SecretKeyUser { sk: sk1 };
|
||||
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut public_keys = HashSet::new();
|
||||
for i in 0..50 {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
public_keys.insert(pk_user);
|
||||
}
|
||||
public_keys.insert(pk_user1.clone());
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER1
|
||||
let (withdrawal_req1, req_info1) = withdrawal_request(¶ms, &sk_user1).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER1
|
||||
let mut partial_wallets1 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req1,
|
||||
pk_user1.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
)
|
||||
.unwrap();
|
||||
let partial_wallet1 = issue_verify(
|
||||
&grp,
|
||||
&auth_keypair.verification_key(),
|
||||
&sk_user1,
|
||||
&blind_signature,
|
||||
&req_info1,
|
||||
)
|
||||
.unwrap();
|
||||
partial_wallets1.push(partial_wallet1);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER1
|
||||
let mut wallet1 =
|
||||
aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
|
||||
|
||||
let pay_info1 = PayInfo { info: [67u8; 32] };
|
||||
let (payment1, new_wallet1) = wallet1
|
||||
.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false)
|
||||
.unwrap();
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = wallet1.l.get();
|
||||
wallet1.l.set(current_l - 7);
|
||||
|
||||
let pay_info2 = PayInfo { info: [52u8; 32] };
|
||||
let (payment2, wallet1) = wallet1
|
||||
.spend(¶ms, &verification_key, &sk_user1, &pay_info2, 10, false)
|
||||
.unwrap();
|
||||
|
||||
let identify_result = identify(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&public_keys,
|
||||
payment1,
|
||||
payment2,
|
||||
pay_info1,
|
||||
pay_info2,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
identify_result,
|
||||
IdentifyResult::DoubleSpendingPublicKeys(pk_user1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_if_two_different_payments() {
|
||||
let rng = thread_rng();
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
// KEY GENERATION FOR THE AUTHORITIES
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
// KEY GENERATION FOR THE USER1
|
||||
let sk1 = grp.random_scalar();
|
||||
let sk_user1 = SecretKeyUser { sk: sk1 };
|
||||
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
|
||||
|
||||
// KEY GENERATION FOR THE USER2
|
||||
let sk2 = grp.random_scalar();
|
||||
let sk_user2 = SecretKeyUser { sk: sk2 };
|
||||
let pk_user2 = SecretKeyUser::public_key(&sk_user2, &grp);
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER1
|
||||
let (withdrawal_req1, req_info1) = withdrawal_request(¶ms, &sk_user1).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER1
|
||||
let mut partial_wallets1 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req1,
|
||||
pk_user1.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
)
|
||||
.unwrap();
|
||||
let partial_wallet1 = issue_verify(
|
||||
&grp,
|
||||
&auth_keypair.verification_key(),
|
||||
&sk_user1,
|
||||
&blind_signature,
|
||||
&req_info1,
|
||||
)
|
||||
.unwrap();
|
||||
partial_wallets1.push(partial_wallet1);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER1
|
||||
let mut wallet1 =
|
||||
aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
|
||||
|
||||
let pay_info1 = PayInfo { info: [67u8; 32] };
|
||||
let (payment1, wallet1) = wallet1
|
||||
.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false)
|
||||
.unwrap();
|
||||
|
||||
// SPEND VERIFICATION for USER1
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER2
|
||||
let (withdrawal_req2, req_info2) = withdrawal_request(¶ms, &sk_user2).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER2
|
||||
let mut partial_wallets2 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req2,
|
||||
pk_user2.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
)
|
||||
.unwrap();
|
||||
let partial_wallet2 = issue_verify(
|
||||
&grp,
|
||||
&auth_keypair.verification_key(),
|
||||
&sk_user2,
|
||||
&blind_signature,
|
||||
&req_info2,
|
||||
)
|
||||
.unwrap();
|
||||
partial_wallets2.push(partial_wallet2);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER2
|
||||
let mut wallet2 =
|
||||
aggregate_wallets(&grp, &verification_key, &sk_user2, &partial_wallets2).unwrap();
|
||||
|
||||
let pay_info2 = PayInfo { info: [67u8; 32] };
|
||||
let (payment2, wallet2) = wallet2
|
||||
.spend(¶ms, &verification_key, &sk_user2, &pay_info2, 10, false)
|
||||
.unwrap();
|
||||
|
||||
// SPEND VERIFICATION for USER2
|
||||
assert!(payment2
|
||||
.spend_verify(¶ms, &verification_key, &pay_info2)
|
||||
.unwrap());
|
||||
|
||||
let public_keys = HashSet::from([pk_user1, pk_user2]);
|
||||
let identify_result = identify(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&public_keys,
|
||||
payment1,
|
||||
payment2,
|
||||
pay_info1,
|
||||
pay_info2,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::NotADuplicatePayment);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
¶ms_a.get_ith_delta((vv - 1) as usize).to_affine(),
|
||||
);
|
||||
let pg_psi0_delta = pairing(
|
||||
&psi_g1.to_affine(),
|
||||
¶ms_a.get_ith_delta((vv - 1) as usize).to_affine(),
|
||||
);
|
||||
let pg_varsigpr2_gen2 = pairing(&varsig_prime2.to_affine(), grp.gen2());
|
||||
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
|
||||
let pg_thetapr1_delta = pairing(
|
||||
&theta_prime1.to_affine(),
|
||||
¶ms_a.get_ith_delta((vv - 1) as usize).to_affine(),
|
||||
);
|
||||
let pg_thetapr2_gen2 = pairing(&theta_prime2.to_affine(), grp.gen2());
|
||||
let yy = params_u.get_sps_pk().get_yy();
|
||||
let pg_rrprime_yy = pairing(&rr_prime.to_affine(), &yy.to_affine());
|
||||
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
|
||||
let pg_ssprime_gen2 = pairing(&ss_prime.to_affine(), grp.gen2());
|
||||
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
|
||||
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
|
||||
let pg_varsigpr2_ww1 = pairing(&varsig_prime2.to_affine(), &ww1.to_affine());
|
||||
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
|
||||
let pg_thetapr2_ww2 = pairing(&theta_prime2.to_affine(), &ww2.to_affine());
|
||||
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
|
||||
let pg_gen1_zz = pairing(grp.gen1(), ¶ms_u.get_sps_pk().get_zz().to_affine());
|
||||
let pg_rr_tt = pairing(&rr_prime.to_affine(), &tt_prime.to_affine());
|
||||
let pg_rr_psi1 = pairing(&rr_prime.to_affine(), &psi_g2.to_affine());
|
||||
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &tt_prime.to_affine());
|
||||
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
|
||||
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
|
||||
|
||||
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
|
||||
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
|
||||
let pg_eq3 =
|
||||
pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 + pg_gen1_zz.neg();
|
||||
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
|
||||
|
||||
let instance = SpendInstance {
|
||||
kappa,
|
||||
phi,
|
||||
varphi,
|
||||
rr,
|
||||
rr_prime,
|
||||
ss_prime,
|
||||
tt_prime,
|
||||
varsig_prime1,
|
||||
theta_prime1,
|
||||
pg_eq1,
|
||||
pg_eq2,
|
||||
pg_eq3,
|
||||
pg_eq4,
|
||||
psi_g1: *psi_g1,
|
||||
psi_g2: *psi_g2,
|
||||
pg_psi0_delta,
|
||||
pg_psi0_gen2,
|
||||
pg_psi0_yy,
|
||||
pg_psi0_ww1,
|
||||
pg_psi0_ww2,
|
||||
pg_rr_psi1,
|
||||
pg_psi0_tt,
|
||||
pg_psi0_psi1,
|
||||
};
|
||||
|
||||
let witness = SpendWitness {
|
||||
sk_u: sk_user.clone(),
|
||||
v: self.v,
|
||||
r: sign_blinding_factor,
|
||||
r1,
|
||||
r2,
|
||||
r_varsig1,
|
||||
r_theta1,
|
||||
r_varsig2,
|
||||
r_theta2,
|
||||
r_rr,
|
||||
r_ss,
|
||||
r_tt,
|
||||
rho1,
|
||||
rho2,
|
||||
rho3,
|
||||
};
|
||||
|
||||
// compute the zk proof
|
||||
let zk_proof = SpendProof::construct(params, &instance, &witness, &verification_key, vv);
|
||||
|
||||
// output pay and updated wallet
|
||||
let pay = Payment {
|
||||
kappa,
|
||||
sig: signature_prime,
|
||||
phi,
|
||||
varphi,
|
||||
varsig_prime1,
|
||||
varsig_prime2,
|
||||
theta_prime1,
|
||||
theta_prime2,
|
||||
rr_prime,
|
||||
ss_prime,
|
||||
tt_prime,
|
||||
rr,
|
||||
zk_proof,
|
||||
vv,
|
||||
};
|
||||
|
||||
// The number of samples collected by the benchmark process is way higher than the
|
||||
// MAX_WALLET_VALUE we ever consider. Thus, we would execute the spending too many times
|
||||
// and the initial condition at the top of this function will crush. Thus, we need a
|
||||
// benchmark flag to signal that we don't want to increase the spending couter but only
|
||||
// care about the function performance.
|
||||
if !bench_flag {
|
||||
let current_l = self.l();
|
||||
self.l.set(current_l + vv);
|
||||
}
|
||||
Ok((pay, self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Payment {
|
||||
pub fn spend_verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
pay_info: &PayInfo,
|
||||
) -> Result<bool> {
|
||||
if bool::from(self.sig.0.is_identity()) {
|
||||
return Err(DivisibleEcashError::Spend(
|
||||
"The element h of the signature equals the identity".to_string(),
|
||||
));
|
||||
}
|
||||
let grp = params.get_grp();
|
||||
let params_a = params.get_params_a();
|
||||
let params_u = params.get_params_u();
|
||||
|
||||
if !check_bilinear_pairing(
|
||||
&self.sig.0.to_affine(),
|
||||
&G2Prepared::from(self.kappa.to_affine()),
|
||||
&self.sig.1.to_affine(),
|
||||
grp.prepared_miller_g2(),
|
||||
) {
|
||||
return Err(DivisibleEcashError::Spend(
|
||||
"The bilinear check for kappa failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if bool::from(self.sig.0.is_identity()) {
|
||||
return Err(DivisibleEcashError::Spend(
|
||||
"The element h of the signature on l equals the identity".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// verify integrity of R
|
||||
if !(self.rr == hash_to_scalar(pay_info.info)) {
|
||||
return Err(DivisibleEcashError::Spend(
|
||||
"Integrity of R does not hold".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
//TODO: verify whether payinfo contains merchent's identifier
|
||||
|
||||
let psi_g1 = params_u.get_psi_g1();
|
||||
let psi_g2 = params_u.get_psi_g2();
|
||||
let pg_varsigpr1_delta = pairing(
|
||||
&self.varsig_prime1.to_affine(),
|
||||
¶ms_a.get_ith_delta((self.vv - 1) as usize).to_affine(),
|
||||
);
|
||||
let pg_psi0_delta = pairing(
|
||||
&psi_g1.to_affine(),
|
||||
¶ms_a.get_ith_delta((self.vv - 1) as usize).to_affine(),
|
||||
);
|
||||
let pg_varsigpr2_gen2 = pairing(&self.varsig_prime2.to_affine(), grp.gen2());
|
||||
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
|
||||
let pg_thetapr1_delta = pairing(
|
||||
&self.theta_prime1.to_affine(),
|
||||
¶ms_a.get_ith_delta((self.vv - 1) as usize).to_affine(),
|
||||
);
|
||||
let pg_thetapr2_gen2 = pairing(&self.theta_prime2.to_affine(), grp.gen2());
|
||||
let yy = params_u.get_sps_pk().get_yy();
|
||||
let pg_rrprime_yy = pairing(&self.rr_prime.to_affine(), &yy.to_affine());
|
||||
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
|
||||
let pg_ssprime_gen2 = pairing(&self.ss_prime.to_affine(), grp.gen2());
|
||||
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
|
||||
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
|
||||
let pg_varsigpr2_ww1 = pairing(&self.varsig_prime2.to_affine(), &ww1.to_affine());
|
||||
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
|
||||
let pg_thetapr2_ww2 = pairing(&self.theta_prime2.to_affine(), &ww2.to_affine());
|
||||
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
|
||||
let pg_gen1_zz = pairing(grp.gen1(), ¶ms_u.get_sps_pk().get_zz().to_affine());
|
||||
let pg_rr_tt = pairing(&self.rr_prime.to_affine(), &self.tt_prime.to_affine());
|
||||
let pg_rr_psi1 = pairing(&self.rr_prime.to_affine(), &psi_g2.to_affine());
|
||||
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &self.tt_prime.to_affine());
|
||||
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
|
||||
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
|
||||
|
||||
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
|
||||
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
|
||||
let pg_eq3 =
|
||||
pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 + pg_gen1_zz.neg();
|
||||
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
|
||||
|
||||
let instance = SpendInstance {
|
||||
kappa: self.kappa,
|
||||
phi: self.phi,
|
||||
varphi: self.varphi,
|
||||
rr: self.rr,
|
||||
rr_prime: self.rr_prime,
|
||||
ss_prime: self.ss_prime,
|
||||
tt_prime: self.tt_prime,
|
||||
varsig_prime1: self.varsig_prime1,
|
||||
theta_prime1: self.theta_prime1,
|
||||
pg_eq1,
|
||||
pg_eq2,
|
||||
pg_eq3,
|
||||
pg_eq4,
|
||||
psi_g1: *psi_g1,
|
||||
psi_g2: *psi_g2,
|
||||
pg_psi0_delta,
|
||||
pg_psi0_gen2,
|
||||
pg_psi0_yy,
|
||||
pg_psi0_ww1,
|
||||
pg_psi0_ww2,
|
||||
pg_rr_psi1,
|
||||
pg_psi0_tt,
|
||||
pg_psi0_psi1,
|
||||
};
|
||||
|
||||
Ok(self
|
||||
.zk_proof
|
||||
.verify(¶ms, &instance, &verification_key, self.vv))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::scheme::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(¶ms, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -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(¶ms.get_grp()),
|
||||
};
|
||||
|
||||
let witness = WithdrawalReqWitness {
|
||||
attributes,
|
||||
com_opening,
|
||||
pc_coms_openings: pc_coms_openings.clone(),
|
||||
};
|
||||
|
||||
let zk_proof = WithdrawalReqProof::construct(¶ms, &instance, &witness);
|
||||
|
||||
let req = WithdrawalRequest {
|
||||
com_hash,
|
||||
com: commitment,
|
||||
pc_coms: pc_coms.clone(),
|
||||
zk_proof,
|
||||
};
|
||||
|
||||
let req_info = RequestInfo {
|
||||
com_hash,
|
||||
pc_coms_openings,
|
||||
v,
|
||||
};
|
||||
|
||||
Ok((req, req_info))
|
||||
}
|
||||
|
||||
pub fn issue(
|
||||
params: &Parameters,
|
||||
req: &WithdrawalRequest,
|
||||
pk_u: PublicKeyUser,
|
||||
sk_a: &SecretKeyAuth,
|
||||
) -> Result<BlindedSignature> {
|
||||
let h = hash_g1(req.com.to_bytes());
|
||||
if !(h == req.com_hash) {
|
||||
return Err(DivisibleEcashError::WithdrawalRequestVerification(
|
||||
"Failed to verify the commitment hash".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// verify zk proof
|
||||
let instance = WithdrawalReqInstance {
|
||||
com: req.com,
|
||||
h: req.com_hash,
|
||||
pc_coms: req.pc_coms.clone(),
|
||||
pk_user: pk_u,
|
||||
};
|
||||
if !req.zk_proof.verify(¶ms, &instance) {
|
||||
return Err(DivisibleEcashError::WithdrawalRequestVerification(
|
||||
"Failed to verify the proof of knowledge".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let sig = req
|
||||
.pc_coms
|
||||
.iter()
|
||||
.zip(sk_a.ys.iter())
|
||||
.map(|(pc, yi)| pc * yi)
|
||||
.chain(std::iter::once(h * sk_a.x))
|
||||
.sum();
|
||||
|
||||
Ok(BlindedSignature(h, sig))
|
||||
}
|
||||
|
||||
pub fn issue_verify(
|
||||
params: &GroupParameters,
|
||||
vk_auth: &VerificationKeyAuth,
|
||||
sk_user: &SecretKeyUser,
|
||||
blind_signature: &BlindedSignature,
|
||||
req_info: &RequestInfo,
|
||||
) -> Result<PartialWallet> {
|
||||
// Parse the blinded signature
|
||||
let h = blind_signature.0;
|
||||
let c = blind_signature.1;
|
||||
|
||||
// Verify the integrity of the response from the authority
|
||||
if !(req_info.com_hash == h) {
|
||||
return Err(DivisibleEcashError::IssuanceVfy(
|
||||
"Integrity verification failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Unblind the blinded signature on the partial wallet
|
||||
let blinding_removers = vk_auth
|
||||
.beta_g1
|
||||
.iter()
|
||||
.zip(req_info.pc_coms_openings.iter())
|
||||
.map(|(beta, opening)| beta * opening)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let unblinded_c = c - blinding_removers;
|
||||
|
||||
let attr = vec![sk_user.sk, req_info.v];
|
||||
|
||||
let signed_attributes = attr
|
||||
.iter()
|
||||
.zip(vk_auth.beta_g2.iter())
|
||||
.map(|(attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
// Verify the signature correctness on the wallet share
|
||||
if !check_bilinear_pairing(
|
||||
&h.to_affine(),
|
||||
&G2Prepared::from((vk_auth.alpha + signed_attributes).to_affine()),
|
||||
&unblinded_c.to_affine(),
|
||||
params.prepared_miller_g2(),
|
||||
) {
|
||||
return Err(DivisibleEcashError::IssuanceVfy(
|
||||
"Verification of wallet share failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(PartialWallet {
|
||||
sig: Signature(h, unblinded_c),
|
||||
v: req_info.v,
|
||||
idx: None,
|
||||
})
|
||||
}
|
||||
@@ -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(¶ms, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
// KEY GENERATION FOR THE USER
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = SecretKeyUser::public_key(&sk_user, &grp);
|
||||
|
||||
// WITHDRAWAL REQUEST
|
||||
let (withdrawal_req, req_info) = withdrawal_request(¶ms, &sk_user)?;
|
||||
|
||||
// ISSUE PARTIAL WALLETS
|
||||
let mut partial_wallets = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req,
|
||||
pk_user.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
)?;
|
||||
let partial_wallet = issue_verify(
|
||||
&grp,
|
||||
&auth_keypair.verification_key(),
|
||||
&sk_user,
|
||||
&blind_signature,
|
||||
&req_info,
|
||||
)?;
|
||||
partial_wallets.push(partial_wallet);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET
|
||||
let mut wallet = aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets)?;
|
||||
|
||||
let pay_info = PayInfo { info: [67u8; 32] };
|
||||
let (payment, wallet) =
|
||||
wallet.spend(¶ms, &verification_key, &sk_user, &pay_info, 10, false)?;
|
||||
|
||||
// SPEND VERIFICATION
|
||||
assert!(payment
|
||||
.spend_verify(¶ms, &verification_key, &pay_info)
|
||||
.unwrap());
|
||||
let payment_bytes = payment.to_bytes();
|
||||
let payment2 = Payment::try_from(&payment_bytes[..]).unwrap();
|
||||
assert_eq!(payment, payment2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user