Compare commits

...

134 Commits

Author SHA1 Message Date
aniampio 9b01d4e24e run cargo fmt 2023-12-13 13:34:31 +00:00
aniampio eb296b175e Remove date_timestamp from the expiration date struct 2023-12-13 13:33:59 +00:00
aniampio d51ab24f7e Update doc for the setup function 2023-12-13 12:51:29 +00:00
aniampio cdbd0396eb Remove the Hashset import 2023-12-11 18:13:34 +00:00
aniampio c800d8c24f Replace the bls12_381 dependency 2023-12-11 18:09:19 +00:00
aniampio 12056f0e70 Code cleaning + cargo fmt 2023-12-11 17:03:57 +00:00
aniampio c3ec669b52 Run cargo fmt 2023-12-11 16:39:54 +00:00
aniampio 84259e7abf Code cleaning 2023-12-11 16:37:53 +00:00
aniampio 8e4bf3ae4f Fix indexing to make kappa_e bilinear check work 2023-12-08 19:04:57 +00:00
aniampio 3292667033 Add docs 2023-12-08 14:21:38 +00:00
aniampio 33121a707f Add kappa and kappa_e into the zk proof 2023-12-08 13:10:38 +00:00
aniampio 0a655aa396 All tests pass, but not all checks yet implemented 2023-12-08 11:28:47 +00:00
aniampio 455e21d3fa Update the zk-proof for the spend phase; not all checks added 2023-12-07 23:27:48 +00:00
aniampio 3a7b7ba55b Fix bilinear check on kappa_k 2023-12-07 14:41:59 +00:00
aniampio 90654710c1 Updates to fix the byte conversation of the payment 2023-12-07 08:55:47 +00:00
aniampio 1ca94fd034 Fix the timestamps conversations 2023-11-30 17:21:12 +00:00
aniampio 397ea41d72 Fix the expiration date casting to timestamp 2023-11-30 17:13:51 +00:00
aniampio 574325265b Updates in the withdrawal and setup phase 2023-11-29 14:45:45 +00:00
aniampio 7cf49aef03 Run cargo fmt 2023-11-24 17:37:10 +00:00
aniampio 9ed51634d9 Remove old bench file from cargo 2023-11-24 17:36:33 +00:00
aniampio 8db9936f93 Split benchmarks into separate files 2023-11-24 17:35:52 +00:00
aniampio 86b293c08b Remove print statements 2023-11-24 17:22:04 +00:00
aniampio f2a695b17d Update the aggregate coin signatures 2023-11-24 16:28:07 +00:00
aniampio 74c88d726d Update aggregate expiration signatures 2023-11-24 16:26:38 +00:00
aniampio abbd773840 Fix breaking aggregate_expiration_date test 2023-11-24 16:21:39 +00:00
aniampio e21ccb7f07 Update the sign expiration date function 2023-11-24 15:13:49 +00:00
aniampio 9a9c48e382 Add docs 2023-11-24 14:54:06 +00:00
aniampio 8773ef7d69 Add documentation 2023-11-24 14:23:09 +00:00
aniampio 9a051dfd2a Alternative solution for parallel verification 2023-11-24 13:41:17 +00:00
aniampio a16f2a1c7b Reverse changes to make the aggregate function test pass 2023-11-24 12:44:01 +00:00
aniampio c3485fc5ae Speed up the issuance of coin indices signatures with par_iter 2023-11-24 12:36:55 +00:00
aniampio 80a4cec616 Add benchmarks and speed up verification with par_iter 2023-11-24 12:30:40 +00:00
aniampio 244e6d225e Run cargo fmt 2023-11-24 12:02:28 +00:00
aniampio 8ef59ccfe3 Add verification for the coin signatures 2023-11-23 18:36:45 +00:00
aniampio 3763f9569e Add function to sign coin indices 2023-11-23 17:29:42 +00:00
aniampio fccd85fa3f Replace for loop with par_iter for signing expiration date 2023-11-23 17:26:11 +00:00
aniampio fde4194820 Update benchmarks 2023-11-22 17:06:11 +00:00
aniampio 88cc8e49fb Add benchmarks for aggregation 2023-11-22 14:54:41 +00:00
aniampio e5d783c6c4 Speed up the verification function and add benchmarks 2023-11-22 14:38:43 +00:00
aniampio 76055c295b Merge indices, verification keys and corresponding signatures into one large vector 2023-11-22 13:23:29 +00:00
aniampio e5ed1313ca Optimise and parallelise the signature aggregation function + add tests 2023-11-22 12:03:52 +00:00
aniampio 07783478be Add check of the hash 2023-11-21 20:43:30 +00:00
aniampio b6f540d69f Add documentation to the function 2023-11-21 20:41:37 +00:00
aniampio 23f7b4c88d Add verification function for a single expiration date signature 2023-11-21 20:36:35 +00:00
aniampio efe8a16050 Add documentation to aggregate_expiration_signatures 2023-11-21 18:34:19 +00:00
aniampio a977af65f9 Clean and optimise the aggregate_expiration_signatures function 2023-11-21 17:37:55 +00:00
aniampio 45739442d6 Add documentation to sign_expiration_date 2023-11-21 17:23:26 +00:00
aniampio baf10e9e98 Clean and optimise the sign_expiration_date function 2023-11-21 17:16:18 +00:00
aniampio 044dae4992 Add the aggregation of expiration date signatures 2023-11-21 17:10:31 +00:00
aniampio 78f01dbe9a Add function to sign an expiration date 2023-11-21 11:23:45 +00:00
aniampio bb35e67106 Remove unused function for gammas and replace with a generic one 2023-11-20 11:26:58 +00:00
aniampio 59260400b6 Move generate payinfo as implementation of the struct 2023-10-24 15:11:58 +01:00
aniampio ea4980c6e2 Improve function performance and readability 2023-10-24 15:02:19 +01:00
aniampio 4a54fd93f6 Add function generating the payinfo 2023-10-24 14:49:56 +01:00
aniampio 5b66701c5d Optimize the identification function 2023-10-17 14:13:23 +01:00
aniampio 3a8b99db10 Add byte calculation for divisible ecash 2023-08-07 21:19:25 +01:00
aniampio 6e7cf42831 Add Wallet, Partial Wallet and Payment to and from bytes converstion 2023-08-07 11:47:38 +01:00
aniampio 0e42f977ee Increase benchmark duration 2023-01-09 14:11:49 +00:00
aniampio ef379cdeb3 Update in divisible ecash benchmark 2022-09-27 13:13:01 +01:00
aniampio 4886312e43 Benchmarks: change the number of public keys 2022-08-31 12:51:04 +03:00
aniampio 3a152366d2 Update benchmarks 2022-08-31 12:39:41 +03:00
aniampio d920bcb2f4 Update compact benchmarks 2022-08-31 11:55:15 +03:00
aniampio 268a1817ab Remove spend verification from the identify function 2022-08-10 18:57:00 +01:00
aniampio f5c493500f Update benchmarks 2022-08-03 19:58:50 +01:00
aniampio 7533f304ff Update benchmarks 2022-08-03 18:58:52 +01:00
aniampio 755d4f2388 Cleaning warnings 2022-08-03 16:03:07 +01:00
aniampio 6884395daa Update for compact e-cash - remove requirement for security tag 2022-08-03 15:46:15 +01:00
aniampio 5299b94f9e Add pairing and exponent benchmarks 2022-07-22 17:11:05 +01:00
aniampio f701787802 Update benchmarks 2022-07-20 14:19:23 +01:00
aniampio 0b58e0ae24 Small updates and code cleaning 2022-07-20 12:19:19 +01:00
aniampio 03226d1487 Add verification of the payments inside identify; speed up the identify checks 2022-07-19 13:18:42 +01:00
aniampio 534f8c6b99 Update the spend function for compact ecash - multi spend 2022-07-15 01:45:58 +01:00
aniampio e544a6d2fa Add all cases for identification 2022-07-15 00:34:27 +01:00
aniampio d696535da5 Add duplicate payinfo check for identify 2022-07-14 23:33:15 +01:00
aniampio 7e444b7c42 Update spend function in the compact ecash 2022-07-14 21:12:26 +01:00
aniampio 0d377d98a7 Remove unused constant 2022-07-06 00:08:42 +01:00
aniampio 5bdbee6dae Fix in the identify function 2022-06-30 18:14:59 +01:00
aniampio 014374e33e Add tests to indetify protocol 2022-06-28 15:35:07 +01:00
aniampio 9adb4fbbbc Add test for identification - case of no double spending 2022-06-23 16:56:59 +01:00
aniampio 5293766856 Add aggregation and e2e test 2022-06-23 14:49:01 +01:00
aniampio a40bb3ae4f Fix the eq 15 for the spend proof 2022-06-09 13:08:21 +01:00
aniampio 226c37cfea Update the verification function for structure preserving signature 2022-06-07 22:17:12 +01:00
aniampio ea8f36dfd2 Add draft of signature verification function 2022-05-26 13:19:41 +01:00
aniampio b466cb6b3d Move the index for signature retrival 2022-05-26 13:12:05 +01:00
aniampio bb0328137a 3 out of 4 pairing tests in the zk proof pass 2022-05-26 13:06:44 +01:00
aniampio e07974ad2b Updates for the assertion and pow function 2022-05-25 17:06:32 +01:00
aniampio e8bb96b7d0 Add assertion check 2022-05-25 14:57:44 +01:00
aniampio 9bd06dbfcc Fix signatures tau_l 2022-05-25 00:28:52 +01:00
aniampio 055b10f7f2 Shifting indices in setup - eq 4 test still passing 2022-05-24 23:53:58 +01:00
aniampio 44ca339ee5 Add eq checks 2022-05-24 23:31:40 +01:00
aniampio b6a43787b3 Add bilinear equations into the zk proof; the last eq passes the tests 2022-05-24 21:16:43 +01:00
aniampio 9cb22d9bde Define spend instance and witness structs 2022-05-12 20:57:16 +01:00
aniampio 68485f8998 Add spend and spend verification functions; fix breaking test for proof of withdrawal 2022-05-10 13:21:53 +01:00
aniampio 9c22c082c0 Draft of the spend function 2022-05-02 17:28:08 +01:00
aniampio 28d7400304 Add issuance and issuance verification - but one of the tests is failing 2022-04-28 13:05:54 +01:00
aniampio 056d189e0e Add key generation functions 2022-04-25 17:12:09 +03:00
aniampio b76eab2363 Update the structure preserving signature to work in the setup function as well 2022-04-25 15:39:10 +03:00
aniampio b8a2ac5719 Rename the package 2022-04-20 13:45:51 +03:00
aniampio c861e40047 Run cargo fmt 2022-04-20 09:58:46 +03:00
aniampio 20426d001e Add explanaition of the benchmark flag 2022-04-20 09:56:28 +03:00
aniampio 2eed9fd247 Add benchmarks 2022-04-19 22:46:58 +03:00
aniampio a1887356dc Run cargo fmt 2022-04-19 19:33:08 +03:00
aniampio ef2420f847 Code cleaning, part 2 2022-04-19 19:32:50 +03:00
aniampio 6f26475055 Code cleaning 2022-04-19 19:11:23 +03:00
aniampio b9bf6e4521 Run cargo fmt 2022-04-19 18:31:50 +03:00
aniampio 7e392ff6c3 Rename capital letter variables 2022-04-19 18:31:21 +03:00
aniampio 9e834ebaef Run cargo fmt 2022-04-19 16:56:27 +03:00
aniampio f96a4ffed3 Update get range proof signature function to return an error 2022-04-19 16:55:57 +03:00
aniampio 8e9c0d2b0d Add into the zk proof the proof that l is within the range 2022-04-19 16:35:16 +03:00
aniampio 33c356b3ec Work in progress: structure preserving signature scheme 2022-04-04 15:24:16 +01:00
aniampio c902e8eaa5 Add divisible ecash crate to the main Cargo 2022-04-01 16:24:56 +01:00
aniampio 1dc8b5894f Add struct for the divisible ecash crate 2022-04-01 16:22:25 +01:00
aniampio ad285af0fa Add test for succesfull identification 2022-04-01 12:45:57 +01:00
aniampio 6621e5c72f Run cargo fmt 2022-04-01 10:57:51 +01:00
aniampio 969a93cf80 Fix the issue with two gamma equations 2022-04-01 10:55:26 +01:00
aniampio f52ee0431c Commit cause need to change branch 2022-03-30 10:08:27 +01:00
aniampio a41cfd47a4 Add S and T into the zk proof for spend 2022-03-28 19:00:42 +01:00
aniampio 0b098687e1 Remove the spend file 2022-03-28 17:47:17 +01:00
aniampio 09709dd8e4 Move signature related structs and functions to utils 2022-03-28 17:45:39 +01:00
aniampio ef0867a7e7 Move spend and spec vfy as functions associated with wallet and payment 2022-03-28 17:36:40 +01:00
aniampio bfa7754ea6 Add identify function 2022-03-28 15:46:03 +01:00
aniampio a4377bf1a6 Add spendVfy function 2022-03-28 13:26:29 +01:00
aniampio 9e30f9a346 Add verification for the spend zkproof and first tests 2022-03-27 14:42:40 +01:00
aniampio 7878895651 Start the spend function and zkproof for spend 2022-03-25 13:25:33 +00:00
aniampio fcc940a4d6 Add new aggregation function and struct for the aggregated wallet 2022-03-24 20:14:10 +00:00
aniampio 036092bb9c Add aggregation into e2e tests 2022-03-23 18:30:19 +00:00
aniampio dcde42c45d Copy and adjust aggregation of verification keys from coconut 2022-03-23 17:34:39 +00:00
aniampio 5b5bbc2a3d Fix bug in the issuance verification 2022-03-23 16:43:59 +00:00
aniampio 0c1cdc4b63 Code cleaning 2022-03-23 16:32:08 +00:00
aniampio 953c1eddd9 Fix a bug in the commitment computation 2022-03-23 15:50:30 +00:00
aniampio 6ae0913aa1 Copy polynomial and ttpcode from Coconut; add first test; add keypair structures 2022-03-22 18:14:13 +00:00
aniampio 92834ff9b8 Add zk proof for withdrawal request 2022-03-22 17:27:01 +00:00
aniampio bfba92de97 Add initial functions 2022-03-21 18:09:05 +00:00
aniampio d37b63f788 Add template for the compact ecash crate 2022-03-21 09:01:12 +00:00
56 changed files with 11050 additions and 71 deletions
Vendored
BIN
View File
Binary file not shown.
Generated
+82 -9
View File
@@ -373,11 +373,24 @@ dependencies = [
"digest 0.9.0",
"ff 0.10.1",
"group 0.10.0",
"pairing",
"pairing 0.20.0",
"rand_core 0.6.3",
"subtle 2.4.1",
]
[[package]]
name = "bls12_381"
version = "0.6.0"
dependencies = [
"digest 0.9.0",
"ff 0.11.0",
"group 0.11.0",
"pairing 0.21.0",
"rand_core 0.6.3",
"subtle 2.4.1",
"zeroize",
]
[[package]]
name = "bs58"
version = "0.4.0"
@@ -824,7 +837,7 @@ dependencies = [
name = "credentials"
version = "0.1.0"
dependencies = [
"bls12_381",
"bls12_381 0.5.0",
"coconut-interface",
"crypto",
"network-defaults",
@@ -1058,9 +1071,9 @@ dependencies = [
[[package]]
name = "curve25519-dalek"
version = "3.2.1"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0"
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
dependencies = [
"byteorder",
"digest 0.9.0",
@@ -1977,6 +1990,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89"
dependencies = [
"byteorder",
"ff 0.11.0",
"rand_core 0.6.3",
"subtle 2.4.1",
@@ -3118,12 +3132,62 @@ dependencies = [
"version-checker",
]
[[package]]
name = "nym_compact_ecash"
version = "0.1.0"
dependencies = [
"bls12_381 0.6.0",
"bs58",
"chrono",
"criterion",
"digest 0.9.0",
"ff 0.11.0",
"group 0.11.0",
"itertools",
"rand 0.8.5",
"rayon",
"sha2",
"thiserror",
]
[[package]]
name = "nym_offline_divisible_ecash"
version = "0.1.0"
dependencies = [
"bls12_381 0.6.0",
"bs58",
"criterion",
"digest 0.9.0",
"ff 0.11.0",
"group 0.11.0",
"itertools",
"rand 0.8.5",
"sha2",
"thiserror",
]
[[package]]
name = "nym_online_divisible_ecash"
version = "0.1.0"
dependencies = [
"bls12_381 0.5.0",
"bs58",
"criterion",
"digest 0.9.0",
"ff 0.10.1",
"group 0.10.0",
"itertools",
"rand 0.8.5",
"sha2",
"thiserror",
]
[[package]]
name = "nymcoconut"
version = "0.5.0"
dependencies = [
"bincode",
"bls12_381",
"bls12_381 0.5.0",
"bs58",
"criterion",
"digest 0.9.0",
@@ -3351,6 +3415,15 @@ dependencies = [
"group 0.10.0",
]
[[package]]
name = "pairing"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42"
dependencies = [
"group 0.11.0",
]
[[package]]
name = "parity-scale-codec"
version = "2.3.1"
@@ -6403,9 +6476,9 @@ checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]]
name = "x25519-dalek"
version = "1.2.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077"
checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f"
dependencies = [
"curve25519-dalek",
"rand_core 0.5.1",
@@ -6420,9 +6493,9 @@ checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
[[package]]
name = "zeroize"
version = "1.3.0"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
dependencies = [
"zeroize_derive",
]
+3
View File
@@ -33,6 +33,9 @@ members = [
"common/network-defaults",
"common/nonexhaustive-delayqueue",
"common/nymcoconut",
"common/nym_offline_compact_ecash",
"common/nym_offline_divisible_ecash",
"common/nym_online_divisible_ecash",
"common/nymsphinx",
"common/nymsphinx/acknowledgements",
"common/nymsphinx/addressing",
@@ -0,0 +1,50 @@
[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]
chrono = "0.4.19"
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381.git", branch = "gt-serialisation", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"]}
itertools = "0.10"
digest = "0.9"
rand = "0.8"
thiserror = "1.0"
sha2 = "0.9"
bs58 = "0.4.0"
rayon = "1.5.0"
[dev-dependencies]
criterion = { version = "0.3", features = ["html_reports"] }
[dependencies.ff]
version = "0.11"
default-features = false
[dependencies.group]
version = "0.11"
default-features = false
[[bench]]
name = "benchmarks_group_operations"
path = "benches/benchmarks_group_operations.rs"
harness = false
[[bench]]
name = "benchmarks_expiration_date_signatures"
path = "benches/benchmarks_expiration_date_signatures.rs"
harness = false
[[bench]]
name = "benchmarks_coin_indices_signatures"
path = "benches/benchmarks_coin_indices_signatures.rs"
harness = false
[[bench]]
name = "benchmarks_ecash_e2e"
path = "benches/benchmarks_ecash_e2e.rs"
harness = false
@@ -0,0 +1,115 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use criterion::{criterion_group, criterion_main, Criterion};
use nym_compact_ecash::constants;
use nym_compact_ecash::scheme::keygen::SecretKeyAuth;
use nym_compact_ecash::setup::{
aggregate_indices_signatures, setup, sign_coin_indices, verify_coin_indices_signatures,
PartialCoinIndexSignature,
};
use nym_compact_ecash::{aggregate_verification_keys, ttp_keygen, VerificationKeyAuth};
fn bench_coin_signing(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-sign-verify-coin-signing");
let L = 32;
let params = setup(L);
let authorities_keypairs = ttp_keygen(&params.grp(), 2, 3).unwrap();
let indices: [u64; 3] = [1, 2, 3];
// Pick one authority to do the signing
let sk_i_auth = authorities_keypairs[0].secret_key();
let vk_i_auth = authorities_keypairs[0].verification_key();
// list of verification keys of each authority
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
// the global master verification key
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
let partial_signatures = sign_coin_indices(&params, &verification_key, &sk_i_auth);
// ISSUING AUTHORITY BENCHMARK: issue a set of (partial) signatures for coin indices
group.bench_function(
&format!("[IssuingAuthority] sign_coin_indices_L_{}", params.L()),
|b| b.iter(|| sign_coin_indices(&params, &verification_key, &sk_i_auth)),
);
// CLIENT: verify the correctness of the (partial)) signatures for coin indices
assert!(verify_coin_indices_signatures(
&params,
&verification_key,
&vk_i_auth,
&partial_signatures
)
.is_ok());
group.bench_function(
&format!("[Client] verify_coin_indices_signatures_L_{}", params.L()),
|b| {
b.iter(|| {
verify_coin_indices_signatures(
&params,
&verification_key,
&vk_i_auth,
&partial_signatures,
)
})
},
);
}
fn bench_aggregate_coin_indices_signatures(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-aggregate-coin-signing");
let L = 32;
let params = setup(L);
let authorities_keypairs = ttp_keygen(&params.grp(), 7, 10).unwrap();
let indices: [u64; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// list of secret keys of each authority
let secret_keys_authorities: Vec<SecretKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.secret_key())
.collect();
// list of verification keys of each authority
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
// the global master verification key
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
// create the partial signatures from each authority
let partial_signatures: Vec<Vec<PartialCoinIndexSignature>> = secret_keys_authorities
.iter()
.map(|sk_auth| sign_coin_indices(&params, &verification_key, sk_auth))
.collect();
let combined_data: Vec<(u64, VerificationKeyAuth, Vec<PartialCoinIndexSignature>)> = indices
.iter()
.zip(verification_keys_auth.iter().zip(partial_signatures.iter()))
.map(|(i, (vk, sigs))| (i.clone(), vk.clone(), sigs.clone()))
.collect();
// CLIENT: verify all the partial signature vectors and aggregate into a single vector of signed coin indices
group.bench_function(
&format!(
"[Client] aggregate_coin_indices_signatures_from_{}_issuing_authorities_L_{}",
authorities_keypairs.len(),
params.L(),
),
|b| b.iter(|| aggregate_indices_signatures(&params, &verification_key, &combined_data)),
);
}
criterion_group!(
benches,
bench_coin_signing,
bench_aggregate_coin_indices_signatures
);
criterion_main!(benches);
@@ -0,0 +1,372 @@
// 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::constants;
use nym_compact_ecash::identify::{identify, IdentifyResult};
use nym_compact_ecash::error::{CompactEcashError, Result};
use nym_compact_ecash::scheme::expiration_date_signatures::{
aggregate_expiration_signatures, sign_expiration_date, ExpirationDateSignature,
PartialExpirationDateSignature,
};
use nym_compact_ecash::scheme::keygen::SecretKeyAuth;
use nym_compact_ecash::scheme::setup::{
aggregate_indices_signatures, setup, sign_coin_indices, CoinIndexSignature, Parameters,
PartialCoinIndexSignature,
};
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,
};
pub fn generate_expiration_date_signatures(
params: &Parameters,
expiration_date: u64,
secret_keys_authorities: &[SecretKeyAuth],
verification_keys_auth: &[VerificationKeyAuth],
verification_key: &VerificationKeyAuth,
indices: &[u64],
) -> Result<Vec<ExpirationDateSignature>> {
let mut edt_partial_signatures: Vec<Vec<PartialExpirationDateSignature>> =
Vec::with_capacity(constants::VALIDITY_PERIOD as usize);
for sk_auth in secret_keys_authorities.iter() {
let sign = sign_expiration_date(&params, &sk_auth, expiration_date);
edt_partial_signatures.push(sign);
}
let combined_data: Vec<(
u64,
VerificationKeyAuth,
Vec<PartialExpirationDateSignature>,
)> = indices
.iter()
.zip(
verification_keys_auth
.iter()
.zip(edt_partial_signatures.iter()),
)
.map(|(i, (vk, sigs))| (i.clone(), vk.clone(), sigs.clone()))
.collect();
aggregate_expiration_signatures(&params, &verification_key, expiration_date, &combined_data)
}
pub fn generate_coin_indices_signatures(
params: &Parameters,
secret_keys_authorities: &[SecretKeyAuth],
verification_keys_auth: &[VerificationKeyAuth],
verification_key: &VerificationKeyAuth,
indices: &[u64],
) -> Result<Vec<CoinIndexSignature>> {
// create the partial signatures from each authority
let partial_signatures: Vec<Vec<PartialCoinIndexSignature>> = secret_keys_authorities
.iter()
.map(|sk_auth| sign_coin_indices(&params, &verification_key, sk_auth))
.collect();
let combined_data: Vec<(u64, VerificationKeyAuth, Vec<PartialCoinIndexSignature>)> = indices
.iter()
.zip(verification_keys_auth.iter().zip(partial_signatures.iter()))
.map(|(i, (vk, sigs))| (i.clone(), vk.clone(), sigs.clone()))
.collect();
aggregate_indices_signatures(&params, &verification_key, &combined_data)
}
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 expiration_date = 1703721600; // Dec 28 2023
let spend_date = Scalar::from(1701960386); // Dec 07 2023
let case = BenchCase {
num_authorities: 100,
threshold_p: 0.7,
L: 1000,
spend_vv: 1,
case_nr_pub_keys: 99,
};
// SETUP PHASE and KEY GENERATION
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 secret_keys_authorities: Vec<SecretKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.secret_key())
.collect();
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();
// PRE-GENERATION OF THE EXPORATION DATE SIGNATURES AND THE COIN INDICES SIGNATURES
// generate valid dates signatures
let dates_signatures = generate_expiration_date_signatures(
&params,
expiration_date,
&secret_keys_authorities,
&verification_keys_auth,
&verification_key,
&indices,
)
.unwrap();
// generate coin indices signatures
let coin_indices_signatures = generate_coin_indices_signatures(
&params,
&secret_keys_authorities,
&verification_keys_auth,
&verification_key,
&indices,
)
.unwrap();
// ISSUANCE PHASE
let (req, req_info) =
withdrawal_request(grp, &user_keypair.secret_key(), expiration_date).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(), expiration_date).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,
expiration_date,
)
})
},
);
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,
expiration_date,
);
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; 88] };
// CLIENT BENCHMARK: spend a single coin from the wallet
group.bench_function(
&format!(
"[Client] spend_a_single_coin_L_{}_threshold_{}",
case.L, case.threshold_p,
),
|b| {
b.iter(|| {
aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info,
false,
case.spend_vv,
dates_signatures.clone(),
coin_indices_signatures.clone(),
spend_date,
)
.unwrap()
})
},
);
let (payment, upd_wallet) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info,
false,
case.spend_vv,
dates_signatures.clone(),
coin_indices_signatures.clone(),
spend_date,
)
.unwrap();
// MERCHANT BENCHMARK: verify whether the submitted payment is legit
group.bench_function(
&format!(
"[Merchant] spend_verify_of_a_single_payment_L_{}_threshold_{}",
case.L, case.threshold_p,
),
|b| {
b.iter(|| {
payment
.spend_verify(&params, &verification_key, &pay_info, spend_date)
.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; 88] };
let (payment2, _) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info2,
true,
case.spend_vv,
dates_signatures.clone(),
coin_indices_signatures.clone(),
spend_date,
)
.unwrap();
// GENERATE KEYS FOR OTHER USERS
let mut public_keys: Vec<PublicKeyUser> = Default::default();
for i in 0..case.case_nr_pub_keys {
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = sk_user.public_key(&grp);
public_keys.push(pk_user);
}
public_keys.push(user_keypair.public_key());
// MERCHANT BENCHMARK: identify double spending
group.bench_function(
&format!(
"[Merchant] identify_L_{}_threshold_{}_spend_vv_{}_pks_{}",
case.L,
case.threshold_p,
case.spend_vv,
public_keys.len()
),
|b| {
b.iter(|| {
identify(
&params,
&verification_key,
payment.clone(),
payment2.clone(),
pay_info.clone(),
pay_info2.clone(),
)
.unwrap()
})
},
);
let identify_result = identify(
&params,
&verification_key,
payment,
payment2,
pay_info.clone(),
pay_info2.clone(),
)
.unwrap();
assert_eq!(
identify_result,
IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key())
);
}
criterion_group!(benches, bench_compact_ecash);
criterion_main!(benches);
@@ -0,0 +1,121 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_compact_ecash::scheme::expiration_date_signatures::{
aggregate_expiration_signatures, sign_expiration_date, verify_valid_dates_signatures,
ExpirationDateSignature, PartialExpirationDateSignature,
};
use criterion::{criterion_group, criterion_main, Criterion};
use nym_compact_ecash::constants;
use nym_compact_ecash::scheme::keygen::SecretKeyAuth;
use nym_compact_ecash::setup::setup;
use nym_compact_ecash::{aggregate_verification_keys, ttp_keygen, VerificationKeyAuth};
fn bench_partial_sign_expiration_date(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-sign-verify-expiration-date");
let L = 32;
let params = setup(L);
let expiration_date = 1703183958;
let authorities_keys = ttp_keygen(&params.grp(), 2, 3).unwrap();
let sk_i_auth = authorities_keys[0].secret_key();
let vk_i_auth = authorities_keys[0].verification_key();
let partial_exp_sig = sign_expiration_date(&params, &sk_i_auth, expiration_date);
// ISSUING AUTHORITY BENCHMARK: issue a set of (partial) signatures for a given expiration date
group.bench_function(
&format!(
"[IssuingAuthority] sign_expiration_date_{}_validity_period",
constants::VALIDITY_PERIOD,
),
|b| b.iter(|| sign_expiration_date(&params, &sk_i_auth, expiration_date)),
);
// CLIENT: verify the correctness of the set of (partial) signatures for a given expiration date
assert!(
verify_valid_dates_signatures(&params, &vk_i_auth, &partial_exp_sig, expiration_date)
.is_ok()
);
group.bench_function(
&format!(
"[Client] verify_valid_dates_signatures_{}_validity_period",
constants::VALIDITY_PERIOD,
),
|b| {
b.iter(|| {
verify_valid_dates_signatures(
&params,
&vk_i_auth,
&partial_exp_sig,
expiration_date,
)
})
},
);
}
fn bench_aggregate_expiration_date_signatures(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-aggregate-verify-expiration-date-signatures");
let L = 32;
let params = setup(L);
let expiration_date = 1703183958;
let authorities_keypairs = ttp_keygen(&params.grp(), 7, 10).unwrap();
let indices: [u64; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// list of secret keys of each authority
let secret_keys_authorities: Vec<SecretKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.secret_key())
.collect();
// list of verification keys of each authority
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
// the global master verification key
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
let mut partial_signatures: Vec<Vec<PartialExpirationDateSignature>> =
Vec::with_capacity(constants::VALIDITY_PERIOD as usize);
for sk in secret_keys_authorities.iter() {
let sign = sign_expiration_date(&params, &sk, expiration_date);
partial_signatures.push(sign);
}
let combined_data: Vec<(
u64,
VerificationKeyAuth,
Vec<PartialExpirationDateSignature>,
)> = indices
.iter()
.zip(verification_keys_auth.iter().zip(partial_signatures.iter()))
.map(|(i, (vk, sigs))| (i.clone(), vk.clone(), sigs.clone()))
.collect();
// CLIENT: verify all the partial signature vectors and aggregate into a single vector of signed valid dates
group.bench_function(
&format!(
"[Client] aggregate_expiration_signatures_from_{}_issuing_authorities_{}_validity_period",
constants::VALIDITY_PERIOD, authorities_keypairs.len(),
),
|b| {
b.iter(|| {
aggregate_expiration_signatures(
&params,
&verification_key,
expiration_date,
&combined_data,
)
})
},
);
}
criterion_group!(
benches,
bench_partial_sign_expiration_date,
bench_aggregate_expiration_date_signatures
);
criterion_main!(benches);
@@ -0,0 +1,136 @@
// 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};
#[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_group_operations(c: &mut Criterion) {
let mut group = c.benchmark_group("bench_group_operations");
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))
});
}
criterion_group!(benches, bench_group_operations);
criterion_main!(benches);
@@ -0,0 +1,4 @@
pub const ATTRIBUTES_LEN: usize = 3; // number of attributes encoded in a single zk-nym credential
pub const VALIDITY_PERIOD: u64 = 30;
pub const TYPE_EXP: [u8; 32] = *b"ZKNYMEXPIRATIONDATE4llCBMEypAxr3";
pub const TYPE_IDX: [u8; 32] = *b"ZKNYMSINDICESh^7gTYbhnap*12n5GG6";
@@ -0,0 +1,58 @@
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 related error: {0}")]
Issuance(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("Expiration Date related error: {0}")]
ExpirationDate(String),
#[error("Coin Indices related error: {0}")]
CoinIndices(String),
#[error(
"Deserailization error, expected at least {} bytes, got {}",
min,
actual
)]
DeserializationMinLength { min: usize, actual: usize },
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {} or {modulus_target} % {modulus} == 0")]
DeserializationInvalidLength {
actual: usize,
target: usize,
modulus_target: usize,
modulus: usize,
object: String,
},
}
@@ -0,0 +1,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;
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::CompactEcashError;
use crate::traits::Bytable;
pub mod constants;
pub mod error;
mod proofs;
pub mod scheme;
#[cfg(test)]
mod tests;
mod traits;
mod utils;
pub type Attribute = Scalar;
impl Bytable for Attribute {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CompactEcashError> {
Ok(Attribute::from_bytes(slice.try_into().unwrap()).unwrap())
}
}
@@ -0,0 +1,56 @@
use std::borrow::Borrow;
use bls12_381::Scalar;
use digest::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,822 @@
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::scheme::PayInfo;
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>,
pub kappa_e: G2Projective,
}
impl TryFrom<&[u8]> for SpendInstance {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<SpendInstance> {
if bytes.len() < 48 * 5 + 3 * 96 || (bytes.len()) % 48 != 0 {
return Err(CompactEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len(),
target: 48 * 5 + 3 * 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 kappa_e_bytes = bytes[j..j + 96].try_into().unwrap();
let kappa_e = try_deserialize_g2_projective(
&kappa_e_bytes,
CompactEcashError::Deserialization("Failed to deserialize kappa_e".to_string()),
)?;
j += 96;
let a_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < a_len as usize * 48 {
return Err(CompactEcashError::DeserializationMinLength {
min: a_len as usize * 48,
actual: bytes[j..].len(),
});
}
let mut aa = Vec::with_capacity(a_len as usize);
for i in 0..a_len as usize {
let start = j + i * 48;
let end = start + 48;
let aa_elem_bytes = bytes[start..end].try_into().unwrap();
let aa_elem = try_deserialize_g1_projective(
&aa_elem_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed A values".to_string(),
),
)?;
aa.push(aa_elem)
}
j += j + a_len as usize * 48;
let cc_bytes = bytes[j..j + 48].try_into().unwrap();
let cc = try_deserialize_g1_projective(
&cc_bytes,
CompactEcashError::Deserialization("Failed to deserialize C".to_string()),
)?;
j += 48;
let s_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < s_len as usize * 48 {
return Err(CompactEcashError::DeserializationMinLength {
min: s_len as usize * 48,
actual: bytes[j..].len(),
});
}
let mut ss = Vec::with_capacity(s_len as usize);
for i in 0..s_len as usize {
let start = j + i * 48;
let end = start + 48;
let ss_elem_bytes = bytes[start..end].try_into().unwrap();
let ss_elem = try_deserialize_g1_projective(
&ss_elem_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed S values".to_string(),
),
)?;
ss.push(ss_elem)
}
j += j + s_len as usize * 48;
let t_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < t_len as usize * 48 {
return Err(CompactEcashError::DeserializationMinLength {
min: t_len as usize * 48,
actual: bytes[j..].len(),
});
}
let mut tt = Vec::with_capacity(t_len as usize);
for i in 0..t_len as usize {
let start = j + i * 48;
let end = start + 48;
let tt_elem_bytes = bytes[start..end].try_into().unwrap();
let tt_elem = try_deserialize_g1_projective(
&tt_elem_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed T values".to_string(),
),
)?;
tt.push(tt_elem)
}
j += j + t_len as usize * 48;
let kappa_k_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < kappa_k_len as usize * 96 {
return Err(CompactEcashError::DeserializationMinLength {
min: kappa_k_len as usize * 96,
actual: bytes[j..].len(),
});
}
let mut kappa_k = Vec::with_capacity(kappa_k_len as usize);
for i in 0..kappa_k_len as usize {
let start = j + i * 48;
let end = start + 48;
let kappa_k_elem_bytes = bytes[start..end].try_into().unwrap();
let kappa_k_elem = try_deserialize_g2_projective(
&kappa_k_elem_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed kappa_k values".to_string(),
),
)?;
kappa_k.push(kappa_k_elem)
}
Ok(SpendInstance {
kappa,
aa,
cc,
ss,
tt,
kappa_k,
kappa_e,
})
}
}
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.kappa_e.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>,
pub r_e: Scalar,
pub expiration_date: Scalar,
}
pub struct WitnessReplacement {
pub r_attributes: Vec<Scalar>,
pub r_r: Scalar,
pub r_r_e: Scalar,
pub r_o_c: Scalar,
pub r_r_lk: Vec<Scalar>,
pub r_lk: Vec<Scalar>,
pub r_o_a: Vec<Scalar>,
pub r_mu: Vec<Scalar>,
pub r_o_mu: Vec<Scalar>,
}
pub struct InstanceCommitments {
pub tt_kappa: G2Projective,
pub tt_kappa_e: G2Projective,
pub tt_cc: G1Projective,
pub tt_aa: Vec<G1Projective>,
pub tt_ss: Vec<G1Projective>,
pub tt_tt: Vec<G1Projective>,
pub tt_gamma1: Vec<G1Projective>,
pub tt_kappa_k: Vec<G2Projective>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SpendProof {
challenge: Scalar,
response_r: Scalar,
response_r_e: Scalar,
responses_r_k: Vec<Scalar>,
responses_l: Vec<Scalar>,
responses_o_a: Vec<Scalar>,
response_o_c: Scalar,
responses_mu: Vec<Scalar>,
responses_o_mu: Vec<Scalar>,
responses_attributes: Vec<Scalar>,
}
pub fn generate_witness_replacement(
params: &Parameters,
witness: &SpendWitness,
) -> WitnessReplacement {
let grp_params = params.grp();
let r_attributes = grp_params.n_random_scalars(witness.attributes.len());
let r_r = grp_params.random_scalar();
let r_r_e = grp_params.random_scalar();
let r_o_c = grp_params.random_scalar();
let r_r_lk = grp_params.n_random_scalars(witness.r_k.len());
let r_lk = grp_params.n_random_scalars(witness.lk.len());
let r_o_a = grp_params.n_random_scalars(witness.o_a.len());
let r_mu = grp_params.n_random_scalars(witness.mu.len());
let r_o_mu = grp_params.n_random_scalars(witness.o_mu.len());
WitnessReplacement {
r_attributes,
r_r,
r_r_e,
r_o_c,
r_r_lk,
r_lk,
r_o_a,
r_mu,
r_o_mu,
}
}
pub fn compute_instance_commitments(
params: &Parameters,
witness_replacement: &WitnessReplacement,
instance: &SpendInstance,
verification_key: &VerificationKeyAuth,
rr: &[Scalar],
) -> InstanceCommitments {
let grp_params = params.grp();
let g1 = *grp_params.gen1();
let gamma0 = grp_params.gamma_idx(0).unwrap();
let gamma1 = grp_params.gamma_idx(1).unwrap();
let gamma2 = grp_params.gamma_idx(2).unwrap();
let tt_kappa = grp_params.gen2() * witness_replacement.r_r
+ verification_key.alpha
+ witness_replacement
.r_attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
let tt_cc = g1 * witness_replacement.r_o_c + gamma0 * witness_replacement.r_attributes[1];
let tt_kappa_e = grp_params.gen2() * witness_replacement.r_r_e
+ verification_key.alpha
+ verification_key.beta_g2[0] * witness_replacement.r_attributes[2];
let tt_aa: Vec<G1Projective> = witness_replacement
.r_o_a
.iter()
.zip(witness_replacement.r_lk.iter())
.map(|(r_o_a_k, r_l_k)| g1 * r_o_a_k + gamma0 * r_l_k)
.collect::<Vec<_>>();
let tt_kappa_k = witness_replacement
.r_lk
.iter()
.zip(witness_replacement.r_r_lk.iter())
.map(|(r_l_k, r_r_k)| {
verification_key.alpha + verification_key.beta_g2[0] * r_l_k + grp_params.gen2() * r_r_k
})
.collect::<Vec<_>>();
let tt_ss = witness_replacement
.r_mu
.iter()
.map(|r_mu_k| grp_params.delta() * r_mu_k)
.collect::<Vec<_>>();
let tt_tt = rr
.iter()
.zip(witness_replacement.r_mu.iter())
.map(|(rr_k, r_mu_k)| g1 * witness_replacement.r_attributes[0] + (g1 * rr_k) * r_mu_k)
.collect::<Vec<_>>();
let tt_gamma1 = instance
.aa
.iter()
.zip(witness_replacement.r_mu.iter())
.zip(witness_replacement.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<_>>();
InstanceCommitments {
tt_kappa,
tt_kappa_e,
tt_cc,
tt_aa,
tt_ss,
tt_tt,
tt_gamma1,
tt_kappa_k,
}
}
impl SpendProof {
pub fn construct(
params: &Parameters,
instance: &SpendInstance,
witness: &SpendWitness,
verification_key: &VerificationKeyAuth,
rr: &[Scalar],
pay_info: &PayInfo,
spend_value: u64,
) -> Self {
let grp_params = params.grp();
// generate random values to replace each witness
let witness_replacement = generate_witness_replacement(&params, &witness);
let g1 = *grp_params.gen1();
let gamma0 = *grp_params.gamma_idx(0).unwrap();
let beta2_bytes = verification_key
.beta_g2
.iter()
.map(|beta_i| beta_i.to_bytes())
.collect::<Vec<_>>();
// compute zkp commitment for each instance
let instance_commitments = compute_instance_commitments(
&params,
&witness_replacement,
&instance,
&verification_key,
&rr,
);
let tt_aa_bytes = instance_commitments
.tt_aa
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
let tt_ss_bytes = instance_commitments
.tt_ss
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
let tt_tt_bytes = instance_commitments
.tt_tt
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
let tt_gamma1_bytes = instance_commitments
.tt_gamma1
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
let tt_kappa_k_bytes = instance_commitments
.tt_kappa_k
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
// compute the challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(grp_params.gen1().to_bytes().as_ref())
.chain(std::iter::once(grp_params.gen2().to_bytes().as_ref()))
.chain(std::iter::once(grp_params.gammas_to_bytes().as_ref()))
.chain(std::iter::once(verification_key.to_bytes().as_ref()))
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(
instance_commitments.tt_kappa.to_bytes().as_ref(),
))
.chain(std::iter::once(
instance_commitments.tt_kappa_e.to_bytes().as_ref(),
))
.chain(std::iter::once(
instance_commitments.tt_cc.to_bytes().as_ref(),
))
.chain(tt_aa_bytes.iter().map(|x| x.as_ref()))
.chain(tt_ss_bytes.iter().map(|x| x.as_ref()))
.chain(tt_kappa_k_bytes.iter().map(|x| x.as_ref()))
.chain(tt_tt_bytes.iter().map(|x| x.as_ref()))
.chain(std::iter::once(pay_info.pay_info_bytes.as_ref()))
.chain(std::iter::once(spend_value.to_le_bytes().as_ref())),
);
// compute response for each witness
let responses_attributes = produce_responses(
&witness_replacement.r_attributes,
&challenge,
&witness.attributes.iter().collect::<Vec<_>>(),
);
let response_r = produce_response(&witness_replacement.r_r, &challenge, &witness.r);
let response_r_e = produce_response(&witness_replacement.r_r_e, &challenge, &witness.r_e);
let response_o_c = produce_response(&witness_replacement.r_o_c, &challenge, &witness.o_c);
let responses_r_k =
produce_responses(&witness_replacement.r_r_lk, &challenge, &witness.r_k);
let responses_l = produce_responses(&witness_replacement.r_lk, &challenge, &witness.lk);
let responses_o_a = produce_responses(&witness_replacement.r_o_a, &challenge, &witness.o_a);
let responses_mu = produce_responses(&witness_replacement.r_mu, &challenge, &witness.mu);
let responses_o_mu =
produce_responses(&witness_replacement.r_o_mu, &challenge, &witness.o_mu);
SpendProof {
challenge,
response_r,
response_r_e,
responses_r_k,
responses_l,
responses_o_a,
response_o_c,
responses_mu,
responses_o_mu,
responses_attributes,
}
}
pub fn verify(
&self,
params: &Parameters,
instance: &SpendInstance,
verification_key: &VerificationKeyAuth,
rr: &[Scalar],
pay_info: &PayInfo,
spend_value: u64,
) -> bool {
let grp_params = params.grp();
let g1 = *grp_params.gen1();
let g2 = *grp_params.gen2();
let gamma0 = *grp_params.gamma_idx(0).unwrap();
let beta2_bytes = verification_key
.beta_g2
.iter()
.map(|beta_i| beta_i.to_bytes())
.collect::<Vec<_>>();
// re-compute each zkp commitment
let tt_kappa = instance.kappa * self.challenge
+ verification_key.alpha * (self.challenge.neg())
+ verification_key.alpha
+ grp_params.gen2() * self.response_r
+ self
.responses_attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
let tt_cc = g1 * self.response_o_c
+ gamma0 * self.responses_attributes[1]
+ instance.cc * self.challenge;
let tt_kappa_e = instance.kappa_e * self.challenge
+ verification_key.alpha * (self.challenge.neg())
+ verification_key.alpha
+ verification_key.beta_g2[0] * self.responses_attributes[2]
+ grp_params.gen2() * self.response_r_e;
let tt_aa = self
.responses_o_a
.iter()
.zip(self.responses_l.iter())
.zip(instance.aa.iter())
.map(|((resp_o_a_k, resp_l_k), aa_k)| {
g1 * resp_o_a_k + gamma0 * resp_l_k + aa_k * self.challenge
})
.collect::<Vec<_>>();
let tt_aa_bytes = tt_aa.iter().map(|x| x.to_bytes()).collect::<Vec<_>>();
let tt_ss = self
.responses_mu
.iter()
.zip(instance.ss.iter())
.map(|(resp_mu_k, ss_k)| grp_params.delta() * resp_mu_k + ss_k * self.challenge)
.collect::<Vec<_>>();
let tt_ss_bytes = tt_ss.iter().map(|x| x.to_bytes()).collect::<Vec<_>>();
let tt_tt = self
.responses_mu
.iter()
.zip(rr.iter())
.zip(instance.tt.iter())
.map(|((resp_mu_k, rr_k), tt_k)| {
g1 * self.responses_attributes[0] + (g1 * rr_k) * resp_mu_k + tt_k * self.challenge
})
.collect::<Vec<_>>();
let tt_tt_bytes = tt_tt.iter().map(|x| x.to_bytes()).collect::<Vec<_>>();
let tt_gamma00 = instance
.aa
.iter()
.zip(self.responses_mu.iter())
.zip(self.responses_o_mu.iter())
.map(|((aa_k, resp_mu_k), resp_o_mu_k)| {
(aa_k + instance.cc + gamma0) * resp_mu_k
+ g1 * resp_o_mu_k
+ gamma0 * self.challenge
})
.collect::<Vec<_>>();
let tt_gamma00_bytes = tt_gamma00.iter().map(|x| x.to_bytes()).collect::<Vec<_>>();
let tt_kappa_k = instance
.kappa_k
.iter()
.zip(self.responses_r_k.iter())
.zip(self.responses_l.iter())
.map(|((kappa_k, resp_r_k), resp_r_l_k)| {
kappa_k * self.challenge
+ grp_params.gen2() * resp_r_k
+ verification_key.alpha * (Scalar::one() - self.challenge)
+ verification_key.beta_g2[0] * resp_r_l_k
})
.collect::<Vec<_>>();
let tt_kappa_k_bytes = tt_kappa_k.iter().map(|x| x.to_bytes()).collect::<Vec<_>>();
// re-compute the challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(grp_params.gen1().to_bytes().as_ref())
.chain(std::iter::once(grp_params.gen2().to_bytes().as_ref()))
.chain(std::iter::once(grp_params.gammas_to_bytes().as_ref()))
.chain(std::iter::once(verification_key.to_bytes().as_ref()))
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(tt_kappa.to_bytes().as_ref()))
.chain(std::iter::once(tt_kappa_e.to_bytes().as_ref()))
.chain(std::iter::once(tt_cc.to_bytes().as_ref()))
.chain(tt_aa_bytes.iter().map(|x| x.as_ref()))
.chain(tt_ss_bytes.iter().map(|x| x.as_ref()))
.chain(tt_kappa_k_bytes.iter().map(|x| x.as_ref()))
.chain(tt_tt_bytes.iter().map(|x| x.as_ref()))
.chain(std::iter::once(pay_info.pay_info_bytes.as_ref()))
.chain(std::iter::once(spend_value.to_le_bytes().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 response_r_e_bytes = self.response_r_e.to_bytes();
let rrk_len = self.responses_r_k.len();
let rrk_len_bytes = rrk_len.to_le_bytes();
let rl_len = self.responses_l.len();
let rl_len_bytes = rl_len.to_le_bytes();
let roa_len = self.responses_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.responses_mu.len();
let rmu_len_bytes = rmu_len.to_le_bytes();
let romu_len = self.responses_o_mu.len();
let romu_len_bytes = romu_len.to_le_bytes();
let rattributes_len = self.responses_attributes.len();
let rattributes_len_bytes = rattributes_len.to_le_bytes();
let mut bytes: Vec<u8> = Vec::with_capacity(
128 + (rrk_len + rl_len + roa_len + rmu_len + romu_len + rattributes_len) * 8
+ (rrk_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(&response_r_e_bytes);
bytes.extend_from_slice(&roc_bytes);
bytes.extend_from_slice(&rrk_len_bytes);
for rrk in &self.responses_r_k {
bytes.extend_from_slice(&rrk.to_bytes());
}
bytes.extend_from_slice(&rl_len_bytes);
for rl in &self.responses_l {
bytes.extend_from_slice(&rl.to_bytes());
}
bytes.extend_from_slice(&roa_len_bytes);
for roa in &self.responses_o_a {
bytes.extend_from_slice(&roa.to_bytes());
}
bytes.extend_from_slice(&rmu_len_bytes);
for rmu in &self.responses_mu {
bytes.extend_from_slice(&rmu.to_bytes());
}
bytes.extend_from_slice(&romu_len_bytes);
for romu in &self.responses_o_mu {
bytes.extend_from_slice(&romu.to_bytes());
}
bytes.extend_from_slice(&rattributes_len_bytes);
for rattr in &self.responses_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() < 368 || (bytes.len() - 128 - 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_r_e_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_r_e = try_deserialize_scalar(
&response_r_e_bytes,
CompactEcashError::Deserialization("Failed to deserialize response_r_e".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 responses_r_k = 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 responses_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 responses_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 responses_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 responses_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 responses_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_r_e,
response_o_c,
responses_r_k,
responses_l,
responses_o_a,
responses_mu,
responses_o_mu,
responses_attributes,
};
Ok(spend_proof)
}
}
@@ -0,0 +1,418 @@
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 joined_commitment: G1Projective,
// Hash of the joined commitment com
pub joined_commitment_hash: G1Projective,
// Pedersen commitments to each attribute
pub private_attributes_commitments: 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 joined_commitment = 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 joined_commitment_hash = 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 private_attributes_commitments = Vec::new();
let mut pc_coms_end: usize = 0;
for i in 0..pc_coms_len {
let start = (104 + i * 48) as usize;
let end = (start + 48) as usize;
let pc_i_bytes = bytes[start..end].try_into().unwrap();
let pc_i = try_deserialize_g1_projective(
&pc_i_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize pedersen commitment".to_string(),
),
)?;
pc_coms_end = end;
private_attributes_commitments.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 {
joined_commitment,
joined_commitment_hash,
private_attributes_commitments,
pk_user: PublicKeyUser { pk },
})
}
}
impl WithdrawalReqInstance {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let pc_coms_len = self.private_attributes_commitments.len();
let mut bytes = Vec::with_capacity(8 + (pc_coms_len + 3) as usize * 48);
bytes.extend_from_slice(self.joined_commitment.to_bytes().as_ref());
bytes.extend_from_slice(self.joined_commitment_hash.to_bytes().as_ref());
bytes.extend_from_slice(&pc_coms_len.to_le_bytes());
for pc in self.private_attributes_commitments.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 private_attributes: Vec<Scalar>,
// Opening for the joined commitment com
pub joined_commitment_opening: Scalar,
// Openings for the pedersen commitments of private attributes
pub private_attributes_openings: Vec<Scalar>,
}
#[derive(Debug, PartialEq, Clone)]
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.private_attributes_openings.len());
let r_attributes = params.n_random_scalars(witness.private_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.joined_commitment_hash * 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.joined_commitment_opening,
);
let response_openings = produce_responses(
&r_pedcom_openings,
&challenge,
&witness
.private_attributes_openings
.iter()
.collect::<Vec<_>>(),
);
let response_attributes = produce_responses(
&r_attributes,
&challenge,
&witness.private_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.joined_commitment * 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.private_attributes_commitments.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.joined_commitment_hash * 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 {
joined_commitment: G1Projective::random(&mut rng),
joined_commitment_hash: G1Projective::random(&mut rng),
private_attributes_commitments: 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 private_attributes = vec![sk, v, t];
let joined_commitment_opening = params.random_scalar();
let joined_commitment = params.gen1() * joined_commitment_opening
+ private_attributes
.iter()
.zip(params.gammas())
.map(|(&m, gamma)| gamma * m)
.sum::<G1Projective>();
let joined_commitment_hash = hash_g1(joined_commitment.to_bytes());
let private_attributes_openings = params.n_random_scalars(private_attributes.len());
let private_attributes_commitments = private_attributes_openings
.iter()
.zip(private_attributes.iter())
.map(|(o_j, m_j)| params.gen1() * o_j + joined_commitment_hash * m_j)
.collect::<Vec<_>>();
let instance = WithdrawalReqInstance {
joined_commitment,
joined_commitment_hash,
private_attributes_commitments,
pk_user,
};
let witness = WithdrawalReqWitness {
private_attributes,
joined_commitment_opening,
private_attributes_openings,
};
let zk_proof = WithdrawalReqProof::construct(&params, &instance, &witness);
assert!(zk_proof.verify(&params, &instance))
}
}
@@ -0,0 +1,171 @@
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 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((verification_key.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().clone(),
req_info.get_expiration_date().clone(),
];
let aggregated_signature =
aggregate_signature_shares(&params, &verification_key, &attributes, &signature_shares)?;
Ok(Wallet {
sig: aggregated_signature,
v: req_info.get_v().clone(),
expiration_date: req_info.get_expiration_date().clone(),
l: Cell::new(0),
})
}
@@ -0,0 +1,442 @@
use crate::constants;
use crate::error::{CompactEcashError, Result};
use crate::scheme::keygen::{SecretKeyAuth, VerificationKeyAuth};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::utils::hash_g1;
use crate::utils::{
check_bilinear_pairing, generate_lagrangian_coefficients_at_origin,
try_deserialize_g1_projective,
};
use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar};
use chrono::{Duration, NaiveDate, NaiveDateTime};
use group::Curve;
use itertools::Itertools;
use rayon::prelude::*;
/// A structure representing an expiration date signature.
#[derive(Debug, PartialEq, Clone)]
pub struct ExpirationDateSignature {
pub(crate) h: G1Projective,
pub(crate) s: G1Projective,
}
pub type PartialExpirationDateSignature = ExpirationDateSignature;
impl ExpirationDateSignature {
/// Function randomises the expiration date signature.
///
/// # Arguments
///
/// * `params` - A reference to group parameters used for the signature generation.
///
/// # Returns
///
/// A tuple containing the randomized expiration date signature and the blinding scalar.
pub fn randomise(&self, params: &GroupParameters) -> (ExpirationDateSignature, Scalar) {
// Generate random blinding scalars
let r = params.random_scalar();
let r_prime = params.random_scalar();
// Calculate h_prime and s_prime using the random scalars
let h_prime = self.h * r_prime;
let s_prime = (self.s * r_prime) + (h_prime * r);
(
ExpirationDateSignature {
h: h_prime,
s: s_prime,
},
r,
)
}
/// Converts the expiration date signature to a byte vector.
///
/// # Returns
///
/// A vector of bytes representing the expiration date signature.
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes: Vec<u8> = Vec::with_capacity(48 + 48);
bytes.extend(self.h.to_affine().to_compressed());
bytes.extend(self.s.to_affine().to_compressed());
bytes
}
}
impl TryFrom<&[u8]> for ExpirationDateSignature {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<ExpirationDateSignature> {
if bytes.len() != 96 {
return Err(CompactEcashError::Deserialization(format!(
"ExpirationDateSignature must be exactly 96 bytes, got {}",
bytes.len()
)));
}
let h_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
let s_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
let h = try_deserialize_g1_projective(
h_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed h of the ExpirationDateSignature".to_string(),
),
)?;
let s = try_deserialize_g1_projective(
s_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed s of the ExpirationDateSignature".to_string(),
),
)?;
Ok(ExpirationDateSignature { h, s })
}
}
/// Signs given expiration date for a specified validity period using the given secret key of a single authority.
///
/// # Arguments
///
/// * `params` - The cryptographic parameters used in the signing process.
/// * `sk_auth` - The secret key of the signing authority.
/// * `expiration_date` - The expiration date for which signatures will be generated (as unix timestamp).
///
/// # Returns
///
/// A vector containing partial signatures for each date within the validity period (i.e.,
/// from expiration_date - VALIDITY_PERIOD till expiration_date.
///
/// # Note
///
/// This function is executed by a single singing authority and generates partial expiration date
/// signatures for a specified validity period. Each signature is created by combining cryptographic
/// attributes derived from the expiration date, and the resulting vector contains signatures for
/// each date within the defined validity period till expiration date.
/// The validity period is determined by the constant `VALIDITY_PERIOD` in the `constants` module.
pub fn sign_expiration_date(
params: &Parameters,
sk_auth: &SecretKeyAuth,
expiration_date: u64,
) -> Vec<PartialExpirationDateSignature> {
let m0: Scalar = Scalar::from(expiration_date);
let m2: Scalar = Scalar::from_bytes(&constants::TYPE_EXP).unwrap();
(0..constants::VALIDITY_PERIOD)
.into_par_iter()
.fold(Vec::new, |mut exp_signs, l| {
let expiration_date = NaiveDateTime::from_timestamp(expiration_date as i64, 0);
let valid_date = expiration_date - Duration::days(constants::VALIDITY_PERIOD as i64)
+ Duration::days(l as i64)
+ Duration::days(1 as i64);
let m1: Scalar = Scalar::from(valid_date.timestamp() as u64);
// Compute the hash
let h = hash_g1([m0.to_bytes(), m1.to_bytes()].concat());
// Sign the attributes by performing scalar-point multiplications and accumulating the result
let mut s_exponent = sk_auth.x;
s_exponent += &sk_auth.ys[0] * m0;
s_exponent += &sk_auth.ys[1] * m1;
s_exponent += &sk_auth.ys[2] * m2;
// Create the signature struct on the expiration date
let exp_sign = PartialExpirationDateSignature {
h,
s: h * s_exponent,
};
exp_signs.push(exp_sign);
exp_signs
})
.reduce(Vec::new, |mut v1, mut v2| {
v1.append(&mut v2);
v1
})
}
/// Verifies the expiration date signatures against the given verification key.
///
/// This function iterates over the provided valid date signatures and verifies each one
/// against the provided verification key. It computes the hash and checks the correctness of the
/// signature using bilinear pairings.
///
/// # Arguments
///
/// * `params` - The cryptographic parameters used in the signing process.
/// * `vkey` - The verification key of the signing authority.
/// * `signatures` - The list of date signatures to be verified.
/// * `expiration_date` - The expiration date for which signatures are being issued (as unix timestamp).
///
/// # Returns
///
/// Returns `Ok(true)` if all signatures are verified successfully, otherwise returns an
/// `Err(CompactEcashError::ExpirationDate)` with an error message.
///
pub fn verify_valid_dates_signatures(
params: &Parameters,
vk: &VerificationKeyAuth,
signatures: &[ExpirationDateSignature],
expiration_date: u64,
) -> Result<()> {
let m0: Scalar = Scalar::from(expiration_date);
let m2: Scalar = Scalar::from_bytes(&constants::TYPE_EXP).unwrap();
signatures.par_iter().enumerate().try_for_each(|(l, sig)| {
let expiration_date = NaiveDateTime::from_timestamp(expiration_date as i64, 0);
let valid_date = expiration_date - Duration::days(constants::VALIDITY_PERIOD as i64)
+ Duration::days(l as i64)
+ Duration::days(1 as i64);
let m1: Scalar = Scalar::from(valid_date.timestamp() as u64);
// Compute the hash
let h = hash_g1([m0.to_bytes(), m1.to_bytes()].concat());
// Verify the signature correctness
if sig.h != h {
return Err(CompactEcashError::ExpirationDate(
"Failed to verify the commitment hash".to_string(),
));
}
let partially_signed_attributes = [m0, m1, m2]
.iter()
.zip(vk.beta_g2.iter())
.map(|(m, beta_i)| beta_i * Scalar::from(*m))
.sum::<G2Projective>();
if !check_bilinear_pairing(
&sig.h.to_affine(),
&G2Prepared::from((vk.alpha + partially_signed_attributes).to_affine()),
&sig.s.to_affine(),
params.grp().prepared_miller_g2(),
) {
return Err(CompactEcashError::ExpirationDate(
"Verification of the date signature failed".to_string(),
));
}
Ok(())
})
}
/// Aggregates partial expiration date signatures into a list of aggregated expiration date signatures.
///
/// # Arguments
///
/// * `params` - The cryptographic parameters used in the signing process.
/// * `vk_auth` - The global verification key.
/// * `expiration_date` - The expiration date for which the signatures are being aggregated (as unix timestamp).
/// * `signatures` - A list of tuples containing unique indices, verification keys, and partial expiration date signatures corresponding to the signing authorities.
///
/// # Returns
///
/// A `Result` containing a vector of `ExpirationDateSignature` if the aggregation is successful,
/// or an `Err` variant with a description of the encountered error.
///
/// # Errors
///
/// This function returns an error if there is a mismatch in the lengths of `signatures`. This occurs
/// when the number of tuples in `signatures` is not equal to the expected number of signing authorities.
/// Each tuple should contain a unique index, a verification key, and a list of partial signatures.
///
/// It also returns an error if there are not enough unique indices. This happens when the number
/// of unique indices in the tuples is less than the total number of signing authorities.
///
/// Additionally, an error is returned if the verification of the partial or aggregated signatures fails.
/// This can occur if the cryptographic verification process fails for any of the provided signatures.
///
pub fn aggregate_expiration_signatures(
params: &Parameters,
vk: &VerificationKeyAuth,
expiration_date: u64,
signatures: &[(
u64,
VerificationKeyAuth,
Vec<PartialExpirationDateSignature>,
)],
) -> Result<Vec<ExpirationDateSignature>> {
// Check if all indices are unique
if signatures
.iter()
.map(|(index, _, _)| index)
.unique()
.count()
!= signatures.len()
{
return Err(CompactEcashError::ExpirationDate(
"Not enough unique indices shares".to_string(),
));
}
// Evaluate at 0 the Lagrange basis polynomials k_i
let coefficients = generate_lagrangian_coefficients_at_origin(
&signatures
.iter()
.map(|(index, _, _)| *index)
.collect::<Vec<_>>(),
);
// Verify that all signatures are valid
signatures
.par_iter()
.try_for_each(|(_, vk_auth, partial_signatures)| {
verify_valid_dates_signatures(params, vk_auth, partial_signatures, expiration_date)
})?;
// Pre-allocate vectors
let mut aggregated_date_signatures: Vec<ExpirationDateSignature> =
Vec::with_capacity(constants::VALIDITY_PERIOD as usize);
let m0: Scalar = Scalar::from(expiration_date);
let m2: Scalar = Scalar::from_bytes(&constants::TYPE_EXP).unwrap();
for l in 0..constants::VALIDITY_PERIOD {
let expiration_date = NaiveDateTime::from_timestamp(expiration_date as i64, 0);
let valid_date = expiration_date - Duration::days(constants::VALIDITY_PERIOD as i64)
+ Duration::days(l as i64)
+ Duration::days(1 as i64);
let m1: Scalar = Scalar::from(valid_date.timestamp() as u64);
// Compute the hash
let h = hash_g1([m0.to_bytes(), m1.to_bytes()].concat());
// Collect the partial signatures for the same valid date
let collected_at_l: Vec<_> = signatures
.iter()
.filter_map(|(_, _, inner_vec)| inner_vec.get(l as usize))
.cloned()
.collect();
// Aggregate partial signatures for each validity date
let aggr_s: G1Projective = coefficients
.iter()
.zip(collected_at_l.iter())
.map(|(coeff, sig)| sig.s * coeff)
.sum();
let aggr_sig = ExpirationDateSignature { h, s: aggr_s };
aggregated_date_signatures.push(aggr_sig);
}
verify_valid_dates_signatures(&params, &vk, &aggregated_date_signatures, expiration_date)?;
Ok(aggregated_date_signatures)
}
/// Finds the index corresponding to the given spend date based on the expiration date.
///
/// This function calculates the index such that the following equality holds:
/// `spend_date = expiration_date - 30 + index`
/// This index is used to retrieve a corresponding signature.
///
/// # Arguments
///
/// * `spend_date` - The spend date for which to find the index.
/// * `expiration_date` - The expiration date used in the calculation.
///
/// # Returns
///
/// If a valid index is found, returns `Ok(index)`. If no valid index is found
/// (i.e., `spend_date` is earlier than `expiration_date - 30`), returns `Err(InvalidDateError)`.
///
pub fn find_index(spend_date: Scalar, expiration_date: Scalar) -> Result<usize> {
let expiration_date_bytes = expiration_date.to_bytes();
let expiration_date_u64 = u64::from_le_bytes(expiration_date_bytes[..8].try_into().unwrap());
let spend_date_bytes = spend_date.to_bytes();
let spend_date_u64 = u64::from_le_bytes(spend_date_bytes[..8].try_into().unwrap());
let start_date = NaiveDateTime::from_timestamp(expiration_date_u64 as i64, 0)
- Duration::days(constants::VALIDITY_PERIOD as i64)
+ Duration::days(1 as i64);
if NaiveDateTime::from_timestamp(spend_date_u64 as i64, 0) >= start_date {
let index_a = (NaiveDateTime::from_timestamp(spend_date_u64 as i64, 0) - start_date)
.num_days() as usize;
Ok(index_a)
} else {
Err(CompactEcashError::ExpirationDate(
"Spend_date is too early, no valid index".to_string(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::keygen::ttp_keygen;
use crate::scheme::setup::setup;
#[test]
fn test_find_index() {
let expiration_date = Scalar::from(1702050209); // Dec 8 2023
let spend_date = Scalar::from(1701963809); // Dec 07 2023
let index_a = find_index(spend_date, expiration_date);
}
#[test]
fn test_sign_expiration_date() {
let total_coins = 32;
let params = setup(total_coins);
let expiration_date = 1702050209; // Dec 8 2023
let authorities_keys = ttp_keygen(&params.grp(), 2, 3).unwrap();
let sk_i_auth = authorities_keys[0].secret_key();
let vk_i_auth = authorities_keys[0].verification_key();
let partial_exp_sig = sign_expiration_date(&params, &sk_i_auth, expiration_date);
assert!(verify_valid_dates_signatures(
&params,
&vk_i_auth,
&partial_exp_sig,
expiration_date
)
.is_ok());
}
#[test]
fn test_aggregate_expiration_signatures() {
let total_coins = 32;
let params = setup(total_coins);
let expiration_date = 1702050209; // Dec 8 2023
let authorities_keypairs = ttp_keygen(&params.grp(), 2, 3).unwrap();
let indices: [u64; 3] = [1, 2, 3];
// list of secret keys of each authority
let secret_keys_authorities: Vec<SecretKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.secret_key())
.collect();
// list of verification keys of each authority
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
// the global master verification key
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
let mut edt_partial_signatures: Vec<Vec<PartialExpirationDateSignature>> =
Vec::with_capacity(constants::VALIDITY_PERIOD as usize);
for sk_auth in secret_keys_authorities.iter() {
let sign = sign_expiration_date(&params, &sk_auth, expiration_date);
edt_partial_signatures.push(sign);
}
let combined_data: Vec<(
u64,
VerificationKeyAuth,
Vec<PartialExpirationDateSignature>,
)> = indices
.iter()
.zip(
verification_keys_auth
.iter()
.zip(edt_partial_signatures.iter()),
)
.map(|(i, (vk, sigs))| (i.clone(), vk.clone(), sigs.clone()))
.collect();
let output = aggregate_expiration_signatures(
&params,
&verification_key,
expiration_date,
&combined_data,
)
.unwrap();
assert!(aggregate_expiration_signatures(
&params,
&verification_key,
expiration_date,
&combined_data,
)
.is_ok());
}
}
@@ -0,0 +1,675 @@
use crate::constants;
use crate::error::{CompactEcashError, Result};
use crate::scheme::expiration_date_signatures::{
aggregate_expiration_signatures, sign_expiration_date, ExpirationDateSignature,
PartialExpirationDateSignature,
};
use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, VerificationKeyAuth};
use crate::scheme::setup::{
aggregate_indices_signatures, sign_coin_indices, CoinIndexSignature, Parameters,
PartialCoinIndexSignature,
};
use crate::scheme::{compute_pay_info_hash, Payment};
use crate::utils::hash_to_scalar;
use crate::PayInfo;
use bls12_381::Scalar;
#[derive(Debug, Eq, PartialEq)]
pub enum IdentifyResult {
NotADuplicatePayment,
DuplicatePayInfo(PayInfo),
DoubleSpendingPublicKeys(PublicKeyUser),
}
pub fn identify(
verification_key: &VerificationKeyAuth,
payment1: Payment,
payment2: Payment,
pay_info1: PayInfo,
pay_info2: PayInfo,
) -> Result<IdentifyResult> {
let mut k = 0;
let mut j = 0;
for (id1, pay1_ss) in payment1.ss.iter().enumerate() {
for (id2, pay2_ss) in payment2.ss.iter().enumerate() {
if pay1_ss == pay2_ss {
k = id1;
j = id2;
break;
}
}
}
if payment1
.ss
.iter()
.any(|pay1_ss| payment2.ss.contains(pay1_ss))
{
if pay_info1 == pay_info2 {
Ok(IdentifyResult::DuplicatePayInfo(pay_info1))
} else {
let rr_k_payment1 = compute_pay_info_hash(&pay_info1, k as u64);
let rr_j_payment2 = compute_pay_info_hash(&pay_info2, j as u64);
let rr_diff = rr_k_payment1 - rr_j_payment2;
let pk = (payment2.tt[j] * rr_k_payment1 - payment1.tt[k] * rr_j_payment2)
* rr_diff.invert().unwrap();
let pk_user = PublicKeyUser { pk };
Ok(IdentifyResult::DoubleSpendingPublicKeys(pk_user))
}
} else {
Ok(IdentifyResult::NotADuplicatePayment)
}
}
pub fn generate_expiration_date_signatures(
params: &Parameters,
expiration_date: u64,
secret_keys_authorities: &[SecretKeyAuth],
verification_keys_auth: &[VerificationKeyAuth],
verification_key: &VerificationKeyAuth,
indices: &[u64],
) -> Result<Vec<ExpirationDateSignature>> {
let mut edt_partial_signatures: Vec<Vec<PartialExpirationDateSignature>> =
Vec::with_capacity(constants::VALIDITY_PERIOD as usize);
for sk_auth in secret_keys_authorities.iter() {
let sign = sign_expiration_date(&params, &sk_auth, expiration_date);
edt_partial_signatures.push(sign);
}
let combined_data: Vec<(
u64,
VerificationKeyAuth,
Vec<PartialExpirationDateSignature>,
)> = indices
.iter()
.zip(
verification_keys_auth
.iter()
.zip(edt_partial_signatures.iter()),
)
.map(|(i, (vk, sigs))| (i.clone(), vk.clone(), sigs.clone()))
.collect();
aggregate_expiration_signatures(&params, &verification_key, expiration_date, &combined_data)
}
pub fn generate_coin_indices_signatures(
params: &Parameters,
secret_keys_authorities: &[SecretKeyAuth],
verification_keys_auth: &[VerificationKeyAuth],
verification_key: &VerificationKeyAuth,
indices: &[u64],
) -> Result<Vec<CoinIndexSignature>> {
// create the partial signatures from each authority
let partial_signatures: Vec<Vec<PartialCoinIndexSignature>> = secret_keys_authorities
.iter()
.map(|sk_auth| sign_coin_indices(&params, &verification_key, sk_auth))
.collect();
let combined_data: Vec<(u64, VerificationKeyAuth, Vec<PartialCoinIndexSignature>)> = indices
.iter()
.zip(verification_keys_auth.iter().zip(partial_signatures.iter()))
.map(|(i, (vk, sigs))| (i.clone(), vk.clone(), sigs.clone()))
.collect();
aggregate_indices_signatures(&params, &verification_key, &combined_data)
}
#[cfg(test)]
mod tests {
use super::*;
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, issue_verify,
ttp_keygen, withdrawal_request, PartialWallet, PayInfo, VerificationKeyAuth,
};
use itertools::izip;
#[test]
fn duplicate_payments_with_the_same_pay_info() {
let total_coins = 32;
let params = setup(total_coins);
// NOTE: Make sure that the date timestamp are calculated at 00:00:00!!
let expiration_date = 1703721600; // Dec 28 2023 00:00:00
let spend_date = Scalar::from(1701907200); // Dec 07 2023 00:00:00
let grp = params.grp();
let user_keypair = generate_keypair_user(&grp);
let (req, req_info) =
withdrawal_request(grp, &user_keypair.secret_key(), expiration_date).unwrap();
let authorities_keypairs = ttp_keygen(&grp, 2, 3).unwrap();
let indices: [u64; 3] = [1, 2, 3];
let secret_keys_authorities: Vec<SecretKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.secret_key())
.collect();
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();
// generate valid dates signatures
let dates_signatures = generate_expiration_date_signatures(
&params,
expiration_date,
&secret_keys_authorities,
&verification_keys_auth,
&verification_key,
&indices,
)
.unwrap();
// generate coin indices signatures
let coin_indices_signatures = generate_coin_indices_signatures(
&params,
&secret_keys_authorities,
&verification_keys_auth,
&verification_key,
&indices,
)
.unwrap();
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue(
&grp,
auth_keypair.secret_key(),
user_keypair.public_key(),
&req,
expiration_date,
);
wallet_blinded_signatures.push(blind_signature.unwrap());
}
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
wallet_blinded_signatures.iter(),
verification_keys_auth.iter()
)
.map(|(w, vk)| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
.collect();
// Aggregate partial wallets
let aggr_wallet = aggregate_wallets(
&grp,
&verification_key,
&user_keypair.secret_key(),
&unblinded_wallet_shares,
&req_info,
)
.unwrap();
// Let's try to spend some coins
let pay_info1 = PayInfo {
pay_info_bytes: [6u8; 88],
};
let spend_vv = 1;
let (payment1, _upd_wallet) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info1,
false,
spend_vv,
dates_signatures,
coin_indices_signatures,
spend_date,
)
.unwrap();
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1, spend_date)
.unwrap());
let payment2 = payment1.clone();
assert!(payment2
.spend_verify(&params, &verification_key, &pay_info1, spend_date)
.unwrap());
let pay_info2 = pay_info1.clone();
let identify_result = identify(
&verification_key,
payment1,
payment2,
pay_info1.clone(),
pay_info2.clone(),
)
.unwrap();
assert_eq!(
identify_result,
IdentifyResult::DuplicatePayInfo(pay_info1.clone())
);
}
#[test]
fn ok_if_two_different_payments() {
let total_coins = 32;
let params = setup(total_coins);
let grp = params.grp();
// NOTE: Make sure that the date timestamp are calculated at 00:00:00!!
let expiration_date = 1703721600; // Dec 28 2023 00:00:00
let spend_date = Scalar::from(1701907200); // Dec 07 2023 00:00:00
let user_keypair = generate_keypair_user(&grp);
let (req, req_info) =
withdrawal_request(grp, &user_keypair.secret_key(), expiration_date).unwrap();
let authorities_keypairs = ttp_keygen(&grp, 2, 3).unwrap();
let indices: [u64; 3] = [1, 2, 3];
let secret_keys_authorities: Vec<SecretKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.secret_key())
.collect();
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();
// generate valid dates signatures
let dates_signatures = generate_expiration_date_signatures(
&params,
expiration_date,
&secret_keys_authorities,
&verification_keys_auth,
&verification_key,
&indices,
)
.unwrap();
// generate coin indices signatures
let coin_indices_signatures = generate_coin_indices_signatures(
&params,
&secret_keys_authorities,
&verification_keys_auth,
&verification_key,
&indices,
)
.unwrap();
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue(
&grp,
auth_keypair.secret_key(),
user_keypair.public_key(),
&req,
expiration_date,
);
wallet_blinded_signatures.push(blind_signature.unwrap());
}
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
wallet_blinded_signatures.iter(),
verification_keys_auth.iter()
)
.map(|(w, vk)| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
.collect();
// Aggregate partial wallets
let aggr_wallet = aggregate_wallets(
&grp,
&verification_key,
&user_keypair.secret_key(),
&unblinded_wallet_shares,
&req_info,
)
.unwrap();
// Let's try to spend some coins
let pay_info1 = PayInfo {
pay_info_bytes: [6u8; 88],
};
let spend_vv = 1;
let (payment1, upd_wallet) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info1,
false,
spend_vv,
dates_signatures.clone(),
coin_indices_signatures.clone(),
spend_date,
)
.unwrap();
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1, spend_date)
.unwrap());
let pay_info2 = PayInfo {
pay_info_bytes: [7u8; 88],
};
let (payment2, _) = upd_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info2,
false,
spend_vv,
dates_signatures,
coin_indices_signatures,
spend_date,
)
.unwrap();
assert!(payment2
.spend_verify(&params, &verification_key, &pay_info2, spend_date)
.unwrap());
let identify_result = identify(
&verification_key,
payment1,
payment2,
pay_info1.clone(),
pay_info2.clone(),
)
.unwrap();
assert_eq!(identify_result, IdentifyResult::NotADuplicatePayment);
}
#[test]
fn two_payments_with_one_repeating_serial_number_but_different_pay_info() {
let total_coins = 32;
let params = setup(total_coins);
let grp = params.grp();
// NOTE: Make sure that the date timestamp are calculated at 00:00:00!!
let expiration_date = 1703721600; // Dec 28 2023 00:00:00
let spend_date = Scalar::from(1701907200); // Dec 07 2023 00:00:00
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(), expiration_date).unwrap();
let authorities_keypairs = ttp_keygen(&grp, 2, 3).unwrap();
let indices: [u64; 3] = [1, 2, 3];
let secret_keys_authorities: Vec<SecretKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.secret_key())
.collect();
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();
// generate valid dates signatures
let dates_signatures = generate_expiration_date_signatures(
&params,
expiration_date,
&secret_keys_authorities,
&verification_keys_auth,
&verification_key,
&indices,
)
.unwrap();
// generate coin indices signatures
let coin_indices_signatures = generate_coin_indices_signatures(
&params,
&secret_keys_authorities,
&verification_keys_auth,
&verification_key,
&indices,
)
.unwrap();
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue(
&grp,
auth_keypair.secret_key(),
user_keypair.public_key(),
&req,
expiration_date,
);
wallet_blinded_signatures.push(blind_signature.unwrap());
}
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
wallet_blinded_signatures.iter(),
verification_keys_auth.iter()
)
.map(|(w, vk)| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
.collect();
// Aggregate partial wallets
let aggr_wallet = aggregate_wallets(
&grp,
&verification_key,
&user_keypair.secret_key(),
&unblinded_wallet_shares,
&req_info,
)
.unwrap();
// Let's try to spend some coins
let pay_info1 = PayInfo {
pay_info_bytes: [6u8; 88],
};
let spend_vv = 1;
let (payment1, _upd_wallet) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info1,
false,
spend_vv,
dates_signatures.clone(),
coin_indices_signatures.clone(),
spend_date,
)
.unwrap();
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1, spend_date)
.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 {
pay_info_bytes: [7u8; 88],
};
let (payment2, _) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info2,
false,
spend_vv,
dates_signatures.clone(),
coin_indices_signatures.clone(),
spend_date,
)
.unwrap();
assert!(payment2
.spend_verify(&params, &verification_key, &pay_info2, spend_date)
.unwrap());
let identify_result = identify(
&verification_key,
payment1,
payment2,
pay_info1.clone(),
pay_info2.clone(),
)
.unwrap();
assert_eq!(
identify_result,
IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key())
);
}
#[test]
fn two_payments_with_multiple_repeating_serial_numbers_but_different_pay_info() {
let total_coins = 32;
let params = setup(total_coins);
let grp = params.grp();
// NOTE: Make sure that the date timestamp are calculated at 00:00:00!!
let expiration_date = 1703721600; // Dec 28 2023 00:00:00
let spend_date = Scalar::from(1701907200); // Dec 07 2023 00:00:00
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(), expiration_date).unwrap();
let authorities_keypairs = ttp_keygen(&grp, 2, 3).unwrap();
let indices: [u64; 3] = [1, 2, 3];
let secret_keys_authorities: Vec<SecretKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.secret_key())
.collect();
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();
// generate valid dates signatures
let dates_signatures = generate_expiration_date_signatures(
&params,
expiration_date,
&secret_keys_authorities,
&verification_keys_auth,
&verification_key,
&indices,
)
.unwrap();
// generate coin indices signatures
let coin_indices_signatures = generate_coin_indices_signatures(
&params,
&secret_keys_authorities,
&verification_keys_auth,
&verification_key,
&indices,
)
.unwrap();
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue(
&grp,
auth_keypair.secret_key(),
user_keypair.public_key(),
&req,
expiration_date,
);
wallet_blinded_signatures.push(blind_signature.unwrap());
}
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
wallet_blinded_signatures.iter(),
verification_keys_auth.iter()
)
.map(|(w, vk)| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
.collect();
// Aggregate partial wallets
let aggr_wallet = aggregate_wallets(
&grp,
&verification_key,
&user_keypair.secret_key(),
&unblinded_wallet_shares,
&req_info,
)
.unwrap();
// Let's try to spend some coins
let pay_info1 = PayInfo {
pay_info_bytes: [6u8; 88],
};
let spend_vv = 10;
let (payment1, _) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info1,
false,
spend_vv,
dates_signatures.clone(),
coin_indices_signatures.clone(),
spend_date,
)
.unwrap();
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1, spend_date)
.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 {
pay_info_bytes: [7u8; 88],
};
let (payment2, _) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info2,
false,
spend_vv,
dates_signatures.clone(),
coin_indices_signatures.clone(),
spend_date,
)
.unwrap();
let identify_result = identify(
&verification_key,
payment1,
payment2,
pay_info1.clone(),
pay_info2.clone(),
)
.unwrap();
assert_eq!(
identify_result,
IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key())
);
}
}
@@ -0,0 +1,450 @@
use core::borrow::Borrow;
use core::iter::Sum;
use core::ops::{Add, Mul};
use std::convert::TryFrom;
use std::convert::TryInto;
use bls12_381::{G1Projective, G2Projective, Scalar};
use group::Curve;
use crate::error::{CompactEcashError, Result};
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::setup::GroupParameters;
use crate::scheme::SignerIndex;
use crate::utils::Polynomial;
use crate::utils::{
try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
try_deserialize_scalar_vec,
};
#[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 get_ys(&self) -> Vec<Scalar> {
self.ys.clone()
}
pub(crate) fn get_y_by_idx(&self, i: usize) -> Option<&Scalar> {
self.ys.get(i)
}
pub fn verification_key(&self, params: &GroupParameters) -> VerificationKeyAuth {
let g1 = params.gen1();
let g2 = params.gen2();
VerificationKeyAuth {
alpha: g2 * self.x,
beta_g1: self.ys.iter().map(|y| g1 * y).collect(),
beta_g2: self.ys.iter().map(|y| g2 * y).collect(),
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let ys_len = self.ys.len();
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) as usize * 32);
bytes.extend_from_slice(&self.x.to_bytes());
bytes.extend_from_slice(&ys_len.to_le_bytes());
for y in self.ys.iter() {
bytes.extend_from_slice(&y.to_bytes())
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<SecretKeyAuth> {
SecretKeyAuth::try_from(bytes)
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct VerificationKeyAuth {
pub(crate) alpha: G2Projective,
pub(crate) beta_g1: Vec<G1Projective>,
pub(crate) beta_g2: Vec<G2Projective>,
}
impl TryFrom<&[u8]> for VerificationKeyAuth {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<VerificationKeyAuth> {
// There should be at least alpha, one betaG1 and one betaG2 and their length
if bytes.len() < 96 * 2 + 48 + 8 || (bytes.len() - 8 - 96) % (96 + 48) != 0 {
return Err(CompactEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8 - 96,
target: 96 * 2 + 48 + 8,
modulus: 96 + 48,
object: "verification key".to_string(),
});
}
// this conversion will not fail as we are taking the same length of data
let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
let betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
let actual_betas_len = (bytes.len() - 104) / (96 + 48);
if betas_len as usize != actual_betas_len {
return Err(
CompactEcashError::Deserialization(
format!("Tried to deserialize verification key with inconsistent betas len (expected {}, got {})",
betas_len, actual_betas_len
)));
}
let alpha = try_deserialize_g2_projective(
&alpha_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize verification key G2 point (alpha)".to_string(),
),
)?;
let mut beta_g1 = Vec::with_capacity(betas_len as usize);
let mut beta_g1_end: u64 = 0;
for i in 0..betas_len {
let start = (104 + i * 48) as usize;
let end = (start + 48) as usize;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g1_projective(
&beta_i_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize verification key G1 point (beta)".to_string(),
),
)?;
beta_g1_end = end as u64;
beta_g1.push(beta_i)
}
let mut beta_g2 = Vec::with_capacity(betas_len as usize);
for i in 0..betas_len {
let start = (beta_g1_end + i * 96) as usize;
let end = (start + 96) as usize;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g2_projective(
&beta_i_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize verification key G2 point (beta)".to_string(),
),
)?;
beta_g2.push(beta_i)
}
Ok(VerificationKeyAuth {
alpha,
beta_g1,
beta_g2,
})
}
}
impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth {
type Output = VerificationKeyAuth;
#[inline]
fn add(self, rhs: &'b VerificationKeyAuth) -> VerificationKeyAuth {
// If you're trying to add two keys together that were created
// for different number of attributes, just panic as it's a
// nonsense operation.
assert_eq!(
self.beta_g1.len(),
rhs.beta_g1.len(),
"trying to add verification keys generated for different number of attributes [G1]"
);
assert_eq!(
self.beta_g2.len(),
rhs.beta_g2.len(),
"trying to add verification keys generated for different number of attributes [G2]"
);
assert_eq!(
self.beta_g1.len(),
self.beta_g2.len(),
"this key is incorrect - the number of elements G1 and G2 does not match"
);
assert_eq!(
rhs.beta_g1.len(),
rhs.beta_g2.len(),
"they key you want to add is incorrect - the number of elements G1 and G2 does not match"
);
VerificationKeyAuth {
alpha: self.alpha + rhs.alpha,
beta_g1: self
.beta_g1
.iter()
.zip(rhs.beta_g1.iter())
.map(|(self_beta_g1, rhs_beta_g1)| self_beta_g1 + rhs_beta_g1)
.collect(),
beta_g2: self
.beta_g2
.iter()
.zip(rhs.beta_g2.iter())
.map(|(self_beta_g2, rhs_beta_g2)| self_beta_g2 + rhs_beta_g2)
.collect(),
}
}
}
impl<'a> Mul<Scalar> for &'a VerificationKeyAuth {
type Output = VerificationKeyAuth;
#[inline]
fn mul(self, rhs: Scalar) -> Self::Output {
VerificationKeyAuth {
alpha: self.alpha * rhs,
beta_g1: self.beta_g1.iter().map(|b_i| b_i * rhs).collect(),
beta_g2: self.beta_g2.iter().map(|b_i| b_i * rhs).collect(),
}
}
}
impl<T> Sum<T> for VerificationKeyAuth
where
T: Borrow<VerificationKeyAuth>,
{
#[inline]
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = T>,
{
let mut peekable = iter.peekable();
let head_attributes = match peekable.peek() {
Some(head) => head.borrow().beta_g2.len(),
None => {
// TODO: this is a really weird edge case. You're trying to sum an EMPTY iterator
// of VerificationKey. So should it panic here or just return some nonsense value?
return VerificationKeyAuth::identity(0);
}
};
peekable.fold(
VerificationKeyAuth::identity(head_attributes),
|acc, item| acc + item.borrow(),
)
}
}
impl VerificationKeyAuth {
/// Create a (kinda) identity verification key using specified
/// number of 'beta' elements
pub(crate) fn identity(beta_size: usize) -> Self {
VerificationKeyAuth {
alpha: G2Projective::identity(),
beta_g1: vec![G1Projective::identity(); beta_size],
beta_g2: vec![G2Projective::identity(); beta_size],
}
}
pub fn aggregate(sigs: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self> {
aggregate_verification_keys(sigs, indices)
}
pub fn alpha(&self) -> &G2Projective {
&self.alpha
}
pub fn beta_g1(&self) -> &Vec<G1Projective> {
&self.beta_g1
}
pub fn beta_g2(&self) -> &Vec<G2Projective> {
&self.beta_g2
}
pub fn to_bytes(&self) -> Vec<u8> {
let beta_g1_len = self.beta_g1.len();
let beta_g2_len = self.beta_g2.len();
let mut bytes = Vec::with_capacity(96 + 8 + beta_g1_len * 48 + beta_g2_len * 96);
bytes.extend_from_slice(&self.alpha.to_affine().to_compressed());
bytes.extend_from_slice(&beta_g1_len.to_le_bytes());
for beta_g1 in self.beta_g1.iter() {
bytes.extend_from_slice(&beta_g1.to_affine().to_compressed())
}
for beta_g2 in self.beta_g2.iter() {
bytes.extend_from_slice(&beta_g2.to_affine().to_compressed())
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<VerificationKeyAuth> {
VerificationKeyAuth::try_from(bytes)
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct SecretKeyUser {
pub sk: Scalar,
}
impl SecretKeyUser {
pub fn public_key(&self, params: &GroupParameters) -> PublicKeyUser {
PublicKeyUser {
pk: params.gen1() * self.sk,
}
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct PublicKeyUser {
pub(crate) pk: G1Projective,
}
pub struct KeyPairAuth {
secret_key: SecretKeyAuth,
verification_key: VerificationKeyAuth,
/// Optional index value specifying polynomial point used during threshold key generation.
pub index: Option<SignerIndex>,
}
impl KeyPairAuth {
pub fn secret_key(&self) -> SecretKeyAuth {
self.secret_key.clone()
}
pub fn verification_key(&self) -> VerificationKeyAuth {
self.verification_key.clone()
}
}
pub struct KeyPairUser {
secret_key: SecretKeyUser,
public_key: PublicKeyUser,
}
impl KeyPairUser {
pub fn secret_key(&self) -> SecretKeyUser {
self.secret_key.clone()
}
pub fn public_key(&self) -> PublicKeyUser {
self.public_key.clone()
}
}
pub fn generate_keypair_user(params: &GroupParameters) -> KeyPairUser {
let sk_user = SecretKeyUser {
sk: params.random_scalar(),
};
let pk_user = PublicKeyUser {
pk: params.gen1() * sk_user.sk,
};
KeyPairUser {
secret_key: sk_user,
public_key: pk_user,
}
}
pub fn ttp_keygen(
params: &GroupParameters,
threshold: u64,
num_authorities: u64,
) -> Result<Vec<KeyPairAuth>> {
if threshold == 0 {
return Err(CompactEcashError::Setup(
"Tried to generate threshold keys with a 0 threshold value".to_string(),
));
}
if threshold > num_authorities {
return Err(
CompactEcashError::Setup(
"Tried to generate threshold keys for threshold value being higher than number of the signing authorities".to_string(),
));
}
let attributes = params.gammas().len();
// generate polynomials
let v = Polynomial::new_random(params, threshold - 1);
let ws = (0..attributes + 1)
.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)
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,546 @@
use std::collections::HashMap;
use std::io::Read;
use std::ops::Index;
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar};
use ff::Field;
use group::{Curve, GroupEncoding};
use rand::thread_rng;
use crate::constants;
use crate::error::{CompactEcashError, Result};
use crate::scheme::keygen::{SecretKeyAuth, VerificationKeyAuth};
use crate::utils::{check_bilinear_pairing, generate_lagrangian_coefficients_at_origin};
use crate::utils::{hash_g1, try_deserialize_g1_projective, Signature};
use itertools::Itertools;
use rayon::prelude::*;
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..=constants::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 gammas_to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(self.gammas.len() * 48);
for g in &self.gammas {
bytes.extend_from_slice(&g.to_bytes().as_ref());
}
bytes
}
pub(crate) fn gamma_idx(&self, i: usize) -> Option<&G1Projective> {
self.gammas.get(i)
}
pub(crate) fn delta(&self) -> &G1Projective {
&self.delta
}
pub fn random_scalar(&self) -> Scalar {
// lazily-initialized thread-local random number generator, seeded by the system
let mut rng = thread_rng();
Scalar::random(&mut rng)
}
pub fn n_random_scalars(&self, n: usize) -> Vec<Scalar> {
(0..n).map(|_| self.random_scalar()).collect()
}
pub(crate) fn prepared_miller_g2(&self) -> &G2Prepared {
&self._g2_prepared_miller
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct SecretKeyRP {
pub(crate) x: Scalar,
pub(crate) y: Scalar,
}
impl SecretKeyRP {
pub fn public_key(&self, params: &GroupParameters) -> PublicKeyRP {
PublicKeyRP {
alpha: params.gen2() * self.x,
beta: params.gen2() * self.y,
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct PublicKeyRP {
pub(crate) alpha: G2Projective,
pub(crate) beta: G2Projective,
}
pub struct Parameters {
/// group parameters
grp: GroupParameters,
/// Number of coins of fixed denomination in the credential wallet; L in construction
total_coins: u64,
}
impl Parameters {
pub fn grp(&self) -> &GroupParameters {
&self.grp
}
pub fn get_total_coins(&self) -> u64 {
self.total_coins
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CoinIndexSignature {
pub(crate) h: G1Projective,
pub(crate) s: G1Projective,
}
pub type PartialCoinIndexSignature = CoinIndexSignature;
impl CoinIndexSignature {
pub fn randomise(&self, params: &GroupParameters) -> (CoinIndexSignature, Scalar) {
let r = params.random_scalar();
let r_prime = params.random_scalar();
let h_prime = self.h * r_prime;
let s_prime = (self.s * r_prime) + (h_prime * r);
(
CoinIndexSignature {
h: h_prime,
s: s_prime,
},
r,
)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes: Vec<u8> = Vec::with_capacity(48 + 48);
bytes.extend(self.h.to_affine().to_compressed());
bytes.extend(self.s.to_affine().to_compressed());
bytes
}
}
impl TryFrom<&[u8]> for CoinIndexSignature {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<CoinIndexSignature> {
if bytes.len() != 96 {
return Err(CompactEcashError::Deserialization(format!(
"CoinIndexSignature must be exactly 96 bytes, got {}",
bytes.len()
)));
}
let h_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
let s_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
let h = try_deserialize_g1_projective(
h_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed h of the CoinIndexSignature".to_string(),
),
)?;
let s = try_deserialize_g1_projective(
s_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed s of the CoinIndexSignature".to_string(),
),
)?;
Ok(CoinIndexSignature { h, s })
}
}
/// Signs coin indices.
///
/// This function takes cryptographic parameters, a global verification key, and a secret key of the signing authority,
/// and generates partial coin index signatures for a specified number of indices using a parallel fold operation.
///
/// # Arguments
///
/// * `params` - The cryptographic parameters used in the signing process.
/// * `vk` - The global verification key.
/// * `sk_auth` - The secret key associated with the individual signing authority.
///
/// # Returns
///
/// A vector containing partial coin index signatures.
///
/// # Panics
///
/// The function may panic if there is an issue with converting bytes to Scalar during initialization.
///
pub fn sign_coin_indices(
params: &Parameters,
vk: &VerificationKeyAuth,
sk_auth: &SecretKeyAuth,
) -> Vec<PartialCoinIndexSignature> {
let m1: Scalar = Scalar::from_bytes(&constants::TYPE_IDX).unwrap();
let m2: Scalar = Scalar::from_bytes(&constants::TYPE_IDX).unwrap();
(0..params.get_total_coins())
.into_par_iter()
.fold(
|| Vec::with_capacity(params.get_total_coins() as usize),
|mut partial_coins_signatures, l| {
let m0: Scalar = Scalar::from(l as u64);
// Compute the hash h
let mut concatenated_bytes =
Vec::with_capacity(vk.to_bytes().len() + l.to_le_bytes().len());
concatenated_bytes.extend_from_slice(&vk.to_bytes());
concatenated_bytes.extend_from_slice(&l.to_le_bytes());
let h = hash_g1(concatenated_bytes);
// Sign the attributes
let mut s_exponent = sk_auth.x;
s_exponent += &sk_auth.ys[0] * m0;
s_exponent += &sk_auth.ys[1] * m1;
s_exponent += &sk_auth.ys[2] * m2;
// Create the signature struct
let coin_idx_sign = PartialCoinIndexSignature {
h,
s: h * s_exponent,
};
partial_coins_signatures.push(coin_idx_sign);
partial_coins_signatures
},
)
.reduce(Vec::new, |mut v1, mut v2| {
v1.append(&mut v2);
v1
})
}
/// Verifies coin index signatures using parallel iterators.
///
/// This function takes cryptographic parameters, verification keys, and a list of coin index
/// signatures. It verifies each signature's commitment hash and performs a bilinear pairing check.
///
/// # Arguments
///
/// * `params` - The cryptographic parameters used in the verification process.
/// * `vk` - The global verification key.
/// * `vk_auth` - The verification key associated with the authority which issued the partial signatures.
/// * `signatures` - A slice containing coin index signatures to be verified.
///
/// # Returns
///
/// Returns `Ok(())` if all signatures are valid, otherwise returns an error with a description
/// of the verification failure.
///
/// # Panics
///
/// The function may panic if there is an issue with converting bytes to Scalar during initialization.
pub fn verify_coin_indices_signatures(
params: &Parameters,
vk: &VerificationKeyAuth,
vk_auth: &VerificationKeyAuth,
signatures: &[CoinIndexSignature],
) -> Result<()> {
let m1: Scalar = Scalar::from_bytes(&constants::TYPE_IDX).unwrap();
let m2: Scalar = Scalar::from_bytes(&constants::TYPE_IDX).unwrap();
// Precompute concatenated_bytes for each l
let concatenated_bytes_list: Vec<Vec<u8>> = signatures
.iter()
.enumerate()
.map(|(l, _)| {
let mut concatenated_bytes =
Vec::with_capacity(vk.to_bytes().len() + l.to_le_bytes().len());
concatenated_bytes.extend_from_slice(&vk.to_bytes());
concatenated_bytes.extend_from_slice(&(l as u64).to_le_bytes());
concatenated_bytes
})
.collect();
// Create a vector of m0 values
let m0_values: Vec<Scalar> = (0..signatures.len() as u64).map(Scalar::from).collect();
// Verify signatures using precomputed concatenated_bytes and m0 values
m0_values
.par_iter()
.zip(
signatures
.par_iter()
.zip(concatenated_bytes_list.par_iter()),
)
.enumerate()
.try_for_each(|(l, (m0, (sig, concatenated_bytes)))| {
// Compute the hash h
let h = hash_g1(concatenated_bytes.clone());
// Check if the hash is matching
if sig.h != h {
return Err(CompactEcashError::CoinIndices(
"Failed to verify the commitment hash".to_string(),
));
}
let partially_signed_attributes = [*m0, m1, m2]
.iter()
.zip(vk_auth.beta_g2.iter())
.map(|(m, beta_i)| beta_i * Scalar::from(*m))
.sum::<G2Projective>();
if !check_bilinear_pairing(
&sig.h.to_affine(),
&G2Prepared::from((vk_auth.alpha + partially_signed_attributes).to_affine()),
&sig.s.to_affine(),
params.grp().prepared_miller_g2(),
) {
return Err(CompactEcashError::CoinIndices(
"Verification of the coin signature failed".to_string(),
));
}
Ok(())
})?;
Ok(())
}
/// Aggregates and verifies partial coin index signatures.
///
/// This function takes cryptographic parameters, a master verification key, and a list of tuples
/// containing indices, verification keys, and partial coin index signatures from different authorities.
/// It aggregates these partial signatures into a final set of coin index signatures, and verifying the
/// final aggregated signatures.
///
/// # Arguments
///
/// * `params` - The cryptographic parameters used in the aggregation process.
/// * `vk` - The master verification key against which the partial signatures are verified.
/// * `signatures` - A slice of tuples, where each tuple contains an index, a verification key, and
/// a vector of partial coin index signatures from a specific authority.
///
/// # Returns
///
/// Returns a vector of aggregated coin index signatures if the aggregation is successful.
/// Otherwise, returns an error describing the nature of the failure.
///
/// # Panics
///
/// The function may panic if there is an issue with converting bytes to Scalar during initialization.
pub fn aggregate_indices_signatures(
params: &Parameters,
vk: &VerificationKeyAuth,
signatures: &[(u64, VerificationKeyAuth, Vec<PartialCoinIndexSignature>)],
) -> Result<Vec<CoinIndexSignature>> {
// Check if all indices are unique
if signatures
.iter()
.map(|(index, _, _)| index)
.unique()
.count()
!= signatures.len()
{
return Err(CompactEcashError::CoinIndices(
"Not enough unique indices shares".to_string(),
));
}
// Evaluate at 0 the Lagrange basis polynomials k_i
let coefficients = generate_lagrangian_coefficients_at_origin(
&signatures
.iter()
.map(|(index, _, _)| *index)
.collect::<Vec<_>>(),
);
let m1: Scalar = Scalar::from_bytes(&constants::TYPE_IDX).unwrap();
let m2: Scalar = Scalar::from_bytes(&constants::TYPE_IDX).unwrap();
// Verify that all signatures are valid
signatures
.par_iter()
.try_for_each(|(_, vk_auth, partial_signatures)| {
verify_coin_indices_signatures(&params, &vk, &vk_auth, &partial_signatures)
})?;
// Pre-allocate vectors
let mut aggregated_coin_signatures: Vec<CoinIndexSignature> =
Vec::with_capacity(params.get_total_coins() as usize);
for l in 0..params.get_total_coins() {
let m0: Scalar = Scalar::from(l);
// Compute the hash h
let mut concatenated_bytes =
Vec::with_capacity(vk.to_bytes().len() + l.to_le_bytes().len());
concatenated_bytes.extend_from_slice(&vk.to_bytes());
concatenated_bytes.extend_from_slice(&l.to_le_bytes());
let h = hash_g1(concatenated_bytes);
// Collect the partial signatures for the same coin index
let collected_at_l: Vec<_> = signatures
.iter()
.filter_map(|(_, _, inner_vec)| inner_vec.get(l as usize))
.cloned()
.collect();
// Aggregate partial signatures for each coin index
let aggr_s: G1Projective = coefficients
.iter()
.zip(collected_at_l.iter())
.map(|(coeff, sig)| sig.s * coeff)
.sum();
let aggr_sig = CoinIndexSignature { h, s: aggr_s };
aggregated_coin_signatures.push(aggr_sig);
}
verify_coin_indices_signatures(&params, &vk, &vk, &aggregated_coin_signatures)?;
Ok(aggregated_coin_signatures)
}
/// Generates parameters for the scheme setup.
///
/// # Arguments
///
/// * `total_coins` - it is the number of coins in a freshly generated wallet. It is the public parameter of the scheme.
///
/// # Returns
///
/// A `Parameters` struct containing group parameters, public key, the number of signatures (`total_coins`),
/// and a map of signatures for each index `l`.
///
pub fn setup(total_coins: u64) -> Parameters {
let grp = GroupParameters::new().unwrap();
Parameters { grp, total_coins }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::keygen::ttp_keygen;
#[test]
fn test_sign_coins() {
let total_coins = 32;
let params = setup(total_coins);
let authorities_keypairs = ttp_keygen(&params.grp(), 2, 3).unwrap();
let indices: [u64; 3] = [1, 2, 3];
// Pick one authority to do the signing
let sk_i_auth = authorities_keypairs[0].secret_key();
let vk_i_auth = authorities_keypairs[0].verification_key();
// list of verification keys of each authority
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
// the global master verification key
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
let partial_signatures = sign_coin_indices(&params, &verification_key, &sk_i_auth);
assert!(verify_coin_indices_signatures(
&params,
&verification_key,
&vk_i_auth,
&partial_signatures
)
.is_ok());
}
#[test]
fn test_sign_coins_fail() {
let total_coins = 32;
let params = setup(total_coins);
let authorities_keypairs = ttp_keygen(&params.grp(), 2, 3).unwrap();
let indices: [u64; 3] = [1, 2, 3];
// Pick one authority to do the signing
let sk_0_auth = authorities_keypairs[0].secret_key();
let vk_1_auth = authorities_keypairs[1].verification_key();
// list of verification keys of each authority
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
// the global master verification key
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
let partial_signatures = sign_coin_indices(&params, &verification_key, &sk_0_auth);
// Since we used a non matching verification key to verify the signature, the verification should fail
assert!(verify_coin_indices_signatures(
&params,
&verification_key,
&vk_1_auth,
&partial_signatures
)
.is_err());
}
#[test]
fn test_aggregate_coin_indices_signatures() {
let total_coins = 32;
let params = setup(total_coins);
let authorities_keypairs = ttp_keygen(&params.grp(), 2, 3).unwrap();
let indices: [u64; 3] = [1, 2, 3];
// list of secret keys of each authority
let secret_keys_authorities: Vec<SecretKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.secret_key())
.collect();
// list of verification keys of each authority
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
// the global master verification key
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
// create the partial signatures from each authority
let partial_signatures: Vec<Vec<PartialCoinIndexSignature>> = secret_keys_authorities
.iter()
.map(|sk_auth| sign_coin_indices(&params, &verification_key, sk_auth))
.collect();
let combined_data: Vec<(u64, VerificationKeyAuth, Vec<PartialCoinIndexSignature>)> =
indices
.iter()
.zip(verification_keys_auth.iter().zip(partial_signatures.iter()))
.map(|(i, (vk, sigs))| (i.clone(), vk.clone(), sigs.clone()))
.collect();
assert!(aggregate_indices_signatures(&params, &verification_key, &combined_data).is_ok());
}
}
@@ -0,0 +1,540 @@
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::utils::{check_bilinear_pairing, hash_g1, try_deserialize_g1_projective};
use crate::utils::{BlindedSignature, Signature};
/// Represents a withdrawal request generate by the client who wants to obtain a zk-nym credential.
///
/// This struct encapsulates the necessary components for a withdrawal request, including the joined commitment hash, the joined commitment,
/// individual Pedersen commitments for private attributes, and a zero-knowledge proof for the withdrawal request.
///
/// # Fields
///
/// * `joined_commitment_hash` - The joined commitment hash represented as a G1Projective element.
/// * `joined_commitment` - The joined commitment represented as a G1Projective element.
/// * `private_attributes_commitments` - A vector of individual Pedersen commitments for private attributes represented as G1Projective elements.
/// * `zk_proof` - The zero-knowledge proof for the withdrawal request.
///
/// # Derives
///
/// The struct derives `Debug` and `PartialEq` to provide debug output and basic comparison functionality.
///
#[derive(Debug, PartialEq)]
pub struct WithdrawalRequest {
joined_commitment_hash: G1Projective,
joined_commitment: G1Projective,
private_attributes_commitments: Vec<G1Projective>,
zk_proof: WithdrawalReqProof,
}
impl WithdrawalRequest {
/// Converts the withdrawal request to a byte vector.
///
/// The resulting byte vector contains the serialized representation of the withdrawal request,
/// including the joined commitment hash, the joined commitment, individual commitments for private attributes,
/// and the zero-knowledge proof.
///
/// # Returns
///
/// A `Vec<u8>` containing the serialized representation of the withdrawal request.
///
pub fn to_bytes(&self) -> Vec<u8> {
let joined_commitment_hash_bytes = self.joined_commitment_hash.to_affine().to_compressed();
let joined_commitment_bytes = self.joined_commitment.to_affine().to_compressed();
let private_attributes_len_bytes =
(self.private_attributes_commitments.len() as u64).to_le_bytes();
let private_attributes_commitments_bytes: Vec<u8> = self
.private_attributes_commitments
.iter()
.flat_map(|c| c.to_affine().to_compressed())
.collect();
let zk_proof_bytes = self.zk_proof.to_bytes();
let total_bytes_len = joined_commitment_hash_bytes.len()
+ joined_commitment_bytes.len()
+ private_attributes_len_bytes.len()
+ private_attributes_commitments_bytes.len()
+ zk_proof_bytes.len();
let mut bytes = Vec::with_capacity(total_bytes_len);
bytes.extend_from_slice(&joined_commitment_hash_bytes);
bytes.extend_from_slice(&joined_commitment_bytes);
bytes.extend_from_slice(&private_attributes_len_bytes);
bytes.extend_from_slice(&private_attributes_commitments_bytes);
bytes.extend_from_slice(&zk_proof_bytes);
bytes
}
}
/// Attempts to deserialize a `WithdrawalRequest` from a byte slice.
///
/// # Arguments
///
/// * `bytes` - A byte slice containing the serialized `WithdrawalRequest`.
///
/// # Errors
///
/// Returns a `CompactEcashError` if deserialization fails, including cases where the byte slice
/// length is insufficient or deserialization of internal components fails.
///
impl TryFrom<&[u8]> for WithdrawalRequest {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<WithdrawalRequest> {
let joined_commitment_hash_bytes_len = 48;
let joined_commitment_bytes_len = 48;
let private_attributes_len_bytes = 8;
let private_attributes_commitments_bytes_len = 48;
let min_length = joined_commitment_hash_bytes_len
+ joined_commitment_bytes_len
+ private_attributes_len_bytes
+ private_attributes_commitments_bytes_len;
if bytes.len() < min_length {
return Err(CompactEcashError::DeserializationMinLength {
min: min_length,
actual: bytes.len(),
});
}
let mut j = 0;
let joined_commitment_hash_bytes = bytes[..j + joined_commitment_hash_bytes_len]
.try_into()
.unwrap();
let joined_commitment_hash = try_deserialize_g1_projective(
&joined_commitment_hash_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed commitment hash".to_string(),
),
)?;
j += joined_commitment_hash_bytes_len;
let joined_commitment_bytes = bytes[j..j + joined_commitment_bytes_len]
.try_into()
.unwrap();
let joined_commitment = try_deserialize_g1_projective(
&joined_commitment_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed commitment".to_string(),
),
)?;
j += joined_commitment_bytes_len;
let private_attributes_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < private_attributes_len as usize * 48 {
return Err(CompactEcashError::DeserializationMinLength {
min: private_attributes_len as usize * 48,
actual: bytes[56..].len(),
});
}
let mut private_attributes_commitments =
Vec::with_capacity(private_attributes_len as usize);
for i in 0..private_attributes_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(),
),
)?;
private_attributes_commitments.push(pc_com)
}
let zk_proof =
WithdrawalReqProof::try_from(&bytes[j + private_attributes_len as usize * 48..])?;
Ok(WithdrawalRequest {
joined_commitment_hash,
joined_commitment,
private_attributes_commitments,
zk_proof,
})
}
}
/// Represents information associated with a withdrawal request.
///
/// This structure holds the commitment hash, commitment opening, private attributes openings,
/// the wallet secret (scalar), and the expiration date related to a withdrawal request.
pub struct RequestInfo {
joined_commitment_hash: G1Projective,
joined_commitment_opening: Scalar,
private_attributes_openings: Vec<Scalar>,
wallet_secret: Scalar,
expiration_date: Scalar,
}
impl RequestInfo {
pub fn get_joined_commitment_hash(&self) -> &G1Projective {
&self.joined_commitment_hash
}
pub fn get_joined_commitment_opening(&self) -> &Scalar {
&self.joined_commitment_opening
}
pub fn get_private_attributes_openings(&self) -> &[Scalar] {
&self.private_attributes_openings
}
pub fn get_v(&self) -> &Scalar {
&self.wallet_secret
}
pub fn get_expiration_date(&self) -> &Scalar {
&self.expiration_date
}
}
/// Computes Pedersen commitments for private attributes.
///
/// Given a set of private attributes and the commitment hash for all attributes,
/// this function generates random blinding factors (`openings`) and computes corresponding
/// Pedersen commitments for each private attribute.
/// Pedersen commitments have the hiding and binding properties, providing a secure way
/// to represent private values in a commitment scheme.
///
/// # Arguments
///
/// * `params` - Group parameters for the cryptographic group.
/// * `joined_commitment_hash` - The commitment hash to be used in the Pedersen commitments.
/// * `private_attributes` - A slice of private attributes to be committed.
///
/// # Returns
///
/// A tuple containing vectors of blinding factors (`openings`) and corresponding
/// Pedersen commitments for each private attribute.
fn compute_private_attribute_commitments(
params: &GroupParameters,
joined_commitment_hash: &G1Projective,
private_attributes: &[Scalar],
) -> (Vec<Scalar>, Vec<G1Projective>) {
let (openings, commitments): (Vec<Scalar>, Vec<G1Projective>) = private_attributes
.iter()
.map(|m_j| {
let o_j = params.random_scalar();
(o_j, params.gen1() * o_j + joined_commitment_hash * m_j)
})
.unzip();
(openings, commitments)
}
/// Generates a withdrawal request for the given user to request a zk-nym credential wallet.
///
/// # Arguments
///
/// * `params` - A reference to the group parameters used in the protocol.
/// * `sk_user` - A reference to the user's secret key.
/// * `expiration_date` - The expiration date for the withdrawal request.
///
/// # Returns
///
/// A tuple containing the generated `WithdrawalRequest` and `RequestInfo`, or an error if the operation fails.
///
/// # Details
///
/// The function starts by generating a random, unique wallet secret `v` and computing the joined commitment for all attributes,
/// including public (expiration date) and private ones (user secret key and wallet secret).
/// It then calculates the commitment hash (`joined_commitment_hash`) and computes Pedersen commitments for private attributes.
/// A zero-knowledge proof of knowledge is constructed to prove possession of specific attributes.
///
/// The resulting `WithdrawalRequest` includes the commitment hash, joined commitment, commitments for private
/// attributes, and the constructed zero-knowledge proof.
///
/// The associated `RequestInfo` includes information such as commitment hash, commitment opening,
/// openings for private attributes, `v`, and the expiration date.
pub fn withdrawal_request(
params: &GroupParameters,
sk_user: &SecretKeyUser,
expiration_date: u64,
) -> Result<(WithdrawalRequest, RequestInfo)> {
let gammas = params.gammas();
// Generate random and unique wallet secret
let v = params.random_scalar();
let joined_commitment_opening = params.random_scalar();
// Compute joined commitment for all attributes (public and private)
let joined_commitment: G1Projective = params.gen1() * joined_commitment_opening
+ params.gamma_idx(0).unwrap() * sk_user.sk
+ params.gamma_idx(1).unwrap() * v;
// Compute commitment hash h
let joined_commitment_hash = hash_g1(
(joined_commitment + params.gamma_idx(2).unwrap() * Scalar::from(expiration_date))
.to_bytes(),
);
// Compute Pedersen commitments for private attributes (wallet secret and user's secret)
let private_attributes = vec![sk_user.sk, v];
let (private_attributes_openings, private_attributes_commitments) =
compute_private_attribute_commitments(
&params,
&joined_commitment_hash,
&private_attributes,
);
// construct a NIZK proof of knowledge proving possession of m1, m2, o, o1, o2
let instance = WithdrawalReqInstance {
joined_commitment,
joined_commitment_hash,
private_attributes_commitments: private_attributes_commitments.clone(),
pk_user: PublicKeyUser {
pk: params.gen1() * sk_user.sk,
},
};
let witness = WithdrawalReqWitness {
private_attributes,
joined_commitment_opening,
private_attributes_openings: private_attributes_openings.clone(),
};
let zk_proof = WithdrawalReqProof::construct(&params, &instance, &witness);
// Create and return WithdrawalRequest and RequestInfo
Ok((
WithdrawalRequest {
joined_commitment_hash,
joined_commitment,
private_attributes_commitments,
zk_proof,
},
RequestInfo {
joined_commitment_hash,
joined_commitment_opening,
private_attributes_openings: private_attributes_openings.clone(),
wallet_secret: v,
expiration_date: Scalar::from(expiration_date),
},
))
}
/// Verifies the integrity of a withdrawal request, including the joined commitment hash
/// and the zero-knowledge proof of knowledge.
///
/// # Arguments
///
/// * `params` - Group parameters used in the cryptographic operations.
/// * `req` - The withdrawal request to be verified.
/// * `pk_user` - Public key of the user associated with the withdrawal request.
/// * `expiration_date` - Expiration date for the withdrawal request.
///
/// # Returns
///
/// Returns `Ok(true)` if the verification is successful, otherwise returns an error
/// with a specific message indicating the verification failure.
pub fn request_verify(
params: &GroupParameters,
req: &WithdrawalRequest,
pk_user: PublicKeyUser,
expiration_date: u64,
) -> Result<bool> {
// Verify the joined commitment hash
let expected_commitment_hash = hash_g1(
(req.joined_commitment + params.gamma_idx(2).unwrap() * Scalar::from(expiration_date))
.to_bytes(),
);
if req.joined_commitment_hash != expected_commitment_hash {
return Err(CompactEcashError::WithdrawalRequestVerification(
"Failed to verify the commitment hash".to_string(),
));
}
// Verify zk proof
let instance = WithdrawalReqInstance {
joined_commitment: req.joined_commitment,
joined_commitment_hash: req.joined_commitment_hash,
private_attributes_commitments: req.private_attributes_commitments.clone(),
pk_user,
};
if !req.zk_proof.verify(&params, &instance) {
return Err(CompactEcashError::WithdrawalRequestVerification(
"Failed to verify the proof of knowledge".to_string(),
));
}
Ok(true)
}
/// Function to blind sign a private attribute commitments.
/// Given a private attribute commitment (`private_attribute_commitment`) and an element of the signing key,
/// this function computes the blinded commitment by multiplying the commitment with the blinding factor.
///
/// # Arguments
///
/// * `private_attribute_commitment` - The G1Projective point representing the commitment to the private attribute.
/// * `yi` - The element of the secret key of the signing authority.
///
/// # Returns
///
/// A new G1Projective point representing the blinded commitment.
///
pub fn blind_sing_private_attribute(
private_attribute_commitment: &G1Projective,
yi: &Scalar,
) -> G1Projective {
private_attribute_commitment * yi
}
/// Signs an expiration date using a joined commitment hash and a secret key.
///
/// Given a joined commitment hash (`joined_commitment_hash`), an expiration date (`expiration_date`),
/// and a secret key for authentication (`sk_auth`), this function computes the signature of the
/// expiration date by multiplying the commitment hash with the blinding factor derived from the secret key
/// and the expiration date.
///
/// # Arguments
///
/// * `joined_commitment_hash` - The G1Projective point representing the joined commitment hash.
/// * `expiration_date` - The expiration date timestamp to be signed.
/// * `sk_auth` - The secret key of the signing authority.
///
/// # Returns
///
/// A `Result` containing the resulting G1Projective point if successful, or an error if the
/// authentication secret key index is out of bounds.
pub fn sign_expiration_date(
joined_commitment_hash: &G1Projective,
expiration_date: u64,
sk_auth: &SecretKeyAuth,
) -> Result<G1Projective> {
if let Some(yi) = sk_auth.get_y_by_idx(2) {
Ok(joined_commitment_hash * (yi * Scalar::from(expiration_date)))
} else {
return Err(CompactEcashError::Issuance(
"The secret key of the authority does not have enough elements".to_string(),
));
}
}
/// Issues a blinded signature for a withdrawal request, after verifying its integrity.
///
/// This function first verifies the withdrawal request using the provided group parameters,
/// user's public key, and expiration date. If the verification is successful,
/// the function proceeds to blind sign the private attributes and sign the expiration date,
/// combining both signatures into a final signature.
///
/// # Arguments
///
/// * `params` - Group parameters used in the cryptographic operations.
/// * `sk_auth` - Secret key of the signing authority.
/// * `pk_user` - Public key of the user associated with the withdrawal request.
/// * `withdrawal_req` - The withdrawal request to be signed.
/// * `expiration_date` - Expiration date for the withdrawal request.
///
/// # Returns
///
/// Returns a `BlindedSignature` if the issuance process is successful, otherwise returns an error
/// with a specific message indicating the failure.
pub fn issue(
params: &GroupParameters,
sk_auth: SecretKeyAuth,
pk_user: PublicKeyUser,
withdrawal_req: &WithdrawalRequest,
expiration_date: u64,
) -> Result<BlindedSignature> {
// Verify the withdrawal request
request_verify(&params, &withdrawal_req, pk_user, expiration_date)?;
// Blind sign the private attributes
let blind_signatures: G1Projective = withdrawal_req
.private_attributes_commitments
.iter()
.zip(sk_auth.ys.iter().take(2))
.map(|(pc, yi)| blind_sing_private_attribute(pc, yi))
.sum();
// Sign the expiration date
let expiration_date_sign = sign_expiration_date(
&withdrawal_req.joined_commitment_hash,
expiration_date,
&sk_auth,
)?;
// Combine both signatures
let signature =
blind_signatures + withdrawal_req.joined_commitment_hash * sk_auth.x + expiration_date_sign;
Ok(BlindedSignature(
withdrawal_req.joined_commitment_hash,
signature,
))
}
/// Verifies the integrity and correctness of a blinded signature
/// and returns an unblinded partial zk-nym wallet.
///
/// This function first verifies the integrity of the received blinded signature by checking
/// if the joined commitment hash matches the one provided in the `req_info`. If the verification
/// is successful, it proceeds to unblind the blinded signature and verify its correctness.
///
/// # Arguments
///
/// * `params` - Group parameters used in the cryptographic operations.
/// * `vk_auth` - Verification key of the signing authority.
/// * `sk_user` - Secret key of the user.
/// * `blind_signature` - Blinded signature received from the authority.
/// * `req_info` - Information associated with the request, including the joined commitment hash,
/// private attributes openings, v, and expiration date.
///
/// # Returns
///
/// Returns a `PartialWallet` if the verification process is successful, otherwise returns an error
/// with a specific message indicating the failure.
pub fn issue_verify(
params: &GroupParameters,
vk_auth: &VerificationKeyAuth,
sk_user: &SecretKeyUser,
blind_signature: &BlindedSignature,
req_info: &RequestInfo,
) -> Result<PartialWallet> {
// Verify the integrity of the response from the authority
if req_info.joined_commitment_hash != blind_signature.0 {
return Err(CompactEcashError::IssuanceVfy(
"Integrity verification failed".to_string(),
));
}
// Unblind the blinded signature on the partial signature
let blinding_removers = vk_auth
.beta_g1
.iter()
.zip(&req_info.private_attributes_openings)
.map(|(beta, opening)| beta * opening)
.sum::<G1Projective>();
let unblinded_c = blind_signature.1 - blinding_removers;
let attr = vec![sk_user.sk, req_info.wallet_secret, req_info.expiration_date];
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(
&blind_signature.0.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(blind_signature.0, unblinded_c),
v: req_info.wallet_secret,
idx: None,
expiration_date: req_info.expiration_date,
})
}
@@ -0,0 +1,190 @@
use itertools::izip;
use crate::constants;
use crate::error::{CompactEcashError, Result};
use crate::scheme::aggregation::{aggregate_verification_keys, aggregate_wallets};
use crate::scheme::expiration_date_signatures::{
aggregate_expiration_signatures, sign_expiration_date, ExpirationDateSignature,
PartialExpirationDateSignature,
};
use crate::scheme::keygen::{
generate_keypair_user, ttp_keygen, SecretKeyAuth, VerificationKeyAuth,
};
use crate::scheme::setup::{
aggregate_indices_signatures, setup, sign_coin_indices, CoinIndexSignature, Parameters,
PartialCoinIndexSignature,
};
use crate::scheme::withdrawal::{issue, issue_verify, withdrawal_request, WithdrawalRequest};
use crate::scheme::PayInfo;
use crate::scheme::{PartialWallet, Payment, Wallet};
use bls12_381::Scalar;
pub fn generate_expiration_date_signatures(
params: &Parameters,
expiration_date: u64,
secret_keys_authorities: &[SecretKeyAuth],
verification_keys_auth: &[VerificationKeyAuth],
verification_key: &VerificationKeyAuth,
indices: &[u64],
) -> Result<Vec<ExpirationDateSignature>> {
let mut edt_partial_signatures: Vec<Vec<PartialExpirationDateSignature>> =
Vec::with_capacity(constants::VALIDITY_PERIOD as usize);
for sk_auth in secret_keys_authorities.iter() {
let sign = sign_expiration_date(&params, &sk_auth, expiration_date);
edt_partial_signatures.push(sign);
}
let combined_data: Vec<(
u64,
VerificationKeyAuth,
Vec<PartialExpirationDateSignature>,
)> = indices
.iter()
.zip(
verification_keys_auth
.iter()
.zip(edt_partial_signatures.iter()),
)
.map(|(i, (vk, sigs))| (i.clone(), vk.clone(), sigs.clone()))
.collect();
aggregate_expiration_signatures(&params, &verification_key, expiration_date, &combined_data)
}
pub fn generate_coin_indices_signatures(
params: &Parameters,
secret_keys_authorities: &[SecretKeyAuth],
verification_keys_auth: &[VerificationKeyAuth],
verification_key: &VerificationKeyAuth,
indices: &[u64],
) -> Result<Vec<CoinIndexSignature>> {
// create the partial signatures from each authority
let partial_signatures: Vec<Vec<PartialCoinIndexSignature>> = secret_keys_authorities
.iter()
.map(|sk_auth| sign_coin_indices(&params, &verification_key, sk_auth))
.collect();
let combined_data: Vec<(u64, VerificationKeyAuth, Vec<PartialCoinIndexSignature>)> = indices
.iter()
.zip(verification_keys_auth.iter().zip(partial_signatures.iter()))
.map(|(i, (vk, sigs))| (i.clone(), vk.clone(), sigs.clone()))
.collect();
aggregate_indices_signatures(&params, &verification_key, &combined_data)
}
#[test]
fn main() -> Result<()> {
let total_coins = 32;
let params = setup(total_coins);
let grp_params = params.grp();
// NOTE: Make sure that the date timestamp are calculated at 00:00:00!!
let expiration_date = 1703721600; // Dec 28 2023 00:00:00
let spend_date = Scalar::from(1701907200); // Dec 07 2023 00:00:00
let user_keypair = generate_keypair_user(&grp_params);
// generate authorities keys
let authorities_keypairs = ttp_keygen(&grp_params, 2, 3).unwrap();
let indices: [u64; 3] = [1, 2, 3];
let secret_keys_authorities: Vec<SecretKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.secret_key())
.collect();
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]))?;
// generate valid dates signatures
let dates_signatures = generate_expiration_date_signatures(
&params,
expiration_date,
&secret_keys_authorities,
&verification_keys_auth,
&verification_key,
&indices,
)?;
// generate coin indices signatures
let coin_indices_signatures = generate_coin_indices_signatures(
&params,
&secret_keys_authorities,
&verification_keys_auth,
&verification_key,
&indices,
)?;
// request a wallet
let (req, req_info) =
withdrawal_request(grp_params, &user_keypair.secret_key(), expiration_date).unwrap();
let req_bytes = req.to_bytes();
let req2 = WithdrawalRequest::try_from(req_bytes.as_slice()).unwrap();
assert_eq!(req, req2);
// issue partial wallets
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue(
&grp_params,
auth_keypair.secret_key(),
user_keypair.public_key(),
&req,
expiration_date,
);
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_params, 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(
&grp_params,
&verification_key,
&user_keypair.secret_key(),
&unblinded_wallet_shares,
&req_info,
)?;
let wallet_bytes = aggr_wallet.to_bytes();
let wallet = Wallet::try_from(&wallet_bytes[..]).unwrap();
assert_eq!(aggr_wallet, wallet);
// Let's try to spend some coins
let pay_info = PayInfo {
pay_info_bytes: [6u8; 88],
};
let spend_vv = 1;
let (payment, _) = aggr_wallet.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info,
false,
spend_vv,
dates_signatures,
coin_indices_signatures,
spend_date,
)?;
assert!(payment
.spend_verify(&params, &verification_key, &pay_info, spend_date)
.unwrap());
let payment_bytes = payment.to_bytes();
let payment2 = Payment::try_from(&payment_bytes[..]).unwrap();
assert_eq!(payment, payment2);
Ok(())
}
@@ -0,0 +1 @@
mod e2e;
@@ -0,0 +1,22 @@
use crate::CompactEcashError;
pub trait Bytable
where
Self: Sized,
{
fn to_byte_vec(&self) -> Vec<u8>;
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CompactEcashError>;
}
pub trait Base58
where
Self: Bytable,
{
fn try_from_bs58<S: AsRef<str>>(x: S) -> Result<Self, CompactEcashError> {
Self::try_from_byte_slice(&bs58::decode(x.as_ref()).into_vec().unwrap())
}
fn to_bs58(&self) -> String {
bs58::encode(self.to_byte_vec()).into_string()
}
}
@@ -0,0 +1,432 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use core::iter::Sum;
use core::ops::Mul;
use std::convert::{TryFrom, TryInto};
use std::ops::Neg;
use bls12_381::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]
pub fn generate_lagrangian_coefficients_at_origin(points: &[u64]) -> Vec<Scalar> {
let x = Scalar::zero();
points
.iter()
.enumerate()
.map(|(i, point_i)| {
let mut numerator = Scalar::one();
let mut denominator = Scalar::one();
let xi = Scalar::from(*point_i);
for (j, point_j) in points.iter().enumerate() {
if j != i {
let xj = Scalar::from(*point_j);
// numerator = (x - xs[0]) * ... * (x - xs[j]), j != i
numerator *= x - xj;
// denominator = (xs[i] - x[0]) * ... * (xs[i] - x[j]), j != i
denominator *= xi - xj;
}
}
// numerator / denominator
numerator * denominator.invert().unwrap()
})
.collect()
}
/// Performs a Lagrange interpolation at the origin for a polynomial defined by `points` and `values`.
/// It can be used for Scalars, G1 and G2 points.
pub(crate) fn perform_lagrangian_interpolation_at_origin<T>(
points: &[SignerIndex],
values: &[T],
) -> Result<T>
where
T: Sum,
for<'a> &'a T: Mul<Scalar, Output = T>,
{
if points.is_empty() || values.is_empty() {
return Err(CompactEcashError::Interpolation(
"Tried to perform lagrangian interpolation for an empty set of coordinates".to_string(),
));
}
if points.len() != values.len() {
return Err(CompactEcashError::Interpolation(
"Tried to perform lagrangian interpolation for an incomplete set of coordinates"
.to_string(),
));
}
let coefficients = generate_lagrangian_coefficients_at_origin(points);
Ok(coefficients
.into_iter()
.zip(values.iter())
.map(|(coeff, val)| val * coeff)
.sum())
}
// A temporary way of hashing particular message into G1.
// Implementation idea was taken from `threshold_crypto`:
// https://github.com/poanetwork/threshold_crypto/blob/7709462f2df487ada3bb3243060504b5881f2628/src/lib.rs#L691
// Eventually it should get replaced by, most likely, the osswu map
// method once ideally it's implemented inside the pairing crate.
// note: I have absolutely no idea what are the correct domains for those. I just used whatever
// was given in the test vectors of `Hashing to Elliptic Curves draft-irtf-cfrg-hash-to-curve-11`
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.9.1
const G1_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_";
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-K.1
const SCALAR_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-expander";
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
}
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
let mut output = vec![Scalar::zero()];
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
msg.as_ref(),
SCALAR_HASH_DOMAIN,
&mut output,
);
output[0]
}
pub fn try_deserialize_scalar_vec(
expected_len: u64,
bytes: &[u8],
err: CompactEcashError,
) -> Result<Vec<Scalar>> {
if bytes.len() != expected_len as usize * 32 {
return Err(err);
}
let mut out = Vec::with_capacity(expected_len as usize);
for i in 0..expected_len as usize {
let s_bytes = bytes[i * 32..(i + 1) * 32].try_into().unwrap();
let s = match Into::<Option<Scalar>>::into(Scalar::from_bytes(&s_bytes)) {
None => return Err(err),
Some(scalar) => scalar,
};
out.push(s)
}
Ok(out)
}
pub fn try_deserialize_scalar(bytes: &[u8; 32], err: CompactEcashError) -> Result<Scalar> {
Into::<Option<Scalar>>::into(Scalar::from_bytes(bytes)).ok_or(err)
}
pub fn try_deserialize_g1_projective(
bytes: &[u8; 48],
err: CompactEcashError,
) -> Result<G1Projective> {
Into::<Option<G1Affine>>::into(G1Affine::from_compressed(bytes))
.ok_or(err)
.map(G1Projective::from)
}
pub fn try_deserialize_g2_projective(
bytes: &[u8; 96],
err: CompactEcashError,
) -> Result<G2Projective> {
Into::<Option<G2Affine>>::into(G2Affine::from_compressed(bytes))
.ok_or(err)
.map(G2Projective::from)
}
/// Checks whether e(P, Q) * e(-R, S) == id
pub fn check_bilinear_pairing(p: &G1Affine, q: &G2Prepared, r: &G1Affine, s: &G2Prepared) -> bool {
// checking e(P, Q) * e(-R, S) == id
// is equivalent to checking e(P, Q) == e(R, S)
// but requires only a single final exponentiation rather than two of them
// and therefore, as seen via benchmarks.rs, is almost 50% faster
// (1.47ms vs 2.45ms, tested on R9 5900X)
let multi_miller = multi_miller_loop(&[(p, q), (&r.neg(), s)]);
multi_miller.final_exponentiation().is_identity().into()
}
pub type SignerIndex = u64;
#[derive(Debug, Clone, Copy, PartialEq)]
// #[cfg_attr(test, derive(PartialEq))]
pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective);
pub type PartialSignature = Signature;
impl TryFrom<&[u8]> for Signature {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<Signature> {
if bytes.len() != 96 {
return Err(CompactEcashError::Deserialization(format!(
"Signature must be exactly 96 bytes, got {}",
bytes.len()
)));
}
let sig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
let sig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
let sig1 = try_deserialize_g1_projective(
sig1_bytes,
CompactEcashError::Deserialization("Failed to deserialize compressed sig1".to_string()),
)?;
let sig2 = try_deserialize_g1_projective(
sig2_bytes,
CompactEcashError::Deserialization("Failed to deserialize compressed sig2".to_string()),
)?;
Ok(Signature(sig1, sig2))
}
}
impl Signature {
pub(crate) fn sig1(&self) -> &G1Projective {
&self.0
}
pub(crate) fn sig2(&self) -> &G1Projective {
&self.1
}
pub fn randomise(&self, params: &GroupParameters) -> (Signature, Scalar) {
let r = params.random_scalar();
let r_prime = params.random_scalar();
let h_prime = self.0 * r_prime;
let s_prime = (self.1 * r_prime) + (h_prime * r);
(Signature(h_prime, s_prime), r)
}
pub fn to_bytes(self) -> [u8; 96] {
let mut bytes = [0u8; 96];
bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed());
bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Signature> {
Signature::try_from(bytes)
}
}
impl Bytable for Signature {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
Signature::from_bytes(slice)
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct BlindedSignature(pub(crate) G1Projective, pub(crate) G1Projective);
pub struct SignatureShare {
signature: Signature,
index: SignerIndex,
}
impl SignatureShare {
pub fn new(signature: Signature, index: SignerIndex) -> Self {
SignatureShare { signature, index }
}
pub fn signature(&self) -> &Signature {
&self.signature
}
pub fn index(&self) -> SignerIndex {
self.index
}
// pub fn aggregate(shares: &[Self]) -> Result<Signature> {
// aggregate_signature_shares(shares)
// }
}
#[cfg(test)]
mod tests {
use rand::RngCore;
use super::*;
#[test]
fn polynomial_evaluation() {
// y = 42 (it should be 42 regardless of x)
let poly = Polynomial {
coefficients: vec![Scalar::from(42)],
};
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(1)));
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(0)));
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(10)));
// y = x + 10, at x = 2 (exp: 12)
let poly = Polynomial {
coefficients: vec![Scalar::from(10), Scalar::from(1)],
};
assert_eq!(Scalar::from(12), poly.evaluate(&Scalar::from(2)));
// y = x^4 - 5x^2 + 2x - 3, at x = 3 (exp: 39)
let poly = Polynomial {
coefficients: vec![
(-Scalar::from(3)),
Scalar::from(2),
(-Scalar::from(5)),
Scalar::zero(),
Scalar::from(1),
],
};
assert_eq!(Scalar::from(39), poly.evaluate(&Scalar::from(3)));
// empty polynomial
let poly = Polynomial {
coefficients: vec![],
};
// should always be 0
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(1)));
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(0)));
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(10)));
}
#[test]
fn performing_lagrangian_scalar_interpolation_at_origin() {
// x^2 + 3
// x, f(x):
// 1, 4,
// 2, 7,
// 3, 12,
let points = vec![1, 2, 3];
let values = vec![Scalar::from(4), Scalar::from(7), Scalar::from(12)];
assert_eq!(
Scalar::from(3),
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
);
// x^3 + 3x^2 - 5x + 11
// x, f(x):
// 1, 10
// 2, 21
// 3, 50
// 4, 103
let points = vec![1, 2, 3, 4];
let values = vec![
Scalar::from(10),
Scalar::from(21),
Scalar::from(50),
Scalar::from(103),
];
assert_eq!(
Scalar::from(11),
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
);
// more points than it is required
// x^2 + x + 10
// x, f(x)
// 1, 12
// 2, 16
// 3, 22
// 4, 30
// 5, 40
let points = vec![1, 2, 3, 4, 5];
let values = vec![
Scalar::from(12),
Scalar::from(16),
Scalar::from(22),
Scalar::from(30),
Scalar::from(40),
];
assert_eq!(
Scalar::from(10),
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
);
}
#[test]
fn hash_g1_sanity_check() {
let mut rng = rand::thread_rng();
let mut msg1 = [0u8; 1024];
rng.fill_bytes(&mut msg1);
let mut msg2 = [0u8; 1024];
rng.fill_bytes(&mut msg2);
assert_eq!(hash_g1(msg1), hash_g1(msg1));
assert_eq!(hash_g1(msg2), hash_g1(msg2));
assert_ne!(hash_g1(msg1), hash_g1(msg2));
}
#[test]
fn hash_scalar_sanity_check() {
let mut rng = rand::thread_rng();
let mut msg1 = [0u8; 1024];
rng.fill_bytes(&mut msg1);
let mut msg2 = [0u8; 1024];
rng.fill_bytes(&mut msg2);
assert_eq!(hash_to_scalar(msg1), hash_to_scalar(msg1));
assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2));
assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2));
}
}
@@ -0,0 +1,32 @@
[package]
name = "nym_offline_divisible_ecash"
version = "0.1.0"
authors = ["Ania Piotrowska <ania@nymtech.net>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
#bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch = "gt-serialisation", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
bls12_381 = { path = "/Users/ania/Documents/Git/andrew_bls12_381", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
itertools = "0.10"
digest = "0.9"
rand = "0.8"
thiserror = "1.0"
sha2 = "0.9"
bs58 = "0.4.0"
[dev-dependencies]
criterion = { version = "0.3", features = ["html_reports"] }
[dependencies.ff]
version = "0.11"
default-features = false
[dependencies.group]
version = "0.11"
default-features = false
[[bench]]
name = "benchmarks"
harness = false
@@ -0,0 +1,329 @@
use std::collections::HashSet;
use std::ops::Neg;
use std::time::Duration;
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, multi_miller_loop, Scalar};
use criterion::{Criterion, criterion_group, criterion_main};
use ff::Field;
use group::{Curve, Group};
use itertools::izip;
use rand::seq::SliceRandom;
use rand::thread_rng;
use nym_offline_divisible_ecash::{aggregate_verification_keys, aggregate_wallets,
issue, issue_verify, PartialWallet,
PayInfo, PublicKeyUser, SecretKeyUser, ttp_keygen_authorities, ttp_keygen_users, VerificationKeyAuth, withdrawal_request};
use nym_offline_divisible_ecash::identification::{identify, IdentifyResult};
use nym_offline_divisible_ecash::setup::{GroupParameters, Parameters};
#[allow(unused)]
fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
let gt1 = bls12_381::pairing(g11, g21);
let gt2 = bls12_381::pairing(g12, g22);
assert_eq!(gt1, gt2)
}
#[allow(unused)]
fn single_pairing(g11: &G1Affine, g21: &G2Affine) {
let gt1 = bls12_381::pairing(g11, g21);
}
#[allow(unused)]
fn exponent_in_g1(g1: G1Projective, r: Scalar) {
let g11 = (g1 * r);
}
#[allow(unused)]
fn exponent_in_g2(g2: G2Projective, r: Scalar) {
let g22 = (g2 * r);
}
#[allow(unused)]
fn exponent_in_gt(gt: Gt, r: Scalar) {
let gtt = (gt * r);
}
#[allow(unused)]
fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
let miller_loop_result = multi_miller_loop(&[
(g11, &G2Prepared::from(*g21)),
(&g12.neg(), &G2Prepared::from(*g22)),
]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
#[allow(unused)]
fn multi_miller_pairing_with_prepared(
g11: &G1Affine,
g21: &G2Prepared,
g12: &G1Affine,
g22: &G2Prepared,
) {
let miller_loop_result = multi_miller_loop(&[(g11, g21), (&g12.neg(), g22)]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
// the case of being able to prepare G2 generator
#[allow(unused)]
fn multi_miller_pairing_with_semi_prepared(
g11: &G1Affine,
g21: &G2Affine,
g12: &G1Affine,
g22: &G2Prepared,
) {
let miller_loop_result =
multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), g22)]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
#[allow(unused)]
fn bench_pairings(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-pairings");
group.measurement_time(Duration::from_secs(200));
let mut rng = rand::thread_rng();
let g1 = G1Affine::generator();
let g2 = G2Affine::generator();
let r = Scalar::random(&mut rng);
let s = Scalar::random(&mut rng);
let g11 = (g1 * r).to_affine();
let g21 = (g2 * s).to_affine();
let g21_prep = G2Prepared::from(g21);
let g12 = (g1 * s).to_affine();
let g22 = (g2 * r).to_affine();
let g22_prep = G2Prepared::from(g22);
let gt = bls12_381::pairing(&g11, &g21);
let gen1 = G1Projective::generator();
let gen2 = G2Projective::generator();
group.bench_function("exponent operation in G1", |b| {
b.iter(|| exponent_in_g1(gen1, r))
});
group.bench_function("exponent operation in G2", |b| {
b.iter(|| exponent_in_g2(gen2, r))
});
group.bench_function("exponent operation in Gt", |b| {
b.iter(|| exponent_in_gt(gt, r))
});
group.bench_function("single pairing", |b| {
b.iter(|| single_pairing(&g11, &g21))
});
group.bench_function("double pairing", |b| {
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
});
group.bench_function("multi miller in affine", |b| {
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
});
group.bench_function("multi miller with prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
});
group.bench_function("multi miller with semi-prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
});
}
struct BenchCase {
num_authorities: u64,
threshold_p: f32,
L: u64,
spend_vv: u64,
case_nr_pub_keys: u64,
}
fn bench_divisible_ecash(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-divisible-ecash");
group.sample_size(300);
group.measurement_time(Duration::from_secs(1500));
let case = BenchCase {
num_authorities: 100,
threshold_p: 0.7,
L: 100,
spend_vv: 10,
case_nr_pub_keys: 99,
};
// SETUP PHASE
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
// KEY GENERATION FOR THE AUTHORITIES
let threshold = (case.threshold_p * case.num_authorities as f32).round() as u64;
let authorities_keypairs = ttp_keygen_authorities(&params, threshold, case.num_authorities).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let indices: Vec<u64> = (1..case.num_authorities + 1).collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
// KEY GENERATION FOR THE USER
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = SecretKeyUser::public_key(&sk_user, &grp);
// GENERATE KEYS FOR OTHER USERS
let mut pk_all_users = HashSet::new();
for i in 0..50 {
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = sk_user.public_key(&grp);
pk_all_users.insert(pk_user);
}
pk_all_users.insert(pk_user.clone());
// WITHDRAWAL REQUEST
let (withdrawal_req, req_info) = withdrawal_request(&params, &sk_user).unwrap();
// CLIENT BENCHMARK: prepare a single withdrawal request
group.bench_function(
&format!(
"[Client] withdrawal_request_{}_authorities_{}_L_{}_threshold",
case.num_authorities, case.L, case.threshold_p,
),
|b| b.iter(|| withdrawal_request(&params, &sk_user).unwrap()),
);
// ISSUE PARTIAL WALLETS
// first one meaningful one just for benchmark
let mut rng = rand::thread_rng();
let keypair = authorities_keypairs.choose(&mut rng).unwrap();
group.bench_function(
&format!("[Issuing Authority] issue_partial_wallet_with_L_{}", case.L, ),
|b| {
b.iter(|| {
issue(
&params,
&withdrawal_req,
pk_user.clone(),
&keypair.secret_key(),
).unwrap()
})
},
);
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue(
&params,
&withdrawal_req,
pk_user.clone(),
&auth_keypair.secret_key(),
).unwrap();
wallet_blinded_signatures.push(blind_signature);
}
// CLIENT BENCHMARK: verify the issued partial wallet
let w = wallet_blinded_signatures.get(0).clone().unwrap();
let vk = verification_keys_auth.get(0).clone().unwrap();
group.bench_function(
&format!("[Client] issue_verify_a_partial_wallet_with_L_{}", case.L, ),
|b| b.iter(|| issue_verify(&grp, vk, &sk_user, w, &req_info).unwrap()),
);
let partial_wallets: Vec<PartialWallet> = izip!(
wallet_blinded_signatures.iter(),
verification_keys_auth.iter()
)
.map(|(w, vk)| issue_verify(&grp, &vk, &sk_user, &w, &req_info).unwrap())
.collect();
// AGGREGATE WALLET
let mut wallet = aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets).unwrap();
// CLIENT BENCHMARK: aggregating all partial wallets
group.bench_function(
&format!(
"[Client] aggregate_wallets_with_L_{}_threshold_{}",
case.L, case.threshold_p,
),
|b| {
b.iter(|| {
aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets)
.unwrap()
})
},
);
let pay_info = PayInfo { info: [67u8; 32] };
let (payment, wallet) = wallet.spend(&params, &verification_key, &sk_user, &pay_info, case.spend_vv, false).unwrap();
// CLIENT BENCHMARK: spend a single coin from the wallet
group.bench_function(
&format!(
"[Client] spend_a_single_coin_L_{}_threshold_{}",
case.L, case.threshold_p,
),
|b| {
b.iter(|| {
wallet.spend(&params, &verification_key, &sk_user, &pay_info, case.spend_vv, true)
.unwrap()
})
},
);
// MERCHANT BENCHMARK: verify whether the submitted payment is legit
group.bench_function(
&format!(
"[Merchant] spend_verify_of_a_single_payment_L_{}_threshold_{}",
case.L, case.threshold_p,
),
|b| {
b.iter(|| {
payment.spend_verify(&params, &verification_key, &pay_info)
.unwrap()
})
},
);
// BENCHMARK IDENTIFICATION
// Let's generate a double spending payment
// let's reverse the spending counter in the wallet to create a double spending payment
let current_l = wallet.l();
wallet.l.set(current_l - 1);
let pay_info2 = PayInfo { info: [52u8; 32] };
let (payment2, wallet) = wallet.spend(&params, &verification_key, &sk_user, &pay_info2, 10, false).unwrap();
// MERCHANT BENCHMARK: identify double spending
group.bench_function(
&format!(
"[Merchant] identify_L_{}_threshold_{}_spend_vv_{}_pks_{}",
case.L, case.threshold_p, case.spend_vv, pk_all_users.len()
),
|b| {
b.iter(|| {
identify(&params, &verification_key, &pk_all_users, payment.clone(), payment2.clone(), pay_info, pay_info2).unwrap()
})
},
);
let identify_result = identify(&params, &verification_key, &pk_all_users, payment.clone(), payment2.clone(), pay_info, pay_info2).unwrap();
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(pk_user));
}
criterion_group!(benches, bench_divisible_ecash);
criterion_main!(benches);
@@ -0,0 +1,2 @@
/// Max value of wallet
pub(crate) const L: u64 = 100;
@@ -0,0 +1,39 @@
use thiserror::Error;
pub type Result<T> = std::result::Result<T, DivisibleEcashError>;
#[derive(Error, Debug)]
pub enum DivisibleEcashError {
#[error("Setup error: {0}")]
Setup(String),
#[error("Aggregation error: {0}")]
Aggregation(String),
#[error("Withdrawal Request Verification related error: {0}")]
WithdrawalRequestVerification(String),
#[error("Deserialization error: {0}")]
Deserialization(String),
#[error("Interpolation error: {0}")]
Interpolation(String),
#[error("Issuance Verification related error: {0}")]
IssuanceVfy(String),
#[error("Spend Verification related error: {0}")]
Spend(String),
#[error("Identify Verification related error: {0}")]
Identify(String),
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {} or {modulus_target} % {modulus} == 0")]
DeserializationInvalidLength {
actual: usize,
target: usize,
modulus_target: usize,
modulus: usize,
object: String,
},
}
@@ -0,0 +1,29 @@
use bls12_381::{G1Projective, G2Prepared, G2Projective, pairing, Scalar};
pub use scheme::aggregation::aggregate_verification_keys;
pub use scheme::aggregation::aggregate_wallets;
pub use scheme::identification;
pub use scheme::keygen::{PublicKeyUser, SecretKeyUser, VerificationKeyAuth};
pub use scheme::keygen::ttp_keygen_authorities;
pub use scheme::keygen::ttp_keygen_users;
pub use scheme::PartialWallet;
pub use scheme::PayInfo;
pub use scheme::setup;
pub use scheme::withdrawal::issue;
pub use scheme::withdrawal::issue_verify;
pub use scheme::withdrawal::withdrawal_request;
pub use traits::Base58;
use crate::error::DivisibleEcashError;
use crate::traits::Bytable;
mod error;
mod proofs;
mod scheme;
#[cfg(test)]
mod tests;
mod traits;
mod utils;
mod constants;
pub type Attribute = Scalar;
@@ -0,0 +1,64 @@
use std::borrow::Borrow;
use std::convert::TryFrom;
use std::convert::TryInto;
use bls12_381::{G1Affine, G1Projective, Scalar};
use digest::Digest;
use digest::generic_array::typenum::Unsigned;
use group::GroupEncoding;
use sha2::Sha256;
use crate::error::{DivisibleEcashError, Result};
use crate::scheme::keygen::PublicKeyUser;
use crate::scheme::setup::GroupParameters;
use crate::utils::try_deserialize_g1_projective;
pub mod proof_withdrawal;
pub mod proof_spend;
type ChallengeDigest = Sha256;
/// Generates a Scalar [or Fp] challenge by hashing a number of elliptic curve points.
fn compute_challenge<D, I, B>(iter: I) -> Scalar
where
D: Digest,
I: Iterator<Item=B>,
B: AsRef<[u8]>,
{
let mut h = D::new();
for point_representation in iter {
h.update(point_representation);
}
let digest = h.finalize();
// TODO: I don't like the 0 padding here (though it's what we've been using before,
// but we never had a security audit anyway...)
// instead we could maybe use the `from_bytes` variant and adding some suffix
// when computing the digest until we produce a valid scalar.
let mut bytes = [0u8; 64];
let pad_size = 64usize
.checked_sub(D::OutputSize::to_usize())
.unwrap_or_default();
bytes[pad_size..].copy_from_slice(&digest);
Scalar::from_bytes_wide(&bytes)
}
fn produce_response(witness_replacement: &Scalar, challenge: &Scalar, secret: &Scalar) -> Scalar {
witness_replacement - challenge * secret
}
// note: it's caller's responsibility to ensure witnesses.len() = secrets.len()
fn produce_responses<S>(witnesses: &[Scalar], challenge: &Scalar, secrets: &[S]) -> Vec<Scalar>
where
S: Borrow<Scalar>,
{
debug_assert_eq!(witnesses.len(), secrets.len());
witnesses
.iter()
.zip(secrets.iter())
.map(|(w, x)| produce_response(w, challenge, x.borrow()))
.collect()
}
@@ -0,0 +1,613 @@
use std::convert::TryFrom;
use std::ops::Neg;
use bls12_381::{G1Projective, G2Projective, Gt, Scalar};
use group::GroupEncoding;
use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produce_responses};
use crate::scheme::{Phi, VarPhi, Wallet};
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::Parameters;
use crate::error::{DivisibleEcashError, Result};
use crate::utils::{try_deserialize_scalar};
pub struct SpendInstance {
pub kappa: G2Projective,
pub phi: Phi,
pub varphi: VarPhi,
pub rr: Scalar,
pub rr_prime: G1Projective,
pub ss_prime: G1Projective,
pub tt_prime: G2Projective,
pub varsig_prime1: G1Projective,
pub theta_prime1: G1Projective,
pub pg_eq1: Gt,
pub pg_eq2: Gt,
pub pg_eq3: Gt,
pub pg_eq4: Gt,
pub psi_g1: G1Projective,
pub psi_g2: G2Projective,
pub pg_psi0_delta: Gt,
pub pg_psi0_gen2: Gt,
pub pg_psi0_yy: Gt,
pub pg_psi0_ww1: Gt,
pub pg_psi0_ww2: Gt,
pub pg_rr_psi1: Gt,
pub pg_psi0_tt: Gt,
pub pg_psi0_psi1: Gt,
}
impl SpendInstance {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(96 + 96 + 3 * 96 + 5 * 48 + 12 * 288);
bytes.extend_from_slice(self.kappa.to_bytes().as_ref());
bytes.extend_from_slice(self.phi.to_bytes().as_ref());
bytes.extend_from_slice(self.varphi.to_bytes().as_ref());
bytes.extend_from_slice(self.rr.to_bytes().as_ref());
bytes.extend_from_slice(self.rr_prime.to_bytes().as_ref());
bytes.extend_from_slice(self.ss_prime.to_bytes().as_ref());
bytes.extend_from_slice(self.tt_prime.to_bytes().as_ref());
bytes.extend_from_slice(self.varsig_prime1.to_bytes().as_ref());
bytes.extend_from_slice(self.theta_prime1.to_bytes().as_ref());
bytes.extend_from_slice(self.pg_eq1.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_eq2.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_eq3.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_eq4.to_compressed().as_ref());
bytes.extend_from_slice(self.psi_g1.to_bytes().as_ref());
bytes.extend_from_slice(self.psi_g2.to_bytes().as_ref());
bytes.extend_from_slice(self.pg_psi0_delta.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_psi0_gen2.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_psi0_yy.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_psi0_ww1.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_psi0_ww2.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_rr_psi1.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_psi0_tt.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_psi0_psi1.to_compressed().as_ref());
bytes
}
}
pub struct SpendWitness {
pub sk_u: SecretKeyUser,
pub v: Scalar,
pub r: Scalar,
pub r1: Scalar,
pub r2: Scalar,
pub r_varsig1: Scalar,
pub r_theta1: Scalar,
pub r_varsig2: Scalar,
pub r_theta2: Scalar,
pub r_rr: Scalar,
pub r_ss: Scalar,
pub r_tt: Scalar,
pub rho1: Scalar,
pub rho2: Scalar,
pub rho3: Scalar,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SpendProof {
challenge: Scalar,
response_r: Scalar,
response_r_sk_u: Scalar,
response_r_v: Scalar,
response_r_r: Scalar,
response_r_r1: Scalar,
response_r_r2: Scalar,
response_r_varsig1: Scalar,
response_r_theta1: Scalar,
response_r_varsig2: Scalar,
response_r_theta2: Scalar,
response_r_rr: Scalar,
response_r_ss: Scalar,
response_r_tt: Scalar,
response_r_rho1: Scalar,
response_r_rho2: Scalar,
response_r_rho3: Scalar,
}
impl SpendProof {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes: Vec<u8> = Vec::with_capacity(32 * 17); // 17 fields, each 32 bytes
bytes.extend_from_slice(&self.challenge.to_bytes());
bytes.extend_from_slice(&self.response_r.to_bytes());
bytes.extend_from_slice(&self.response_r_sk_u.to_bytes());
bytes.extend_from_slice(&self.response_r_v.to_bytes());
bytes.extend_from_slice(&self.response_r_r.to_bytes());
bytes.extend_from_slice(&self.response_r_r1.to_bytes());
bytes.extend_from_slice(&self.response_r_r2.to_bytes());
bytes.extend_from_slice(&self.response_r_varsig1.to_bytes());
bytes.extend_from_slice(&self.response_r_theta1.to_bytes());
bytes.extend_from_slice(&self.response_r_varsig2.to_bytes());
bytes.extend_from_slice(&self.response_r_theta2.to_bytes());
bytes.extend_from_slice(&self.response_r_rr.to_bytes());
bytes.extend_from_slice(&self.response_r_ss.to_bytes());
bytes.extend_from_slice(&self.response_r_tt.to_bytes());
bytes.extend_from_slice(&self.response_r_rho1.to_bytes());
bytes.extend_from_slice(&self.response_r_rho2.to_bytes());
bytes.extend_from_slice(&self.response_r_rho3.to_bytes());
bytes
}
pub fn construct(
params: &Parameters,
instance: &SpendInstance,
witness: &SpendWitness,
verification_key: &VerificationKeyAuth,
vv: u64) -> Self {
let grp = params.get_grp();
let params_u = params.get_params_u();
let params_a = params.get_params_a();
// generate random values to replace each witness
let r_attributes = grp.n_random_scalars(2);
let r_sk_u = r_attributes[0];
let r_v = r_attributes[1];
let r_r = grp.random_scalar();
let r_r1 = grp.random_scalar();
let r_r2 = grp.random_scalar();
let r_r_varsig1 = grp.random_scalar();
let r_r_theta1 = grp.random_scalar();
let r_r_varsig2 = grp.random_scalar();
let r_r_theta2 = grp.random_scalar();
let r_r_rr = grp.random_scalar();
let r_r_ss = grp.random_scalar();
let r_r_tt = grp.random_scalar();
let r_rho1 = grp.random_scalar();
let r_rho2 = grp.random_scalar();
let r_rho3 = grp.random_scalar();
let g1 = grp.gen1();
// compute zkp commitment for each instance
let zkcm_kappa = grp.gen2() * r_r
+ verification_key.alpha
+ r_attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
let zkcm_phi0 = g1 * r_r1;
let zkcm_phi1 = instance.varsig_prime1 * r_v + instance.psi_g1 * r_rho1 + params_u.get_ith_eta(vv as usize) * r_r1;
let zkcm_varphi0 = g1 * r_r2;
let zkcm_varphi1 = (g1 * instance.rr) * r_sk_u + instance.theta_prime1 * r_v + instance.psi_g1 * r_rho2 + params_u.get_ith_eta(vv as usize) * r_r2;
let zkcm_pg_eq1 = instance.pg_psi0_delta * r_r_varsig1 + instance.pg_psi0_gen2 * r_r_varsig2.neg();
let zkcm_pg_eq2 = instance.pg_psi0_delta * r_r_theta1 + instance.pg_psi0_gen2 * r_r_theta2.neg();
let zkcm_pg_eq3 = instance.pg_psi0_yy * r_r_rr + instance.pg_psi0_gen2 * r_r_ss + instance.pg_psi0_ww1 * r_r_varsig2 + instance.pg_psi0_ww2 * r_r_theta2;
let zkcm_pg_eq4 = instance.pg_rr_psi1 * r_r_tt + instance.pg_psi0_tt * r_r_rr + instance.pg_psi0_psi1 * r_rho3.neg();
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(g1.to_bytes().as_ref())
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_kappa.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_phi0.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_phi1.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_varphi0.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_varphi1.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_pg_eq1.to_compressed().as_ref()))
.chain(std::iter::once(zkcm_pg_eq2.to_compressed().as_ref()))
.chain(std::iter::once(zkcm_pg_eq3.to_compressed().as_ref()))
.chain(std::iter::once(zkcm_pg_eq4.to_compressed().as_ref()))
);
// compute response for each witness
let response_r = produce_response(&r_r, &challenge, &witness.r);
let response_r_sk_u = produce_response(&r_sk_u, &challenge, &witness.sk_u.sk);
let response_r_v = produce_response(&r_v, &challenge, &witness.v);
let response_r_r = produce_response(&r_r, &challenge, &witness.r);
let response_r_r1 = produce_response(&r_r1, &challenge, &witness.r1);
let response_r_r2 = produce_response(&r_r2, &challenge, &witness.r2);
let response_r_varsig1 = produce_response(&r_r_varsig1, &challenge, &witness.r_varsig1);
let response_r_theta1 = produce_response(&r_r_theta1, &challenge, &witness.r_theta1);
let response_r_varsig2 = produce_response(&r_r_varsig2, &challenge, &witness.r_varsig2);
let response_r_theta2 = produce_response(&r_r_theta2, &challenge, &witness.r_theta2);
let response_r_rr = produce_response(&r_r_rr, &challenge, &witness.r_rr);
let response_r_ss = produce_response(&r_r_ss, &challenge, &witness.r_ss);
let response_r_tt = produce_response(&r_r_tt, &challenge, &witness.r_tt);
let response_r_rho1 = produce_response(&r_rho1, &challenge, &witness.rho1);
let response_r_rho2 = produce_response(&r_rho2, &challenge, &witness.rho2);
let response_r_rho3 = produce_response(&r_rho3, &challenge, &witness.rho3);
SpendProof {
challenge,
response_r,
response_r_sk_u,
response_r_v,
response_r_r,
response_r_r1,
response_r_r2,
response_r_varsig1,
response_r_theta1,
response_r_varsig2,
response_r_theta2,
response_r_rr,
response_r_ss,
response_r_tt,
response_r_rho1,
response_r_rho2,
response_r_rho3,
}
}
pub fn verify(
&self,
params: &Parameters,
instance: &SpendInstance,
verification_key: &VerificationKeyAuth,
vv: u64,
) -> bool {
let grp = params.get_grp();
let params_u = params.get_params_u();
let params_a = params.get_params_a();
let g1 = grp.gen1();
// re-compute each zkp commitment
let zkcm_kappa = instance.kappa * self.challenge
+ grp.gen2() * self.response_r
+ verification_key.alpha * (Scalar::one() - self.challenge)
+ [self.response_r_sk_u, self.response_r_v]
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
let zkcm_phi0 = g1 * self.response_r_r1 + instance.phi.0 * self.challenge;
let zkcm_phi1 = instance.varsig_prime1 * self.response_r_v
+ instance.psi_g1 * self.response_r_rho1
+ params_u.get_ith_eta(vv as usize) * self.response_r_r1
+ instance.phi.1 * self.challenge;
let zkcm_varphi0 = g1 * self.response_r_r2 + instance.varphi.0 * self.challenge;
let zkcm_varphi1 = (g1 * instance.rr) * self.response_r_sk_u
+ instance.theta_prime1 * self.response_r_v
+ instance.psi_g1 * self.response_r_rho2
+ params_u.get_ith_eta(vv as usize) * self.response_r_r2
+ instance.varphi.1 * self.challenge;
let zkcm_pg_eq1 = instance.pg_psi0_delta * self.response_r_varsig1
+ instance.pg_psi0_gen2 * self.response_r_varsig2.neg()
+ instance.pg_eq1 * self.challenge;
let zkcm_pg_eq2 = instance.pg_psi0_delta * self.response_r_theta1
+ instance.pg_psi0_gen2 * self.response_r_theta2.neg()
+ instance.pg_eq2 * self.challenge;
let zkcm_pg_eq3 = instance.pg_psi0_yy * self.response_r_rr
+ instance.pg_psi0_gen2 * self.response_r_ss
+ instance.pg_psi0_ww1 * self.response_r_varsig2
+ instance.pg_psi0_ww2 * self.response_r_theta2
+ instance.pg_eq3 * self.challenge;
let zkcm_pg_eq4 = instance.pg_rr_psi1 * self.response_r_tt
+ instance.pg_psi0_tt * self.response_r_rr
+ instance.pg_psi0_psi1 * self.response_r_rho3.neg()
+ instance.pg_eq4 * self.challenge;
// re-compute the challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(g1.to_bytes().as_ref())
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_kappa.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_phi0.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_phi1.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_varphi0.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_varphi1.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_pg_eq1.to_compressed().as_ref()))
.chain(std::iter::once(zkcm_pg_eq2.to_compressed().as_ref()))
.chain(std::iter::once(zkcm_pg_eq3.to_compressed().as_ref()))
.chain(std::iter::once(zkcm_pg_eq4.to_compressed().as_ref()))
);
challenge == self.challenge
}
}
impl TryFrom<&[u8]> for SpendProof {
type Error = DivisibleEcashError;
fn try_from(bytes: &[u8]) -> Result<Self> {
const FIELD_SIZE: usize = 32; // Each Scalar field is 32 bytes
if bytes.len() != FIELD_SIZE * 17 {
return Err(DivisibleEcashError::Deserialization(
"Invalid byte array for SpendProof deserialization".to_string(),
));
}
let challenge_bytes = bytes[0..FIELD_SIZE].try_into().unwrap();
let challenge = try_deserialize_scalar(
&challenge_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize challenge".to_string()),
)?;
let response_r_bytes = bytes[FIELD_SIZE..FIELD_SIZE * 2].try_into().unwrap();
let response_r = try_deserialize_scalar(
&response_r_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r".to_string()),
)?;
let response_r_sk_u_bytes = bytes[FIELD_SIZE * 2..FIELD_SIZE * 3].try_into().unwrap();
let response_r_sk_u = try_deserialize_scalar(
&response_r_sk_u_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_sk_u".to_string()),
)?;
let response_r_v_bytes = bytes[FIELD_SIZE * 3..FIELD_SIZE * 4].try_into().unwrap();
let response_r_v = try_deserialize_scalar(
&response_r_v_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_v".to_string()),
)?;
let response_r_r_bytes = bytes[FIELD_SIZE * 4..FIELD_SIZE * 5].try_into().unwrap();
let response_r_r = try_deserialize_scalar(
&response_r_r_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_r".to_string()),
)?;
let response_r_r1_bytes = bytes[FIELD_SIZE * 5..FIELD_SIZE * 6].try_into().unwrap();
let response_r_r1 = try_deserialize_scalar(
&response_r_r1_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_r1".to_string()),
)?;
let response_r_r2_bytes = bytes[FIELD_SIZE * 6..FIELD_SIZE * 7].try_into().unwrap();
let response_r_r2 = try_deserialize_scalar(
&response_r_r2_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_r2".to_string()),
)?;
let response_r_varsig1_bytes = bytes[FIELD_SIZE * 7..FIELD_SIZE * 8].try_into().unwrap();
let response_r_varsig1 = try_deserialize_scalar(
response_r_varsig1_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_varsig1".to_string()),
)?;
let response_r_theta1_bytes = bytes[FIELD_SIZE * 8..FIELD_SIZE * 9].try_into().unwrap();
let response_r_theta1 = try_deserialize_scalar(
&response_r_theta1_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_theta1".to_string()),
)?;
let response_r_varsig2_bytes = bytes[FIELD_SIZE * 9..FIELD_SIZE * 10].try_into().unwrap();
let response_r_varsig2 = try_deserialize_scalar(
&response_r_varsig2_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_varsig2".to_string()),
)?;
let response_r_theta2_bytes = bytes[FIELD_SIZE * 10..FIELD_SIZE * 11].try_into().unwrap();
let response_r_theta2 = try_deserialize_scalar(
&response_r_theta2_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_theta2".to_string()),
)?;
let response_r_rr_bytes = bytes[FIELD_SIZE * 11..FIELD_SIZE * 12].try_into().unwrap();
let response_r_rr = try_deserialize_scalar(
&response_r_rr_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_rr".to_string()),
)?;
let response_r_ss_bytes = bytes[FIELD_SIZE * 12..FIELD_SIZE * 13].try_into().unwrap();
let response_r_ss = try_deserialize_scalar(
&response_r_ss_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_ss".to_string()),
)?;
let response_r_tt_bytes = bytes[FIELD_SIZE * 13..FIELD_SIZE * 14].try_into().unwrap();
let response_r_tt = try_deserialize_scalar(
&response_r_tt_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_tt".to_string()),
)?;
let response_r_rho1_bytes = bytes[FIELD_SIZE * 14..FIELD_SIZE * 15].try_into().unwrap();
let response_r_rho1 = try_deserialize_scalar(
&response_r_rho1_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_rho1".to_string()),
)?;
let response_r_rho2_bytes = bytes[FIELD_SIZE * 15..FIELD_SIZE * 16].try_into().unwrap();
let response_r_rho2 = try_deserialize_scalar(
&response_r_rho2_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_rho2".to_string()),
)?;
let response_r_rho3_bytes = bytes[FIELD_SIZE * 16..FIELD_SIZE * 17].try_into().unwrap();
let response_r_rho3 = try_deserialize_scalar(
&response_r_rho3_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_rho3".to_string()),
)?;
Ok(SpendProof {
challenge,
response_r,
response_r_sk_u,
response_r_v,
response_r_r,
response_r_r1,
response_r_r2,
response_r_varsig1,
response_r_theta1,
response_r_varsig2,
response_r_theta2,
response_r_rr,
response_r_ss,
response_r_tt,
response_r_rho1,
response_r_rho2,
response_r_rho3,
})
}
}
#[cfg(test)]
mod tests {
use std::ops::Neg;
use bls12_381::{G2Projective, pairing};
use group::Curve;
use rand::thread_rng;
use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
use crate::scheme::{PayInfo, Phi, VarPhi};
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::keygen::{PublicKeyUser, SecretKeyUser, ttp_keygen_authorities, VerificationKeyAuth};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::utils::hash_to_scalar;
#[test]
fn spend_proof_construct_and_verify() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
let params_u = params.get_params_u();
let params_a = params.get_params_a();
let sk = grp.random_scalar();
let pk_user = PublicKeyUser {
pk: grp.gen1() * sk,
};
let v = grp.random_scalar();
let attributes = vec![sk, v];
let l: usize = 10;
let vv: u64 = 20;
let authorities_keypairs = ttp_keygen_authorities(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
let r = grp.random_scalar();
let kappa = grp.gen2() * r
+ verification_key.alpha
+ attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
.sum::<G2Projective>();
let r1 = grp.random_scalar();
let r2 = grp.random_scalar();
let phi = Phi(grp.gen1() * r1, params_u.get_ith_sigma(l as usize) * v + params_u.get_ith_eta(vv as usize) * r1);
let pay_info = PayInfo { info: [78u8; 32] };
let rr = hash_to_scalar(pay_info.info);
let varphi = VarPhi(grp.gen1() * r2, (grp.gen1() * rr) * sk + params_u.get_ith_theta(l as usize) * v + params_u.get_ith_eta(vv as usize) * r2);
// random value used to compute blinded bases
let r_varsig1 = grp.random_scalar();
let r_theta1 = grp.random_scalar();
let r_varsig2 = grp.random_scalar();
let r_theta2 = grp.random_scalar();
let r_rr = grp.random_scalar();
let r_ss = grp.random_scalar();
let r_tt = grp.random_scalar();
// compute blinded bases
let psi_g1 = params_u.get_psi_g1();
let psi_g2 = params_u.get_psi_g2();
let varsig_prime1 = params_u.get_ith_sigma(l as usize) + (psi_g1 * r_varsig1);
let theta_prime1 = params_u.get_ith_theta(l as usize) + (psi_g1 * r_theta1);
let varsig_prime2 = params_u.get_ith_sigma(l as usize + vv as usize - 1) + (psi_g1 * r_varsig2);
let theta_prime2 = params_u.get_ith_theta(l as usize + vv as usize - 1) + (psi_g1 * r_theta2);
let rr_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).rr + (psi_g1 * r_rr);
let ss_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).ss + (psi_g1 * r_ss);
let tt_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).tt + (psi_g2 * r_tt);
let rho1 = v.neg() * r_varsig1;
let rho2 = v.neg() * r_theta1;
let rho3 = r_rr * r_tt;
let pg_varsigpr1_delta = pairing(&varsig_prime1.to_affine(), &params_a.get_ith_delta((vv - 1) as usize).to_affine());
let pg_psi0_delta = pairing(&psi_g1.to_affine(), &params_a.get_ith_delta((vv - 1) as usize).to_affine());
let pg_varsigpr2_gen2 = pairing(&varsig_prime2.to_affine(), grp.gen2());
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
let pg_thetapr1_delta = pairing(&theta_prime1.to_affine(), &params_a.get_ith_delta((vv - 1) as usize).to_affine());
let pg_thetapr2_gen2 = pairing(&theta_prime2.to_affine(), grp.gen2());
let yy = params_u.get_sps_pk().get_yy();
let pg_rrprime_yy = pairing(&rr_prime.to_affine(), &yy.to_affine());
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
let pg_ssprime_gen2 = pairing(&ss_prime.to_affine(), grp.gen2());
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
let pg_varsigpr2_ww1 = pairing(&varsig_prime2.to_affine(), &ww1.to_affine());
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
let pg_thetapr2_ww2 = pairing(&theta_prime2.to_affine(), &ww2.to_affine());
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
let pg_gen1_zz = pairing(grp.gen1(), &params_u.get_sps_pk().get_zz().to_affine());
let pg_rr_tt = pairing(&rr_prime.to_affine(), &tt_prime.to_affine());
let pg_rr_psi1 = pairing(&rr_prime.to_affine(), &psi_g2.to_affine());
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &tt_prime.to_affine());
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
let pg_eq3 = pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 - pg_gen1_zz;
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
let instance = SpendInstance {
kappa,
phi,
varphi,
rr,
rr_prime,
ss_prime: ss_prime,
tt_prime: tt_prime,
varsig_prime1,
theta_prime1,
pg_eq1,
pg_eq2,
pg_eq3,
pg_eq4,
psi_g1: *psi_g1,
psi_g2: *psi_g2,
pg_psi0_delta,
pg_psi0_gen2,
pg_psi0_yy,
pg_psi0_ww1,
pg_psi0_ww2,
pg_rr_psi1,
pg_psi0_tt,
pg_psi0_psi1,
};
let witness = SpendWitness {
sk_u: SecretKeyUser { sk },
v,
r,
r1,
r2,
r_varsig1,
r_theta1,
r_varsig2,
r_theta2,
r_rr,
r_ss,
r_tt,
rho1,
rho2,
rho3,
};
// compute the zk proof
let zk_proof = SpendProof::construct(&params, &instance, &witness, &verification_key, vv);
assert!(zk_proof.verify(&params, &instance, &verification_key, vv));
// do a to and from bytes check
let zk_proof_bytes = zk_proof.to_bytes();
let zk_proof2 = SpendProof::try_from(&zk_proof_bytes[..]).unwrap();
assert_eq!(zk_proof, zk_proof2);
}
}
@@ -0,0 +1,335 @@
use std::convert::{TryFrom, TryInto};
use bls12_381::{G1Projective, Scalar};
use group::GroupEncoding;
use itertools::izip;
use crate::error::{DivisibleEcashError, Result};
use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produce_responses};
use crate::scheme::keygen::PublicKeyUser;
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::utils::try_deserialize_g1_projective;
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
// instance: g, gamma1, gamma2, gamma3, com, h, com1, com2, com3, pkUser
pub struct WithdrawalReqInstance {
// Joined commitment to all attributes
pub com: G1Projective,
// Hash of the joined commitment com
pub h: G1Projective,
// Pedersen commitments to each attribute
pub pc_coms: Vec<G1Projective>,
// Public key of a user
pub pk_user: PublicKeyUser,
}
impl TryFrom<&[u8]> for WithdrawalReqInstance {
type Error = DivisibleEcashError;
fn try_from(bytes: &[u8]) -> Result<WithdrawalReqInstance> {
if bytes.len() < 48 * 4 + 8 || (bytes.len() - 8) % 48 != 0 {
return Err(DivisibleEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8,
target: 48 * 4 + 8,
modulus: 48,
object: "withdrawal request zkp instance".to_string(),
});
}
let com_bytes: [u8; 48] = bytes[..48].try_into().unwrap();
let com = try_deserialize_g1_projective(
&com_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize com".to_string()),
)?;
let h_bytes: [u8; 48] = bytes[48..96].try_into().unwrap();
let h = try_deserialize_g1_projective(
&h_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize h".to_string()),
)?;
let pc_coms_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
let actual_pc_coms_len = (bytes.len() - 152) / 48;
if pc_coms_len as usize != actual_pc_coms_len {
return Err(DivisibleEcashError::Deserialization(format!(
"Tried to deserialize pedersen commitments with inconsistent pc_coms_len (expected {}, got {})",
pc_coms_len, actual_pc_coms_len
)));
}
let mut pc_coms = Vec::new();
let mut pc_coms_end: usize = 0;
for i in 0..pc_coms_len {
let start = (104 + i * 48) as usize;
let end = (start + 48) as usize;
let pc_i_bytes = bytes[start..end].try_into().unwrap();
let pc_i = try_deserialize_g1_projective(
&pc_i_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize pedersen commitment".to_string(),
),
)?;
pc_coms_end = end;
pc_coms.push(pc_i);
}
let pk_bytes = bytes[pc_coms_end..].try_into().unwrap();
let pk = try_deserialize_g1_projective(
&pk_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize user's public key".to_string(),
),
)?;
Ok(WithdrawalReqInstance {
com,
h,
pc_coms,
pk_user: PublicKeyUser { pk },
})
}
}
impl WithdrawalReqInstance {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let pc_coms_len = self.pc_coms.len();
let mut bytes = Vec::with_capacity(8 + (pc_coms_len + 3) as usize * 48);
bytes.extend_from_slice(self.com.to_bytes().as_ref());
bytes.extend_from_slice(self.h.to_bytes().as_ref());
bytes.extend_from_slice(&pc_coms_len.to_le_bytes());
for pc in self.pc_coms.iter() {
bytes.extend_from_slice((pc.to_bytes()).as_ref());
}
bytes.extend_from_slice(self.pk_user.pk.to_bytes().as_ref());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<WithdrawalReqInstance> {
WithdrawalReqInstance::try_from(bytes)
}
}
// witness: m1, m2, m3, o, o1, o2, o3,
pub struct WithdrawalReqWitness {
pub attributes: Vec<Scalar>,
// Opening for the joined commitment com
pub com_opening: Scalar,
// Openings for the pedersen commitments
pub pc_coms_openings: Vec<Scalar>,
}
pub struct WithdrawalReqProof {
challenge: Scalar,
response_opening: Scalar,
response_openings: Vec<Scalar>,
response_attributes: Vec<Scalar>,
}
impl WithdrawalReqProof {
pub(crate) fn construct(
params: &Parameters,
instance: &WithdrawalReqInstance,
witness: &WithdrawalReqWitness,
) -> Self {
let grp = params.get_grp();
let g1 = grp.gen1();
let params_u = params.get_params_u();
// generate random values to replace the witnesses
let r_com_opening = grp.random_scalar();
let r_pedcom_openings = grp.n_random_scalars(witness.pc_coms_openings.len());
let r_attributes = grp.n_random_scalars(witness.attributes.len());
// compute zkp commitments for each instance
let zkcm_com = g1 * r_com_opening
+ r_attributes
.iter()
.zip(params_u.get_gammas().iter())
.map(|(rm_i, gamma_i)| gamma_i * rm_i)
.sum::<G1Projective>();
let zkcm_pedcom = r_pedcom_openings
.iter()
.zip(r_attributes.iter())
.map(|(o_j, m_j)| g1 * o_j + instance.h * m_j)
.collect::<Vec<_>>();
let zkcm_user_sk = g1 * r_attributes[0];
// covert to bytes
let gammas_bytes = params_u
.get_gammas()
.iter()
.map(|gamma| gamma.to_bytes())
.collect::<Vec<_>>();
let zkcm_pedcom_bytes = zkcm_pedcom
.iter()
.map(|cm| cm.to_bytes())
.collect::<Vec<_>>();
// compute zkp challenge using g1, gammas, c, h, c1, c2, c3, zk commitments
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(g1.to_bytes().as_ref())
.chain(gammas_bytes.iter().map(|gamma| gamma.as_ref()))
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_com.to_bytes().as_ref()))
.chain(zkcm_pedcom_bytes.iter().map(|c| c.as_ref()))
.chain(std::iter::once(zkcm_user_sk.to_bytes().as_ref())),
);
// compute response
let response_opening = produce_response(&r_com_opening, &challenge, &witness.com_opening);
let response_openings = produce_responses(
&r_pedcom_openings,
&challenge,
&witness.pc_coms_openings.iter().collect::<Vec<_>>(),
);
let response_attributes = produce_responses(
&r_attributes,
&challenge,
&witness.attributes.iter().collect::<Vec<_>>(),
);
WithdrawalReqProof {
challenge,
response_opening,
response_openings,
response_attributes,
}
}
pub(crate) fn verify(
&self,
params: &Parameters,
instance: &WithdrawalReqInstance,
) -> bool {
let grp = params.get_grp();
let g1 = grp.gen1();
let params_u = params.get_params_u();
// recompute zk commitments for each instance
let zkcm_com = instance.com * self.challenge
+ g1 * self.response_opening
+ self
.response_attributes
.iter()
.zip(params_u.get_gammas().iter())
.map(|(m_i, gamma_i)| gamma_i * m_i)
.sum::<G1Projective>();
let zkcm_pedcom = izip!(
instance.pc_coms.iter(),
self.response_openings.iter(),
self.response_attributes.iter()
)
.map(|(cm_j, resp_o_j, resp_m_j)| {
cm_j * self.challenge + g1 * resp_o_j + instance.h * resp_m_j
})
.collect::<Vec<_>>();
let zk_commitment_user_sk =
instance.pk_user.pk * self.challenge + g1 * self.response_attributes[0];
// covert to bytes
let gammas_bytes = params_u
.get_gammas()
.iter()
.map(|gamma| gamma.to_bytes())
.collect::<Vec<_>>();
let zkcm_pedcom_bytes = zkcm_pedcom
.iter()
.map(|cm| cm.to_bytes())
.collect::<Vec<_>>();
// recompute zkp challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(g1.to_bytes().as_ref())
.chain(gammas_bytes.iter().map(|hs| hs.as_ref()))
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_com.to_bytes().as_ref()))
.chain(zkcm_pedcom_bytes.iter().map(|c| c.as_ref()))
.chain(std::iter::once(zk_commitment_user_sk.to_bytes().as_ref())),
);
challenge == self.challenge
}
}
#[cfg(test)]
mod tests {
use group::Group;
use rand::thread_rng;
use crate::scheme::setup::Parameters;
use crate::utils::hash_g1;
use super::*;
#[test]
fn withdrawal_request_instance_roundtrip() {
let mut rng = thread_rng();
let params = GroupParameters::new().unwrap();
let instance = WithdrawalReqInstance {
com: G1Projective::random(&mut rng),
h: G1Projective::random(&mut rng),
pc_coms: vec![
G1Projective::random(&mut rng),
G1Projective::random(&mut rng),
G1Projective::random(&mut rng),
],
pk_user: PublicKeyUser {
pk: params.gen1() * params.random_scalar(),
},
};
let instance_bytes = instance.to_bytes();
let instance_p = WithdrawalReqInstance::from_bytes(&instance_bytes).unwrap();
assert_eq!(instance, instance_p)
}
#[test]
fn withdrawal_proof_construct_and_verify() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
let sk = grp.random_scalar();
let pk_user = PublicKeyUser {
pk: grp.gen1() * sk,
};
let v = grp.random_scalar();
let t = grp.random_scalar();
let attr = vec![sk, v, t];
let com_opening = grp.random_scalar();
let com = grp.gen1() * com_opening
+ attr
.iter()
.zip(params.get_params_u().get_gammas())
.map(|(&m, gamma)| gamma * m)
.sum::<G1Projective>();
let h = hash_g1(com.to_bytes());
let pc_openings = grp.n_random_scalars(attr.len());
let pc_coms = pc_openings
.iter()
.zip(attr.iter())
.map(|(o_j, m_j)| grp.gen1() * o_j + h * m_j)
.collect::<Vec<_>>();
let instance = WithdrawalReqInstance {
com,
h,
pc_coms,
pk_user,
};
let witness = WithdrawalReqWitness {
attributes: attr,
com_opening,
pc_coms_openings: pc_openings,
};
let zk_proof = WithdrawalReqProof::construct(&params, &instance, &witness);
assert!(zk_proof.verify(&params, &instance))
}
}
@@ -0,0 +1,167 @@
use core::iter::Sum;
use core::ops::Mul;
use std::cell::Cell;
use bls12_381::{G2Prepared, G2Projective, pairing, Scalar};
use group::Curve;
use itertools::Itertools;
use crate::Attribute;
use crate::error::{DivisibleEcashError, Result};
use crate::scheme::{PartialWallet, Wallet};
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::GroupParameters;
use crate::utils::{
check_bilinear_pairing, PartialSignature, perform_lagrangian_interpolation_at_origin,
Signature, SignatureShare, SignerIndex,
};
pub(crate) trait Aggregatable: Sized {
fn aggregate(aggregatable: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self>;
fn check_unique_indices(indices: &[SignerIndex]) -> bool {
// if aggregation is a threshold one, all indices should be unique
indices.iter().unique_by(|&index| index).count() == indices.len()
}
}
impl<T> Aggregatable for T
where
T: Sum,
for<'a> T: Sum<&'a T>,
for<'a> &'a T: Mul<Scalar, Output=T>,
{
fn aggregate(aggregatable: &[T], indices: Option<&[u64]>) -> Result<T> {
if aggregatable.is_empty() {
return Err(DivisibleEcashError::Aggregation(
"Empty set of values".to_string(),
));
}
if let Some(indices) = indices {
if !Self::check_unique_indices(indices) {
return Err(DivisibleEcashError::Aggregation(
"Non-unique indices".to_string(),
));
}
perform_lagrangian_interpolation_at_origin(indices, aggregatable)
} else {
// non-threshold
Ok(aggregatable.iter().sum())
}
}
}
impl Aggregatable for PartialSignature {
fn aggregate(sigs: &[PartialSignature], indices: Option<&[u64]>) -> Result<Signature> {
let h = sigs
.get(0)
.ok_or_else(|| DivisibleEcashError::Aggregation("Empty set of signatures".to_string()))?
.sig1();
// TODO: is it possible to avoid this allocation?
let sigmas = sigs.iter().map(|sig| *sig.sig2()).collect::<Vec<_>>();
let aggr_sigma = Aggregatable::aggregate(&sigmas, indices)?;
Ok(Signature(*h, aggr_sigma))
}
}
/// Ensures all provided verification keys were generated to verify the same number of attributes.
fn check_same_key_size(keys: &[VerificationKeyAuth]) -> bool {
keys.iter().map(|vk| vk.beta_g1.len()).all_equal()
&& keys.iter().map(|vk| vk.beta_g2.len()).all_equal()
}
pub fn aggregate_verification_keys(
keys: &[VerificationKeyAuth],
indices: Option<&[SignerIndex]>,
) -> Result<VerificationKeyAuth> {
if !check_same_key_size(keys) {
return Err(DivisibleEcashError::Aggregation(
"Verification keys are of different sizes".to_string(),
));
}
Aggregatable::aggregate(keys, indices)
}
pub fn aggregate_signature_shares(
params: &GroupParameters,
verification_key: &VerificationKeyAuth,
attributes: &[Attribute],
shares: &[SignatureShare],
) -> Result<Signature> {
let (signatures, indices): (Vec<_>, Vec<_>) = shares
.iter()
.map(|share| (*share.signature(), share.index()))
.unzip();
aggregate_signatures(
params,
verification_key,
attributes,
&signatures,
Some(&indices),
)
}
pub fn aggregate_signatures(
params: &GroupParameters,
verification_key: &VerificationKeyAuth,
attributes: &[Attribute],
signatures: &[PartialSignature],
indices: Option<&[SignerIndex]>,
) -> Result<Signature> {
// aggregate the signature
let signature = match Aggregatable::aggregate(signatures, indices) {
Ok(res) => res,
Err(err) => return Err(err),
};
// Verify the signature
let alpha = verification_key.alpha;
let tmp = attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
if !check_bilinear_pairing(
&signature.0.to_affine(),
&G2Prepared::from((alpha + tmp).to_affine()),
&signature.1.to_affine(),
params.prepared_miller_g2(),
) {
return Err(DivisibleEcashError::Aggregation(
"Verification of the aggregated signature failed".to_string(),
));
}
Ok(signature)
}
pub fn aggregate_wallets(
grp: &GroupParameters,
verification_key: &VerificationKeyAuth,
sk_user: &SecretKeyUser,
wallets: &[PartialWallet],
) -> Result<Wallet> {
let signature_shares: Vec<SignatureShare> = wallets
.iter()
.enumerate()
.map(|(idx, wallet)| SignatureShare::new(*wallet.signature(), (idx + 1) as u64))
.collect();
let v = wallets.get(0).unwrap().v;
let attributes = vec![sk_user.sk, v];
let aggregated_signature =
aggregate_signature_shares(&grp, &verification_key, &attributes, &signature_shares)?;
Ok(Wallet {
sig: aggregated_signature,
v,
l: Cell::new(1),
})
}
@@ -0,0 +1,381 @@
use std::collections::{HashMap, HashSet};
use std::ops::Neg;
use bls12_381::{Gt, pairing, Scalar};
use group::Curve;
use crate::error::{DivisibleEcashError, Result};
use crate::scheme::{PayInfo, Payment};
use crate::scheme::identification::IdentifyResult::DoubleSpendingPublicKeys;
use crate::scheme::keygen::{PublicKeyUser, VerificationKeyAuth};
use crate::scheme::setup::Parameters;
#[derive(Debug, Eq, PartialEq)]
pub enum IdentifyResult {
NotADuplicatePayment,
DuplicatePayInfo(PayInfo),
DoubleSpendingPublicKeys(PublicKeyUser),
Whatever,
}
// how do we get the list of all pkU ?
pub fn identify(
params: &Parameters,
verification_key: &VerificationKeyAuth,
public_keys_u: &HashSet<PublicKeyUser>,
payment1: Payment,
payment2: Payment,
pay_info1: PayInfo,
pay_info2: PayInfo) -> Result<IdentifyResult> {
let params_a = params.get_params_a();
// compute the serial numbers for k1 in [0, V1-1]
let mut serial_numbers = HashMap::new();
for k in 0..payment1.vv {
let sn = pairing(&payment1.phi.1.to_affine(), &params_a.get_ith_delta(k as usize).to_affine())
+ pairing(&payment1.phi.0.to_affine(), &params_a.get_etas_ith_jth_elem(payment1.vv as usize, k as usize).to_affine());
serial_numbers.insert(sn, k);
}
// compute the serial numbers fo k2 in [0, V2-1]
let mut k1 = 0;
let mut k2 = 0;
let mut duplicate_serial_numbers: Vec<(Gt, u64, u64)> = Default::default();
for j in 0..payment2.vv {
let sn = pairing(&payment2.phi.1.to_affine(), &params_a.get_ith_delta(j as usize).to_affine())
+ pairing(&payment2.phi.0.to_affine(), &params_a.get_etas_ith_jth_elem(payment2.vv as usize, j as usize).to_affine());
if !serial_numbers.contains_key(&sn) {
serial_numbers.insert(sn, j);
} else {
k1 = *serial_numbers.get(&sn).unwrap() as u64;
k2 = j.clone();
break;
}
return Ok(IdentifyResult::NotADuplicatePayment);
}
if pay_info1 == pay_info2 {
Ok(IdentifyResult::DuplicatePayInfo(pay_info1))
} else {
let delta_k1 = params_a.get_ith_delta(k1 as usize);
let delta_k2 = params_a.get_ith_delta(k2 as usize);
let tt1 = pairing(&payment1.varphi.1.to_affine(), &delta_k1.to_affine())
+ pairing(&payment1.varphi.0.to_affine(), &params_a.get_etas_ith_jth_elem(payment1.vv as usize, k1 as usize).to_affine());
let tt2 = pairing(&payment2.varphi.1.to_affine(), &delta_k2.to_affine())
+ pairing(&payment2.varphi.0.to_affine(), &params_a.get_etas_ith_jth_elem(payment2.vv as usize, k2 as usize).to_affine());
for pk_u in public_keys_u.iter() {
let pg_pku_deltas = pairing(&pk_u.pk.to_affine(), &(delta_k1 * payment1.rr + delta_k2 * payment2.rr.neg()).to_affine());
if tt1 - tt2 == pg_pku_deltas {
return Ok(IdentifyResult::DoubleSpendingPublicKeys(pk_u.clone()));
}
}
return Err(DivisibleEcashError::Identify(
"A duplicate serial number was detected, the payinfo1 and payinfo2 are different, but we failed to identify the double-spending public key".to_string(),
));
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use bls12_381::pairing;
use group::Curve;
use rand::thread_rng;
use crate::scheme::{PayInfo, Payment};
use crate::scheme::aggregation::{aggregate_verification_keys, aggregate_wallets};
use crate::scheme::identification::{identify, IdentifyResult};
use crate::scheme::keygen::{PublicKeyUser, SecretKeyUser, ttp_keygen_authorities, VerificationKeyAuth};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::scheme::withdrawal::{issue, issue_verify, withdrawal_request};
use crate::utils::hash_g1;
#[test]
fn duplicate_payments_with_the_same_pay_info() {
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
let params_u = params.get_params_u();
let params_a = params.get_params_a();
// KEY GENERATION FOR THE AUTHORITIES
let authorities_keypairs = ttp_keygen_authorities(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
// KEY GENERATION FOR THE USER1
let sk1 = grp.random_scalar();
let sk_user1 = SecretKeyUser { sk: sk1 };
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
// KEY GENERATION FOR THE USER2
let sk2 = grp.random_scalar();
let sk_user2 = SecretKeyUser { sk: sk2 };
let pk_user2 = SecretKeyUser::public_key(&sk_user2, &grp);
// WITHDRAWAL REQUEST FOR USER1
let (withdrawal_req1, req_info1) = withdrawal_request(&params, &sk_user1).unwrap();
// ISSUE PARTIAL WALLETS for USER1
let mut partial_wallets1 = Vec::new();
for auth_keypair in authorities_keypairs.clone() {
let blind_signature = issue(
&params,
&withdrawal_req1,
pk_user1.clone(),
&auth_keypair.secret_key(),
).unwrap();
let partial_wallet1 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user1, &blind_signature, &req_info1).unwrap();
partial_wallets1.push(partial_wallet1);
}
// AGGREGATE WALLET FOR USER1
let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
let pay_info1 = PayInfo { info: [67u8; 32] };
let (payment1, wallet1) = wallet1.spend(&params, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
// SPEND VERIFICATION for USER1
assert!(payment1.spend_verify(&params, &verification_key, &pay_info1).unwrap());
let payment2 = payment1.clone();
// SPEND VERIFICATION for the duplicate payment
assert!(payment1.spend_verify(&params, &verification_key, &pay_info1).unwrap());
let pay_info2 = pay_info1.clone();
let public_keys = HashSet::from([pk_user1, pk_user2]);
let identify_result = identify(&params, &verification_key, &public_keys, payment1, payment2, pay_info1, pay_info2).unwrap();
assert_eq!(identify_result, IdentifyResult::DuplicatePayInfo(pay_info1));
}
#[test]
fn two_payments_with_one_repeating_serial_number_but_different_pay_info() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
let params_u = params.get_params_u();
let params_a = params.get_params_a();
// KEY GENERATION FOR THE AUTHORITIES
let authorities_keypairs = ttp_keygen_authorities(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
// KEY GENERATION FOR THE USER1
let sk1 = grp.random_scalar();
let sk_user1 = SecretKeyUser { sk: sk1 };
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
// GENERATE KEYS FOR OTHER USERS
let mut pk_all_users = HashSet::new();
for i in 0..50 {
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = sk_user.public_key(&grp);
pk_all_users.insert(pk_user);
}
pk_all_users.insert(pk_user1.clone());
// WITHDRAWAL REQUEST FOR USER1
let (withdrawal_req1, req_info1) = withdrawal_request(&params, &sk_user1).unwrap();
// ISSUE PARTIAL WALLETS for USER1
let mut partial_wallets1 = Vec::new();
for auth_keypair in authorities_keypairs.clone() {
let blind_signature = issue(
&params,
&withdrawal_req1,
pk_user1.clone(),
&auth_keypair.secret_key(),
).unwrap();
let partial_wallet1 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user1, &blind_signature, &req_info1).unwrap();
partial_wallets1.push(partial_wallet1);
}
// AGGREGATE WALLET FOR USER1
let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
let pay_info1 = PayInfo { info: [67u8; 32] };
let (payment1, new_wallet1) = wallet1.spend(&params, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
// let's reverse the spending counter in the wallet to create a double spending payment
let current_l = wallet1.l.get();
wallet1.l.set(current_l - 1);
let pay_info2 = PayInfo { info: [52u8; 32] };
let (payment2, wallet1) = wallet1.spend(&params, &verification_key, &sk_user1, &pay_info2, 10, false).unwrap();
let identify_result = identify(&params, &verification_key, &pk_all_users, payment1, payment2, pay_info1, pay_info2).unwrap();
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(pk_user1));
}
#[test]
fn two_payments_with_multiple_repeating_serial_number_but_different_pay_info() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
let params_u = params.get_params_u();
let params_a = params.get_params_a();
// KEY GENERATION FOR THE AUTHORITIES
let authorities_keypairs = ttp_keygen_authorities(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
// KEY GENERATION FOR THE USER1
let sk1 = grp.random_scalar();
let sk_user1 = SecretKeyUser { sk: sk1 };
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
// GENERATE KEYS FOR OTHER USERS
let mut public_keys = HashSet::new();
for i in 0..50 {
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = sk_user.public_key(&grp);
public_keys.insert(pk_user);
}
public_keys.insert(pk_user1.clone());
// WITHDRAWAL REQUEST FOR USER1
let (withdrawal_req1, req_info1) = withdrawal_request(&params, &sk_user1).unwrap();
// ISSUE PARTIAL WALLETS for USER1
let mut partial_wallets1 = Vec::new();
for auth_keypair in authorities_keypairs.clone() {
let blind_signature = issue(
&params,
&withdrawal_req1,
pk_user1.clone(),
&auth_keypair.secret_key(),
).unwrap();
let partial_wallet1 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user1, &blind_signature, &req_info1).unwrap();
partial_wallets1.push(partial_wallet1);
}
// AGGREGATE WALLET FOR USER1
let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
let pay_info1 = PayInfo { info: [67u8; 32] };
let (payment1, new_wallet1) = wallet1.spend(&params, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
// let's reverse the spending counter in the wallet to create a double spending payment
let current_l = wallet1.l.get();
wallet1.l.set(current_l - 7);
let pay_info2 = PayInfo { info: [52u8; 32] };
let (payment2, wallet1) = wallet1.spend(&params, &verification_key, &sk_user1, &pay_info2, 10, false).unwrap();
let identify_result = identify(&params, &verification_key, &public_keys, payment1, payment2, pay_info1, pay_info2).unwrap();
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(pk_user1));
}
#[test]
fn ok_if_two_different_payments() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
let params_u = params.get_params_u();
let params_a = params.get_params_a();
// KEY GENERATION FOR THE AUTHORITIES
let authorities_keypairs = ttp_keygen_authorities(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
// KEY GENERATION FOR THE USER1
let sk1 = grp.random_scalar();
let sk_user1 = SecretKeyUser { sk: sk1 };
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
// KEY GENERATION FOR THE USER2
let sk2 = grp.random_scalar();
let sk_user2 = SecretKeyUser { sk: sk2 };
let pk_user2 = SecretKeyUser::public_key(&sk_user2, &grp);
// WITHDRAWAL REQUEST FOR USER1
let (withdrawal_req1, req_info1) = withdrawal_request(&params, &sk_user1).unwrap();
// ISSUE PARTIAL WALLETS for USER1
let mut partial_wallets1 = Vec::new();
for auth_keypair in authorities_keypairs.clone() {
let blind_signature = issue(
&params,
&withdrawal_req1,
pk_user1.clone(),
&auth_keypair.secret_key(),
).unwrap();
let partial_wallet1 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user1, &blind_signature, &req_info1).unwrap();
partial_wallets1.push(partial_wallet1);
}
// AGGREGATE WALLET FOR USER1
let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
let pay_info1 = PayInfo { info: [67u8; 32] };
let (payment1, wallet1) = wallet1.spend(&params, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
// SPEND VERIFICATION for USER1
assert!(payment1.spend_verify(&params, &verification_key, &pay_info1).unwrap());
// WITHDRAWAL REQUEST FOR USER2
let (withdrawal_req2, req_info2) = withdrawal_request(&params, &sk_user2).unwrap();
// ISSUE PARTIAL WALLETS for USER2
let mut partial_wallets2 = Vec::new();
for auth_keypair in authorities_keypairs.clone() {
let blind_signature = issue(
&params,
&withdrawal_req2,
pk_user2.clone(),
&auth_keypair.secret_key(),
).unwrap();
let partial_wallet2 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user2, &blind_signature, &req_info2).unwrap();
partial_wallets2.push(partial_wallet2);
}
// AGGREGATE WALLET FOR USER2
let mut wallet2 = aggregate_wallets(&grp, &verification_key, &sk_user2, &partial_wallets2).unwrap();
let pay_info2 = PayInfo { info: [67u8; 32] };
let (payment2, wallet2) = wallet2.spend(&params, &verification_key, &sk_user2, &pay_info2, 10, false).unwrap();
// SPEND VERIFICATION for USER2
assert!(payment2.spend_verify(&params, &verification_key, &pay_info2).unwrap());
let public_keys = HashSet::from([pk_user1, pk_user2]);
let identify_result = identify(&params, &verification_key, &public_keys, payment1, payment2, pay_info1, pay_info2).unwrap();
assert_eq!(identify_result, IdentifyResult::NotADuplicatePayment);
}
}
@@ -0,0 +1,432 @@
use std::borrow::Borrow;
use std::convert::{TryFrom, TryInto};
use std::iter::Sum;
use std::ops::{Add, Mul};
use bls12_381::{G1Projective, G2Projective, Scalar};
use group::Curve;
use crate::error::{DivisibleEcashError, Result};
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::utils::{Polynomial, SignerIndex, try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar, try_deserialize_scalar_vec};
#[derive(Eq, Debug, PartialEq, Clone)]
pub struct SecretKeyAuth {
pub(crate) x: Scalar,
pub(crate) ys: Vec<Scalar>,
}
impl TryFrom<&[u8]> for SecretKeyAuth {
type Error = DivisibleEcashError;
fn try_from(bytes: &[u8]) -> Result<SecretKeyAuth> {
// There should be x and at least one y
if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 {
return Err(DivisibleEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8,
target: 32 * 2 + 8,
modulus: 32,
object: "secret key".to_string(),
});
}
// this conversion will not fail as we are taking the same length of data
let x_bytes: [u8; 32] = bytes[..32].try_into().unwrap();
let ys_len = u64::from_le_bytes(bytes[32..40].try_into().unwrap());
let actual_ys_len = (bytes.len() - 40) / 32;
if ys_len as usize != actual_ys_len {
return Err(DivisibleEcashError::Deserialization(format!(
"Tried to deserialize secret key with inconsistent ys len (expected {}, got {})",
ys_len, actual_ys_len
)));
}
let x = try_deserialize_scalar(
&x_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize secret key scalar".to_string(),
),
)?;
let ys = try_deserialize_scalar_vec(
ys_len,
&bytes[40..],
DivisibleEcashError::Deserialization(
"Failed to deserialize secret key scalars".to_string(),
),
)?;
Ok(SecretKeyAuth { x, ys })
}
}
impl SecretKeyAuth {
pub fn verification_key(&self, grp: &GroupParameters) -> VerificationKeyAuth {
let g1 = grp.gen1();
let g2 = grp.gen2();
VerificationKeyAuth {
alpha: g2 * self.x,
beta_g1: self.ys.iter().map(|y| g1 * y).collect(),
beta_g2: self.ys.iter().map(|y| g2 * y).collect(),
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let ys_len = self.ys.len();
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) as usize * 32);
bytes.extend_from_slice(&self.x.to_bytes());
bytes.extend_from_slice(&ys_len.to_le_bytes());
for y in self.ys.iter() {
bytes.extend_from_slice(&y.to_bytes())
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<SecretKeyAuth> {
SecretKeyAuth::try_from(bytes)
}
}
#[derive(Eq, Debug, PartialEq, Clone)]
pub struct VerificationKeyAuth {
pub(crate) alpha: G2Projective,
pub(crate) beta_g1: Vec<G1Projective>,
pub(crate) beta_g2: Vec<G2Projective>,
}
impl TryFrom<&[u8]> for VerificationKeyAuth {
type Error = DivisibleEcashError;
fn try_from(bytes: &[u8]) -> Result<VerificationKeyAuth> {
// There should be at least alpha, one betaG1 and one betaG2 and their length
if bytes.len() < 96 * 2 + 48 + 8 || (bytes.len() - 8 - 96) % (96 + 48) != 0 {
return Err(DivisibleEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8 - 96,
target: 96 * 2 + 48 + 8,
modulus: 96 + 48,
object: "verification key".to_string(),
});
}
// this conversion will not fail as we are taking the same length of data
let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
let betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
let actual_betas_len = (bytes.len() - 104) / (96 + 48);
if betas_len as usize != actual_betas_len {
return Err(
DivisibleEcashError::Deserialization(
format!("Tried to deserialize verification key with inconsistent betas len (expected {}, got {})",
betas_len, actual_betas_len
)));
}
let alpha = try_deserialize_g2_projective(
&alpha_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize verification key G2 point (alpha)".to_string(),
),
)?;
let mut beta_g1 = Vec::with_capacity(betas_len as usize);
let mut beta_g1_end: u64 = 0;
for i in 0..betas_len {
let start = (104 + i * 48) as usize;
let end = (start + 48) as usize;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g1_projective(
&beta_i_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize verification key G1 point (beta)".to_string(),
),
)?;
beta_g1_end = end as u64;
beta_g1.push(beta_i)
}
let mut beta_g2 = Vec::with_capacity(betas_len as usize);
for i in 0..betas_len {
let start = (beta_g1_end + i * 96) as usize;
let end = (start + 96) as usize;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g2_projective(
&beta_i_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize verification key G2 point (beta)".to_string(),
),
)?;
beta_g2.push(beta_i)
}
Ok(VerificationKeyAuth {
alpha,
beta_g1,
beta_g2,
})
}
}
impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth {
type Output = VerificationKeyAuth;
#[inline]
fn add(self, rhs: &'b VerificationKeyAuth) -> VerificationKeyAuth {
// If you're trying to add two keys together that were created
// for different number of attributes, just panic as it's a
// nonsense operation.
assert_eq!(
self.beta_g1.len(),
rhs.beta_g1.len(),
"trying to add verification keys generated for different number of attributes [G1]"
);
assert_eq!(
self.beta_g2.len(),
rhs.beta_g2.len(),
"trying to add verification keys generated for different number of attributes [G2]"
);
assert_eq!(
self.beta_g1.len(),
self.beta_g2.len(),
"this key is incorrect - the number of elements G1 and G2 does not match"
);
assert_eq!(
rhs.beta_g1.len(),
rhs.beta_g2.len(),
"they key you want to add is incorrect - the number of elements G1 and G2 does not match"
);
VerificationKeyAuth {
alpha: self.alpha + rhs.alpha,
beta_g1: self
.beta_g1
.iter()
.zip(rhs.beta_g1.iter())
.map(|(self_beta_g1, rhs_beta_g1)| self_beta_g1 + rhs_beta_g1)
.collect(),
beta_g2: self
.beta_g2
.iter()
.zip(rhs.beta_g2.iter())
.map(|(self_beta_g2, rhs_beta_g2)| self_beta_g2 + rhs_beta_g2)
.collect(),
}
}
}
impl<'a> Mul<Scalar> for &'a VerificationKeyAuth {
type Output = VerificationKeyAuth;
#[inline]
fn mul(self, rhs: Scalar) -> Self::Output {
VerificationKeyAuth {
alpha: self.alpha * rhs,
beta_g1: self.beta_g1.iter().map(|b_i| b_i * rhs).collect(),
beta_g2: self.beta_g2.iter().map(|b_i| b_i * rhs).collect(),
}
}
}
impl<T> Sum<T> for VerificationKeyAuth
where
T: Borrow<VerificationKeyAuth>,
{
#[inline]
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item=T>,
{
let mut peekable = iter.peekable();
let head_attributes = match peekable.peek() {
Some(head) => head.borrow().beta_g2.len(),
None => {
// TODO: this is a really weird edge case. You're trying to sum an EMPTY iterator
// of VerificationKey. So should it panic here or just return some nonsense value?
return VerificationKeyAuth::identity(0);
}
};
peekable.fold(
VerificationKeyAuth::identity(head_attributes),
|acc, item| acc + item.borrow(),
)
}
}
impl VerificationKeyAuth {
/// Create a (kinda) identity verification key using specified
/// number of 'beta' elements
pub(crate) fn identity(beta_size: usize) -> Self {
VerificationKeyAuth {
alpha: G2Projective::identity(),
beta_g1: vec![G1Projective::identity(); beta_size],
beta_g2: vec![G2Projective::identity(); beta_size],
}
}
pub fn aggregate(sigs: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self> {
aggregate_verification_keys(sigs, indices)
}
pub fn alpha(&self) -> &G2Projective {
&self.alpha
}
pub fn beta_g1(&self) -> &Vec<G1Projective> {
&self.beta_g1
}
pub fn beta_g2(&self) -> &Vec<G2Projective> {
&self.beta_g2
}
pub fn to_bytes(&self) -> Vec<u8> {
let beta_g1_len = self.beta_g1.len();
let beta_g2_len = self.beta_g2.len();
let mut bytes = Vec::with_capacity(96 + 8 + beta_g1_len * 48 + beta_g2_len * 96);
bytes.extend_from_slice(&self.alpha.to_affine().to_compressed());
bytes.extend_from_slice(&beta_g1_len.to_le_bytes());
for beta_g1 in self.beta_g1.iter() {
bytes.extend_from_slice(&beta_g1.to_affine().to_compressed())
}
for beta_g2 in self.beta_g2.iter() {
bytes.extend_from_slice(&beta_g2.to_affine().to_compressed())
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<VerificationKeyAuth> {
VerificationKeyAuth::try_from(bytes)
}
}
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct KeyPairUser {
secret_key: SecretKeyUser,
public_key: PublicKeyUser,
}
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct KeyPairAuth {
secret_key: SecretKeyAuth,
verification_key: VerificationKeyAuth,
/// Optional index value specifying polynomial point used during threshold key generation.
pub index: Option<SignerIndex>,
}
impl KeyPairAuth {
pub fn secret_key(&self) -> SecretKeyAuth {
self.secret_key.clone()
}
pub fn verification_key(&self) -> VerificationKeyAuth {
self.verification_key.clone()
}
}
#[derive(Eq, Debug, PartialEq, Clone)]
pub struct SecretKeyUser {
pub sk: Scalar,
}
impl SecretKeyUser {
pub fn public_key(&self, params: &GroupParameters) -> PublicKeyUser {
PublicKeyUser {
pk: params.gen1() * self.sk,
}
}
}
#[derive(Hash, Eq, Debug, PartialEq, Clone)]
pub struct PublicKeyUser {
pub(crate) pk: G1Projective,
}
impl KeyPairUser {
pub fn secret_key(&self) -> SecretKeyUser {
self.secret_key.clone()
}
pub fn public_key(&self) -> PublicKeyUser {
self.public_key.clone()
}
}
pub fn ttp_keygen_authorities(
params: &Parameters,
threshold: u64,
num_authorities: u64) -> Result<Vec<KeyPairAuth>> {
if threshold == 0 {
return Err(DivisibleEcashError::Setup(
"Tried to generate threshold keys with a 0 threshold value".to_string(),
));
}
if threshold > num_authorities {
return Err(
DivisibleEcashError::Setup(
"Tried to generate threshold keys for threshold value being higher than number of the signing authorities".to_string(),
));
}
let grp = params.get_grp();
// generate polynomials
let v = Polynomial::new_random(&grp, threshold - 1);
let ws = (0..2)
.map(|_| Polynomial::new_random(&grp, threshold - 1))
.collect::<Vec<_>>();
let polynomial_indices = (1..=num_authorities).collect::<Vec<_>>();
// generate polynomial shares
let x = polynomial_indices
.iter()
.map(|&id| v.evaluate(&Scalar::from(id)));
let ys = polynomial_indices.iter().map(|&id| {
ws.iter()
.map(|w| w.evaluate(&Scalar::from(id)))
.collect::<Vec<_>>()
});
let secret_keys = x.zip(ys).map(|(x, ys)| SecretKeyAuth { x, ys });
let keypairs = secret_keys
.zip(polynomial_indices.iter())
.map(|(secret_key, index)| {
let verification_key = secret_key.verification_key(&grp);
KeyPairAuth {
secret_key,
verification_key,
index: Some(*index),
}
})
.collect();
Ok(keypairs)
}
pub fn ttp_keygen_users(params: &Parameters) -> KeyPairUser {
let grp = params.get_grp();
let sk_user = SecretKeyUser { sk: grp.random_scalar() };
let pk_user = PublicKeyUser { pk: grp.gen1() * sk_user.sk };
KeyPairUser {
secret_key: sk_user,
public_key: pk_user,
}
}
@@ -0,0 +1,660 @@
use std::cell::Cell;
use std::convert::{TryFrom, TryInto};
use std::ops::Neg;
use bls12_381::{G1Projective, G2Prepared, G2Projective, pairing, Scalar};
use group::{Curve, GroupEncoding};
use crate::Attribute;
use crate::constants::L;
use crate::error::{DivisibleEcashError, Result};
use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::utils::{check_bilinear_pairing, hash_to_scalar, Signature, SignerIndex, try_deserialize_g1_projective, try_deserialize_scalar, try_deserialize_g2_projective};
pub mod aggregation;
pub mod keygen;
pub mod setup;
pub mod structure_preserving_signature;
pub mod withdrawal;
pub mod identification;
pub fn compute_kappa(
params: &GroupParameters,
verification_key: &VerificationKeyAuth,
attributes: &[Attribute],
blinding_factor: Scalar,
) -> G2Projective {
params.gen2() * blinding_factor
+ verification_key.alpha
+ attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
.sum::<G2Projective>()
}
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
pub struct Phi(pub(crate) G1Projective, pub(crate) G1Projective);
impl Phi {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(48 + 48);
bytes.extend_from_slice(self.0.to_bytes().as_ref());
bytes.extend_from_slice(self.1.to_bytes().as_ref());
bytes
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 48 * 2 || (bytes.len()) % 48 != 0 {
return Err(DivisibleEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len(),
target: 48 * 2,
modulus: 48,
object: "phi".to_string(),
});
}
let elem_0_bytes = bytes[0..48].try_into().unwrap();
let elem_0 = try_deserialize_g1_projective(
elem_0_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize element 0 of Phi".to_string()),
)?;
let elem_1_bytes = bytes[48..96].try_into().unwrap();
let elem_1 = try_deserialize_g1_projective(
elem_1_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize element 1 of Phi".to_string()),
)?;
Ok(Phi(elem_0, elem_1))
}
}
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
pub struct VarPhi(pub(crate) G1Projective, pub(crate) G1Projective);
impl VarPhi {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(48 + 48);
bytes.extend_from_slice(self.0.to_bytes().as_ref());
bytes.extend_from_slice(self.1.to_bytes().as_ref());
bytes
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 48 * 2 || (bytes.len()) % 48 != 0 {
return Err(DivisibleEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len(),
target: 48 * 2,
modulus: 48,
object: "varphi".to_string(),
});
}
let elem_0_bytes = bytes[0..48].try_into().unwrap();
let elem_0 = try_deserialize_g1_projective(
elem_0_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize element 0 of VarPhi".to_string()),
)?;
let elem_1_bytes = bytes[48..96].try_into().unwrap();
let elem_1 = try_deserialize_g1_projective(
elem_1_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize element 1 of VarPhi".to_string()),
)?;
Ok(VarPhi(elem_0, elem_1))
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct PayInfo {
pub info: [u8; 32],
}
#[derive(Debug, Clone, PartialEq)]
pub struct Payment {
pub kappa: G2Projective,
pub sig: Signature,
pub phi: Phi,
pub varphi: VarPhi,
pub varsig_prime1: G1Projective,
pub varsig_prime2: G1Projective,
pub theta_prime1: G1Projective,
pub theta_prime2: G1Projective,
pub rr_prime: G1Projective,
pub ss_prime: G1Projective,
pub tt_prime: G2Projective,
pub rr: Scalar,
pub zk_proof: SpendProof,
pub vv: u64,
}
impl Payment {
pub fn get_kappa(&self) -> G2Projective { self.kappa }
pub fn get_sig(&self) -> Signature { self.sig }
pub fn get_phi(&self) -> Phi { self.phi }
pub fn get_varphi(&self) -> VarPhi { self.varphi }
pub fn get_varsig_prime1(&self) -> G1Projective { self.varsig_prime1 }
pub fn get_varsig_prime2(&self) -> G1Projective { self.varsig_prime2 }
pub fn get_theta_prime1(&self) -> G1Projective { self.theta_prime1 }
pub fn get_theta_prime2(&self) -> G1Projective { self.theta_prime2 }
pub fn get_rr_prime(&self) -> G1Projective { self.rr_prime }
pub fn get_ss_prime(&self) -> G1Projective { self.ss_prime }
pub fn get_tt_prime(&self) -> G2Projective { self.tt_prime }
pub fn get_rr(&self) -> Scalar { self.rr }
pub fn get_zk_proof(&self) -> SpendProof { self.zk_proof.clone() }
pub fn get_vv(&self) -> u64 { self.vv }
pub fn to_bytes(&self) -> Vec<u8>{
let kappa_bytes = self.kappa.to_affine().to_compressed();
let sig_bytes = self.sig.to_bytes();
let phi_bytes = self.phi.to_bytes();
let varphi_bytes = self.varphi.to_bytes();
let varsig_prime1_bytes = self.varsig_prime1.to_affine().to_compressed();
let varsig_prime2_bytes = self.varsig_prime2.to_affine().to_compressed();
let theta_prime1_bytes = self.theta_prime1.to_affine().to_compressed();
let theta_prime2 = self.theta_prime2.to_affine().to_compressed();
let rr_prime_bytes = self.rr_prime.to_affine().to_compressed();
let ss_prime_bytes = self.ss_prime.to_affine().to_compressed();
let tt_prime_bytes = self.tt_prime.to_affine().to_compressed();
let rr_bytes = self.rr.to_bytes();
let zk_proof_bytes = self.zk_proof.to_bytes();
let vv_bytes = self.vv.to_le_bytes();
let mut bytes: Vec<u8> = Vec::with_capacity(760);
bytes.extend_from_slice(&kappa_bytes);
bytes.extend_from_slice(&sig_bytes);
bytes.extend_from_slice(&phi_bytes);
bytes.extend_from_slice(&varphi_bytes);
bytes.extend_from_slice(&varsig_prime1_bytes);
bytes.extend_from_slice(&varsig_prime2_bytes);
bytes.extend_from_slice(&theta_prime1_bytes);
bytes.extend_from_slice(&theta_prime2);
bytes.extend_from_slice(&rr_prime_bytes);
bytes.extend_from_slice(&ss_prime_bytes);
bytes.extend_from_slice(&tt_prime_bytes);
bytes.extend_from_slice(&rr_bytes);
bytes.extend_from_slice(&vv_bytes);
bytes.extend_from_slice(&zk_proof_bytes);
bytes
}
}
impl TryFrom<&[u8]> for Payment {
type Error = DivisibleEcashError;
fn try_from(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 760 {
return Err(DivisibleEcashError::Deserialization(
"Invalid byte array for Payment deserialization".to_string(),
));
}
let mut idx = 0;
let kappa_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap();
let kappa = try_deserialize_g2_projective(
&kappa_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize kappa".to_string()),
)?;
idx += 96;
let sig_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap();
let sig = Signature::try_from(sig_bytes.as_slice())?;
idx += 96;
let phi_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap();
let phi = Phi::from_bytes(&phi_bytes).unwrap();
idx += 96;
let varphi_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap();
let varphi = VarPhi::from_bytes(&varphi_bytes).unwrap();
idx += 96;
let varsig_prime1_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap();
let varsig_prime1 = try_deserialize_g1_projective(
&varsig_prime1_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize varsig_prime1".to_string()),
)?;
idx += 48;
let varsig_prime2_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap();
let varsig_prime2 = try_deserialize_g1_projective(
&varsig_prime2_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize varsig_prime2".to_string()),
)?;
idx += 48;
let theta_prime1_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap();
let theta_prime1 = try_deserialize_g1_projective(
&theta_prime1_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize theta_prime1".to_string()),
)?;
idx += 48;
let theta_prime2_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap();
let theta_prime2 = try_deserialize_g1_projective(
&theta_prime2_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize theta_prime2".to_string()),
)?;
idx += 48;
let rr_prime_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap();
let rr_prime = try_deserialize_g1_projective(
&rr_prime_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize rr_prime".to_string()),
)?;
idx += 48;
let ss_prime_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap();
let ss_prime = try_deserialize_g1_projective(
&ss_prime_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize ss_prime".to_string()),
)?;
idx += 48;
let tt_prime_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap();
let tt_prime = try_deserialize_g2_projective(
&tt_prime_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize tt_prime".to_string()),
)?;
idx += 96;
let rr_bytes: [u8; 32] = bytes[idx..idx+32].try_into().unwrap();
let rr = try_deserialize_scalar(
&rr_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize rr element".to_string()),
)?;
idx += 32;
let vv = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap());
idx += 8;
// Deserialize the SpendProof struct
let zk_proof_bytes = &bytes[idx..];
let zk_proof = SpendProof::try_from(zk_proof_bytes)?;
Ok(Payment{
kappa,
sig,
phi,
varphi,
varsig_prime1,
varsig_prime2,
theta_prime1,
theta_prime2,
rr_prime,
ss_prime,
tt_prime,
rr,
zk_proof,
vv,
})
}
}
pub struct PartialWallet {
sig: Signature,
v: Scalar,
idx: Option<SignerIndex>,
}
impl PartialWallet {
pub fn signature(&self) -> &Signature {
&self.sig
}
pub fn v(&self) -> Scalar {
self.v
}
pub fn index(&self) -> Option<SignerIndex> {
self.idx
}
}
pub struct Wallet {
sig: Signature,
v: Scalar,
pub l: Cell<u64>,
}
impl Wallet {
pub fn signature(&self) -> &Signature {
&self.sig
}
pub fn v(&self) -> Scalar {
self.v
}
pub fn l(&self) -> u64 {
self.l.get()
}
pub fn spend(
&self,
params: &Parameters,
verification_key: &VerificationKeyAuth,
sk_user: &SecretKeyUser,
pay_info: &PayInfo,
vv: u64,
bench_flag: bool,
) -> Result<(Payment, &Self)> {
if self.l() + vv >= L {
return Err(DivisibleEcashError::Spend(
"The counter l is higher than max L".to_string(),
));
}
let grp = params.get_grp();
let params_u = params.get_params_u();
let params_a = params.get_params_a();
// randomize signature in the wallet
let (signature_prime, sign_blinding_factor) = self.signature().randomise(grp);
// construct kappa i.e., blinded attributes for show
let attributes = vec![sk_user.sk, self.v()];
// compute kappa
let kappa = compute_kappa(
&grp,
&verification_key,
&attributes,
sign_blinding_factor,
);
let r1 = grp.random_scalar();
let r2 = grp.random_scalar();
let phi = Phi(grp.gen1() * r1, params_u.get_ith_sigma(self.l() as usize) * self.v + params_u.get_ith_eta(vv as usize) * r1);
// compute hash of the payment info
let rr = hash_to_scalar(pay_info.info);
let varphi = VarPhi(grp.gen1() * r2, (grp.gen1() * rr) * sk_user.sk + params_u.get_ith_theta(self.l() as usize) * self.v + params_u.get_ith_eta(vv as usize) * r2);
// random value used to compute blinded bases
let r_varsig1 = grp.random_scalar();
let r_theta1 = grp.random_scalar();
let r_varsig2 = grp.random_scalar();
let r_theta2 = grp.random_scalar();
let r_rr = grp.random_scalar();
let r_ss = grp.random_scalar();
let r_tt = grp.random_scalar();
// compute blinded bases
let psi_g1 = params_u.get_psi_g1();
let psi_g2 = params_u.get_psi_g2();
let varsig_prime1 = params_u.get_ith_sigma(self.l() as usize) + (psi_g1 * r_varsig1);
let theta_prime1 = params_u.get_ith_theta(self.l() as usize) + (psi_g1 * r_theta1);
let varsig_prime2 = params_u.get_ith_sigma(self.l() as usize + vv as usize - 1) + (psi_g1 * r_varsig2);
let theta_prime2 = params_u.get_ith_theta(self.l() as usize + vv as usize - 1) + (psi_g1 * r_theta2);
let tau_l_vv = params_u.get_ith_sps_sign(self.l() as usize + vv as usize - 1);
let rr_prime = tau_l_vv.rr + (psi_g1 * r_rr);
let ss_prime = tau_l_vv.ss + (psi_g1 * r_ss);
let tt_prime = tau_l_vv.tt + (psi_g2 * r_tt);
let rho1 = self.v.neg() * r_varsig1;
let rho2 = self.v.neg() * r_theta1;
let rho3 = r_rr * r_tt;
let pg_varsigpr1_delta = pairing(&varsig_prime1.to_affine(), &params_a.get_ith_delta((vv - 1) as usize).to_affine());
let pg_psi0_delta = pairing(&psi_g1.to_affine(), &params_a.get_ith_delta((vv - 1) as usize).to_affine());
let pg_varsigpr2_gen2 = pairing(&varsig_prime2.to_affine(), grp.gen2());
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
let pg_thetapr1_delta = pairing(&theta_prime1.to_affine(), &params_a.get_ith_delta((vv - 1) as usize).to_affine());
let pg_thetapr2_gen2 = pairing(&theta_prime2.to_affine(), grp.gen2());
let yy = params_u.get_sps_pk().get_yy();
let pg_rrprime_yy = pairing(&rr_prime.to_affine(), &yy.to_affine());
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
let pg_ssprime_gen2 = pairing(&ss_prime.to_affine(), grp.gen2());
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
let pg_varsigpr2_ww1 = pairing(&varsig_prime2.to_affine(), &ww1.to_affine());
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
let pg_thetapr2_ww2 = pairing(&theta_prime2.to_affine(), &ww2.to_affine());
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
let pg_gen1_zz = pairing(grp.gen1(), &params_u.get_sps_pk().get_zz().to_affine());
let pg_rr_tt = pairing(&rr_prime.to_affine(), &tt_prime.to_affine());
let pg_rr_psi1 = pairing(&rr_prime.to_affine(), &psi_g2.to_affine());
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &tt_prime.to_affine());
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
let pg_eq3 = pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 + pg_gen1_zz.neg();
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
let instance = SpendInstance {
kappa,
phi,
varphi,
rr,
rr_prime,
ss_prime,
tt_prime,
varsig_prime1,
theta_prime1,
pg_eq1,
pg_eq2,
pg_eq3,
pg_eq4,
psi_g1: *psi_g1,
psi_g2: *psi_g2,
pg_psi0_delta,
pg_psi0_gen2,
pg_psi0_yy,
pg_psi0_ww1,
pg_psi0_ww2,
pg_rr_psi1,
pg_psi0_tt,
pg_psi0_psi1,
};
let witness = SpendWitness {
sk_u: sk_user.clone(),
v: self.v,
r: sign_blinding_factor,
r1,
r2,
r_varsig1,
r_theta1,
r_varsig2,
r_theta2,
r_rr,
r_ss,
r_tt,
rho1,
rho2,
rho3,
};
// compute the zk proof
let zk_proof = SpendProof::construct(params, &instance, &witness, &verification_key, vv);
// output pay and updated wallet
let pay = Payment {
kappa,
sig: signature_prime,
phi,
varphi,
varsig_prime1,
varsig_prime2,
theta_prime1,
theta_prime2,
rr_prime,
ss_prime,
tt_prime,
rr,
zk_proof,
vv,
};
// The number of samples collected by the benchmark process is way higher than the
// MAX_WALLET_VALUE we ever consider. Thus, we would execute the spending too many times
// and the initial condition at the top of this function will crush. Thus, we need a
// benchmark flag to signal that we don't want to increase the spending couter but only
// care about the function performance.
if !bench_flag {
let current_l = self.l();
self.l.set(current_l + vv);
}
Ok((pay, self))
}
}
impl Payment {
pub fn spend_verify(
&self,
params: &Parameters,
verification_key: &VerificationKeyAuth,
pay_info: &PayInfo) -> Result<bool> {
if bool::from(self.sig.0.is_identity()) {
return Err(DivisibleEcashError::Spend(
"The element h of the signature equals the identity".to_string(),
));
}
let grp = params.get_grp();
let params_a = params.get_params_a();
let params_u = params.get_params_u();
if !check_bilinear_pairing(
&self.sig.0.to_affine(),
&G2Prepared::from(self.kappa.to_affine()),
&self.sig.1.to_affine(),
grp.prepared_miller_g2(),
) {
return Err(DivisibleEcashError::Spend(
"The bilinear check for kappa failed".to_string(),
));
}
if bool::from(self.sig.0.is_identity()) {
return Err(DivisibleEcashError::Spend(
"The element h of the signature on l equals the identity".to_string(),
));
}
// verify integrity of R
if !(self.rr == hash_to_scalar(pay_info.info)) {
return Err(DivisibleEcashError::Spend(
"Integrity of R does not hold".to_string(),
));
}
//TODO: verify whether payinfo contains merchent's identifier
let psi_g1 = params_u.get_psi_g1();
let psi_g2 = params_u.get_psi_g2();
let pg_varsigpr1_delta = pairing(&self.varsig_prime1.to_affine(), &params_a.get_ith_delta((self.vv - 1) as usize).to_affine());
let pg_psi0_delta = pairing(&psi_g1.to_affine(), &params_a.get_ith_delta((self.vv - 1) as usize).to_affine());
let pg_varsigpr2_gen2 = pairing(&self.varsig_prime2.to_affine(), grp.gen2());
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
let pg_thetapr1_delta = pairing(&self.theta_prime1.to_affine(), &params_a.get_ith_delta((self.vv - 1) as usize).to_affine());
let pg_thetapr2_gen2 = pairing(&self.theta_prime2.to_affine(), grp.gen2());
let yy = params_u.get_sps_pk().get_yy();
let pg_rrprime_yy = pairing(&self.rr_prime.to_affine(), &yy.to_affine());
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
let pg_ssprime_gen2 = pairing(&self.ss_prime.to_affine(), grp.gen2());
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
let pg_varsigpr2_ww1 = pairing(&self.varsig_prime2.to_affine(), &ww1.to_affine());
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
let pg_thetapr2_ww2 = pairing(&self.theta_prime2.to_affine(), &ww2.to_affine());
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
let pg_gen1_zz = pairing(grp.gen1(), &params_u.get_sps_pk().get_zz().to_affine());
let pg_rr_tt = pairing(&self.rr_prime.to_affine(), &self.tt_prime.to_affine());
let pg_rr_psi1 = pairing(&self.rr_prime.to_affine(), &psi_g2.to_affine());
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &self.tt_prime.to_affine());
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
let pg_eq3 = pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 + pg_gen1_zz.neg();
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
let instance = SpendInstance {
kappa: self.kappa,
phi: self.phi,
varphi: self.varphi,
rr: self.rr,
rr_prime: self.rr_prime,
ss_prime: self.ss_prime,
tt_prime: self.tt_prime,
varsig_prime1: self.varsig_prime1,
theta_prime1: self.theta_prime1,
pg_eq1,
pg_eq2,
pg_eq3,
pg_eq4,
psi_g1: *psi_g1,
psi_g2: *psi_g2,
pg_psi0_delta,
pg_psi0_gen2,
pg_psi0_yy,
pg_psi0_ww1,
pg_psi0_ww2,
pg_rr_psi1,
pg_psi0_tt,
pg_psi0_psi1,
};
Ok(self.zk_proof.verify(&params, &instance, &verification_key, self.vv))
}
}
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
use rand::thread_rng;
use crate::scheme::{PayInfo, Phi, VarPhi, Wallet};
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::keygen::{PublicKeyUser, ttp_keygen_authorities, VerificationKeyAuth};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::utils::hash_g1;
#[test]
fn phi_to_and_from_bytes() {
let phi = Phi(hash_g1("Element 0 of Phi"), hash_g1("Element 1 of Phi"));
let phi_bytes = phi.to_bytes();
let phi_from_bytes = Phi::from_bytes(&phi_bytes).unwrap();
assert_eq!(phi, phi_from_bytes);
}
#[test]
fn varphi_to_and_from_bytes() {
let varphi = VarPhi(hash_g1("Element 0 of VarPhi"), hash_g1("Element 1 of VarPhi"));
let varphi_bytes = varphi.to_bytes();
let varphi_from_bytes = VarPhi::from_bytes(&varphi_bytes).unwrap();
assert_eq!(varphi, varphi_from_bytes);
}
#[test]
fn spend_verification_is_correct() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
let params_u = params.get_params_u();
let params_a = params.get_params_a();
let sk = grp.random_scalar();
let pk_user = PublicKeyUser {
pk: grp.gen1() * sk,
};
let authorities_keypairs = ttp_keygen_authorities(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
}
}
@@ -0,0 +1,212 @@
use std::convert::TryFrom;
use std::net::ToSocketAddrs;
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, pairing, Scalar};
use ff::Field;
use group::Curve;
use rand::thread_rng;
use crate::constants::L;
use crate::error::Result;
use crate::scheme::structure_preserving_signature::{SPSKeyPair, SPSSignature, SPSVerificationKey};
use crate::utils::{hash_g1, hash_g2};
#[derive(Debug, Clone)]
pub struct GroupParameters {
/// Generator of the G1 group
g1: G1Affine,
/// Generator of the G2 group
g2: G2Affine,
/// Precomputed G2 generator used for the miller loop
_g2_prepared_miller: G2Prepared,
}
impl GroupParameters {
pub fn new() -> Result<GroupParameters> {
Ok(GroupParameters {
g1: G1Affine::generator(),
g2: G2Affine::generator(),
_g2_prepared_miller: G2Prepared::from(G2Affine::generator()),
})
}
pub(crate) fn gen1(&self) -> &G1Affine {
&self.g1
}
pub(crate) fn gen2(&self) -> &G2Affine {
&self.g2
}
pub(crate) fn prepared_miller_g2(&self) -> &G2Prepared {
&self._g2_prepared_miller
}
pub fn random_scalar(&self) -> Scalar {
// lazily-initialized thread-local random number generator, seeded by the system
let mut rng = thread_rng();
Scalar::random(&mut rng)
}
pub(crate) fn n_random_scalars(&self, n: usize) -> Vec<Scalar> {
(0..n).map(|_| self.random_scalar()).collect()
}
}
#[derive(Debug, Clone)]
pub struct Parameters {
grp: GroupParameters,
params_u: ParametersUser,
params_a: ParametersAuthority,
tmp_sigma: G1Projective,
pub y: Scalar,
}
impl Parameters {
pub(crate) fn get_grp(&self) -> &GroupParameters { &self.grp }
pub(crate) fn get_params_u(&self) -> &ParametersUser { &self.params_u }
pub(crate) fn get_params_a(&self) -> &ParametersAuthority { &self.params_a }
pub fn new(grp: GroupParameters) -> Parameters {
let g1 = grp.gen1();
let g2 = grp.gen2();
let psi_g1 = hash_g1("psi1");
let psi_g2 = hash_g2("psi2");
let gamma1 = hash_g1("gamma1");
let gamma2 = hash_g1("gamma2");
let eta = hash_g1("eta");
let z = grp.random_scalar();
let y = grp.random_scalar();
let vec_a = grp.n_random_scalars(L as usize);
let sigma = g1 * z;
let theta = eta * z;
let sigmas_u: Vec<G1Projective> = (1..=L)
.map(|i| sigma * (y.pow(&[i as u64, 0, 0, 0])))
.collect();
let thetas_u: Vec<G1Projective> = (1..=L)
.map(|i| theta * (y.pow(&[i as u64, 0, 0, 0])))
.collect();
let deltas_a: Vec<G2Projective> = (0..=L - 1)
.map(|i| g2 * (y.pow(&[i as u64, 0, 0, 0])))
.collect();
let etas_u: Vec<G1Projective> = vec_a.iter().map(|x| g1 * x).collect();
let mut etas_a: Vec<Vec<G2Projective>> = Default::default();
for l in 1..=L {
let mut etas_a_l: Vec<G2Projective> = Default::default();
for k in 0..=l - 1 {
etas_a_l.push(g2 * (vec_a[l as usize - 1].neg() * (y.pow(&[k as u64, 0, 0, 0]))));
}
etas_a.push(etas_a_l);
}
let sps_keypair = SPSKeyPair::new(grp.clone(), 2, 0);
let sps_signatures: Vec<SPSSignature> = sigmas_u
.iter()
.zip(thetas_u.iter())
.map(|(sigma, theta)| sps_keypair.sps_sk.sign(&grp, Some(&vec![*sigma, *theta]), None))
.collect();
// Compute signature for each pair sigma, theta
let params_u = ParametersUser {
gammas: vec![gamma1, gamma2],
psi_g1,
psi_g2,
eta,
etas: etas_u,
sigmas: sigmas_u,
thetas: thetas_u,
sps_signatures,
sps_pk: sps_keypair.sps_vk,
};
let params_a = ParametersAuthority {
deltas: deltas_a,
etas: etas_a,
};
return Parameters {
grp,
params_u,
params_a,
tmp_sigma: sigma,
y,
};
}
}
impl Parameters {
pub(crate) fn get_sigma(&self) -> &G1Projective { &self.tmp_sigma }
}
#[derive(Debug, Clone)]
pub struct ParametersUser {
gammas: Vec<G1Projective>,
psi_g1: G1Projective,
psi_g2: G2Projective,
eta: G1Projective,
etas: Vec<G1Projective>,
sigmas: Vec<G1Projective>,
thetas: Vec<G1Projective>,
sps_signatures: Vec<SPSSignature>,
sps_pk: SPSVerificationKey,
}
impl ParametersUser {
pub(crate) fn get_gammas(&self) -> &Vec<G1Projective> { &self.gammas }
pub(crate) fn get_psi_g1(&self) -> &G1Projective { &self.psi_g1 }
pub(crate) fn get_psi_g2(&self) -> &G2Projective { &self.psi_g2 }
pub(crate) fn get_eta(&self) -> &G1Projective { &self.eta }
pub(crate) fn get_etas(&self) -> &[G1Projective] { &self.etas }
pub(crate) fn get_ith_eta(&self, idx: usize) -> &G1Projective { self.etas.get(idx - 1).unwrap() }
pub(crate) fn get_sigmas(&self) -> &[G1Projective] { &self.sigmas }
pub(crate) fn get_ith_sigma(&self, idx: usize) -> &G1Projective { self.sigmas.get(idx - 1).unwrap() }
pub(crate) fn get_thetas(&self) -> &[G1Projective] { &self.thetas }
pub(crate) fn get_ith_theta(&self, idx: usize) -> &G1Projective { self.thetas.get(idx - 1).unwrap() }
pub(crate) fn get_sps_signs(&self) -> &[SPSSignature] { &self.sps_signatures }
pub(crate) fn get_ith_sps_sign(&self, idx: usize) -> &SPSSignature { &self.sps_signatures.get(idx - 1).unwrap() }
pub(crate) fn get_sps_pk(&self) -> &SPSVerificationKey { &self.sps_pk }
}
#[derive(Debug, Clone)]
pub struct ParametersAuthority {
deltas: Vec<G2Projective>,
etas: Vec<Vec<G2Projective>>,
}
impl ParametersAuthority {
pub(crate) fn get_deltas(&self) -> &[G2Projective] { &self.deltas }
pub(crate) fn get_ith_delta(&self, idx: usize) -> &G2Projective { self.deltas.get(idx).unwrap() }
pub(crate) fn get_etas(&self) -> &Vec<Vec<G2Projective>> { &self.etas }
pub(crate) fn get_eta_ith_row(&self, idx: usize) -> &[G2Projective] { self.etas.get(idx).unwrap() }
pub(crate) fn get_etas_ith_jth_elem(&self, row: usize, column: usize) -> &G2Projective { self.etas.get(row - 1).unwrap().get(column).unwrap() }
}
@@ -0,0 +1,219 @@
use std::fmt::Debug;
use std::ops::Neg;
use bls12_381::{G1Projective, G2Projective, Gt, pairing, Scalar};
use group::Curve;
use crate::scheme::setup::GroupParameters;
#[derive(Debug, Clone)]
pub struct SPSVerificationKey {
pub grp: GroupParameters,
pub uus: Vec<G1Projective>,
pub wws: Vec<G2Projective>,
pub yy: G2Projective,
pub zz: G2Projective,
}
pub struct SPSSecretKey {
sps_vk: SPSVerificationKey,
us: Vec<Scalar>,
ws: Vec<Scalar>,
y: Scalar,
z: Scalar,
}
impl SPSSecretKey {
pub fn z(&self) -> Scalar {
self.z
}
pub fn y(&self) -> Scalar {
self.y
}
pub fn sign(&self, grp: &GroupParameters, messages_a: Option<&[G1Projective]>, messages_b: Option<&[G2Projective]>) -> SPSSignature {
let r = grp.random_scalar();
let rr = grp.gen1() * r;
let ss: G1Projective = match messages_a {
Some(msgs_a) => {
let prod_s: Vec<G1Projective> = msgs_a
.iter()
.zip(self.ws.iter())
.map(|(m_i, w_i)| m_i * w_i.neg())
.collect();
grp.gen1() * (self.z() - r * self.y()) + prod_s.iter().fold(G1Projective::identity(), |acc, elem| acc + elem)
}
None => grp.gen1() * (self.z() - r * self.y())
};
let tt = match messages_b {
Some(msgs_b) => {
let prod_t: Vec<G2Projective> = msgs_b
.iter()
.zip(self.us.iter())
.map(|(m_i, u_i)| m_i * u_i.neg())
.collect();
(grp.gen2() + prod_t.iter().fold(G2Projective::identity(), |acc, elem| acc + elem)) * r.invert().unwrap()
}
None => grp.gen2() * r.invert().unwrap()
};
SPSSignature
{
rr,
ss,
tt,
}
}
}
impl SPSVerificationKey {
pub fn verify(&self, grp: &GroupParameters, signature: SPSSignature, messages_a: &[G1Projective], messages_b: Option<&[G2Projective]>) -> bool {
let pg_rr_yy = pairing(&signature.rr.to_affine(), &self.yy.to_affine());
let pg_ss_g2 = pairing(&signature.ss.to_affine(), grp.gen2());
let pg_g1_zz = pairing(grp.gen1(), &self.zz.to_affine());
let pg_ma_ww: Vec<Gt> = messages_a.iter()
.zip(self.wws.iter())
.map(|(ma, ww)| pairing(&ma.to_affine(), &ww.to_affine()))
.collect();
let mut prod_pg_ma_ww = Gt::identity();
for elem in pg_ma_ww.iter() {
prod_pg_ma_ww = prod_pg_ma_ww + elem;
}
// let prod_pg_ma_ww = pg_ma_ww.iter().fold(Gt::identity() | acc, elem | acc + elem);
assert_eq!(pg_rr_yy + pg_ss_g2 + prod_pg_ma_ww, pg_g1_zz);
let result = match messages_b {
Some(msgs_b) => {
let pg_rr_tt = pairing(&signature.rr.to_affine(), &signature.tt.to_affine());
let pg_g1_g2 = pairing(grp.gen1(), grp.gen2());
let pg_uu_mb: Vec<Gt> = self.uus.iter()
.zip(msgs_b.iter())
.map(|(uu, mb)| pairing(&uu.to_affine(), &mb.to_affine()))
.collect();
let mut prod_pg_uu_mb = Gt::identity();
for elem in pg_uu_mb.iter() {
prod_pg_uu_mb = prod_pg_uu_mb + elem;
}
// let prod_pg_uu_mb = pg_uu_mb.iter().fold(Gt::identity() | acc, elem | acc + elem);
if pg_rr_tt + prod_pg_uu_mb == pg_g1_g2 {
true
} else {
false
}
}
None => {
let pg_sign_rr_yy = pairing(&signature.rr.to_affine(), &self.yy.to_affine());
let pg_sign_ss_gen2 = pairing(&signature.ss.to_affine(), &grp.gen2());
let pg_ma_wws: Vec<Gt> = messages_a.iter()
.zip(self.wws.iter())
.map(|(ma, ww)| pairing(&ma.to_affine(), &ww.to_affine()))
.collect();
let mut prod_pg_ma_wws = Gt::identity();
for elem in pg_ma_wws.iter() {
prod_pg_ma_wws = prod_pg_ma_wws + elem;
}
let pg_gen1_zz = pairing(&grp.gen1(), &self.zz.to_affine());
let pg_rr_tt = pairing(&signature.rr.to_affine(), &signature.tt.to_affine());
let pg_gen1_gen2 = pairing(&grp.gen1(), &grp.gen2());
assert_eq!(pg_sign_rr_yy + pg_sign_ss_gen2 + prod_pg_ma_wws, pg_gen1_zz);
assert_eq!(pg_rr_tt, pg_gen1_gen2);
if pg_sign_rr_yy + pg_sign_ss_gen2 + prod_pg_ma_wws == pg_gen1_zz && pg_rr_tt == pg_gen1_gen2 {
true
} else {
false
}
}
};
return result;
}
pub fn get_ith_ww(&self, idx: usize) -> &G2Projective { return self.wws.get(idx).unwrap(); }
pub fn get_zz(&self) -> &G2Projective { return &self.zz; }
pub fn get_yy(&self) -> &G2Projective { return &self.yy; }
}
pub struct SPSKeyPair {
pub sps_sk: SPSSecretKey,
pub sps_vk: SPSVerificationKey,
}
impl SPSKeyPair {
pub fn new(grp: GroupParameters, a: usize, b: usize) -> SPSKeyPair {
let us = grp.n_random_scalars(b);
let ws = grp.n_random_scalars(a);
let y = grp.random_scalar();
let z = grp.random_scalar();
let uus: Vec<G1Projective> = us.iter().map(|u| grp.gen1() * u).collect();
let yy = grp.gen2() * y;
let wws: Vec<G2Projective> = ws.iter().map(|w| grp.gen2() * w).collect();
let zz = grp.gen2() * z;
let sps_vk = SPSVerificationKey {
grp: grp.clone(),
uus,
wws,
yy,
zz,
};
let sps_sk = SPSSecretKey {
sps_vk: sps_vk.clone(),
us,
ws,
y,
z,
};
SPSKeyPair { sps_sk, sps_vk }
}
}
#[derive(Debug, Clone)]
pub struct SPSSignature {
pub rr: G1Projective,
pub ss: G1Projective,
pub tt: G2Projective,
}
#[cfg(test)]
mod tests {
use rand::thread_rng;
use crate::scheme::setup::GroupParameters;
use crate::scheme::structure_preserving_signature::SPSKeyPair;
use crate::utils::{hash_g1, hash_g2};
#[test]
fn sign_and_verify_for_two_msg_in_G1_and_two_msgs_in_G2() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let sps_keypair = SPSKeyPair::new(grp.clone(), 2, 2);
let msgs_a = vec![hash_g1("messageA1"), hash_g1("messageA2")];
let msgs_b = vec![hash_g2("messageB1"), hash_g2("messageB2")];
let signature = sps_keypair.sps_sk.sign(&grp, Some(&msgs_a), Some(&msgs_b));
assert!(sps_keypair.sps_vk.verify(&grp, signature, &msgs_a, Some(&msgs_b)));
}
#[test]
fn sign_and_verify_for_two_msg_in_G1_and_no_msgs_in_G2() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let sps_keypair = SPSKeyPair::new(grp.clone(), 2, 2);
let msgs_a = vec![hash_g1("messageA1"), hash_g1("messageA2")];
let signature = sps_keypair.sps_sk.sign(&grp, Some(&msgs_a), None);
assert!(sps_keypair.sps_vk.verify(&grp, signature, &msgs_a, None));
}
}
@@ -0,0 +1,166 @@
use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar};
use group::{Curve, GroupEncoding};
use crate::error::{DivisibleEcashError, Result};
use crate::proofs::proof_withdrawal::{WithdrawalReqInstance, WithdrawalReqProof, WithdrawalReqWitness};
use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser, VerificationKeyAuth};
use crate::scheme::PartialWallet;
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::utils::{BlindedSignature, check_bilinear_pairing, hash_g1, Signature};
pub struct WithdrawalRequest {
com_hash: G1Projective,
com: G1Projective,
pc_coms: Vec<G1Projective>,
zk_proof: WithdrawalReqProof,
}
pub struct RequestInfo {
com_hash: G1Projective,
pc_coms_openings: Vec<Scalar>,
v: Scalar,
}
pub fn withdrawal_request(params: &Parameters, sk_user: &SecretKeyUser) -> Result<(WithdrawalRequest, RequestInfo)> {
let grp = params.get_grp();
let g1 = grp.gen1();
let params_u = params.get_params_u();
let v = grp.random_scalar();
let attributes = vec![sk_user.sk, v];
let com_opening = grp.random_scalar();
let commitment = g1 * com_opening
+ attributes
.iter()
.zip(params_u.get_gammas())
.map(|(&m, gamma)| gamma * m)
.sum::<G1Projective>();
// Value h in the paper
let com_hash = hash_g1(commitment.to_bytes());
let pc_coms_openings = grp.n_random_scalars(attributes.len());
// Compute Pedersen commitment for each attribute
let pc_coms = pc_coms_openings
.iter()
.zip(attributes.iter())
.map(|(o_j, m_j)| g1 * o_j + com_hash * m_j)
.collect::<Vec<_>>();
// construct a zk proof of knowledge proving possession of m1, m2, o, o1, o2
let instance = WithdrawalReqInstance {
com: commitment,
h: com_hash,
pc_coms: pc_coms.clone(),
pk_user: sk_user.public_key(&params.get_grp()),
};
let witness = WithdrawalReqWitness {
attributes,
com_opening,
pc_coms_openings: pc_coms_openings.clone(),
};
let zk_proof = WithdrawalReqProof::construct(&params, &instance, &witness);
let req = WithdrawalRequest {
com_hash,
com: commitment,
pc_coms: pc_coms.clone(),
zk_proof,
};
let req_info = RequestInfo {
com_hash,
pc_coms_openings,
v,
};
Ok((req, req_info))
}
pub fn issue(params: &Parameters, req: &WithdrawalRequest, pk_u: PublicKeyUser, sk_a: &SecretKeyAuth) -> Result<BlindedSignature> {
let h = hash_g1(req.com.to_bytes());
if !(h == req.com_hash) {
return Err(DivisibleEcashError::WithdrawalRequestVerification(
"Failed to verify the commitment hash".to_string(),
));
}
// verify zk proof
let instance = WithdrawalReqInstance {
com: req.com,
h: req.com_hash,
pc_coms: req.pc_coms.clone(),
pk_user: pk_u,
};
if !req.zk_proof.verify(&params, &instance) {
return Err(DivisibleEcashError::WithdrawalRequestVerification(
"Failed to verify the proof of knowledge".to_string(),
));
}
let sig = req
.pc_coms
.iter()
.zip(sk_a.ys.iter())
.map(|(pc, yi)| pc * yi)
.chain(std::iter::once(h * sk_a.x))
.sum();
Ok(BlindedSignature(h, sig))
}
pub fn issue_verify(
params: &GroupParameters,
vk_auth: &VerificationKeyAuth,
sk_user: &SecretKeyUser,
blind_signature: &BlindedSignature,
req_info: &RequestInfo) -> Result<PartialWallet> {
// Parse the blinded signature
let h = blind_signature.0;
let c = blind_signature.1;
// Verify the integrity of the response from the authority
if !(req_info.com_hash == h) {
return Err(DivisibleEcashError::IssuanceVfy(
"Integrity verification failed".to_string(),
));
}
// Unblind the blinded signature on the partial wallet
let blinding_removers = vk_auth
.beta_g1
.iter()
.zip(req_info.pc_coms_openings.iter())
.map(|(beta, opening)| beta * opening)
.sum::<G1Projective>();
let unblinded_c = c - blinding_removers;
let attr = vec![sk_user.sk, req_info.v];
let signed_attributes = attr
.iter()
.zip(vk_auth.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
// Verify the signature correctness on the wallet share
if !check_bilinear_pairing(
&h.to_affine(),
&G2Prepared::from((vk_auth.alpha + signed_attributes).to_affine()),
&unblinded_c.to_affine(),
params.prepared_miller_g2(),
) {
return Err(DivisibleEcashError::IssuanceVfy(
"Verification of wallet share failed".to_string(),
));
}
Ok(PartialWallet {
sig: Signature(h, unblinded_c),
v: req_info.v,
idx: None,
})
}
@@ -0,0 +1,63 @@
use rand::thread_rng;
use crate::error::DivisibleEcashError;
use crate::scheme::{PayInfo, Payment};
use crate::scheme::aggregation::{aggregate_signatures, aggregate_verification_keys, aggregate_wallets};
use crate::scheme::identification::identify;
use crate::scheme::keygen::{PublicKeyUser, SecretKeyUser, ttp_keygen_authorities, VerificationKeyAuth};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::scheme::withdrawal::{issue, issue_verify, withdrawal_request};
#[test]
// Test wa full end to end flow of withdrawal request, issuance,
// and spending.
fn main() -> Result<(), DivisibleEcashError> {
// SETUP PHASE
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
// KEY GENERATION FOR THE AUTHORITIES
let authorities_keypairs = ttp_keygen_authorities(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
// KEY GENERATION FOR THE USER
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = SecretKeyUser::public_key(&sk_user, &grp);
// WITHDRAWAL REQUEST
let (withdrawal_req, req_info) = withdrawal_request(&params, &sk_user)?;
// ISSUE PARTIAL WALLETS
let mut partial_wallets = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue(
&params,
&withdrawal_req,
pk_user.clone(),
&auth_keypair.secret_key(),
)?;
let partial_wallet = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user, &blind_signature, &req_info)?;
partial_wallets.push(partial_wallet);
}
// AGGREGATE WALLET
let mut wallet = aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets)?;
let pay_info = PayInfo { info: [67u8; 32] };
let (payment, wallet) = wallet.spend(&params, &verification_key, &sk_user, &pay_info, 10, false)?;
// SPEND VERIFICATION
assert!(payment.spend_verify(&params, &verification_key, &pay_info).unwrap());
let payment_bytes = payment.to_bytes();
let payment2 = Payment::try_from(&payment_bytes[..]).unwrap();
assert_eq!(payment, payment2);
Ok(())
}
@@ -0,0 +1 @@
mod e2e;
@@ -0,0 +1,22 @@
use crate::error::DivisibleEcashError;
pub trait Bytable
where
Self: Sized,
{
fn to_byte_vec(&self) -> Vec<u8>;
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, DivisibleEcashError>;
}
pub trait Base58
where
Self: Bytable,
{
fn try_from_bs58<S: AsRef<str>>(x: S) -> Result<Self, DivisibleEcashError> {
Self::try_from_byte_slice(&bs58::decode(x.as_ref()).into_vec().unwrap())
}
fn to_bs58(&self) -> String {
bs58::encode(self.to_byte_vec()).into_string()
}
}
@@ -0,0 +1,442 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use core::iter::Sum;
use core::ops::Mul;
use std::convert::{TryFrom, TryInto};
use std::ops::Neg;
use bls12_381::{
G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, multi_miller_loop, Scalar,
};
use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField};
use ff::Field;
use group::{Curve, Group};
use crate::error::{DivisibleEcashError, Result};
use crate::scheme::setup::GroupParameters;
use crate::traits::Bytable;
pub struct Polynomial {
coefficients: Vec<Scalar>,
}
impl Polynomial {
// for polynomial of degree n, we generate n+1 values
// (for example for degree 1, like y = x + 2, we need [2,1])
pub fn new_random(grp: &GroupParameters, degree: u64) -> Self {
Polynomial {
coefficients: grp.n_random_scalars((degree + 1) as usize),
}
}
/// Evaluates the polynomial at point x.
pub fn evaluate(&self, x: &Scalar) -> Scalar {
if self.coefficients.is_empty() {
Scalar::zero()
// if x is zero then we can ignore most of the expensive computation and
// just return the last term of the polynomial
} else if x.is_zero().unwrap_u8() == 1 {
// we checked that coefficients are not empty so unwrap here is fine
*self.coefficients.first().unwrap()
} else {
self.coefficients
.iter()
.enumerate()
// coefficient[n] * x ^ n
.map(|(i, coefficient)| coefficient * x.pow(&[i as u64, 0, 0, 0]))
.sum()
}
}
}
#[inline]
fn generate_lagrangian_coefficients_at_origin(points: &[u64]) -> Vec<Scalar> {
let x = Scalar::zero();
points
.iter()
.enumerate()
.map(|(i, point_i)| {
let mut numerator = Scalar::one();
let mut denominator = Scalar::one();
let xi = Scalar::from(*point_i);
for (j, point_j) in points.iter().enumerate() {
if j != i {
let xj = Scalar::from(*point_j);
// numerator = (x - xs[0]) * ... * (x - xs[j]), j != i
numerator *= x - xj;
// denominator = (xs[i] - x[0]) * ... * (xs[i] - x[j]), j != i
denominator *= xi - xj;
}
}
// numerator / denominator
numerator * denominator.invert().unwrap()
})
.collect()
}
/// Performs a Lagrange interpolation at the origin for a polynomial defined by `points` and `values`.
/// It can be used for Scalars, G1 and G2 points.
pub(crate) fn perform_lagrangian_interpolation_at_origin<T>(
points: &[SignerIndex],
values: &[T],
) -> Result<T>
where
T: Sum,
for<'a> &'a T: Mul<Scalar, Output=T>,
{
if points.is_empty() || values.is_empty() {
return Err(DivisibleEcashError::Interpolation(
"Tried to perform lagrangian interpolation for an empty set of coordinates".to_string(),
));
}
if points.len() != values.len() {
return Err(DivisibleEcashError::Interpolation(
"Tried to perform lagrangian interpolation for an incomplete set of coordinates"
.to_string(),
));
}
let coefficients = generate_lagrangian_coefficients_at_origin(points);
Ok(coefficients
.into_iter()
.zip(values.iter())
.map(|(coeff, val)| val * coeff)
.sum())
}
// A temporary way of hashing particular message into G1.
// Implementation idea was taken from `threshold_crypto`:
// https://github.com/poanetwork/threshold_crypto/blob/7709462f2df487ada3bb3243060504b5881f2628/src/lib.rs#L691
// Eventually it should get replaced by, most likely, the osswu map
// method once ideally it's implemented inside the pairing crate.
// note: I have absolutely no idea what are the correct domains for those. I just used whatever
// was given in the test vectors of `Hashing to Elliptic Curves draft-irtf-cfrg-hash-to-curve-11`
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.9.1
const G1_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_";
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.10.1
const G2_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_";
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-K.1
const SCALAR_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-expander";
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
}
pub fn hash_g2<M: AsRef<[u8]>>(msg: M) -> G2Projective {
<G2Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G2_HASH_DOMAIN)
}
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
let mut output = vec![Scalar::zero()];
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
msg.as_ref(),
SCALAR_HASH_DOMAIN,
&mut output,
);
output[0]
}
pub fn try_deserialize_scalar_vec(
expected_len: u64,
bytes: &[u8],
err: DivisibleEcashError,
) -> Result<Vec<Scalar>> {
if bytes.len() != expected_len as usize * 32 {
return Err(err);
}
let mut out = Vec::with_capacity(expected_len as usize);
for i in 0..expected_len as usize {
let s_bytes = bytes[i * 32..(i + 1) * 32].try_into().unwrap();
let s = match Into::<Option<Scalar>>::into(Scalar::from_bytes(&s_bytes)) {
None => return Err(err),
Some(scalar) => scalar,
};
out.push(s)
}
Ok(out)
}
pub fn try_deserialize_scalar(bytes: &[u8; 32], err: DivisibleEcashError) -> Result<Scalar> {
Into::<Option<Scalar>>::into(Scalar::from_bytes(bytes)).ok_or(err)
}
pub fn try_deserialize_g1_projective(
bytes: &[u8; 48],
err: DivisibleEcashError,
) -> Result<G1Projective> {
Into::<Option<G1Affine>>::into(G1Affine::from_compressed(bytes))
.ok_or(err)
.map(G1Projective::from)
}
pub fn try_deserialize_g2_projective(
bytes: &[u8; 96],
err: DivisibleEcashError,
) -> Result<G2Projective> {
Into::<Option<G2Affine>>::into(G2Affine::from_compressed(bytes))
.ok_or(err)
.map(G2Projective::from)
}
/// Checks whether e(P, Q) * e(-R, S) == id
pub fn check_bilinear_pairing(p: &G1Affine, q: &G2Prepared, r: &G1Affine, s: &G2Prepared) -> bool {
// checking e(P, Q) * e(-R, S) == id
// is equivalent to checking e(P, Q) == e(R, S)
// but requires only a single final exponentiation rather than two of them
// and therefore, as seen via benchmarks.rs, is almost 50% faster
// (1.47ms vs 2.45ms, tested on R9 5900X)
let multi_miller = multi_miller_loop(&[(p, q), (&r.neg(), s)]);
multi_miller.final_exponentiation().is_identity().into()
}
pub type SignerIndex = u64;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective);
pub type PartialSignature = Signature;
impl TryFrom<&[u8]> for Signature {
type Error = DivisibleEcashError;
fn try_from(bytes: &[u8]) -> Result<Signature> {
if bytes.len() != 96 {
return Err(DivisibleEcashError::Deserialization(format!(
"Signature must be exactly 96 bytes, got {}",
bytes.len()
)));
}
let sig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
let sig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
let sig1 = try_deserialize_g1_projective(
sig1_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize compressed sig1".to_string(),
),
)?;
let sig2 = try_deserialize_g1_projective(
sig2_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize compressed sig2".to_string(),
),
)?;
Ok(Signature(sig1, sig2))
}
}
impl Signature {
pub(crate) fn sig1(&self) -> &G1Projective {
&self.0
}
pub(crate) fn sig2(&self) -> &G1Projective {
&self.1
}
pub fn randomise(&self, grp: &GroupParameters) -> (Signature, Scalar) {
let r = grp.random_scalar();
let r_prime = grp.random_scalar();
let h_prime = self.0 * r_prime;
let s_prime = (self.1 * r_prime) + (h_prime * r);
(Signature(h_prime, s_prime), r)
}
pub fn to_bytes(self) -> [u8; 96] {
let mut bytes = [0u8; 96];
bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed());
bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Signature> {
Signature::try_from(bytes)
}
}
impl Bytable for Signature {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
Signature::from_bytes(slice)
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct BlindedSignature(pub(crate) G1Projective, pub(crate) G1Projective);
pub struct SignatureShare {
signature: Signature,
index: SignerIndex,
}
impl SignatureShare {
pub fn new(signature: Signature, index: SignerIndex) -> Self {
SignatureShare { signature, index }
}
pub fn signature(&self) -> &Signature {
&self.signature
}
pub fn index(&self) -> SignerIndex {
self.index
}
// pub fn aggregate(shares: &[Self]) -> Result<Signature> {
// aggregate_signature_shares(shares)
// }
}
#[cfg(test)]
mod tests {
use rand::RngCore;
use super::*;
#[test]
fn polynomial_evaluation() {
// y = 42 (it should be 42 regardless of x)
let poly = Polynomial {
coefficients: vec![Scalar::from(42)],
};
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(1)));
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(0)));
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(10)));
// y = x + 10, at x = 2 (exp: 12)
let poly = Polynomial {
coefficients: vec![Scalar::from(10), Scalar::from(1)],
};
assert_eq!(Scalar::from(12), poly.evaluate(&Scalar::from(2)));
// y = x^4 - 5x^2 + 2x - 3, at x = 3 (exp: 39)
let poly = Polynomial {
coefficients: vec![
(-Scalar::from(3)),
Scalar::from(2),
(-Scalar::from(5)),
Scalar::zero(),
Scalar::from(1),
],
};
assert_eq!(Scalar::from(39), poly.evaluate(&Scalar::from(3)));
// empty polynomial
let poly = Polynomial {
coefficients: vec![],
};
// should always be 0
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(1)));
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(0)));
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(10)));
}
#[test]
fn performing_lagrangian_scalar_interpolation_at_origin() {
// x^2 + 3
// x, f(x):
// 1, 4,
// 2, 7,
// 3, 12,
let points = vec![1, 2, 3];
let values = vec![Scalar::from(4), Scalar::from(7), Scalar::from(12)];
assert_eq!(
Scalar::from(3),
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
);
// x^3 + 3x^2 - 5x + 11
// x, f(x):
// 1, 10
// 2, 21
// 3, 50
// 4, 103
let points = vec![1, 2, 3, 4];
let values = vec![
Scalar::from(10),
Scalar::from(21),
Scalar::from(50),
Scalar::from(103),
];
assert_eq!(
Scalar::from(11),
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
);
// more points than it is required
// x^2 + x + 10
// x, f(x)
// 1, 12
// 2, 16
// 3, 22
// 4, 30
// 5, 40
let points = vec![1, 2, 3, 4, 5];
let values = vec![
Scalar::from(12),
Scalar::from(16),
Scalar::from(22),
Scalar::from(30),
Scalar::from(40),
];
assert_eq!(
Scalar::from(10),
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
);
}
#[test]
fn hash_g1_sanity_check() {
let mut rng = rand::thread_rng();
let mut msg1 = [0u8; 1024];
rng.fill_bytes(&mut msg1);
let mut msg2 = [0u8; 1024];
rng.fill_bytes(&mut msg2);
assert_eq!(hash_g1(msg1), hash_g1(msg1));
assert_eq!(hash_g1(msg2), hash_g1(msg2));
assert_ne!(hash_g1(msg1), hash_g1(msg2));
}
#[test]
fn hash_scalar_sanity_check() {
let mut rng = rand::thread_rng();
let mut msg1 = [0u8; 1024];
rng.fill_bytes(&mut msg1);
let mut msg2 = [0u8; 1024];
rng.fill_bytes(&mut msg2);
assert_eq!(hash_to_scalar(msg1), hash_to_scalar(msg1));
assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2));
assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2));
}
}
@@ -0,0 +1,30 @@
[package]
name = "nym_online_divisible_ecash"
version = "0.1.0"
author = ["Ania Piotrowska <ania@nymtech.net>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
itertools = "0.10"
digest = "0.9"
rand = "0.8"
thiserror = "1.0"
sha2 = "0.9"
bs58 = "0.4.0"
[dev-dependencies]
criterion = { version = "0.3", features = ["html_reports"] }
[dependencies.ff]
version = "0.10"
default-features = false
[dependencies.group]
version = "0.10"
default-features = false
@@ -0,0 +1,4 @@
mod error;
mod scheme;
mod traits;
mod utils;
@@ -0,0 +1 @@
mod e2e;
+52 -50
View File
@@ -1,19 +1,21 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bls12_381::{multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, Scalar};
use criterion::{criterion_group, criterion_main, Criterion};
use ff::Field;
use group::{Curve, Group};
use nymcoconut::{
aggregate_signature_shares, aggregate_verification_keys, blind_sign, elgamal_keygen,
prepare_blind_sign, prove_bandwidth_credential, setup, ttp_keygen, verify_credential,
Attribute, BlindedSignature, Parameters, Signature, SignatureShare, VerificationKey,
};
use rand::seq::SliceRandom;
use std::ops::Neg;
use std::time::Duration;
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, multi_miller_loop, Scalar};
use criterion::{Criterion, criterion_group, criterion_main};
use ff::Field;
use group::{Curve, Group};
use rand::seq::SliceRandom;
use nymcoconut::{
aggregate_signature_shares, aggregate_verification_keys, Attribute, blind_sign,
BlindedSignature, elgamal_keygen, Parameters, prepare_blind_sign, prove_bandwidth_credential,
setup, Signature, SignatureShare, ttp_keygen, VerificationKey, verify_credential,
};
#[allow(unused)]
fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
let gt1 = bls12_381::pairing(g11, g21);
@@ -32,6 +34,40 @@ fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g
))
}
#[allow(unused)]
fn bench_pairings(c: &mut Criterion) {
let mut rng = rand::thread_rng();
let g1 = G1Affine::generator();
let g2 = G2Affine::generator();
let r = Scalar::random(&mut rng);
let s = Scalar::random(&mut rng);
let g11 = (g1 * r).to_affine();
let g21 = (g2 * s).to_affine();
let g21_prep = G2Prepared::from(g21);
let g12 = (g1 * s).to_affine();
let g22 = (g2 * r).to_affine();
let g22_prep = G2Prepared::from(g22);
c.bench_function("double pairing", |b| {
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
});
c.bench_function("multi miller in affine", |b| {
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
});
c.bench_function("multi miller with prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
});
c.bench_function("multi miller with semi-prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
});
}
#[allow(unused)]
fn multi_miller_pairing_with_prepared(
g11: &G1Affine,
@@ -104,7 +140,7 @@ fn unblind_and_aggregate(
&attributes,
&unblinded_signature_shares,
)
.unwrap()
.unwrap()
}
struct BenchCase {
@@ -124,43 +160,9 @@ impl BenchCase {
}
}
#[allow(unused)]
fn bench_pairings(c: &mut Criterion) {
let mut rng = rand::thread_rng();
let g1 = G1Affine::generator();
let g2 = G2Affine::generator();
let r = Scalar::random(&mut rng);
let s = Scalar::random(&mut rng);
let g11 = (g1 * r).to_affine();
let g21 = (g2 * s).to_affine();
let g21_prep = G2Prepared::from(g21);
let g12 = (g1 * s).to_affine();
let g22 = (g2 * r).to_affine();
let g22_prep = G2Prepared::from(g22);
c.bench_function("double pairing", |b| {
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
});
c.bench_function("multi miller in affine", |b| {
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
});
c.bench_function("multi miller with prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
});
c.bench_function("multi miller with semi-prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
});
}
fn bench_coconut(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-coconut");
group.measurement_time(Duration::from_secs(100));
group.measurement_time(Duration::from_secs(1000));
let case = BenchCase {
num_authorities: 100,
threshold_p: 0.7,
@@ -218,7 +220,7 @@ fn bench_coconut(c: &mut Criterion) {
&blind_sign_request,
&public_attributes,
)
.unwrap()
.unwrap()
})
},
);
@@ -233,7 +235,7 @@ fn bench_coconut(c: &mut Criterion) {
&blind_sign_request,
&public_attributes,
)
.unwrap();
.unwrap();
blinded_signatures.push(blinded_signature)
}
@@ -291,7 +293,7 @@ fn bench_coconut(c: &mut Criterion) {
serial_number,
binding_number,
)
.unwrap();
.unwrap();
// CLIENT BENCHMARK
group.bench_function(
@@ -310,7 +312,7 @@ fn bench_coconut(c: &mut Criterion) {
serial_number,
binding_number,
)
.unwrap()
.unwrap()
})
},
);
+1 -1
View File
@@ -38,7 +38,7 @@ mod scheme;
#[cfg(test)]
mod tests;
mod traits;
mod utils;
pub mod utils;
pub type Attribute = Scalar;
pub type PrivateAttribute = Attribute;
+5 -11
View File
@@ -122,7 +122,7 @@ const G1_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_R
// 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(crate) fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
}
@@ -137,7 +137,7 @@ pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
output[0]
}
pub(crate) fn try_deserialize_scalar_vec(
pub fn try_deserialize_scalar_vec(
expected_len: u64,
bytes: &[u8],
err: CoconutError,
@@ -159,23 +159,17 @@ pub(crate) fn try_deserialize_scalar_vec(
Ok(out)
}
pub(crate) fn try_deserialize_scalar(bytes: &[u8; 32], err: CoconutError) -> Result<Scalar> {
pub fn try_deserialize_scalar(bytes: &[u8; 32], err: CoconutError) -> Result<Scalar> {
Into::<Option<Scalar>>::into(Scalar::from_bytes(bytes)).ok_or(err)
}
pub(crate) fn try_deserialize_g1_projective(
bytes: &[u8; 48],
err: CoconutError,
) -> Result<G1Projective> {
pub fn try_deserialize_g1_projective(bytes: &[u8; 48], err: CoconutError) -> Result<G1Projective> {
Into::<Option<G1Affine>>::into(G1Affine::from_compressed(bytes))
.ok_or(err)
.map(G1Projective::from)
}
pub(crate) fn try_deserialize_g2_projective(
bytes: &[u8; 96],
err: CoconutError,
) -> Result<G2Projective> {
pub fn try_deserialize_g2_projective(bytes: &[u8; 96], err: CoconutError) -> Result<G2Projective> {
Into::<Option<G2Affine>>::into(G2Affine::from_compressed(bytes))
.ok_or(err)
.map(G2Projective::from)