Compare commits
80 Commits
gear
...
research/ecash
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b66701c5d | |||
| 3a8b99db10 | |||
| 6e7cf42831 | |||
| 0e42f977ee | |||
| ef379cdeb3 | |||
| 4886312e43 | |||
| 3a152366d2 | |||
| d920bcb2f4 | |||
| 268a1817ab | |||
| f5c493500f | |||
| 7533f304ff | |||
| 755d4f2388 | |||
| 6884395daa | |||
| 5299b94f9e | |||
| f701787802 | |||
| 0b58e0ae24 | |||
| 03226d1487 | |||
| 534f8c6b99 | |||
| e544a6d2fa | |||
| d696535da5 | |||
| 7e444b7c42 | |||
| 0d377d98a7 | |||
| 5bdbee6dae | |||
| 014374e33e | |||
| 9adb4fbbbc | |||
| 5293766856 | |||
| a40bb3ae4f | |||
| 226c37cfea | |||
| ea8f36dfd2 | |||
| b466cb6b3d | |||
| bb0328137a | |||
| e07974ad2b | |||
| e8bb96b7d0 | |||
| 9bd06dbfcc | |||
| 055b10f7f2 | |||
| 44ca339ee5 | |||
| b6a43787b3 | |||
| 9cb22d9bde | |||
| 68485f8998 | |||
| 9c22c082c0 | |||
| 28d7400304 | |||
| 056d189e0e | |||
| b76eab2363 | |||
| b8a2ac5719 | |||
| c861e40047 | |||
| 20426d001e | |||
| 2eed9fd247 | |||
| a1887356dc | |||
| ef2420f847 | |||
| 6f26475055 | |||
| b9bf6e4521 | |||
| 7e392ff6c3 | |||
| 9e834ebaef | |||
| f96a4ffed3 | |||
| 8e9c0d2b0d | |||
| 33c356b3ec | |||
| c902e8eaa5 | |||
| 1dc8b5894f | |||
| ad285af0fa | |||
| 6621e5c72f | |||
| 969a93cf80 | |||
| f52ee0431c | |||
| a41cfd47a4 | |||
| 0b098687e1 | |||
| 09709dd8e4 | |||
| ef0867a7e7 | |||
| bfa7754ea6 | |||
| a4377bf1a6 | |||
| 9e30f9a346 | |||
| 7878895651 | |||
| fcc940a4d6 | |||
| 036092bb9c | |||
| dcde42c45d | |||
| 5b5bbc2a3d | |||
| 0c1cdc4b63 | |||
| 953c1eddd9 | |||
| 6ae0913aa1 | |||
| 92834ff9b8 | |||
| bfba92de97 | |||
| d37b63f788 |
Generated
+80
-9
@@ -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,60 @@ dependencies = [
|
||||
"version-checker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym_compact_ecash"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bls12_381 0.6.0",
|
||||
"bs58",
|
||||
"criterion",
|
||||
"digest 0.9.0",
|
||||
"ff 0.11.0",
|
||||
"group 0.11.0",
|
||||
"itertools",
|
||||
"rand 0.8.5",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym_offline_divisible_ecash"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bls12_381 0.6.0",
|
||||
"bs58",
|
||||
"criterion",
|
||||
"digest 0.9.0",
|
||||
"ff 0.11.0",
|
||||
"group 0.11.0",
|
||||
"itertools",
|
||||
"rand 0.8.5",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym_online_divisible_ecash"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bls12_381 0.5.0",
|
||||
"bs58",
|
||||
"criterion",
|
||||
"digest 0.9.0",
|
||||
"ff 0.10.1",
|
||||
"group 0.10.0",
|
||||
"itertools",
|
||||
"rand 0.8.5",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nymcoconut"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bls12_381",
|
||||
"bls12_381 0.5.0",
|
||||
"bs58",
|
||||
"criterion",
|
||||
"digest 0.9.0",
|
||||
@@ -3351,6 +3413,15 @@ dependencies = [
|
||||
"group 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pairing"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42"
|
||||
dependencies = [
|
||||
"group 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-scale-codec"
|
||||
version = "2.3.1"
|
||||
@@ -6403,9 +6474,9 @@ checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
||||
|
||||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "1.2.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077"
|
||||
checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"rand_core 0.5.1",
|
||||
@@ -6420,9 +6491,9 @@ checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.3.0"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
|
||||
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
@@ -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,32 @@
|
||||
[package]
|
||||
name = "nym_compact_ecash"
|
||||
version = "0.1.0"
|
||||
authors = ["Ania Piotrowska <ania@nymtech.net>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
#bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
|
||||
bls12_381 = { path = "/Users/ania/Documents/Git/andrew_bls12_381", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
|
||||
itertools = "0.10"
|
||||
digest = "0.9"
|
||||
rand = "0.8"
|
||||
thiserror = "1.0"
|
||||
sha2 = "0.9"
|
||||
bs58 = "0.4.0"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
|
||||
[dependencies.ff]
|
||||
version = "0.11"
|
||||
default-features = false
|
||||
|
||||
[dependencies.group]
|
||||
version = "0.11"
|
||||
default-features = false
|
||||
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
@@ -0,0 +1,360 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::ops::Neg;
|
||||
use std::time::Duration;
|
||||
|
||||
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, multi_miller_loop, Scalar};
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use ff::Field;
|
||||
use group::{Curve, Group};
|
||||
use itertools::izip;
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
use nym_compact_ecash::{
|
||||
aggregate_verification_keys, aggregate_wallets, generate_keypair_user,
|
||||
issue_verify, issue_wallet, PartialWallet,
|
||||
PayInfo, PublicKeyUser, SecretKeyUser, ttp_keygen, VerificationKeyAuth, withdrawal_request,
|
||||
};
|
||||
use nym_compact_ecash::identify::{identify, IdentifyResult};
|
||||
use nym_compact_ecash::setup::setup;
|
||||
|
||||
#[allow(unused)]
|
||||
fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
|
||||
let gt1 = bls12_381::pairing(g11, g21);
|
||||
let gt2 = bls12_381::pairing(g12, g22);
|
||||
assert_eq!(gt1, gt2)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn single_pairing(g11: &G1Affine, g21: &G2Affine) {
|
||||
let gt1 = bls12_381::pairing(g11, g21);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn exponent_in_g1(g1: G1Projective, r: Scalar) {
|
||||
let g11 = (g1 * r);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn exponent_in_g2(g2: G2Projective, r: Scalar) {
|
||||
let g22 = (g2 * r);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn exponent_in_gt(gt: Gt, r: Scalar) {
|
||||
let gtt = (gt * r);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
|
||||
let miller_loop_result = multi_miller_loop(&[
|
||||
(g11, &G2Prepared::from(*g21)),
|
||||
(&g12.neg(), &G2Prepared::from(*g22)),
|
||||
]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_with_prepared(
|
||||
g11: &G1Affine,
|
||||
g21: &G2Prepared,
|
||||
g12: &G1Affine,
|
||||
g22: &G2Prepared,
|
||||
) {
|
||||
let miller_loop_result = multi_miller_loop(&[(g11, g21), (&g12.neg(), g22)]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
// the case of being able to prepare G2 generator
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_with_semi_prepared(
|
||||
g11: &G1Affine,
|
||||
g21: &G2Affine,
|
||||
g12: &G1Affine,
|
||||
g22: &G2Prepared,
|
||||
) {
|
||||
let miller_loop_result =
|
||||
multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), g22)]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn bench_pairings(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("benchmark-pairings");
|
||||
group.measurement_time(Duration::from_secs(200));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let g1 = G1Affine::generator();
|
||||
let g2 = G2Affine::generator();
|
||||
let r = Scalar::random(&mut rng);
|
||||
let s = Scalar::random(&mut rng);
|
||||
|
||||
let g11 = (g1 * r).to_affine();
|
||||
let g21 = (g2 * s).to_affine();
|
||||
let g21_prep = G2Prepared::from(g21);
|
||||
|
||||
let g12 = (g1 * s).to_affine();
|
||||
let g22 = (g2 * r).to_affine();
|
||||
let g22_prep = G2Prepared::from(g22);
|
||||
|
||||
let gt = bls12_381::pairing(&g11, &g21);
|
||||
let gen1 = G1Projective::generator();
|
||||
let gen2 = G2Projective::generator();
|
||||
|
||||
group.bench_function("exponent operation in G1", |b| {
|
||||
b.iter(|| exponent_in_g1(gen1, r))
|
||||
});
|
||||
|
||||
group.bench_function("exponent operation in G2", |b| {
|
||||
b.iter(|| exponent_in_g2(gen2, r))
|
||||
});
|
||||
|
||||
group.bench_function("exponent operation in Gt", |b| {
|
||||
b.iter(|| exponent_in_gt(gt, r))
|
||||
});
|
||||
|
||||
group.bench_function("single pairing", |b| {
|
||||
b.iter(|| single_pairing(&g11, &g21))
|
||||
});
|
||||
|
||||
group.bench_function("double pairing", |b| {
|
||||
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
|
||||
});
|
||||
|
||||
group.bench_function("multi miller in affine", |b| {
|
||||
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
|
||||
});
|
||||
|
||||
group.bench_function("multi miller with prepared g2", |b| {
|
||||
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
|
||||
});
|
||||
|
||||
group.bench_function("multi miller with semi-prepared g2", |b| {
|
||||
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
|
||||
});
|
||||
}
|
||||
|
||||
struct BenchCase {
|
||||
num_authorities: u64,
|
||||
threshold_p: f32,
|
||||
L: u64,
|
||||
spend_vv: u64,
|
||||
case_nr_pub_keys: u64,
|
||||
}
|
||||
|
||||
fn bench_compact_ecash(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("benchmark-compact-ecash");
|
||||
// group.sample_size(300);
|
||||
// group.measurement_time(Duration::from_secs(1500));
|
||||
|
||||
let case = BenchCase {
|
||||
num_authorities: 100,
|
||||
threshold_p: 0.7,
|
||||
L: 100,
|
||||
spend_vv: 1,
|
||||
case_nr_pub_keys: 99,
|
||||
};
|
||||
|
||||
let params = setup(case.L);
|
||||
let grp = params.grp();
|
||||
let user_keypair = generate_keypair_user(&grp);
|
||||
let threshold = (case.threshold_p * case.num_authorities as f32).round() as u64;
|
||||
let authorities_keypairs = ttp_keygen(&grp, threshold, case.num_authorities).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let indices: Vec<u64> = (1..case.num_authorities + 1).collect();
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
|
||||
// ISSUANCE PHASE
|
||||
|
||||
let (req, req_info) = withdrawal_request(grp, &user_keypair.secret_key()).unwrap();
|
||||
|
||||
// CLIENT BENCHMARK: prepare a single withdrawal request
|
||||
// group.bench_function(
|
||||
// &format!(
|
||||
// "[Client] withdrawal_request_{}_authorities_{}_L_{}_threshold",
|
||||
// case.num_authorities, case.L, case.threshold_p,
|
||||
// ),
|
||||
// |b| b.iter(|| withdrawal_request(grp, &user_keypair.secret_key()).unwrap()),
|
||||
// );
|
||||
|
||||
// ISSUING AUTHRORITY BENCHMARK: Benchmark the issue_wallet function
|
||||
// called by an authority to issue a blind signature on a partial wallet
|
||||
let mut rng = rand::thread_rng();
|
||||
let keypair = authorities_keypairs.choose(&mut rng).unwrap();
|
||||
// group.bench_function(
|
||||
// &format!("[Issuing Authority] issue_partial_wallet_with_L_{}", case.L, ),
|
||||
// |b| {
|
||||
// b.iter(|| {
|
||||
// issue_wallet(
|
||||
// &grp,
|
||||
// keypair.secret_key(),
|
||||
// user_keypair.public_key(),
|
||||
// &req,
|
||||
// ).unwrap()
|
||||
// })
|
||||
// },
|
||||
// );
|
||||
|
||||
let mut wallet_blinded_signatures = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue_wallet(
|
||||
&grp,
|
||||
auth_keypair.secret_key(),
|
||||
user_keypair.public_key(),
|
||||
&req,
|
||||
);
|
||||
wallet_blinded_signatures.push(blind_signature.unwrap());
|
||||
}
|
||||
|
||||
// CLIENT BENCHMARK: verify the issued partial wallet
|
||||
let w = wallet_blinded_signatures.get(0).clone().unwrap();
|
||||
let vk = verification_keys_auth.get(0).clone().unwrap();
|
||||
// group.bench_function(
|
||||
// &format!("[Client] issue_verify_a_partial_wallet_with_L_{}", case.L, ),
|
||||
// |b| b.iter(|| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap()),
|
||||
// );
|
||||
|
||||
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
|
||||
wallet_blinded_signatures.iter(),
|
||||
verification_keys_auth.iter()
|
||||
)
|
||||
.map(|(w, vk)| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
|
||||
.collect();
|
||||
|
||||
// CLIENT BENCHMARK: aggregating all partial wallets
|
||||
// group.bench_function(
|
||||
// &format!(
|
||||
// "[Client] aggregate_wallets_with_L_{}_threshold_{}",
|
||||
// case.L, case.threshold_p,
|
||||
// ),
|
||||
// |b| {
|
||||
// b.iter(|| {
|
||||
// aggregate_wallets(
|
||||
// &grp,
|
||||
// &verification_key,
|
||||
// &user_keypair.secret_key(),
|
||||
// &unblinded_wallet_shares,
|
||||
// &req_info,
|
||||
// )
|
||||
// .unwrap()
|
||||
// })
|
||||
// },
|
||||
// );
|
||||
|
||||
// Aggregate partial wallets
|
||||
let aggr_wallet = aggregate_wallets(
|
||||
&grp,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&unblinded_wallet_shares,
|
||||
&req_info,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// SPENDING PHASE
|
||||
let pay_info = PayInfo { info: [6u8; 32] };
|
||||
// CLIENT BENCHMARK: spend a single coin from the wallet
|
||||
// group.bench_function(
|
||||
// &format!(
|
||||
// "[Client] spend_a_single_coin_L_{}_threshold_{}",
|
||||
// case.L, case.threshold_p,
|
||||
// ),
|
||||
// |b| {
|
||||
// b.iter(|| {
|
||||
// aggr_wallet
|
||||
// .spend(
|
||||
// ¶ms,
|
||||
// &verification_key,
|
||||
// &user_keypair.secret_key(),
|
||||
// &pay_info,
|
||||
// true,
|
||||
// case.spend_vv,
|
||||
// )
|
||||
// .unwrap()
|
||||
// })
|
||||
// },
|
||||
// );
|
||||
|
||||
let (payment, upd_wallet) = aggr_wallet
|
||||
.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info,
|
||||
false,
|
||||
case.spend_vv,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// MERCHANT BENCHMARK: verify whether the submitted payment is legit
|
||||
// group.bench_function(
|
||||
// &format!(
|
||||
// "[Merchant] spend_verify_of_a_single_payment_L_{}_threshold_{}",
|
||||
// case.L, case.threshold_p,
|
||||
// ),
|
||||
// |b| {
|
||||
// b.iter(|| {
|
||||
// payment
|
||||
// .spend_verify(¶ms, &verification_key, &pay_info)
|
||||
// .unwrap()
|
||||
// })
|
||||
// },
|
||||
// );
|
||||
|
||||
// BENCHMARK IDENTIFICATION
|
||||
// Let's generate a double spending payment
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = aggr_wallet.l.get();
|
||||
aggr_wallet.l.set(current_l - case.spend_vv);
|
||||
|
||||
let pay_info2 = PayInfo { info: [7u8; 32] };
|
||||
let (payment2, _) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info2,
|
||||
true,
|
||||
case.spend_vv,
|
||||
).unwrap();
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut public_keys: Vec<PublicKeyUser> = Default::default();
|
||||
for i in 0..case.case_nr_pub_keys {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
public_keys.push(pk_user);
|
||||
}
|
||||
public_keys.push(user_keypair.public_key());
|
||||
|
||||
// MERCHANT BENCHMARK: identify double spending
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Merchant] identify_L_{}_threshold_{}_spend_vv_{}_pks_{}",
|
||||
case.L, case.threshold_p, case.spend_vv, public_keys.len()
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
identify(¶ms, &verification_key, payment.clone(), payment2.clone(), pay_info.clone(), pay_info2.clone()).unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
let identify_result = identify(¶ms, &verification_key, payment, payment2, pay_info.clone(), pay_info2.clone()).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key()));
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_compact_ecash);
|
||||
criterion_main!(benches);
|
||||
@@ -0,0 +1,49 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, CompactEcashError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CompactEcashError {
|
||||
#[error("Setup error: {0}")]
|
||||
Setup(String),
|
||||
|
||||
#[error("Aggregation error: {0}")]
|
||||
Aggregation(String),
|
||||
|
||||
#[error("Withdrawal Request Verification related error: {0}")]
|
||||
WithdrawalRequestVerification(String),
|
||||
|
||||
#[error("Deserialization error: {0}")]
|
||||
Deserialization(String),
|
||||
|
||||
#[error("Interpolation error: {0}")]
|
||||
Interpolation(String),
|
||||
|
||||
#[error("Issuance Verification related error: {0}")]
|
||||
IssuanceVfy(String),
|
||||
|
||||
#[error("Spend Verification related error: {0}")]
|
||||
Spend(String),
|
||||
|
||||
#[error("ZKP Proof related error: {0}")]
|
||||
RangeProofOutOfBound(String),
|
||||
|
||||
#[error("Identify Verification related error: {0}")]
|
||||
Identify(String),
|
||||
|
||||
#[error(
|
||||
"Deserailization error, expected at least {} bytes, got {}",
|
||||
min,
|
||||
actual
|
||||
)]
|
||||
DeserializationMinLength { min: usize, actual: usize },
|
||||
|
||||
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {} or {modulus_target} % {modulus} == 0")]
|
||||
DeserializationInvalidLength {
|
||||
actual: usize,
|
||||
target: usize,
|
||||
modulus_target: usize,
|
||||
modulus: usize,
|
||||
object: String,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use std::convert::TryInto;
|
||||
|
||||
use bls12_381::Scalar;
|
||||
|
||||
pub use scheme::aggregation::aggregate_verification_keys;
|
||||
pub use scheme::aggregation::aggregate_wallets;
|
||||
pub use scheme::identify;
|
||||
pub use scheme::keygen::{PublicKeyUser, SecretKeyUser, VerificationKeyAuth};
|
||||
pub use scheme::keygen::generate_keypair_user;
|
||||
pub use scheme::keygen::ttp_keygen;
|
||||
pub use scheme::PartialWallet;
|
||||
pub use scheme::PayInfo;
|
||||
pub use scheme::setup;
|
||||
pub use scheme::withdrawal::issue_verify;
|
||||
pub use scheme::withdrawal::issue_wallet;
|
||||
pub use scheme::withdrawal::withdrawal_request;
|
||||
pub use traits::Base58;
|
||||
|
||||
use crate::error::CompactEcashError;
|
||||
use crate::traits::Bytable;
|
||||
|
||||
mod error;
|
||||
mod proofs;
|
||||
mod scheme;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod traits;
|
||||
mod utils;
|
||||
|
||||
pub type Attribute = Scalar;
|
||||
|
||||
impl Bytable for Attribute {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.to_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CompactEcashError> {
|
||||
Ok(Attribute::from_bytes(slice.try_into().unwrap()).unwrap())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use bls12_381::Scalar;
|
||||
use digest::Digest;
|
||||
use digest::generic_array::typenum::Unsigned;
|
||||
use sha2::Sha256;
|
||||
|
||||
pub mod proof_spend;
|
||||
pub mod proof_withdrawal;
|
||||
|
||||
type ChallengeDigest = Sha256;
|
||||
|
||||
/// Generates a Scalar [or Fp] challenge by hashing a number of elliptic curve points.
|
||||
fn compute_challenge<D, I, B>(iter: I) -> Scalar
|
||||
where
|
||||
D: Digest,
|
||||
I: Iterator<Item=B>,
|
||||
B: AsRef<[u8]>,
|
||||
{
|
||||
let mut h = D::new();
|
||||
for point_representation in iter {
|
||||
h.update(point_representation);
|
||||
}
|
||||
let digest = h.finalize();
|
||||
|
||||
// TODO: I don't like the 0 padding here (though it's what we've been using before,
|
||||
// but we never had a security audit anyway...)
|
||||
// instead we could maybe use the `from_bytes` variant and adding some suffix
|
||||
// when computing the digest until we produce a valid scalar.
|
||||
let mut bytes = [0u8; 64];
|
||||
let pad_size = 64usize
|
||||
.checked_sub(D::OutputSize::to_usize())
|
||||
.unwrap_or_default();
|
||||
|
||||
bytes[pad_size..].copy_from_slice(&digest);
|
||||
|
||||
Scalar::from_bytes_wide(&bytes)
|
||||
}
|
||||
|
||||
fn produce_response(witness_replacement: &Scalar, challenge: &Scalar, secret: &Scalar) -> Scalar {
|
||||
witness_replacement - challenge * secret
|
||||
}
|
||||
|
||||
// note: it's caller's responsibility to ensure witnesses.len() = secrets.len()
|
||||
fn produce_responses<S>(witnesses: &[Scalar], challenge: &Scalar, secrets: &[S]) -> Vec<Scalar>
|
||||
where
|
||||
S: Borrow<Scalar>,
|
||||
{
|
||||
debug_assert_eq!(witnesses.len(), secrets.len());
|
||||
|
||||
witnesses
|
||||
.iter()
|
||||
.zip(secrets.iter())
|
||||
.map(|(w, x)| produce_response(w, challenge, x.borrow()))
|
||||
.collect()
|
||||
}
|
||||
@@ -0,0 +1,777 @@
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use bls12_381::{G1Projective, G2Projective, Scalar};
|
||||
use group::{Curve, GroupEncoding};
|
||||
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produce_responses};
|
||||
use crate::scheme::keygen::VerificationKeyAuth;
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::utils::{try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar_vec, try_deserialize_scalar};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct SpendInstance {
|
||||
pub kappa: G2Projective,
|
||||
pub cc: G1Projective,
|
||||
pub aa: Vec<G1Projective>,
|
||||
pub ss: Vec<G1Projective>,
|
||||
pub tt: Vec<G1Projective>,
|
||||
pub kappa_k: Vec<G2Projective>,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for SpendInstance {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<SpendInstance> {
|
||||
if bytes.len() < 48 * 5 + 2 * 96 || (bytes.len()) % 48 != 0 {
|
||||
return Err(CompactEcashError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len(),
|
||||
target: 48 * 5 + 2 * 96,
|
||||
modulus: 48,
|
||||
object: "spend instance".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut j = 0;
|
||||
let kappa_bytes = bytes[j..j + 96].try_into().unwrap();
|
||||
let kappa = try_deserialize_g2_projective(
|
||||
&kappa_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize kappa".to_string()),
|
||||
)?;
|
||||
j += 96;
|
||||
|
||||
let a_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
|
||||
j += 8;
|
||||
if bytes[j..].len() < a_len as usize * 48 {
|
||||
return Err(CompactEcashError::DeserializationMinLength {
|
||||
min: a_len as usize * 48,
|
||||
actual: bytes[j..].len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut aa = Vec::with_capacity(a_len as usize);
|
||||
for i in 0..a_len as usize {
|
||||
let start = j + i * 48;
|
||||
let end = start + 48;
|
||||
|
||||
let aa_elem_bytes = bytes[start..end].try_into().unwrap();
|
||||
let aa_elem = try_deserialize_g1_projective(
|
||||
&aa_elem_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize compressed A values".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
aa.push(aa_elem)
|
||||
}
|
||||
j += j + a_len as usize * 48;
|
||||
|
||||
|
||||
let cc_bytes = bytes[j..j + 48].try_into().unwrap();
|
||||
let cc = try_deserialize_g1_projective(
|
||||
&cc_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize C".to_string()),
|
||||
)?;
|
||||
j += 48;
|
||||
|
||||
let s_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
|
||||
j += 8;
|
||||
if bytes[j..].len() < s_len as usize * 48 {
|
||||
return Err(CompactEcashError::DeserializationMinLength {
|
||||
min: s_len as usize * 48,
|
||||
actual: bytes[j..].len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut ss = Vec::with_capacity(s_len as usize);
|
||||
for i in 0..s_len as usize {
|
||||
let start = j + i * 48;
|
||||
let end = start + 48;
|
||||
|
||||
let ss_elem_bytes = bytes[start..end].try_into().unwrap();
|
||||
let ss_elem = try_deserialize_g1_projective(
|
||||
&ss_elem_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize compressed S values".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
ss.push(ss_elem)
|
||||
}
|
||||
j += j + s_len as usize * 48;
|
||||
|
||||
let t_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
|
||||
j += 8;
|
||||
if bytes[j..].len() < t_len as usize * 48 {
|
||||
return Err(CompactEcashError::DeserializationMinLength {
|
||||
min: t_len as usize * 48,
|
||||
actual: bytes[j..].len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut tt = Vec::with_capacity(t_len as usize);
|
||||
for i in 0..t_len as usize {
|
||||
let start = j + i * 48;
|
||||
let end = start + 48;
|
||||
|
||||
let tt_elem_bytes = bytes[start..end].try_into().unwrap();
|
||||
let tt_elem = try_deserialize_g1_projective(
|
||||
&tt_elem_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize compressed T values".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
tt.push(tt_elem)
|
||||
}
|
||||
j += j + t_len as usize * 48;
|
||||
|
||||
|
||||
let kappa_k_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
|
||||
j += 8;
|
||||
if bytes[j..].len() < kappa_k_len as usize * 96 {
|
||||
return Err(CompactEcashError::DeserializationMinLength {
|
||||
min: kappa_k_len as usize * 96,
|
||||
actual: bytes[j..].len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut kappa_k = Vec::with_capacity(kappa_k_len as usize);
|
||||
for i in 0..kappa_k_len as usize {
|
||||
let start = j + i * 48;
|
||||
let end = start + 48;
|
||||
|
||||
let kappa_k_elem_bytes = bytes[start..end].try_into().unwrap();
|
||||
let kappa_k_elem = try_deserialize_g2_projective(
|
||||
&kappa_k_elem_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize compressed kappa_k values".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
kappa_k.push(kappa_k_elem)
|
||||
}
|
||||
|
||||
Ok(SpendInstance {
|
||||
kappa,
|
||||
aa,
|
||||
cc,
|
||||
ss,
|
||||
tt,
|
||||
kappa_k,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SpendInstance {
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes: Vec<u8> = Default::default();
|
||||
bytes.extend_from_slice(self.kappa.to_bytes().as_ref());
|
||||
|
||||
bytes.extend_from_slice(&self.aa.len().to_le_bytes());
|
||||
for a in &self.aa {
|
||||
bytes.extend_from_slice(&a.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(self.cc.to_bytes().as_ref());
|
||||
|
||||
bytes.extend_from_slice(&self.ss.len().to_le_bytes());
|
||||
for s in &self.ss {
|
||||
bytes.extend_from_slice(&s.to_affine().to_compressed());
|
||||
}
|
||||
bytes.extend_from_slice(&self.tt.len().to_le_bytes());
|
||||
for t in &self.tt {
|
||||
bytes.extend_from_slice(&t.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&self.kappa_k.len().to_le_bytes());
|
||||
for k in &self.kappa_k {
|
||||
bytes.extend_from_slice(&k.to_affine().to_compressed());
|
||||
}
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpendWitness {
|
||||
// includes skUser, v, t
|
||||
pub attributes: Vec<Scalar>,
|
||||
// signature randomizing element
|
||||
pub r: Scalar,
|
||||
pub o_c: Scalar,
|
||||
pub lk: Vec<Scalar>,
|
||||
pub o_a: Vec<Scalar>,
|
||||
pub mu: Vec<Scalar>,
|
||||
pub o_mu: Vec<Scalar>,
|
||||
pub r_k: Vec<Scalar>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SpendProof {
|
||||
challenge: Scalar,
|
||||
response_r: Scalar,
|
||||
response_r_l: Vec<Scalar>,
|
||||
response_l: Vec<Scalar>,
|
||||
response_o_a: Vec<Scalar>,
|
||||
response_o_c: Scalar,
|
||||
response_mu: Vec<Scalar>,
|
||||
response_o_mu: Vec<Scalar>,
|
||||
response_attributes: Vec<Scalar>,
|
||||
}
|
||||
|
||||
impl SpendProof {
|
||||
pub fn construct(
|
||||
params: &Parameters,
|
||||
instance: &SpendInstance,
|
||||
witness: &SpendWitness,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
rr: &[Scalar],
|
||||
) -> Self {
|
||||
let grparams = params.grp();
|
||||
// generate random values to replace each witness
|
||||
let r_attributes = grparams.n_random_scalars(witness.attributes.len());
|
||||
let r_sk = r_attributes[0];
|
||||
let r_v = r_attributes[1];
|
||||
let r_r = grparams.random_scalar();
|
||||
let r_o_c = grparams.random_scalar();
|
||||
|
||||
let r_r_lk = grparams.n_random_scalars(witness.r_k.len());
|
||||
let r_lk = grparams.n_random_scalars(witness.lk.len());
|
||||
let r_o_a = grparams.n_random_scalars(witness.o_a.len());
|
||||
let r_mu = grparams.n_random_scalars(witness.mu.len());
|
||||
let r_o_mu = grparams.n_random_scalars(witness.o_mu.len());
|
||||
|
||||
let g1 = *grparams.gen1();
|
||||
let gamma1 = *grparams.gamma1();
|
||||
let beta2_bytes = verification_key
|
||||
.beta_g2
|
||||
.iter()
|
||||
.map(|beta_i| beta_i.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// compute zkp commitment for each instance
|
||||
let zkcm_kappa = grparams.gen2() * r_r
|
||||
+ verification_key.alpha
|
||||
+ r_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
let zkcm_cc = g1 * r_o_c + gamma1 * r_v;
|
||||
|
||||
let zkcm_aa: Vec<G1Projective> =
|
||||
r_o_a
|
||||
.iter()
|
||||
.zip(r_lk.iter()).map(|(r_o_a_k, r_l_k)| g1 * r_o_a_k + gamma1 * r_l_k)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_aa_bytes = zkcm_aa
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_ss = r_mu.iter().map(|r_mu_k| grparams.delta() * r_mu_k).collect::<Vec<_>>();
|
||||
|
||||
let zkcm_ss_bytes = zkcm_ss
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_tt = rr
|
||||
.iter()
|
||||
.zip(r_mu.iter()).map(|(rr_k, r_mu_k)| g1 * r_sk + (g1 * rr_k) * r_mu_k).collect::<Vec<_>>();
|
||||
|
||||
let zkcm_tt_bytes = zkcm_tt
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_gamma11 = instance.aa
|
||||
.iter()
|
||||
.zip(r_mu.iter())
|
||||
.zip(r_o_mu.iter())
|
||||
.map(|((aa_k, r_mu_k), r_o_mu_k)| (aa_k + instance.cc + gamma1) * r_mu_k + g1 * r_o_mu_k)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_gamma11_bytes = zkcm_gamma11
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_kappa_k = r_lk.iter()
|
||||
.zip(r_r_lk.iter())
|
||||
.map(|(r_k, r_r_k)| params.pk_rp().alpha + params.pk_rp().beta * r_k + grparams.gen2() * r_r_k)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_kappa_k_bytes = zkcm_kappa_k
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// compute the challenge
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(grparams.gen1().to_bytes().as_ref())
|
||||
.chain(std::iter::once(gamma1.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(verification_key.alpha.to_bytes().as_ref()))
|
||||
.chain(beta2_bytes.iter().map(|b| b.as_ref()))
|
||||
.chain(std::iter::once(instance.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_kappa.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_cc.to_bytes().as_ref()))
|
||||
.chain(zkcm_aa_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_ss_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_kappa_k_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_tt_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_gamma11_bytes.iter().map(|x| x.as_ref()))
|
||||
);
|
||||
|
||||
// compute response for each witness
|
||||
let response_attributes = produce_responses(
|
||||
&r_attributes,
|
||||
&challenge,
|
||||
&witness.attributes.iter().collect::<Vec<_>>(),
|
||||
);
|
||||
let response_r = produce_response(&r_r, &challenge, &witness.r);
|
||||
let response_r_l = produce_responses(&r_r_lk, &challenge, &witness.r_k);
|
||||
let response_l = produce_responses(&r_lk, &challenge, &witness.lk);
|
||||
let response_o_a = produce_responses(&r_o_a, &challenge, &witness.o_a);
|
||||
let response_o_c = produce_response(&r_o_c, &challenge, &witness.o_c);
|
||||
|
||||
let response_mu = produce_responses(&r_mu, &challenge, &witness.mu);
|
||||
let response_o_mu = produce_responses(&r_o_mu, &challenge, &witness.o_mu);
|
||||
|
||||
SpendProof {
|
||||
challenge,
|
||||
response_r,
|
||||
response_r_l,
|
||||
response_l,
|
||||
response_o_a,
|
||||
response_o_c,
|
||||
response_mu,
|
||||
response_o_mu,
|
||||
response_attributes,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
instance: &SpendInstance,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
rr: &[Scalar],
|
||||
) -> bool {
|
||||
let grparams = params.grp();
|
||||
let g1 = *grparams.gen1();
|
||||
let gamma1 = *grparams.gamma1();
|
||||
let beta2_bytes = verification_key
|
||||
.beta_g2
|
||||
.iter()
|
||||
.map(|beta_i| beta_i.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// re-compute each zkp commitment
|
||||
let zkcm_kappa = instance.kappa * self.challenge
|
||||
+ grparams.gen2() * self.response_r
|
||||
+ verification_key.alpha * (Scalar::one() - self.challenge)
|
||||
+ self
|
||||
.response_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
|
||||
let zkcm_aa = self.response_o_a
|
||||
.iter()
|
||||
.zip(self.response_l.iter())
|
||||
.zip(instance.aa.iter())
|
||||
.map(|((resp_o_a_k, resp_l_k), aa_k)| g1 * resp_o_a_k + gamma1 * resp_l_k + aa_k * self.challenge)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_aa_bytes = zkcm_aa
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_cc = g1 * self.response_o_c
|
||||
+ gamma1 * self.response_attributes[1]
|
||||
+ instance.cc * self.challenge;
|
||||
|
||||
let zkcm_ss = self.response_mu
|
||||
.iter()
|
||||
.zip(instance.ss.iter())
|
||||
.map(|(resp_mu_k, ss_k)| grparams.delta() * resp_mu_k + ss_k * self.challenge)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_ss_bytes = zkcm_ss
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_tt = self.response_mu
|
||||
.iter()
|
||||
.zip(rr.iter())
|
||||
.zip(instance.tt.iter())
|
||||
.map(|((resp_mu_k, rr_k), tt_k)| g1 * self.response_attributes[0] + (g1 * rr_k) * resp_mu_k + tt_k * self.challenge)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_tt_bytes = zkcm_tt
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_gamma11 = instance.aa
|
||||
.iter()
|
||||
.zip(self.response_mu.iter())
|
||||
.zip(self.response_o_mu.iter())
|
||||
.map(|((aa_k, resp_mu_k), resp_o_mu_k)| (aa_k + instance.cc + gamma1) * resp_mu_k
|
||||
+ g1 * resp_o_mu_k + gamma1 * self.challenge)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_gamma11_bytes = zkcm_gamma11
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
let zkcm_kappa_k = instance.kappa_k
|
||||
.iter()
|
||||
.zip(self.response_r_l.iter())
|
||||
.zip(self.response_l.iter())
|
||||
.map(|((kappa_k, resp_r_k), resp_r_l_k)| kappa_k * self.challenge + grparams.gen2() * resp_r_k + params.pk_rp().alpha * (Scalar::one() - self.challenge) + params.pk_rp().beta * resp_r_l_k)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_kappa_k_bytes = zkcm_kappa_k
|
||||
.iter()
|
||||
.map(|x| x.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// re-compute the challenge
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(grparams.gen1().to_bytes().as_ref())
|
||||
.chain(std::iter::once(gamma1.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(verification_key.alpha.to_bytes().as_ref()))
|
||||
.chain(beta2_bytes.iter().map(|b| b.as_ref()))
|
||||
.chain(std::iter::once(instance.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_kappa.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_cc.to_bytes().as_ref()))
|
||||
.chain(zkcm_aa_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_ss_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_kappa_k_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_tt_bytes.iter().map(|x| x.as_ref()))
|
||||
.chain(zkcm_gamma11_bytes.iter().map(|x| x.as_ref()))
|
||||
);
|
||||
|
||||
challenge == self.challenge
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let challenge_bytes = self.challenge.to_bytes();
|
||||
let response_r_bytes = self.response_r.to_bytes();
|
||||
|
||||
let rrl_len = self.response_r_l.len();
|
||||
let rrl_len_bytes = rrl_len.to_le_bytes();
|
||||
|
||||
let rl_len = self.response_l.len();
|
||||
let rl_len_bytes = rl_len.to_le_bytes();
|
||||
|
||||
let roa_len = self.response_o_a.len();
|
||||
let roa_len_bytes = roa_len.to_le_bytes();
|
||||
|
||||
let roc_bytes = self.response_o_c.to_bytes();
|
||||
|
||||
let rmu_len = self.response_mu.len();
|
||||
let rmu_len_bytes = rmu_len.to_le_bytes();
|
||||
|
||||
let romu_len = self.response_o_mu.len();
|
||||
let romu_len_bytes = romu_len.to_le_bytes();
|
||||
|
||||
let rattributes_len = self.response_attributes.len();
|
||||
let rattributes_len_bytes = rattributes_len.to_le_bytes();
|
||||
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(
|
||||
96 + (rrl_len + rl_len + roa_len + rmu_len + romu_len + rattributes_len) * 8
|
||||
+ (rrl_len + rl_len + roa_len + rmu_len + romu_len + rattributes_len) * 32);
|
||||
|
||||
bytes.extend_from_slice(&challenge_bytes);
|
||||
bytes.extend_from_slice(&response_r_bytes);
|
||||
bytes.extend_from_slice(&roc_bytes);
|
||||
|
||||
bytes.extend_from_slice(&rrl_len_bytes);
|
||||
for rrl in &self.response_r_l {
|
||||
bytes.extend_from_slice(&rrl.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&rl_len_bytes);
|
||||
for rl in &self.response_l {
|
||||
bytes.extend_from_slice(&rl.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&roa_len_bytes);
|
||||
for roa in &self.response_o_a {
|
||||
bytes.extend_from_slice(&roa.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&rmu_len_bytes);
|
||||
for rmu in &self.response_mu {
|
||||
bytes.extend_from_slice(&rmu.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&romu_len_bytes);
|
||||
for romu in &self.response_o_mu {
|
||||
bytes.extend_from_slice(&romu.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&rattributes_len_bytes);
|
||||
for rattr in &self.response_attributes {
|
||||
bytes.extend_from_slice(&rattr.to_bytes());
|
||||
}
|
||||
|
||||
bytes
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for SpendProof {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<SpendProof> {
|
||||
if bytes.len() < 336 || (bytes.len() - 96 - 48) % 32 != 0 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"tried to deserialize proof of spending with bytes of invalid length"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut idx = 0;
|
||||
let challenge_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
let response_r_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
let response_o_c_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
|
||||
let challenge = try_deserialize_scalar(
|
||||
&challenge_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize challenge".to_string()),
|
||||
)?;
|
||||
|
||||
let response_r = try_deserialize_scalar(
|
||||
&response_r_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_r".to_string()),
|
||||
)?;
|
||||
|
||||
let response_o_c = try_deserialize_scalar(
|
||||
&response_o_c_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_o_c".to_string()),
|
||||
)?;
|
||||
|
||||
let rrl_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
|
||||
idx += 8;
|
||||
if bytes[idx..].len() < rrl_len as usize * 32 {
|
||||
return Err(
|
||||
CompactEcashError::Deserialization(
|
||||
"tried to deserialize response_r_l".to_string()),
|
||||
);
|
||||
}
|
||||
let rrl_end = idx + rrl_len as usize * 32;
|
||||
let response_r_l = try_deserialize_scalar_vec(
|
||||
rrl_len,
|
||||
&bytes[idx..rrl_end],
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_r_l".to_string()),
|
||||
)?;
|
||||
|
||||
let rl_len = u64::from_le_bytes(bytes[rrl_end..rrl_end + 8].try_into().unwrap());
|
||||
let response_l_start = rrl_end + 8;
|
||||
if bytes[response_l_start..].len() < rl_len as usize * 32 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"tried to deserialize response_l".to_string(),
|
||||
));
|
||||
}
|
||||
let rl_end = response_l_start + rl_len as usize * 32;
|
||||
let response_l = try_deserialize_scalar_vec(
|
||||
rl_len,
|
||||
&bytes[response_l_start..rl_end],
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_l".to_string()),
|
||||
)?;
|
||||
|
||||
let roa_len = u64::from_le_bytes(bytes[rl_end..rl_end + 8].try_into().unwrap());
|
||||
let roa_end = rl_end + 8;
|
||||
if bytes[roa_end..].len() < roa_len as usize * 32 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"tried to deserialize response_o_a".to_string(),
|
||||
));
|
||||
}
|
||||
let roa_end = roa_end + roa_len as usize * 32;
|
||||
let response_o_a = try_deserialize_scalar_vec(
|
||||
roa_len,
|
||||
&bytes[rl_end + 8..roa_end],
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_o_a".to_string()),
|
||||
)?;
|
||||
|
||||
let response_mu_len = u64::from_le_bytes(bytes[roa_end..roa_end + 8].try_into().unwrap());
|
||||
let response_mu_end = roa_end + 8;
|
||||
if bytes[response_mu_end..].len() < response_mu_len as usize * 32 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"tried to deserialize response_mu".to_string(),
|
||||
));
|
||||
}
|
||||
let response_mu_end = response_mu_end + response_mu_len as usize * 32;
|
||||
let response_mu = try_deserialize_scalar_vec(
|
||||
response_mu_len,
|
||||
&bytes[roa_end + 8..response_mu_end],
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_mu".to_string()),
|
||||
)?;
|
||||
|
||||
let response_o_mu_len = u64::from_le_bytes(bytes[response_mu_end..response_mu_end + 8].try_into().unwrap());
|
||||
let response_o_mu_end = response_mu_end + 8;
|
||||
if bytes[response_o_mu_end..].len() < response_o_mu_len as usize * 32 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"tried to deserialize response_o_mu".to_string(),
|
||||
));
|
||||
}
|
||||
let response_o_mu_end = response_o_mu_end + response_o_mu_len as usize * 32;
|
||||
let response_o_mu = try_deserialize_scalar_vec(
|
||||
response_o_mu_len,
|
||||
&bytes[response_mu_end + 8..response_o_mu_end],
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_o_mu".to_string()),
|
||||
)?;
|
||||
|
||||
let response_attributes_len = u64::from_le_bytes(bytes[response_o_mu_end..response_o_mu_end + 8].try_into().unwrap());
|
||||
let response_attributes_end = response_o_mu_end + 8;
|
||||
if bytes[response_attributes_end..].len() < response_attributes_len as usize * 32 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"tried to deserialize response_attributes".to_string(),
|
||||
));
|
||||
}
|
||||
let response_attributes_end = response_attributes_end + response_attributes_len as usize * 32;
|
||||
let response_attributes = try_deserialize_scalar_vec(
|
||||
response_attributes_len,
|
||||
&bytes[response_o_mu_end + 8..response_attributes_end],
|
||||
CompactEcashError::Deserialization("Failed to deserialize response_attributes".to_string()),
|
||||
)?;
|
||||
|
||||
|
||||
// Construct the SpendProof struct from the deserialized data
|
||||
let spend_proof = SpendProof {
|
||||
challenge,
|
||||
response_r,
|
||||
response_o_c,
|
||||
response_r_l,
|
||||
response_l,
|
||||
response_o_a,
|
||||
response_mu,
|
||||
response_o_mu,
|
||||
response_attributes,
|
||||
};
|
||||
|
||||
Ok(spend_proof)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bls12_381::{G2Projective, Scalar};
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
|
||||
use crate::scheme::{pseudorandom_f_delta_v, pseudorandom_f_g_v};
|
||||
use crate::scheme::aggregation::aggregate_verification_keys;
|
||||
use crate::scheme::keygen::{PublicKeyUser, ttp_keygen, VerificationKeyAuth};
|
||||
use crate::scheme::PayInfo;
|
||||
use crate::scheme::setup::setup;
|
||||
use crate::utils::hash_to_scalar;
|
||||
|
||||
#[test]
|
||||
fn spend_proof_construct_and_verify() {
|
||||
let _rng = thread_rng();
|
||||
let L = 32;
|
||||
let params = setup(L);
|
||||
let grparams = params.grp();
|
||||
let sk = grparams.random_scalar();
|
||||
let _pk_user = PublicKeyUser {
|
||||
pk: grparams.gen1() * sk,
|
||||
};
|
||||
let authorities_keypairs = ttp_keygen(&grparams, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
let v = grparams.random_scalar();
|
||||
let t = grparams.random_scalar();
|
||||
let attributes = vec![sk, v, t];
|
||||
// the below value must be from range 0 to params.L()
|
||||
let l = 5;
|
||||
let gamma1 = *grparams.gamma1();
|
||||
let g1 = *grparams.gen1();
|
||||
|
||||
let r = grparams.random_scalar();
|
||||
let kappa = grparams.gen2() * r
|
||||
+ verification_key.alpha
|
||||
+ attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
let o_a = grparams.random_scalar();
|
||||
let o_c = grparams.random_scalar();
|
||||
|
||||
// compute commitments A, C, D
|
||||
let aa = g1 * o_a + gamma1 * Scalar::from(l);
|
||||
let cc = g1 * o_c + gamma1 * v;
|
||||
|
||||
// compute hash of the payment info
|
||||
let pay_info = PayInfo { info: [37u8; 32] };
|
||||
let rr = hash_to_scalar(pay_info.info);
|
||||
|
||||
// evaluate the pseudorandom functions
|
||||
let ss = pseudorandom_f_delta_v(&grparams, v, l);
|
||||
let tt = g1 * sk + pseudorandom_f_g_v(&grparams, v, l) * rr;
|
||||
|
||||
// compute values mu, o_mu, lambda, o_lambda
|
||||
let mu: Scalar = (v + Scalar::from(l) + Scalar::from(1)).invert().unwrap();
|
||||
let o_mu = ((o_a + o_c) * mu).neg();
|
||||
|
||||
// parse the signature associated with value l
|
||||
let sign_l = params.get_sign_by_idx(l).unwrap();
|
||||
// randomise the signature associated with value l
|
||||
let (_sign_l_prime, r_l) = sign_l.randomise(grparams);
|
||||
// compute kappa_l
|
||||
let kappa_k =
|
||||
grparams.gen2() * r_l + params.pk_rp().alpha + params.pk_rp().beta * Scalar::from(l);
|
||||
|
||||
let instance = SpendInstance {
|
||||
kappa,
|
||||
aa: vec![aa],
|
||||
cc,
|
||||
ss: vec![ss],
|
||||
tt: vec![tt],
|
||||
kappa_k: vec![kappa_k],
|
||||
};
|
||||
|
||||
let witness = SpendWitness {
|
||||
attributes,
|
||||
r,
|
||||
o_c,
|
||||
lk: vec![Scalar::from(l)],
|
||||
o_a: vec![o_a],
|
||||
mu: vec![mu],
|
||||
o_mu: vec![o_mu],
|
||||
r_k: vec![r_l],
|
||||
};
|
||||
|
||||
let zk_proof = SpendProof::construct(¶ms, &instance, &witness, &verification_key, &[rr]);
|
||||
assert!(zk_proof.verify(¶ms, &instance, &verification_key, &[rr]));
|
||||
|
||||
let zk_proof_bytes = zk_proof.to_bytes();
|
||||
let zk_proof2 = SpendProof::try_from(zk_proof_bytes.as_slice()).unwrap();
|
||||
assert_eq!(zk_proof, zk_proof2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use bls12_381::{G1Projective, Scalar};
|
||||
use group::GroupEncoding;
|
||||
use itertools::izip;
|
||||
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produce_responses};
|
||||
use crate::scheme::keygen::PublicKeyUser;
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
use crate::utils::{try_deserialize_g1_projective, try_deserialize_scalar_vec, try_deserialize_scalar};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
// instance: g, gamma1, gamma2, gamma3, com, h, com1, com2, com3, pkUser
|
||||
pub struct WithdrawalReqInstance {
|
||||
// Joined commitment to all attributes
|
||||
pub com: G1Projective,
|
||||
// Hash of the joined commitment com
|
||||
pub h: G1Projective,
|
||||
// Pedersen commitments to each attribute
|
||||
pub pc_coms: Vec<G1Projective>,
|
||||
// Public key of a user
|
||||
pub pk_user: PublicKeyUser,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for WithdrawalReqInstance {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<WithdrawalReqInstance> {
|
||||
if bytes.len() < 48 * 4 + 8 || (bytes.len() - 8) % 48 != 0 {
|
||||
return Err(CompactEcashError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len() - 8,
|
||||
target: 48 * 4 + 8,
|
||||
modulus: 48,
|
||||
object: "withdrawal request zkp instance".to_string(),
|
||||
});
|
||||
}
|
||||
let com_bytes: [u8; 48] = bytes[..48].try_into().unwrap();
|
||||
let com = try_deserialize_g1_projective(
|
||||
&com_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize com".to_string()),
|
||||
)?;
|
||||
let h_bytes: [u8; 48] = bytes[48..96].try_into().unwrap();
|
||||
let h = try_deserialize_g1_projective(
|
||||
&h_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize h".to_string()),
|
||||
)?;
|
||||
let pc_coms_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
|
||||
let actual_pc_coms_len = (bytes.len() - 152) / 48;
|
||||
if pc_coms_len as usize != actual_pc_coms_len {
|
||||
return Err(CompactEcashError::Deserialization(format!(
|
||||
"Tried to deserialize pedersen commitments with inconsistent pc_coms_len (expected {}, got {})",
|
||||
pc_coms_len, actual_pc_coms_len
|
||||
)));
|
||||
}
|
||||
let mut pc_coms = Vec::new();
|
||||
let mut pc_coms_end: usize = 0;
|
||||
for i in 0..pc_coms_len {
|
||||
let start = (104 + i * 48) as usize;
|
||||
let end = (start + 48) as usize;
|
||||
let pc_i_bytes = bytes[start..end].try_into().unwrap();
|
||||
let pc_i = try_deserialize_g1_projective(
|
||||
&pc_i_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize pedersen commitment".to_string(),
|
||||
),
|
||||
)?;
|
||||
pc_coms_end = end;
|
||||
pc_coms.push(pc_i);
|
||||
}
|
||||
let pk_bytes = bytes[pc_coms_end..].try_into().unwrap();
|
||||
let pk = try_deserialize_g1_projective(
|
||||
&pk_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize user's public key".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
Ok(WithdrawalReqInstance {
|
||||
com,
|
||||
h,
|
||||
pc_coms,
|
||||
pk_user: PublicKeyUser { pk },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WithdrawalReqInstance {
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let pc_coms_len = self.pc_coms.len();
|
||||
let mut bytes = Vec::with_capacity(8 + (pc_coms_len + 3) as usize * 48);
|
||||
bytes.extend_from_slice(self.com.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.h.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(&pc_coms_len.to_le_bytes());
|
||||
for pc in self.pc_coms.iter() {
|
||||
bytes.extend_from_slice((pc.to_bytes()).as_ref());
|
||||
}
|
||||
bytes.extend_from_slice(self.pk_user.pk.to_bytes().as_ref());
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<WithdrawalReqInstance> {
|
||||
WithdrawalReqInstance::try_from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
// witness: m1, m2, m3, o, o1, o2, o3,
|
||||
pub struct WithdrawalReqWitness {
|
||||
pub attributes: Vec<Scalar>,
|
||||
// Opening for the joined commitment com
|
||||
pub com_opening: Scalar,
|
||||
// Openings for the pedersen commitments
|
||||
pub pc_coms_openings: Vec<Scalar>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct WithdrawalReqProof {
|
||||
challenge: Scalar,
|
||||
response_opening: Scalar,
|
||||
response_openings: Vec<Scalar>,
|
||||
response_attributes: Vec<Scalar>,
|
||||
}
|
||||
|
||||
impl WithdrawalReqProof {
|
||||
pub(crate) fn construct(
|
||||
params: &GroupParameters,
|
||||
instance: &WithdrawalReqInstance,
|
||||
witness: &WithdrawalReqWitness,
|
||||
) -> Self {
|
||||
// generate random values to replace the witnesses
|
||||
let r_com_opening = params.random_scalar();
|
||||
let r_pedcom_openings = params.n_random_scalars(witness.pc_coms_openings.len());
|
||||
let r_attributes = params.n_random_scalars(witness.attributes.len());
|
||||
|
||||
// compute zkp commitments for each instance
|
||||
let zkcm_com = params.gen1() * r_com_opening
|
||||
+ r_attributes
|
||||
.iter()
|
||||
.zip(params.gammas().iter())
|
||||
.map(|(rm_i, gamma_i)| gamma_i * rm_i)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let zkcm_pedcom = r_pedcom_openings
|
||||
.iter()
|
||||
.zip(r_attributes.iter())
|
||||
.map(|(o_j, m_j)| params.gen1() * o_j + instance.h * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_user_sk = params.gen1() * r_attributes[0];
|
||||
|
||||
// covert to bytes
|
||||
let gammas_bytes = params
|
||||
.gammas()
|
||||
.iter()
|
||||
.map(|gamma| gamma.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_pedcom_bytes = zkcm_pedcom
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// compute zkp challenge using g1, gammas, c, h, c1, c2, c3, zk commitments
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(params.gen1().to_bytes().as_ref())
|
||||
.chain(gammas_bytes.iter().map(|gamma| gamma.as_ref()))
|
||||
.chain(std::iter::once(instance.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_com.to_bytes().as_ref()))
|
||||
.chain(zkcm_pedcom_bytes.iter().map(|c| c.as_ref()))
|
||||
.chain(std::iter::once(zkcm_user_sk.to_bytes().as_ref())),
|
||||
);
|
||||
|
||||
// compute response
|
||||
let response_opening = produce_response(&r_com_opening, &challenge, &witness.com_opening);
|
||||
let response_openings = produce_responses(
|
||||
&r_pedcom_openings,
|
||||
&challenge,
|
||||
&witness.pc_coms_openings.iter().collect::<Vec<_>>(),
|
||||
);
|
||||
let response_attributes = produce_responses(
|
||||
&r_attributes,
|
||||
&challenge,
|
||||
&witness.attributes.iter().collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
WithdrawalReqProof {
|
||||
challenge,
|
||||
response_opening,
|
||||
response_openings,
|
||||
response_attributes,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn verify(
|
||||
&self,
|
||||
params: &GroupParameters,
|
||||
instance: &WithdrawalReqInstance,
|
||||
) -> bool {
|
||||
// recompute zk commitments for each instance
|
||||
let zkcm_com = instance.com * self.challenge
|
||||
+ params.gen1() * self.response_opening
|
||||
+ self
|
||||
.response_attributes
|
||||
.iter()
|
||||
.zip(params.gammas().iter())
|
||||
.map(|(m_i, gamma_i)| gamma_i * m_i)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let zkcm_pedcom = izip!(
|
||||
instance.pc_coms.iter(),
|
||||
self.response_openings.iter(),
|
||||
self.response_attributes.iter()
|
||||
)
|
||||
.map(|(cm_j, resp_o_j, resp_m_j)| {
|
||||
cm_j * self.challenge + params.gen1() * resp_o_j + instance.h * resp_m_j
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zk_commitment_user_sk =
|
||||
instance.pk_user.pk * self.challenge + params.gen1() * self.response_attributes[0];
|
||||
|
||||
// covert to bytes
|
||||
let gammas_bytes = params
|
||||
.gammas()
|
||||
.iter()
|
||||
.map(|gamma| gamma.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zkcm_pedcom_bytes = zkcm_pedcom
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// recompute zkp challenge
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(params.gen1().to_bytes().as_ref())
|
||||
.chain(gammas_bytes.iter().map(|hs| hs.as_ref()))
|
||||
.chain(std::iter::once(instance.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zkcm_com.to_bytes().as_ref()))
|
||||
.chain(zkcm_pedcom_bytes.iter().map(|c| c.as_ref()))
|
||||
.chain(std::iter::once(zk_commitment_user_sk.to_bytes().as_ref())),
|
||||
);
|
||||
|
||||
challenge == self.challenge
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8>{
|
||||
let challenge_bytes = self.challenge.to_bytes();
|
||||
let response_opening_bytes = self.response_opening.to_bytes();
|
||||
let ro_len = self.response_openings.len() as u64;
|
||||
let ra_len = self.response_attributes.len() as u64;
|
||||
|
||||
let mut bytes = Vec::with_capacity(32 + 32 + 8 + ro_len as usize * 32 + 8 + ra_len as usize * 32);
|
||||
bytes.extend_from_slice(&challenge_bytes);
|
||||
bytes.extend_from_slice(&response_opening_bytes);
|
||||
bytes.extend_from_slice(&ro_len.to_le_bytes());
|
||||
for ro in &self.response_openings {
|
||||
bytes.extend_from_slice(&ro.to_bytes());
|
||||
}
|
||||
bytes.extend_from_slice(&ra_len.to_le_bytes());
|
||||
for ra in &self.response_attributes {
|
||||
bytes.extend_from_slice(&ra.to_bytes());
|
||||
}
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for WithdrawalReqProof {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<WithdrawalReqProof> {
|
||||
if bytes.len() < 32 + 32 + 16 + 32 + 32 || (bytes.len() - 16) % 32 != 0 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"tried to deserialize proof of withdrawal with bytes of invalid length"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut idx = 0;
|
||||
let challenge_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
let response_opening_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
|
||||
let challenge = try_deserialize_scalar(
|
||||
&challenge_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize challenge".to_string()),
|
||||
)?;
|
||||
|
||||
let response_opening = try_deserialize_scalar(
|
||||
&response_opening_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize the response to the random".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
let ro_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
|
||||
idx += 8;
|
||||
if bytes[idx..].len() < ro_len as usize * 32 + 8 {
|
||||
return Err(
|
||||
CompactEcashError::Deserialization(
|
||||
"tried to deserialize response openings".to_string()),
|
||||
);
|
||||
}
|
||||
let ro_end = idx + ro_len as usize * 32;
|
||||
let response_openings = try_deserialize_scalar_vec(
|
||||
ro_len,
|
||||
&bytes[idx..ro_end],
|
||||
CompactEcashError::Deserialization("Failed to deserialize openings response".to_string()),
|
||||
)?;
|
||||
|
||||
let ra_len = u64::from_le_bytes(bytes[ro_end..ro_end + 8].try_into().unwrap());
|
||||
let response_attributes = try_deserialize_scalar_vec(
|
||||
ra_len,
|
||||
&bytes[ro_end + 8..],
|
||||
CompactEcashError::Deserialization("Failed to deserialize attributes response".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(WithdrawalReqProof{
|
||||
challenge,
|
||||
response_opening,
|
||||
response_openings,
|
||||
response_attributes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use group::Group;
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::utils::hash_g1;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn withdrawal_request_instance_roundtrip() {
|
||||
let mut rng = thread_rng();
|
||||
let params = GroupParameters::new().unwrap();
|
||||
let instance = WithdrawalReqInstance {
|
||||
com: G1Projective::random(&mut rng),
|
||||
h: G1Projective::random(&mut rng),
|
||||
pc_coms: vec![
|
||||
G1Projective::random(&mut rng),
|
||||
G1Projective::random(&mut rng),
|
||||
G1Projective::random(&mut rng),
|
||||
],
|
||||
pk_user: PublicKeyUser {
|
||||
pk: params.gen1() * params.random_scalar(),
|
||||
},
|
||||
};
|
||||
|
||||
let instance_bytes = instance.to_bytes();
|
||||
let instance_p = WithdrawalReqInstance::from_bytes(&instance_bytes).unwrap();
|
||||
assert_eq!(instance, instance_p)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn withdrawal_proof_construct_and_verify() {
|
||||
let _rng = thread_rng();
|
||||
let params = GroupParameters::new().unwrap();
|
||||
let sk = params.random_scalar();
|
||||
let pk_user = PublicKeyUser {
|
||||
pk: params.gen1() * sk,
|
||||
};
|
||||
let v = params.random_scalar();
|
||||
let t = params.random_scalar();
|
||||
let attr = vec![sk, v, t];
|
||||
|
||||
let com_opening = params.random_scalar();
|
||||
let com = params.gen1() * com_opening
|
||||
+ attr
|
||||
.iter()
|
||||
.zip(params.gammas())
|
||||
.map(|(&m, gamma)| gamma * m)
|
||||
.sum::<G1Projective>();
|
||||
let h = hash_g1(com.to_bytes());
|
||||
|
||||
let pc_openings = params.n_random_scalars(attr.len());
|
||||
let pc_coms = pc_openings
|
||||
.iter()
|
||||
.zip(attr.iter())
|
||||
.map(|(o_j, m_j)| params.gen1() * o_j + h * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance = WithdrawalReqInstance {
|
||||
com,
|
||||
h,
|
||||
pc_coms,
|
||||
pk_user,
|
||||
};
|
||||
|
||||
let witness = WithdrawalReqWitness {
|
||||
attributes: attr,
|
||||
com_opening,
|
||||
pc_coms_openings: pc_openings,
|
||||
};
|
||||
let zk_proof = WithdrawalReqProof::construct(¶ms, &instance, &witness);
|
||||
assert!(zk_proof.verify(¶ms, &instance))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
use core::iter::Sum;
|
||||
use core::ops::Mul;
|
||||
use std::cell::Cell;
|
||||
|
||||
use bls12_381::{G2Prepared, G2Projective, Scalar};
|
||||
use group::Curve;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::Attribute;
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::scheme::{PartialWallet, Wallet};
|
||||
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
use crate::scheme::withdrawal::RequestInfo;
|
||||
use crate::utils::{
|
||||
check_bilinear_pairing, PartialSignature, perform_lagrangian_interpolation_at_origin,
|
||||
Signature, SignatureShare, SignerIndex,
|
||||
};
|
||||
|
||||
pub(crate) trait Aggregatable: Sized {
|
||||
fn aggregate(aggregatable: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self>;
|
||||
|
||||
fn check_unique_indices(indices: &[SignerIndex]) -> bool {
|
||||
// if aggregation is a threshold one, all indices should be unique
|
||||
indices.iter().unique_by(|&index| index).count() == indices.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Aggregatable for T
|
||||
where
|
||||
T: Sum,
|
||||
for<'a> T: Sum<&'a T>,
|
||||
for<'a> &'a T: Mul<Scalar, Output=T>,
|
||||
{
|
||||
fn aggregate(aggregatable: &[T], indices: Option<&[u64]>) -> Result<T> {
|
||||
if aggregatable.is_empty() {
|
||||
return Err(CompactEcashError::Aggregation(
|
||||
"Empty set of values".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(indices) = indices {
|
||||
if !Self::check_unique_indices(indices) {
|
||||
return Err(CompactEcashError::Aggregation(
|
||||
"Non-unique indices".to_string(),
|
||||
));
|
||||
}
|
||||
perform_lagrangian_interpolation_at_origin(indices, aggregatable)
|
||||
} else {
|
||||
// non-threshold
|
||||
Ok(aggregatable.iter().sum())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Aggregatable for PartialSignature {
|
||||
fn aggregate(sigs: &[PartialSignature], indices: Option<&[u64]>) -> Result<Signature> {
|
||||
let h = sigs
|
||||
.get(0)
|
||||
.ok_or_else(|| CompactEcashError::Aggregation("Empty set of signatures".to_string()))?
|
||||
.sig1();
|
||||
|
||||
// TODO: is it possible to avoid this allocation?
|
||||
let sigmas = sigs.iter().map(|sig| *sig.sig2()).collect::<Vec<_>>();
|
||||
let aggr_sigma = Aggregatable::aggregate(&sigmas, indices)?;
|
||||
|
||||
Ok(Signature(*h, aggr_sigma))
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures all provided verification keys were generated to verify the same number of attributes.
|
||||
fn check_same_key_size(keys: &[VerificationKeyAuth]) -> bool {
|
||||
keys.iter().map(|vk| vk.beta_g1.len()).all_equal()
|
||||
&& keys.iter().map(|vk| vk.beta_g2.len()).all_equal()
|
||||
}
|
||||
|
||||
pub fn aggregate_verification_keys(
|
||||
keys: &[VerificationKeyAuth],
|
||||
indices: Option<&[SignerIndex]>,
|
||||
) -> Result<VerificationKeyAuth> {
|
||||
if !check_same_key_size(keys) {
|
||||
return Err(CompactEcashError::Aggregation(
|
||||
"Verification keys are of different sizes".to_string(),
|
||||
));
|
||||
}
|
||||
Aggregatable::aggregate(keys, indices)
|
||||
}
|
||||
|
||||
pub fn aggregate_signature_shares(
|
||||
params: &GroupParameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
attributes: &[Attribute],
|
||||
shares: &[SignatureShare],
|
||||
) -> Result<Signature> {
|
||||
let (signatures, indices): (Vec<_>, Vec<_>) = shares
|
||||
.iter()
|
||||
.map(|share| (*share.signature(), share.index()))
|
||||
.unzip();
|
||||
|
||||
aggregate_signatures(
|
||||
params,
|
||||
verification_key,
|
||||
attributes,
|
||||
&signatures,
|
||||
Some(&indices),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn aggregate_signatures(
|
||||
params: &GroupParameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
attributes: &[Attribute],
|
||||
signatures: &[PartialSignature],
|
||||
indices: Option<&[SignerIndex]>,
|
||||
) -> Result<Signature> {
|
||||
// aggregate the signature
|
||||
|
||||
let signature = match Aggregatable::aggregate(signatures, indices) {
|
||||
Ok(res) => res,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
// Verify the signature
|
||||
let alpha = verification_key.alpha;
|
||||
|
||||
let tmp = attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
if !check_bilinear_pairing(
|
||||
&signature.0.to_affine(),
|
||||
&G2Prepared::from((alpha + tmp).to_affine()),
|
||||
&signature.1.to_affine(),
|
||||
params.prepared_miller_g2(),
|
||||
) {
|
||||
return Err(CompactEcashError::Aggregation(
|
||||
"Verification of the aggregated signature failed".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(signature)
|
||||
}
|
||||
|
||||
pub fn aggregate_wallets(
|
||||
params: &GroupParameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
sk_user: &SecretKeyUser,
|
||||
wallets: &[PartialWallet],
|
||||
req_info: &RequestInfo,
|
||||
) -> Result<Wallet> {
|
||||
// Aggregate partial wallets
|
||||
let signature_shares: Vec<SignatureShare> = wallets
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, wallet)| SignatureShare::new(*wallet.signature(), (idx + 1) as u64))
|
||||
.collect();
|
||||
|
||||
let attributes = vec![sk_user.sk, req_info.get_v()];
|
||||
let aggregated_signature =
|
||||
aggregate_signature_shares(¶ms, &verification_key, &attributes, &signature_shares)?;
|
||||
|
||||
Ok(Wallet {
|
||||
sig: aggregated_signature,
|
||||
v: req_info.get_v(),
|
||||
l: Cell::new(0),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
use crate::{PayInfo, VerificationKeyAuth};
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::scheme::keygen::PublicKeyUser;
|
||||
use crate::scheme::Payment;
|
||||
use crate::scheme::setup::Parameters;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum IdentifyResult {
|
||||
NotADuplicatePayment,
|
||||
DuplicatePayInfo(PayInfo),
|
||||
DoubleSpendingPublicKeys(PublicKeyUser),
|
||||
}
|
||||
|
||||
pub fn identify(params: &Parameters, verification_key: &VerificationKeyAuth, payment1: Payment, payment2: Payment, pay_info1: PayInfo, pay_info2: PayInfo) -> Result<IdentifyResult> {
|
||||
let mut k = 0;
|
||||
let mut j = 0;
|
||||
for (id1, pay1_ss) in payment1.ss.iter().enumerate() {
|
||||
for (id2, pay2_ss) in payment2.ss.iter().enumerate() {
|
||||
if pay1_ss == pay2_ss {
|
||||
k = id1;
|
||||
j = id2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if payment1.ss.iter().any(|pay1_ss| payment2.ss.contains(pay1_ss)) {
|
||||
if pay_info1 == pay_info2 {
|
||||
Ok(IdentifyResult::DuplicatePayInfo(pay_info1))
|
||||
} else {
|
||||
let rr_diff = payment1.rr[k] - payment2.rr[j];
|
||||
let pk = (payment2.tt[j] * payment1.rr[k] - payment1.tt[k] * payment2.rr[j]) * rr_diff.invert().unwrap();
|
||||
let pk_user = PublicKeyUser { pk };
|
||||
Ok(IdentifyResult::DoubleSpendingPublicKeys(pk_user))
|
||||
}
|
||||
} else {
|
||||
Ok(IdentifyResult::NotADuplicatePayment)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use itertools::izip;
|
||||
|
||||
use crate::{aggregate_verification_keys, aggregate_wallets, generate_keypair_user, issue_verify, issue_wallet, PartialWallet, PayInfo, ttp_keygen, VerificationKeyAuth, withdrawal_request};
|
||||
use crate::scheme::identify::{identify, IdentifyResult};
|
||||
use crate::scheme::keygen::{PublicKeyUser, SecretKeyUser};
|
||||
use crate::scheme::setup::setup;
|
||||
|
||||
#[test]
|
||||
fn duplicate_payments_with_the_same_pay_info() {
|
||||
let L = 32;
|
||||
let params = setup(L);
|
||||
let grparams = params.grp();
|
||||
let user_keypair = generate_keypair_user(&grparams);
|
||||
|
||||
let (req, req_info) = withdrawal_request(grparams, &user_keypair.secret_key()).unwrap();
|
||||
let authorities_keypairs = ttp_keygen(&grparams, 2, 3).unwrap();
|
||||
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key = aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
let mut wallet_blinded_signatures = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue_wallet(
|
||||
&grparams,
|
||||
auth_keypair.secret_key(),
|
||||
user_keypair.public_key(),
|
||||
&req,
|
||||
);
|
||||
wallet_blinded_signatures.push(blind_signature.unwrap());
|
||||
}
|
||||
|
||||
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
|
||||
wallet_blinded_signatures.iter(),
|
||||
verification_keys_auth.iter()
|
||||
)
|
||||
.map(|(w, vk)| issue_verify(&grparams, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
|
||||
.collect();
|
||||
|
||||
// Aggregate partial wallets
|
||||
let aggr_wallet = aggregate_wallets(
|
||||
&grparams,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&unblinded_wallet_shares,
|
||||
&req_info,
|
||||
).unwrap();
|
||||
|
||||
// Let's try to spend some coins
|
||||
let pay_info1 = PayInfo { info: [6u8; 32] };
|
||||
let spend_vv = 1;
|
||||
|
||||
let (payment1, _upd_wallet) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info1,
|
||||
false,
|
||||
spend_vv,
|
||||
).unwrap();
|
||||
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
let payment2 = payment1.clone();
|
||||
assert!(payment2
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
let pay_info2 = pay_info1.clone();
|
||||
let identify_result = identify(¶ms, &verification_key, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::DuplicatePayInfo(pay_info1.clone()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_if_two_different_payments() {
|
||||
let L = 32;
|
||||
let params = setup(L);
|
||||
let grparams = params.grp();
|
||||
let user_keypair = generate_keypair_user(&grparams);
|
||||
|
||||
let (req, req_info) = withdrawal_request(grparams, &user_keypair.secret_key()).unwrap();
|
||||
let authorities_keypairs = ttp_keygen(&grparams, 2, 3).unwrap();
|
||||
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key = aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
let mut wallet_blinded_signatures = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue_wallet(
|
||||
&grparams,
|
||||
auth_keypair.secret_key(),
|
||||
user_keypair.public_key(),
|
||||
&req,
|
||||
);
|
||||
wallet_blinded_signatures.push(blind_signature.unwrap());
|
||||
}
|
||||
|
||||
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
|
||||
wallet_blinded_signatures.iter(),
|
||||
verification_keys_auth.iter()
|
||||
)
|
||||
.map(|(w, vk)| issue_verify(&grparams, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
|
||||
.collect();
|
||||
|
||||
// Aggregate partial wallets
|
||||
let aggr_wallet = aggregate_wallets(
|
||||
&grparams,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&unblinded_wallet_shares,
|
||||
&req_info,
|
||||
).unwrap();
|
||||
|
||||
// Let's try to spend some coins
|
||||
let pay_info1 = PayInfo { info: [6u8; 32] };
|
||||
let spend_vv = 1;
|
||||
|
||||
let (payment1, upd_wallet) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info1,
|
||||
false,
|
||||
spend_vv,
|
||||
).unwrap();
|
||||
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
|
||||
let pay_info2 = PayInfo { info: [7u8; 32] };
|
||||
let (payment2, _) = upd_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info2,
|
||||
false,
|
||||
spend_vv,
|
||||
).unwrap();
|
||||
|
||||
assert!(payment2
|
||||
.spend_verify(¶ms, &verification_key, &pay_info2)
|
||||
.unwrap());
|
||||
|
||||
let identify_result = identify(¶ms, &verification_key, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::NotADuplicatePayment);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_payments_with_one_repeating_serial_number_but_different_pay_info() {
|
||||
let L = 32;
|
||||
let params = setup(L);
|
||||
let grp = params.grp();
|
||||
let user_keypair = generate_keypair_user(&grp);
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut public_keys: Vec<PublicKeyUser> = Default::default();
|
||||
for _i in 0..50 {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
public_keys.push(pk_user.clone());
|
||||
}
|
||||
public_keys.push(user_keypair.public_key().clone());
|
||||
|
||||
|
||||
let (req, req_info) = withdrawal_request(grp, &user_keypair.secret_key()).unwrap();
|
||||
let authorities_keypairs = ttp_keygen(&grp, 2, 3).unwrap();
|
||||
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key = aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
let mut wallet_blinded_signatures = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue_wallet(
|
||||
&grp,
|
||||
auth_keypair.secret_key(),
|
||||
user_keypair.public_key(),
|
||||
&req,
|
||||
);
|
||||
wallet_blinded_signatures.push(blind_signature.unwrap());
|
||||
}
|
||||
|
||||
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
|
||||
wallet_blinded_signatures.iter(),
|
||||
verification_keys_auth.iter()
|
||||
)
|
||||
.map(|(w, vk)| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
|
||||
.collect();
|
||||
|
||||
// Aggregate partial wallets
|
||||
let aggr_wallet = aggregate_wallets(
|
||||
&grp,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&unblinded_wallet_shares,
|
||||
&req_info,
|
||||
).unwrap();
|
||||
|
||||
// Let's try to spend some coins
|
||||
let pay_info1 = PayInfo { info: [6u8; 32] };
|
||||
let spend_vv = 1;
|
||||
|
||||
let (payment1, _upd_wallet) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info1,
|
||||
false,
|
||||
spend_vv,
|
||||
).unwrap();
|
||||
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = aggr_wallet.l.get();
|
||||
aggr_wallet.l.set(current_l - 1);
|
||||
|
||||
let pay_info2 = PayInfo { info: [7u8; 32] };
|
||||
|
||||
let (payment2, _) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info2,
|
||||
false,
|
||||
spend_vv,
|
||||
).unwrap();
|
||||
|
||||
assert!(payment2
|
||||
.spend_verify(¶ms, &verification_key, &pay_info2)
|
||||
.unwrap());
|
||||
|
||||
let identify_result = identify(¶ms, &verification_key, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_payments_with_multiple_repeating_serial_numbers_but_different_pay_info() {
|
||||
let L = 32;
|
||||
let params = setup(L);
|
||||
let grp = params.grp();
|
||||
let user_keypair = generate_keypair_user(&grp);
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut public_keys: Vec<PublicKeyUser> = Default::default();
|
||||
for i in 0..50 {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
public_keys.push(pk_user.clone());
|
||||
}
|
||||
public_keys.push(user_keypair.public_key().clone());
|
||||
|
||||
let (req, req_info) = withdrawal_request(grp, &user_keypair.secret_key()).unwrap();
|
||||
let authorities_keypairs = ttp_keygen(&grp, 2, 3).unwrap();
|
||||
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key = aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
let mut wallet_blinded_signatures = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue_wallet(
|
||||
&grp,
|
||||
auth_keypair.secret_key(),
|
||||
user_keypair.public_key(),
|
||||
&req,
|
||||
);
|
||||
wallet_blinded_signatures.push(blind_signature.unwrap());
|
||||
}
|
||||
|
||||
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
|
||||
wallet_blinded_signatures.iter(),
|
||||
verification_keys_auth.iter()
|
||||
)
|
||||
.map(|(w, vk)| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
|
||||
.collect();
|
||||
|
||||
// Aggregate partial wallets
|
||||
let aggr_wallet = aggregate_wallets(
|
||||
&grp,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&unblinded_wallet_shares,
|
||||
&req_info,
|
||||
).unwrap();
|
||||
|
||||
// Let's try to spend some coins
|
||||
let pay_info1 = PayInfo { info: [6u8; 32] };
|
||||
let spend_vv = 10;
|
||||
|
||||
let (payment1, _) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info1,
|
||||
false,
|
||||
spend_vv,
|
||||
).unwrap();
|
||||
|
||||
assert!(payment1
|
||||
.spend_verify(¶ms, &verification_key, &pay_info1)
|
||||
.unwrap());
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = aggr_wallet.l.get();
|
||||
aggr_wallet.l.set(current_l - 10);
|
||||
|
||||
let pay_info2 = PayInfo { info: [7u8; 32] };
|
||||
let (payment2, _) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info2,
|
||||
false,
|
||||
spend_vv,
|
||||
).unwrap();
|
||||
|
||||
|
||||
let identify_result = identify(¶ms, &verification_key, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
use core::borrow::Borrow;
|
||||
use core::iter::Sum;
|
||||
use core::ops::{Add, Mul};
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use bls12_381::{G1Projective, G2Projective, Scalar};
|
||||
use group::Curve;
|
||||
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::scheme::aggregation::aggregate_verification_keys;
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
use crate::scheme::SignerIndex;
|
||||
use crate::utils::{
|
||||
try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
|
||||
try_deserialize_scalar_vec,
|
||||
};
|
||||
use crate::utils::Polynomial;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct SecretKeyAuth {
|
||||
pub(crate) x: Scalar,
|
||||
pub(crate) ys: Vec<Scalar>,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for SecretKeyAuth {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<SecretKeyAuth> {
|
||||
// There should be x and at least one y
|
||||
if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 {
|
||||
return Err(CompactEcashError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len() - 8,
|
||||
target: 32 * 2 + 8,
|
||||
modulus: 32,
|
||||
object: "secret key".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// this conversion will not fail as we are taking the same length of data
|
||||
let x_bytes: [u8; 32] = bytes[..32].try_into().unwrap();
|
||||
let ys_len = u64::from_le_bytes(bytes[32..40].try_into().unwrap());
|
||||
let actual_ys_len = (bytes.len() - 40) / 32;
|
||||
|
||||
if ys_len as usize != actual_ys_len {
|
||||
return Err(CompactEcashError::Deserialization(format!(
|
||||
"Tried to deserialize secret key with inconsistent ys len (expected {}, got {})",
|
||||
ys_len, actual_ys_len
|
||||
)));
|
||||
}
|
||||
|
||||
let x = try_deserialize_scalar(
|
||||
&x_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize secret key scalar".to_string(),
|
||||
),
|
||||
)?;
|
||||
let ys = try_deserialize_scalar_vec(
|
||||
ys_len,
|
||||
&bytes[40..],
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize secret key scalars".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
Ok(SecretKeyAuth { x, ys })
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretKeyAuth {
|
||||
pub fn verification_key(&self, params: &GroupParameters) -> VerificationKeyAuth {
|
||||
let g1 = params.gen1();
|
||||
let g2 = params.gen2();
|
||||
VerificationKeyAuth {
|
||||
alpha: g2 * self.x,
|
||||
beta_g1: self.ys.iter().map(|y| g1 * y).collect(),
|
||||
beta_g2: self.ys.iter().map(|y| g2 * y).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let ys_len = self.ys.len();
|
||||
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) as usize * 32);
|
||||
bytes.extend_from_slice(&self.x.to_bytes());
|
||||
bytes.extend_from_slice(&ys_len.to_le_bytes());
|
||||
for y in self.ys.iter() {
|
||||
bytes.extend_from_slice(&y.to_bytes())
|
||||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<SecretKeyAuth> {
|
||||
SecretKeyAuth::try_from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct VerificationKeyAuth {
|
||||
pub(crate) alpha: G2Projective,
|
||||
pub(crate) beta_g1: Vec<G1Projective>,
|
||||
pub(crate) beta_g2: Vec<G2Projective>,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for VerificationKeyAuth {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<VerificationKeyAuth> {
|
||||
// There should be at least alpha, one betaG1 and one betaG2 and their length
|
||||
if bytes.len() < 96 * 2 + 48 + 8 || (bytes.len() - 8 - 96) % (96 + 48) != 0 {
|
||||
return Err(CompactEcashError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len() - 8 - 96,
|
||||
target: 96 * 2 + 48 + 8,
|
||||
modulus: 96 + 48,
|
||||
object: "verification key".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// this conversion will not fail as we are taking the same length of data
|
||||
let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
|
||||
let betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
|
||||
|
||||
let actual_betas_len = (bytes.len() - 104) / (96 + 48);
|
||||
|
||||
if betas_len as usize != actual_betas_len {
|
||||
return Err(
|
||||
CompactEcashError::Deserialization(
|
||||
format!("Tried to deserialize verification key with inconsistent betas len (expected {}, got {})",
|
||||
betas_len, actual_betas_len
|
||||
)));
|
||||
}
|
||||
|
||||
let alpha = try_deserialize_g2_projective(
|
||||
&alpha_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize verification key G2 point (alpha)".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
let mut beta_g1 = Vec::with_capacity(betas_len as usize);
|
||||
let mut beta_g1_end: u64 = 0;
|
||||
for i in 0..betas_len {
|
||||
let start = (104 + i * 48) as usize;
|
||||
let end = (start + 48) as usize;
|
||||
let beta_i_bytes = bytes[start..end].try_into().unwrap();
|
||||
let beta_i = try_deserialize_g1_projective(
|
||||
&beta_i_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize verification key G1 point (beta)".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
beta_g1_end = end as u64;
|
||||
beta_g1.push(beta_i)
|
||||
}
|
||||
|
||||
let mut beta_g2 = Vec::with_capacity(betas_len as usize);
|
||||
for i in 0..betas_len {
|
||||
let start = (beta_g1_end + i * 96) as usize;
|
||||
let end = (start + 96) as usize;
|
||||
let beta_i_bytes = bytes[start..end].try_into().unwrap();
|
||||
let beta_i = try_deserialize_g2_projective(
|
||||
&beta_i_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize verification key G2 point (beta)".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
beta_g2.push(beta_i)
|
||||
}
|
||||
|
||||
Ok(VerificationKeyAuth {
|
||||
alpha,
|
||||
beta_g1,
|
||||
beta_g2,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth {
|
||||
type Output = VerificationKeyAuth;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: &'b VerificationKeyAuth) -> VerificationKeyAuth {
|
||||
// If you're trying to add two keys together that were created
|
||||
// for different number of attributes, just panic as it's a
|
||||
// nonsense operation.
|
||||
assert_eq!(
|
||||
self.beta_g1.len(),
|
||||
rhs.beta_g1.len(),
|
||||
"trying to add verification keys generated for different number of attributes [G1]"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
self.beta_g2.len(),
|
||||
rhs.beta_g2.len(),
|
||||
"trying to add verification keys generated for different number of attributes [G2]"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
self.beta_g1.len(),
|
||||
self.beta_g2.len(),
|
||||
"this key is incorrect - the number of elements G1 and G2 does not match"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
rhs.beta_g1.len(),
|
||||
rhs.beta_g2.len(),
|
||||
"they key you want to add is incorrect - the number of elements G1 and G2 does not match"
|
||||
);
|
||||
|
||||
VerificationKeyAuth {
|
||||
alpha: self.alpha + rhs.alpha,
|
||||
beta_g1: self
|
||||
.beta_g1
|
||||
.iter()
|
||||
.zip(rhs.beta_g1.iter())
|
||||
.map(|(self_beta_g1, rhs_beta_g1)| self_beta_g1 + rhs_beta_g1)
|
||||
.collect(),
|
||||
beta_g2: self
|
||||
.beta_g2
|
||||
.iter()
|
||||
.zip(rhs.beta_g2.iter())
|
||||
.map(|(self_beta_g2, rhs_beta_g2)| self_beta_g2 + rhs_beta_g2)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul<Scalar> for &'a VerificationKeyAuth {
|
||||
type Output = VerificationKeyAuth;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, rhs: Scalar) -> Self::Output {
|
||||
VerificationKeyAuth {
|
||||
alpha: self.alpha * rhs,
|
||||
beta_g1: self.beta_g1.iter().map(|b_i| b_i * rhs).collect(),
|
||||
beta_g2: self.beta_g2.iter().map(|b_i| b_i * rhs).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Sum<T> for VerificationKeyAuth
|
||||
where
|
||||
T: Borrow<VerificationKeyAuth>,
|
||||
{
|
||||
#[inline]
|
||||
fn sum<I>(iter: I) -> Self
|
||||
where
|
||||
I: Iterator<Item=T>,
|
||||
{
|
||||
let mut peekable = iter.peekable();
|
||||
let head_attributes = match peekable.peek() {
|
||||
Some(head) => head.borrow().beta_g2.len(),
|
||||
None => {
|
||||
// TODO: this is a really weird edge case. You're trying to sum an EMPTY iterator
|
||||
// of VerificationKey. So should it panic here or just return some nonsense value?
|
||||
return VerificationKeyAuth::identity(0);
|
||||
}
|
||||
};
|
||||
|
||||
peekable.fold(
|
||||
VerificationKeyAuth::identity(head_attributes),
|
||||
|acc, item| acc + item.borrow(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl VerificationKeyAuth {
|
||||
/// Create a (kinda) identity verification key using specified
|
||||
/// number of 'beta' elements
|
||||
pub(crate) fn identity(beta_size: usize) -> Self {
|
||||
VerificationKeyAuth {
|
||||
alpha: G2Projective::identity(),
|
||||
beta_g1: vec![G1Projective::identity(); beta_size],
|
||||
beta_g2: vec![G2Projective::identity(); beta_size],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn aggregate(sigs: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self> {
|
||||
aggregate_verification_keys(sigs, indices)
|
||||
}
|
||||
|
||||
pub fn alpha(&self) -> &G2Projective {
|
||||
&self.alpha
|
||||
}
|
||||
|
||||
pub fn beta_g1(&self) -> &Vec<G1Projective> {
|
||||
&self.beta_g1
|
||||
}
|
||||
|
||||
pub fn beta_g2(&self) -> &Vec<G2Projective> {
|
||||
&self.beta_g2
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let beta_g1_len = self.beta_g1.len();
|
||||
let beta_g2_len = self.beta_g2.len();
|
||||
let mut bytes = Vec::with_capacity(96 + 8 + beta_g1_len * 48 + beta_g2_len * 96);
|
||||
|
||||
bytes.extend_from_slice(&self.alpha.to_affine().to_compressed());
|
||||
|
||||
bytes.extend_from_slice(&beta_g1_len.to_le_bytes());
|
||||
|
||||
for beta_g1 in self.beta_g1.iter() {
|
||||
bytes.extend_from_slice(&beta_g1.to_affine().to_compressed())
|
||||
}
|
||||
|
||||
for beta_g2 in self.beta_g2.iter() {
|
||||
bytes.extend_from_slice(&beta_g2.to_affine().to_compressed())
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<VerificationKeyAuth> {
|
||||
VerificationKeyAuth::try_from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct SecretKeyUser {
|
||||
pub sk: Scalar,
|
||||
}
|
||||
|
||||
impl SecretKeyUser {
|
||||
pub fn public_key(&self, params: &GroupParameters) -> PublicKeyUser {
|
||||
PublicKeyUser {
|
||||
pk: params.gen1() * self.sk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub struct PublicKeyUser {
|
||||
pub(crate) pk: G1Projective,
|
||||
}
|
||||
|
||||
pub struct KeyPairAuth {
|
||||
secret_key: SecretKeyAuth,
|
||||
verification_key: VerificationKeyAuth,
|
||||
/// Optional index value specifying polynomial point used during threshold key generation.
|
||||
pub index: Option<SignerIndex>,
|
||||
}
|
||||
|
||||
impl KeyPairAuth {
|
||||
pub fn secret_key(&self) -> SecretKeyAuth {
|
||||
self.secret_key.clone()
|
||||
}
|
||||
|
||||
pub fn verification_key(&self) -> VerificationKeyAuth {
|
||||
self.verification_key.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KeyPairUser {
|
||||
secret_key: SecretKeyUser,
|
||||
public_key: PublicKeyUser,
|
||||
}
|
||||
|
||||
impl KeyPairUser {
|
||||
pub fn secret_key(&self) -> SecretKeyUser {
|
||||
self.secret_key.clone()
|
||||
}
|
||||
|
||||
pub fn public_key(&self) -> PublicKeyUser {
|
||||
self.public_key.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_keypair_user(params: &GroupParameters) -> KeyPairUser {
|
||||
let sk_user = SecretKeyUser {
|
||||
sk: params.random_scalar(),
|
||||
};
|
||||
let pk_user = PublicKeyUser {
|
||||
pk: params.gen1() * sk_user.sk,
|
||||
};
|
||||
|
||||
KeyPairUser {
|
||||
secret_key: sk_user,
|
||||
public_key: pk_user,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ttp_keygen(
|
||||
params: &GroupParameters,
|
||||
threshold: u64,
|
||||
num_authorities: u64,
|
||||
) -> Result<Vec<KeyPairAuth>> {
|
||||
if threshold == 0 {
|
||||
return Err(CompactEcashError::Setup(
|
||||
"Tried to generate threshold keys with a 0 threshold value".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if threshold > num_authorities {
|
||||
return Err(
|
||||
CompactEcashError::Setup(
|
||||
"Tried to generate threshold keys for threshold value being higher than number of the signing authorities".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let attributes = params.gammas().len();
|
||||
|
||||
// generate polynomials
|
||||
let v = Polynomial::new_random(params, threshold - 1);
|
||||
let ws = (0..attributes)
|
||||
.map(|_| Polynomial::new_random(params, threshold - 1))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// TODO: potentially if we had some known authority identifier we could use that instead
|
||||
// of the increasing (1,2,3,...) sequence
|
||||
let polynomial_indices = (1..=num_authorities).collect::<Vec<_>>();
|
||||
|
||||
// generate polynomial shares
|
||||
let x = polynomial_indices
|
||||
.iter()
|
||||
.map(|&id| v.evaluate(&Scalar::from(id)));
|
||||
let ys = polynomial_indices.iter().map(|&id| {
|
||||
ws.iter()
|
||||
.map(|w| w.evaluate(&Scalar::from(id)))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
// finally set the keys
|
||||
let secret_keys = x.zip(ys).map(|(x, ys)| SecretKeyAuth { x, ys });
|
||||
|
||||
let keypairs = secret_keys
|
||||
.zip(polynomial_indices.iter())
|
||||
.map(|(secret_key, index)| {
|
||||
let verification_key = secret_key.verification_key(params);
|
||||
KeyPairAuth {
|
||||
secret_key,
|
||||
verification_key,
|
||||
index: Some(*index),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(keypairs)
|
||||
}
|
||||
@@ -0,0 +1,593 @@
|
||||
use std::cell::Cell;
|
||||
|
||||
use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar};
|
||||
use group::Curve;
|
||||
|
||||
use crate::Attribute;
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
|
||||
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
|
||||
use crate::scheme::setup::{GroupParameters, Parameters};
|
||||
use crate::utils::{
|
||||
check_bilinear_pairing, hash_to_scalar, Signature, SignerIndex,
|
||||
try_deserialize_g1_projective, try_deserialize_scalar, try_deserialize_g2_projective,
|
||||
};
|
||||
|
||||
pub mod aggregation;
|
||||
pub mod identify;
|
||||
pub mod keygen;
|
||||
pub mod setup;
|
||||
pub mod withdrawal;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PartialWallet {
|
||||
sig: Signature,
|
||||
v: Scalar,
|
||||
idx: Option<SignerIndex>,
|
||||
}
|
||||
|
||||
impl PartialWallet {
|
||||
pub fn signature(&self) -> &Signature {
|
||||
&self.sig
|
||||
}
|
||||
pub fn v(&self) -> Scalar {
|
||||
self.v
|
||||
}
|
||||
pub fn index(&self) -> Option<SignerIndex> {
|
||||
self.idx
|
||||
}
|
||||
pub fn to_bytes(&self) -> [u8; 136]{
|
||||
let mut bytes = [0u8; 136];
|
||||
bytes[0..96].copy_from_slice(&self.sig.to_bytes());
|
||||
bytes[96..128].copy_from_slice(&self.v.to_bytes());
|
||||
// Check if idx is Some and copy its bytes if it exists
|
||||
if let Some(idx) = &self.idx {
|
||||
bytes[128..136].copy_from_slice(&idx.to_le_bytes());
|
||||
}
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for PartialWallet {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<PartialWallet> {
|
||||
if bytes.len() != 136 {
|
||||
return Err(CompactEcashError::Deserialization(format!(
|
||||
"PartialWallet should be exactly 136 bytes, got {}",
|
||||
bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let sig_bytes: &[u8; 96] = &bytes[..96].try_into().expect("Slice size != 96");
|
||||
let v_bytes: &[u8; 32] = &bytes[96..128].try_into().expect("Slice size != 32");
|
||||
let idx_bytes: &[u8; 8] = &bytes[128..136].try_into().expect("Slice size != 8");
|
||||
|
||||
let sig = Signature::try_from(sig_bytes.as_slice()).unwrap();
|
||||
let v = Scalar::from_bytes(&v_bytes).unwrap();
|
||||
let idx = None;
|
||||
if !idx_bytes.iter().all(|&x| x == 0){
|
||||
let idx = Some(u64::from_le_bytes(*idx_bytes));
|
||||
}
|
||||
|
||||
Ok(PartialWallet{
|
||||
sig,
|
||||
v,
|
||||
idx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Wallet {
|
||||
sig: Signature,
|
||||
v: Scalar,
|
||||
pub l: Cell<u64>,
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
pub fn signature(&self) -> &Signature {
|
||||
&self.sig
|
||||
}
|
||||
|
||||
pub fn v(&self) -> Scalar {
|
||||
self.v
|
||||
}
|
||||
|
||||
pub fn l(&self) -> u64 {
|
||||
self.l.get()
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; 136]{
|
||||
let mut bytes = [0u8; 136];
|
||||
bytes[0..96].copy_from_slice(&self.sig.to_bytes());
|
||||
bytes[96..128].copy_from_slice(&self.v.to_bytes());
|
||||
bytes[128..136].copy_from_slice(&self.l.get().to_le_bytes());
|
||||
bytes
|
||||
}
|
||||
fn up(&self) {
|
||||
self.l.set(self.l.get() + 1);
|
||||
}
|
||||
|
||||
|
||||
pub fn spend(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
sk_user: &SecretKeyUser,
|
||||
pay_info: &PayInfo,
|
||||
bench_flag: bool,
|
||||
spend_vv: u64,
|
||||
) -> Result<(Payment, &Self)> {
|
||||
if self.l() + spend_vv > params.L() {
|
||||
return Err(CompactEcashError::Spend(
|
||||
"The counter l is higher than max L".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let grparams = params.grp();
|
||||
// randomize signature in the wallet
|
||||
let (signature_prime, sign_blinding_factor) = self.signature().randomise(grparams);
|
||||
// construct kappa i.e., blinded attributes for show
|
||||
let attributes = vec![sk_user.sk, self.v()];
|
||||
// compute kappa
|
||||
let kappa = compute_kappa(
|
||||
&grparams,
|
||||
&verification_key,
|
||||
&attributes,
|
||||
sign_blinding_factor,
|
||||
);
|
||||
|
||||
// pick random openings o_c
|
||||
let o_c = grparams.random_scalar();
|
||||
|
||||
// compute commitments C
|
||||
let cc = grparams.gen1() * o_c + grparams.gamma1() * self.v();
|
||||
|
||||
|
||||
let mut aa: Vec<G1Projective> = Default::default();
|
||||
let mut ss: Vec<G1Projective> = Default::default();
|
||||
let mut tt: Vec<G1Projective> = Default::default();
|
||||
let mut rr: Vec<Scalar> = Default::default();
|
||||
let mut o_a: Vec<Scalar> = Default::default();
|
||||
let mut o_mu: Vec<Scalar> = Default::default();
|
||||
let mut mu: Vec<Scalar> = Default::default();
|
||||
let mut r_k_vec: Vec<Scalar> = Default::default();
|
||||
let mut kappa_k_vec: Vec<G2Projective> = Default::default();
|
||||
let mut sign_lk_prime_vec: Vec<Signature> = Default::default();
|
||||
let mut lk: Vec<Scalar> = Default::default();
|
||||
|
||||
for k in 0..spend_vv {
|
||||
lk.push(Scalar::from(self.l() + k));
|
||||
|
||||
// compute hashes R_k of the payment info
|
||||
let rr_k = hash_to_scalar(pay_info.info);
|
||||
rr.push(rr_k);
|
||||
|
||||
let o_a_k = grparams.random_scalar();
|
||||
o_a.push(o_a_k);
|
||||
let aa_k = grparams.gen1() * o_a_k + grparams.gamma1() * Scalar::from(self.l() + k);
|
||||
aa.push(aa_k);
|
||||
|
||||
// evaluate the pseudorandom functions
|
||||
let ss_k = pseudorandom_f_delta_v(&grparams, self.v(), self.l() + k);
|
||||
ss.push(ss_k);
|
||||
let tt_k =
|
||||
grparams.gen1() * sk_user.sk + pseudorandom_f_g_v(&grparams, self.v(), self.l() + k) * rr_k;
|
||||
tt.push(tt_k);
|
||||
|
||||
// compute values mu, o_mu, lambda, o_lambda
|
||||
let mu_k: Scalar = (self.v() + Scalar::from(self.l() + k) + Scalar::from(1))
|
||||
.invert()
|
||||
.unwrap();
|
||||
mu.push(mu_k);
|
||||
|
||||
let o_mu_k = ((o_a_k + o_c) * mu_k).neg();
|
||||
o_mu.push(o_mu_k);
|
||||
|
||||
// parse the signature associated with value l+k
|
||||
let sign_lk = params.get_sign_by_idx(self.l() + k)?;
|
||||
// randomise the signature associated with value l+k
|
||||
let (sign_lk_prime, r_k) = sign_lk.randomise(grparams);
|
||||
sign_lk_prime_vec.push(sign_lk_prime);
|
||||
r_k_vec.push(r_k);
|
||||
// compute kappa_k
|
||||
let kappa_k = grparams.gen2() * r_k
|
||||
+ params.pk_rp().alpha
|
||||
+ params.pk_rp().beta * Scalar::from(self.l() + k);
|
||||
kappa_k_vec.push(kappa_k);
|
||||
}
|
||||
|
||||
|
||||
// construct the zkp proof
|
||||
let spend_instance = SpendInstance {
|
||||
kappa,
|
||||
cc,
|
||||
aa: aa.clone(),
|
||||
ss: ss.clone(),
|
||||
tt: tt.clone(),
|
||||
kappa_k: kappa_k_vec.clone(),
|
||||
};
|
||||
let spend_witness = SpendWitness {
|
||||
attributes,
|
||||
r: sign_blinding_factor,
|
||||
o_c,
|
||||
lk,
|
||||
o_a,
|
||||
mu,
|
||||
o_mu,
|
||||
r_k: r_k_vec,
|
||||
};
|
||||
let zk_proof = SpendProof::construct(
|
||||
¶ms,
|
||||
&spend_instance,
|
||||
&spend_witness,
|
||||
&verification_key,
|
||||
&rr,
|
||||
);
|
||||
|
||||
// output pay and updated wallet
|
||||
let pay = Payment {
|
||||
kappa,
|
||||
sig: signature_prime,
|
||||
ss: ss.clone(),
|
||||
tt: tt.clone(),
|
||||
aa: aa.clone(),
|
||||
rr: rr.clone(),
|
||||
kappa_k: kappa_k_vec.clone(),
|
||||
sig_lk: sign_lk_prime_vec,
|
||||
cc,
|
||||
zk_proof,
|
||||
vv: spend_vv,
|
||||
};
|
||||
|
||||
// The number of samples collected by the benchmark process is way higher than the
|
||||
// MAX_WALLET_VALUE we ever consider. Thus, we would execute the spending too many times
|
||||
// and the initial condition at the top of this function will crush. Thus, we need a
|
||||
// benchmark flag to signal that we don't want to increase the spending couter but only
|
||||
// care about the function performance.
|
||||
if !bench_flag {
|
||||
let current_l = self.l();
|
||||
self.l.set(current_l + spend_vv);
|
||||
}
|
||||
|
||||
Ok((pay, self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for Wallet {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Wallet> {
|
||||
if bytes.len() != 136 {
|
||||
return Err(CompactEcashError::Deserialization(format!(
|
||||
"Wallet should be exactly 136 bytes, got {}",
|
||||
bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let sig_bytes: &[u8; 96] = &bytes[..96].try_into().expect("Slice size != 96");
|
||||
let v_bytes: &[u8; 32] = &bytes[96..128].try_into().expect("Slice size != 32");
|
||||
let l_bytes: &[u8; 8] = &bytes[128..136].try_into().expect("Slice size != 8");
|
||||
|
||||
let sig = Signature::try_from(sig_bytes.as_slice()).unwrap();
|
||||
let v = Scalar::from_bytes(&v_bytes).unwrap();
|
||||
let l = Cell::new(u64::from_le_bytes(*l_bytes));
|
||||
|
||||
Ok(Wallet{
|
||||
sig,
|
||||
v,
|
||||
l
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pseudorandom_f_delta_v(params: &GroupParameters, v: Scalar, l: u64) -> G1Projective {
|
||||
let pow = (v + Scalar::from(l) + Scalar::from(1)).invert().unwrap();
|
||||
params.delta() * pow
|
||||
}
|
||||
|
||||
pub fn pseudorandom_f_g_v(params: &GroupParameters, v: Scalar, l: u64) -> G1Projective {
|
||||
let pow = (v + Scalar::from(l) + Scalar::from(1)).invert().unwrap();
|
||||
params.gen1() * pow
|
||||
}
|
||||
|
||||
pub fn compute_kappa(
|
||||
params: &GroupParameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
attributes: &[Attribute],
|
||||
blinding_factor: Scalar,
|
||||
) -> G2Projective {
|
||||
params.gen2() * blinding_factor
|
||||
+ verification_key.alpha
|
||||
+ attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
|
||||
.sum::<G2Projective>()
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub struct PayInfo {
|
||||
pub info: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Payment {
|
||||
pub kappa: G2Projective,
|
||||
pub sig: Signature,
|
||||
pub ss: Vec<G1Projective>,
|
||||
pub tt: Vec<G1Projective>,
|
||||
pub aa: Vec<G1Projective>,
|
||||
pub rr: Vec<Scalar>,
|
||||
pub kappa_k: Vec<G2Projective>,
|
||||
pub sig_lk: Vec<Signature>,
|
||||
pub cc: G1Projective,
|
||||
pub zk_proof: SpendProof,
|
||||
pub vv: u64,
|
||||
}
|
||||
|
||||
impl Payment {
|
||||
pub fn spend_verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
pay_info: &PayInfo,
|
||||
) -> Result<bool> {
|
||||
if bool::from(self.sig.0.is_identity()) {
|
||||
return Err(CompactEcashError::Spend(
|
||||
"The element h of the signature equals the identity".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if !check_bilinear_pairing(
|
||||
&self.sig.0.to_affine(),
|
||||
&G2Prepared::from(self.kappa.to_affine()),
|
||||
&self.sig.1.to_affine(),
|
||||
params.grp().prepared_miller_g2(),
|
||||
) {
|
||||
return Err(CompactEcashError::Spend(
|
||||
"The bilinear check for kappa failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
for k in 0..self.vv {
|
||||
if bool::from(self.sig_lk[k as usize].0.is_identity()) {
|
||||
return Err(CompactEcashError::Spend(
|
||||
"The element h of the signature on l equals the identity".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if !check_bilinear_pairing(
|
||||
&self.sig_lk[k as usize].0.to_affine(),
|
||||
&G2Prepared::from(self.kappa_k[k as usize].to_affine()),
|
||||
&self.sig_lk[k as usize].1.to_affine(),
|
||||
params.grp().prepared_miller_g2(),
|
||||
) {
|
||||
return Err(CompactEcashError::Spend(
|
||||
"The bilinear check for kappa_l failed".to_string(),
|
||||
));
|
||||
}
|
||||
// verify integrity of R_k
|
||||
if !(self.rr[k as usize] == hash_to_scalar(pay_info.info)) {
|
||||
return Err(CompactEcashError::Spend(
|
||||
"Integrity of R_k does not hold".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: verify whether payinfo contains merchent's identifier
|
||||
|
||||
// verify the zk proof
|
||||
let instance = SpendInstance {
|
||||
kappa: self.kappa,
|
||||
aa: self.aa.clone(),
|
||||
cc: self.cc,
|
||||
ss: self.ss.clone(),
|
||||
tt: self.tt.clone(),
|
||||
kappa_k: self.kappa_k.clone(),
|
||||
};
|
||||
|
||||
if !self
|
||||
.zk_proof
|
||||
.verify(¶ms, &instance, &verification_key, &self.rr)
|
||||
{
|
||||
return Err(CompactEcashError::Spend(
|
||||
"ZkProof verification failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let kappa_bytes = self.kappa.to_affine().to_compressed();
|
||||
let sig_bytes = self.sig.to_bytes();
|
||||
let cc_bytes = self.cc.to_affine().to_compressed();
|
||||
let vv_bytes: [u8; 8] = self.vv.to_le_bytes();
|
||||
let ss_len = self.ss.len() as u64;
|
||||
let tt_len = self.tt.len() as u64;
|
||||
let aa_len = self.aa.len() as u64;
|
||||
let rr_len = self.rr.len() as u64;
|
||||
let kappa_k_len = self.kappa_k.len() as u64;
|
||||
let sig_lk_len = self.sig_lk.len() as u64;
|
||||
let zk_proof_bytes = self.zk_proof.to_bytes();
|
||||
let zk_proof_bytes_len = self.zk_proof.to_bytes().len() as u64;
|
||||
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(
|
||||
(96 + 96 + 48 + 8 + ss_len * 48 + 8 + tt_len * 48 + 8 + aa_len * 48 + 8 + rr_len * 32 + 8 + kappa_k_len * 96 + 8 + sig_lk_len * 96 + zk_proof_bytes_len) as usize);
|
||||
|
||||
|
||||
bytes.extend_from_slice(&kappa_bytes);
|
||||
bytes.extend_from_slice(&sig_bytes);
|
||||
bytes.extend_from_slice(&cc_bytes);
|
||||
bytes.extend_from_slice(&vv_bytes);
|
||||
|
||||
|
||||
let ss_len_bytes = ss_len.to_le_bytes();
|
||||
bytes.extend_from_slice(&ss_len_bytes);
|
||||
for s in &self.ss {
|
||||
bytes.extend_from_slice(&s.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
let tt_len_bytes = tt_len.to_le_bytes();
|
||||
bytes.extend_from_slice(&tt_len_bytes);
|
||||
for t in &self.tt {
|
||||
bytes.extend_from_slice(&t.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
let aa_len_bytes = aa_len.to_le_bytes();
|
||||
bytes.extend_from_slice(&aa_len_bytes);
|
||||
for a in &self.aa {
|
||||
bytes.extend_from_slice(&a.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
let rr_len_bytes = rr_len.to_le_bytes();
|
||||
bytes.extend_from_slice(&rr_len_bytes);
|
||||
for r in &self.rr {
|
||||
bytes.extend_from_slice(&r.to_bytes());
|
||||
}
|
||||
|
||||
let kappa_k_len_bytes = kappa_k_len.to_le_bytes();
|
||||
bytes.extend_from_slice(&kappa_k_len_bytes);
|
||||
for kk in &self.kappa_k {
|
||||
bytes.extend_from_slice(&kk.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
let sig_lk_len_bytes = sig_lk_len.to_le_bytes();
|
||||
bytes.extend_from_slice(&sig_lk_len_bytes);
|
||||
for sig in &self.sig_lk {
|
||||
bytes.extend_from_slice(&sig.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&zk_proof_bytes);
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for Payment {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Payment> {
|
||||
if bytes.len() < 656 {
|
||||
return Err(CompactEcashError::Deserialization(
|
||||
"Invalid byte array for Payment deserialization".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let kappa_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
|
||||
let sig_bytes: [u8; 96] = bytes[96..192].try_into().unwrap();
|
||||
let cc_bytes: [u8; 48] = bytes[192..240].try_into().unwrap();
|
||||
let vv_bytes: [u8; 8] = bytes[240..248].try_into().unwrap();
|
||||
let ss_len = u64::from_le_bytes(bytes[248..256].try_into().unwrap()) as usize;
|
||||
|
||||
// Convert the byte arrays back into their respective types
|
||||
let kappa = try_deserialize_g2_projective(
|
||||
&kappa_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize kappa".to_string()),
|
||||
)?;
|
||||
let sig = Signature::try_from(sig_bytes.as_slice())?;
|
||||
|
||||
let cc = try_deserialize_g1_projective(
|
||||
&cc_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize cc".to_string()),
|
||||
)?;
|
||||
let vv = u64::from_le_bytes(vv_bytes);
|
||||
|
||||
let mut idx = 256;
|
||||
let mut ss = Vec::with_capacity(ss_len);
|
||||
for _ in 0..ss_len {
|
||||
let ss_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
|
||||
let ss_elem = try_deserialize_g1_projective(
|
||||
&ss_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize ss element".to_string()),
|
||||
)?;
|
||||
ss.push(ss_elem);
|
||||
idx += 48;
|
||||
}
|
||||
|
||||
let tt_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize;
|
||||
idx += 8;
|
||||
let mut tt = Vec::with_capacity(tt_len);
|
||||
for _ in 0..tt_len {
|
||||
let tt_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
|
||||
let tt_elem = try_deserialize_g1_projective(
|
||||
&tt_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize tt element".to_string()),
|
||||
)?;
|
||||
tt.push(tt_elem);
|
||||
idx += 48;
|
||||
}
|
||||
|
||||
let aa_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize;
|
||||
idx += 8;
|
||||
let mut aa = Vec::with_capacity(aa_len);
|
||||
for _ in 0..aa_len {
|
||||
let aa_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
|
||||
let aa_elem = try_deserialize_g1_projective(
|
||||
&aa_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize aa element".to_string()),
|
||||
)?;
|
||||
aa.push(aa_elem);
|
||||
idx += 48;
|
||||
}
|
||||
|
||||
let rr_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize;
|
||||
idx += 8;
|
||||
let mut rr = Vec::with_capacity(rr_len);
|
||||
for _ in 0..rr_len {
|
||||
let rr_bytes: [u8; 32] = bytes[idx..idx + 32].try_into().unwrap();
|
||||
let rr_elem = try_deserialize_scalar(
|
||||
&rr_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize rr element".to_string()),
|
||||
)?;
|
||||
rr.push(rr_elem);
|
||||
idx += 32;
|
||||
}
|
||||
|
||||
let kappa_k_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize;
|
||||
idx += 8;
|
||||
let mut kappa_k = Vec::with_capacity(kappa_k_len);
|
||||
for _ in 0..kappa_k_len {
|
||||
let kappa_k_bytes: [u8; 96] = bytes[idx..idx + 96].try_into().unwrap();
|
||||
let kappa_k_elem = try_deserialize_g2_projective(
|
||||
&kappa_k_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize kappa_k element".to_string()),
|
||||
)?;
|
||||
kappa_k.push(kappa_k_elem);
|
||||
idx += 96;
|
||||
}
|
||||
|
||||
// sig_lk
|
||||
let sig_lk_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize;
|
||||
idx += 8;
|
||||
let mut sig_lk = Vec::with_capacity(sig_lk_len);
|
||||
for _ in 0..sig_lk_len {
|
||||
let sig_lk_bytes: [u8; 96] = bytes[idx..idx + 96].try_into().unwrap();
|
||||
let sig_lk_elem = Signature::try_from(sig_lk_bytes.as_slice())?;
|
||||
sig_lk.push(sig_lk_elem);
|
||||
idx += 96;
|
||||
}
|
||||
|
||||
// Deserialize the SpendProof struct
|
||||
let zk_proof_bytes = &bytes[idx..];
|
||||
let zk_proof = SpendProof::try_from(zk_proof_bytes)?;
|
||||
|
||||
// Construct the Payment struct from the deserialized data
|
||||
let payment = Payment {
|
||||
kappa,
|
||||
sig,
|
||||
ss,
|
||||
tt,
|
||||
aa,
|
||||
rr,
|
||||
kappa_k,
|
||||
sig_lk,
|
||||
cc,
|
||||
zk_proof,
|
||||
vv,
|
||||
};
|
||||
|
||||
Ok(payment)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar};
|
||||
use ff::Field;
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::utils::{hash_g1, Signature};
|
||||
|
||||
const ATTRIBUTES_LEN: usize = 3;
|
||||
|
||||
pub struct GroupParameters {
|
||||
/// Generator of the G1 group
|
||||
g1: G1Affine,
|
||||
/// Generator of the G2 group
|
||||
g2: G2Affine,
|
||||
/// Additional generators of the G1 group
|
||||
gammas: Vec<G1Projective>,
|
||||
// Additional generator of the G1 group
|
||||
delta: G1Projective,
|
||||
/// Precomputed G2 generator used for the miller loop
|
||||
_g2_prepared_miller: G2Prepared,
|
||||
}
|
||||
|
||||
impl GroupParameters {
|
||||
pub fn new() -> Result<GroupParameters> {
|
||||
let gammas = (1..=ATTRIBUTES_LEN)
|
||||
.map(|i| hash_g1(format!("gamma{}", i)))
|
||||
.collect();
|
||||
|
||||
let delta = hash_g1("delta");
|
||||
|
||||
Ok(GroupParameters {
|
||||
g1: G1Affine::generator(),
|
||||
g2: G2Affine::generator(),
|
||||
gammas,
|
||||
delta,
|
||||
_g2_prepared_miller: G2Prepared::from(G2Affine::generator()),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn gen1(&self) -> &G1Affine {
|
||||
&self.g1
|
||||
}
|
||||
|
||||
pub(crate) fn gen2(&self) -> &G2Affine {
|
||||
&self.g2
|
||||
}
|
||||
|
||||
pub(crate) fn gammas(&self) -> &Vec<G1Projective> {
|
||||
&self.gammas
|
||||
}
|
||||
|
||||
pub(crate) fn gamma1(&self) -> &G1Projective {
|
||||
&self.gammas[0]
|
||||
}
|
||||
|
||||
pub(crate) fn gamma2(&self) -> Option<&G1Projective> {
|
||||
self.gammas.get(2)
|
||||
}
|
||||
|
||||
pub(crate) fn delta(&self) -> &G1Projective { &self.delta }
|
||||
|
||||
pub fn random_scalar(&self) -> Scalar {
|
||||
// lazily-initialized thread-local random number generator, seeded by the system
|
||||
let mut rng = thread_rng();
|
||||
Scalar::random(&mut rng)
|
||||
}
|
||||
|
||||
pub fn n_random_scalars(&self, n: usize) -> Vec<Scalar> {
|
||||
(0..n).map(|_| self.random_scalar()).collect()
|
||||
}
|
||||
|
||||
pub(crate) fn prepared_miller_g2(&self) -> &G2Prepared {
|
||||
&self._g2_prepared_miller
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct SecretKeyRP {
|
||||
pub(crate) x: Scalar,
|
||||
pub(crate) y: Scalar,
|
||||
}
|
||||
|
||||
impl SecretKeyRP {
|
||||
pub fn public_key(&self, params: &GroupParameters) -> PublicKeyRP {
|
||||
PublicKeyRP {
|
||||
alpha: params.gen2() * self.x,
|
||||
beta: params.gen2() * self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct PublicKeyRP {
|
||||
pub(crate) alpha: G2Projective,
|
||||
pub(crate) beta: G2Projective,
|
||||
}
|
||||
|
||||
pub struct Parameters {
|
||||
/// group parameters
|
||||
grp: GroupParameters,
|
||||
/// Public Key for range proof verification
|
||||
pk_rp: PublicKeyRP,
|
||||
/// Max value of wallet
|
||||
L: u64,
|
||||
/// list of signatures for values l in [0, L]
|
||||
signs: HashMap<u64, Signature>,
|
||||
}
|
||||
|
||||
impl Parameters {
|
||||
pub fn grp(&self) -> &GroupParameters {
|
||||
&self.grp
|
||||
}
|
||||
pub fn pk_rp(&self) -> &PublicKeyRP {
|
||||
&self.pk_rp
|
||||
}
|
||||
pub fn L(&self) -> u64 {
|
||||
self.L
|
||||
}
|
||||
pub fn signs(&self) -> &HashMap<u64, Signature> {
|
||||
&self.signs
|
||||
}
|
||||
pub fn get_sign_by_idx(&self, idx: u64) -> Result<&Signature> {
|
||||
match self.signs.get(&idx) {
|
||||
Some(val) => return Ok(val),
|
||||
None => {
|
||||
return Err(CompactEcashError::RangeProofOutOfBound(
|
||||
"Cannot find the range proof signature for the given value. \
|
||||
Check if the requested value is within the bound 0..L"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup(L: u64) -> Parameters {
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let x = grp.random_scalar();
|
||||
let y = grp.random_scalar();
|
||||
let sk_rp = SecretKeyRP { x, y };
|
||||
let pk_rp = sk_rp.public_key(&grp);
|
||||
let mut signs = HashMap::new();
|
||||
for l in 0..L {
|
||||
let r = grp.random_scalar();
|
||||
let h = grp.gen1() * r;
|
||||
signs.insert(
|
||||
l,
|
||||
Signature {
|
||||
0: h,
|
||||
1: h * (x + y * Scalar::from(l)),
|
||||
},
|
||||
);
|
||||
}
|
||||
Parameters {
|
||||
grp,
|
||||
pk_rp,
|
||||
L,
|
||||
signs,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar};
|
||||
use group::{Curve, GroupEncoding};
|
||||
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::proofs::proof_withdrawal::{
|
||||
WithdrawalReqInstance, WithdrawalReqProof, WithdrawalReqWitness,
|
||||
};
|
||||
use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser, VerificationKeyAuth};
|
||||
use crate::scheme::PartialWallet;
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
use crate::utils::{check_bilinear_pairing, hash_g1, try_deserialize_g1_projective};
|
||||
use crate::utils::{BlindedSignature, Signature};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct WithdrawalRequest {
|
||||
com_hash: G1Projective,
|
||||
com: G1Projective,
|
||||
pc_coms: Vec<G1Projective>,
|
||||
zk_proof: WithdrawalReqProof,
|
||||
}
|
||||
|
||||
impl WithdrawalRequest {
|
||||
pub fn to_bytes(&self) -> Vec<u8>{
|
||||
let com_hash_bytes = self.com_hash.to_affine().to_compressed();
|
||||
let com_bytes = self.com.to_affine().to_compressed();
|
||||
let pr_coms_len = self.pc_coms.len() as u64;
|
||||
let zk_proof_bytes = self.zk_proof.to_bytes();
|
||||
|
||||
let mut bytes = Vec::with_capacity(48 + 48 + 8 + pr_coms_len as usize * 48 + zk_proof_bytes.len());
|
||||
bytes.extend_from_slice(&com_hash_bytes);
|
||||
bytes.extend_from_slice(&com_bytes);
|
||||
bytes.extend_from_slice(&pr_coms_len.to_le_bytes());
|
||||
for c in &self.pc_coms {
|
||||
bytes.extend_from_slice(&c.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&zk_proof_bytes);
|
||||
|
||||
bytes
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for WithdrawalRequest {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<WithdrawalRequest> {
|
||||
if bytes.len() < 48 + 48 + 8 + 48{
|
||||
return Err(CompactEcashError::DeserializationMinLength {
|
||||
min: 48 + 48 + 8 + 48,
|
||||
actual: bytes.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut j = 0;
|
||||
let commitment_hash_bytes_len = 48;
|
||||
let commitment_bytes_len = 48;
|
||||
|
||||
let com_hash_bytes = bytes[..j + commitment_hash_bytes_len].try_into().unwrap();
|
||||
let com_hash = try_deserialize_g1_projective(
|
||||
&com_hash_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize compressed commitment hash".to_string(),
|
||||
),
|
||||
)?;
|
||||
j += commitment_hash_bytes_len;
|
||||
|
||||
let com_bytes = bytes[j..j + commitment_bytes_len].try_into().unwrap();
|
||||
let com = try_deserialize_g1_projective(
|
||||
&com_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize compressed commitment".to_string(),
|
||||
),
|
||||
)?;
|
||||
j += commitment_bytes_len;
|
||||
|
||||
let pc_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
|
||||
j += 8;
|
||||
if bytes[j..].len() < pc_len as usize * 48 {
|
||||
return Err(CompactEcashError::DeserializationMinLength {
|
||||
min: pc_len as usize * 48,
|
||||
actual: bytes[56..].len(),
|
||||
});
|
||||
}
|
||||
let mut pc_coms = Vec::with_capacity(pc_len as usize);
|
||||
for i in 0..pc_len as usize {
|
||||
let start = j + i * 48;
|
||||
let end = start + 48;
|
||||
|
||||
let pc_com_bytes = bytes[start..end].try_into().unwrap();
|
||||
let pc_com = try_deserialize_g1_projective(
|
||||
&pc_com_bytes,
|
||||
CompactEcashError::Deserialization(
|
||||
"Failed to deserialize compressed Pedersen commitment".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
pc_coms.push(pc_com)
|
||||
}
|
||||
|
||||
let zk_proof = WithdrawalReqProof::try_from(&bytes[j + pc_len as usize * 48..])?;
|
||||
|
||||
Ok(WithdrawalRequest{
|
||||
com_hash,
|
||||
com,
|
||||
pc_coms,
|
||||
zk_proof,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RequestInfo {
|
||||
com_hash: G1Projective,
|
||||
com_opening: Scalar,
|
||||
pc_coms_openings: Vec<Scalar>,
|
||||
v: Scalar,
|
||||
}
|
||||
|
||||
impl RequestInfo {
|
||||
pub fn get_com(&self) -> G1Projective {
|
||||
self.com_hash
|
||||
}
|
||||
pub fn get_com_openings(&self) -> Scalar {
|
||||
self.com_opening
|
||||
}
|
||||
pub fn get_pc_coms_openings(&self) -> &Vec<Scalar> {
|
||||
&self.pc_coms_openings
|
||||
}
|
||||
pub fn get_v(&self) -> Scalar {
|
||||
self.v
|
||||
}
|
||||
}
|
||||
|
||||
pub fn withdrawal_request(
|
||||
params: &GroupParameters,
|
||||
sk_user: &SecretKeyUser,
|
||||
) -> Result<(WithdrawalRequest, RequestInfo)> {
|
||||
let v = params.random_scalar();
|
||||
|
||||
let attributes = vec![sk_user.sk, v];
|
||||
let gammas = params.gammas();
|
||||
let com_opening = params.random_scalar();
|
||||
let com = params.gen1() * com_opening
|
||||
+ attributes
|
||||
.iter()
|
||||
.zip(gammas)
|
||||
.map(|(&m, gamma)| gamma * m)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
// Value h in the paper
|
||||
let com_hash = hash_g1(com.to_bytes());
|
||||
|
||||
// For each private attribute we compute a pedersen commitment
|
||||
let pc_coms_openings = params.n_random_scalars(attributes.len());
|
||||
|
||||
// Compute Pedersen commitment for each attribute
|
||||
let pc_coms = pc_coms_openings
|
||||
.iter()
|
||||
.zip(attributes.iter())
|
||||
.map(|(o_j, m_j)| params.gen1() * o_j + com_hash * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// construct a zk proof of knowledge proving possession of m1, m2, m3, o, o1, o2, o3
|
||||
let instance = WithdrawalReqInstance {
|
||||
com,
|
||||
h: com_hash,
|
||||
pc_coms: pc_coms.clone(),
|
||||
pk_user: PublicKeyUser {
|
||||
pk: params.gen1() * sk_user.sk,
|
||||
},
|
||||
};
|
||||
|
||||
let witness = WithdrawalReqWitness {
|
||||
attributes,
|
||||
com_opening,
|
||||
pc_coms_openings: pc_coms_openings.clone(),
|
||||
};
|
||||
|
||||
let zk_proof = WithdrawalReqProof::construct(¶ms, &instance, &witness);
|
||||
|
||||
let req = WithdrawalRequest {
|
||||
com_hash,
|
||||
com,
|
||||
pc_coms: pc_coms.clone(),
|
||||
zk_proof,
|
||||
};
|
||||
|
||||
let req_info = RequestInfo {
|
||||
com_hash,
|
||||
com_opening,
|
||||
pc_coms_openings: pc_coms_openings.clone(),
|
||||
v,
|
||||
};
|
||||
|
||||
Ok((req, req_info))
|
||||
}
|
||||
|
||||
pub fn issue_wallet(
|
||||
params: &GroupParameters,
|
||||
sk_auth: SecretKeyAuth,
|
||||
pk_user: PublicKeyUser,
|
||||
withdrawal_req: &WithdrawalRequest,
|
||||
) -> Result<BlindedSignature> {
|
||||
let h = hash_g1(withdrawal_req.com.to_bytes());
|
||||
if !(h == withdrawal_req.com_hash) {
|
||||
return Err(CompactEcashError::WithdrawalRequestVerification(
|
||||
"Failed to verify the commitment hash".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// verify zk proof
|
||||
let instance = WithdrawalReqInstance {
|
||||
com: withdrawal_req.com,
|
||||
h: withdrawal_req.com_hash,
|
||||
pc_coms: withdrawal_req.pc_coms.clone(),
|
||||
pk_user,
|
||||
};
|
||||
if !withdrawal_req.zk_proof.verify(¶ms, &instance) {
|
||||
return Err(CompactEcashError::WithdrawalRequestVerification(
|
||||
"Failed to verify the proof of knowledge".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let sig = withdrawal_req
|
||||
.pc_coms
|
||||
.iter()
|
||||
.zip(sk_auth.ys.iter())
|
||||
.map(|(pc, yi)| pc * yi)
|
||||
.chain(std::iter::once(h * sk_auth.x))
|
||||
.sum();
|
||||
|
||||
Ok(BlindedSignature(h, sig))
|
||||
}
|
||||
|
||||
pub fn issue_verify(
|
||||
params: &GroupParameters,
|
||||
vk_auth: &VerificationKeyAuth,
|
||||
sk_user: &SecretKeyUser,
|
||||
blind_signature: &BlindedSignature,
|
||||
req_info: &RequestInfo,
|
||||
) -> Result<PartialWallet> {
|
||||
// Parse the blinded signature
|
||||
let h = blind_signature.0;
|
||||
let c = blind_signature.1;
|
||||
|
||||
// Verify the integrity of the response from the authority
|
||||
if !(req_info.com_hash == h) {
|
||||
return Err(CompactEcashError::IssuanceVfy(
|
||||
"Integrity verification failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Unblind the blinded signature on the partial wallet
|
||||
let blinding_removers = vk_auth
|
||||
.beta_g1
|
||||
.iter()
|
||||
.zip(req_info.pc_coms_openings.iter())
|
||||
.map(|(beta, opening)| beta * opening)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let unblinded_c = c - blinding_removers;
|
||||
|
||||
let attr = vec![sk_user.sk, req_info.v];
|
||||
|
||||
let signed_attributes = attr
|
||||
.iter()
|
||||
.zip(vk_auth.beta_g2.iter())
|
||||
.map(|(attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
// Verify the signature correctness on the wallet share
|
||||
if !check_bilinear_pairing(
|
||||
&h.to_affine(),
|
||||
&G2Prepared::from((vk_auth.alpha + signed_attributes).to_affine()),
|
||||
&unblinded_c.to_affine(),
|
||||
params.prepared_miller_g2(),
|
||||
) {
|
||||
return Err(CompactEcashError::IssuanceVfy(
|
||||
"Verification of wallet share failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(PartialWallet {
|
||||
sig: Signature(h, unblinded_c),
|
||||
v: req_info.v,
|
||||
idx: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
use itertools::izip;
|
||||
|
||||
use crate::error::CompactEcashError;
|
||||
use crate::scheme::aggregation::{
|
||||
aggregate_verification_keys, aggregate_wallets,
|
||||
};
|
||||
use crate::scheme::keygen::{
|
||||
generate_keypair_user, ttp_keygen, VerificationKeyAuth,
|
||||
};
|
||||
use crate::scheme::{Wallet, PartialWallet, Payment};
|
||||
use crate::scheme::PayInfo;
|
||||
use crate::scheme::setup::setup;
|
||||
use crate::scheme::withdrawal::{issue_verify, issue_wallet, withdrawal_request, WithdrawalRequest};
|
||||
|
||||
#[test]
|
||||
fn main() -> Result<(), CompactEcashError> {
|
||||
let L = 32;
|
||||
let params = setup(L);
|
||||
let grparams = params.grp();
|
||||
let user_keypair = generate_keypair_user(&grparams);
|
||||
|
||||
let (req, req_info) = withdrawal_request(grparams, &user_keypair.secret_key()).unwrap();
|
||||
let req_bytes = req.to_bytes();
|
||||
let req2 = WithdrawalRequest::try_from(req_bytes.as_slice()).unwrap();
|
||||
assert_eq!(req, req2);
|
||||
|
||||
let authorities_keypairs = ttp_keygen(&grparams, 2, 3).unwrap();
|
||||
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key = aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3]))?;
|
||||
|
||||
let mut wallet_blinded_signatures = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue_wallet(
|
||||
&grparams,
|
||||
auth_keypair.secret_key(),
|
||||
user_keypair.public_key(),
|
||||
&req,
|
||||
);
|
||||
wallet_blinded_signatures.push(blind_signature.unwrap());
|
||||
}
|
||||
|
||||
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
|
||||
wallet_blinded_signatures.iter(),
|
||||
verification_keys_auth.iter()
|
||||
)
|
||||
.map(|(w, vk)| issue_verify(&grparams, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
|
||||
.collect();
|
||||
|
||||
let partial_wallet = unblinded_wallet_shares.get(0).unwrap().clone();
|
||||
let partial_wallet_bytes = partial_wallet.to_bytes();
|
||||
let partial_wallet2 = PartialWallet::try_from(&partial_wallet_bytes[..]).unwrap();
|
||||
assert_eq!(partial_wallet, partial_wallet2);
|
||||
|
||||
// Aggregate partial wallets
|
||||
let aggr_wallet = aggregate_wallets(
|
||||
&grparams,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&unblinded_wallet_shares,
|
||||
&req_info,
|
||||
)?;
|
||||
|
||||
let wallet_bytes = aggr_wallet.to_bytes();
|
||||
let wallet = Wallet::try_from(&wallet_bytes[..]).unwrap();
|
||||
assert_eq!(aggr_wallet, wallet);
|
||||
|
||||
// Let's try to spend some coins
|
||||
let pay_info = PayInfo { info: [6u8; 32] };
|
||||
let spend_vv = 1;
|
||||
|
||||
let (payment, _) = aggr_wallet.spend(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&user_keypair.secret_key(),
|
||||
&pay_info,
|
||||
false,
|
||||
spend_vv,
|
||||
)?;
|
||||
|
||||
assert!(payment
|
||||
.spend_verify(¶ms, &verification_key, &pay_info)
|
||||
.unwrap());
|
||||
|
||||
let payment_bytes = payment.to_bytes();
|
||||
let payment2 = Payment::try_from(&payment_bytes[..]).unwrap();
|
||||
assert_eq!(payment, payment2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
mod e2e;
|
||||
@@ -0,0 +1,22 @@
|
||||
use crate::CompactEcashError;
|
||||
|
||||
pub trait Bytable
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn to_byte_vec(&self) -> Vec<u8>;
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CompactEcashError>;
|
||||
}
|
||||
|
||||
pub trait Base58
|
||||
where
|
||||
Self: Bytable,
|
||||
{
|
||||
fn try_from_bs58<S: AsRef<str>>(x: S) -> Result<Self, CompactEcashError> {
|
||||
Self::try_from_byte_slice(&bs58::decode(x.as_ref()).into_vec().unwrap())
|
||||
}
|
||||
fn to_bs58(&self) -> String {
|
||||
bs58::encode(self.to_byte_vec()).into_string()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use core::iter::Sum;
|
||||
use core::ops::Mul;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::ops::Neg;
|
||||
|
||||
use bls12_381::{
|
||||
G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, multi_miller_loop, Scalar,
|
||||
};
|
||||
use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField};
|
||||
use ff::Field;
|
||||
use group::{Curve, Group};
|
||||
|
||||
use crate::error::{CompactEcashError, Result};
|
||||
use crate::scheme::setup::GroupParameters;
|
||||
use crate::traits::Bytable;
|
||||
|
||||
pub struct Polynomial {
|
||||
coefficients: Vec<Scalar>,
|
||||
}
|
||||
|
||||
impl Polynomial {
|
||||
// for polynomial of degree n, we generate n+1 values
|
||||
// (for example for degree 1, like y = x + 2, we need [2,1])
|
||||
pub fn new_random(params: &GroupParameters, degree: u64) -> Self {
|
||||
Polynomial {
|
||||
coefficients: params.n_random_scalars((degree + 1) as usize),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluates the polynomial at point x.
|
||||
pub fn evaluate(&self, x: &Scalar) -> Scalar {
|
||||
if self.coefficients.is_empty() {
|
||||
Scalar::zero()
|
||||
// if x is zero then we can ignore most of the expensive computation and
|
||||
// just return the last term of the polynomial
|
||||
} else if x.is_zero().unwrap_u8() == 1 {
|
||||
// we checked that coefficients are not empty so unwrap here is fine
|
||||
*self.coefficients.first().unwrap()
|
||||
} else {
|
||||
self.coefficients
|
||||
.iter()
|
||||
.enumerate()
|
||||
// coefficient[n] * x ^ n
|
||||
.map(|(i, coefficient)| coefficient * x.pow(&[i as u64, 0, 0, 0]))
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn generate_lagrangian_coefficients_at_origin(points: &[u64]) -> Vec<Scalar> {
|
||||
let x = Scalar::zero();
|
||||
|
||||
points
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, point_i)| {
|
||||
let mut numerator = Scalar::one();
|
||||
let mut denominator = Scalar::one();
|
||||
let xi = Scalar::from(*point_i);
|
||||
|
||||
for (j, point_j) in points.iter().enumerate() {
|
||||
if j != i {
|
||||
let xj = Scalar::from(*point_j);
|
||||
|
||||
// numerator = (x - xs[0]) * ... * (x - xs[j]), j != i
|
||||
numerator *= x - xj;
|
||||
|
||||
// denominator = (xs[i] - x[0]) * ... * (xs[i] - x[j]), j != i
|
||||
denominator *= xi - xj;
|
||||
}
|
||||
}
|
||||
// numerator / denominator
|
||||
numerator * denominator.invert().unwrap()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Performs a Lagrange interpolation at the origin for a polynomial defined by `points` and `values`.
|
||||
/// It can be used for Scalars, G1 and G2 points.
|
||||
pub(crate) fn perform_lagrangian_interpolation_at_origin<T>(
|
||||
points: &[SignerIndex],
|
||||
values: &[T],
|
||||
) -> Result<T>
|
||||
where
|
||||
T: Sum,
|
||||
for<'a> &'a T: Mul<Scalar, Output=T>,
|
||||
{
|
||||
if points.is_empty() || values.is_empty() {
|
||||
return Err(CompactEcashError::Interpolation(
|
||||
"Tried to perform lagrangian interpolation for an empty set of coordinates".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if points.len() != values.len() {
|
||||
return Err(CompactEcashError::Interpolation(
|
||||
"Tried to perform lagrangian interpolation for an incomplete set of coordinates"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let coefficients = generate_lagrangian_coefficients_at_origin(points);
|
||||
|
||||
Ok(coefficients
|
||||
.into_iter()
|
||||
.zip(values.iter())
|
||||
.map(|(coeff, val)| val * coeff)
|
||||
.sum())
|
||||
}
|
||||
|
||||
// A temporary way of hashing particular message into G1.
|
||||
// Implementation idea was taken from `threshold_crypto`:
|
||||
// https://github.com/poanetwork/threshold_crypto/blob/7709462f2df487ada3bb3243060504b5881f2628/src/lib.rs#L691
|
||||
// Eventually it should get replaced by, most likely, the osswu map
|
||||
// method once ideally it's implemented inside the pairing crate.
|
||||
|
||||
// note: I have absolutely no idea what are the correct domains for those. I just used whatever
|
||||
// was given in the test vectors of `Hashing to Elliptic Curves draft-irtf-cfrg-hash-to-curve-11`
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.9.1
|
||||
const G1_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_";
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-K.1
|
||||
const SCALAR_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-expander";
|
||||
|
||||
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
|
||||
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
|
||||
}
|
||||
|
||||
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
|
||||
let mut output = vec![Scalar::zero()];
|
||||
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
|
||||
msg.as_ref(),
|
||||
SCALAR_HASH_DOMAIN,
|
||||
&mut output,
|
||||
);
|
||||
output[0]
|
||||
}
|
||||
|
||||
pub fn try_deserialize_scalar_vec(
|
||||
expected_len: u64,
|
||||
bytes: &[u8],
|
||||
err: CompactEcashError,
|
||||
) -> Result<Vec<Scalar>> {
|
||||
if bytes.len() != expected_len as usize * 32 {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let mut out = Vec::with_capacity(expected_len as usize);
|
||||
for i in 0..expected_len as usize {
|
||||
let s_bytes = bytes[i * 32..(i + 1) * 32].try_into().unwrap();
|
||||
let s = match Into::<Option<Scalar>>::into(Scalar::from_bytes(&s_bytes)) {
|
||||
None => return Err(err),
|
||||
Some(scalar) => scalar,
|
||||
};
|
||||
out.push(s)
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn try_deserialize_scalar(bytes: &[u8; 32], err: CompactEcashError) -> Result<Scalar> {
|
||||
Into::<Option<Scalar>>::into(Scalar::from_bytes(bytes)).ok_or(err)
|
||||
}
|
||||
|
||||
pub fn try_deserialize_g1_projective(
|
||||
bytes: &[u8; 48],
|
||||
err: CompactEcashError,
|
||||
) -> Result<G1Projective> {
|
||||
Into::<Option<G1Affine>>::into(G1Affine::from_compressed(bytes))
|
||||
.ok_or(err)
|
||||
.map(G1Projective::from)
|
||||
}
|
||||
|
||||
pub fn try_deserialize_g2_projective(
|
||||
bytes: &[u8; 96],
|
||||
err: CompactEcashError,
|
||||
) -> Result<G2Projective> {
|
||||
Into::<Option<G2Affine>>::into(G2Affine::from_compressed(bytes))
|
||||
.ok_or(err)
|
||||
.map(G2Projective::from)
|
||||
}
|
||||
|
||||
/// Checks whether e(P, Q) * e(-R, S) == id
|
||||
pub fn check_bilinear_pairing(p: &G1Affine, q: &G2Prepared, r: &G1Affine, s: &G2Prepared) -> bool {
|
||||
// checking e(P, Q) * e(-R, S) == id
|
||||
// is equivalent to checking e(P, Q) == e(R, S)
|
||||
// but requires only a single final exponentiation rather than two of them
|
||||
// and therefore, as seen via benchmarks.rs, is almost 50% faster
|
||||
// (1.47ms vs 2.45ms, tested on R9 5900X)
|
||||
|
||||
let multi_miller = multi_miller_loop(&[(p, q), (&r.neg(), s)]);
|
||||
multi_miller.final_exponentiation().is_identity().into()
|
||||
}
|
||||
|
||||
pub type SignerIndex = u64;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
// #[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective);
|
||||
|
||||
pub type PartialSignature = Signature;
|
||||
|
||||
impl TryFrom<&[u8]> for Signature {
|
||||
type Error = CompactEcashError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Signature> {
|
||||
if bytes.len() != 96 {
|
||||
return Err(CompactEcashError::Deserialization(format!(
|
||||
"Signature must be exactly 96 bytes, got {}",
|
||||
bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let sig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
|
||||
let sig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
|
||||
|
||||
let sig1 = try_deserialize_g1_projective(
|
||||
sig1_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize compressed sig1".to_string()),
|
||||
)?;
|
||||
|
||||
let sig2 = try_deserialize_g1_projective(
|
||||
sig2_bytes,
|
||||
CompactEcashError::Deserialization("Failed to deserialize compressed sig2".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(Signature(sig1, sig2))
|
||||
}
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
pub(crate) fn sig1(&self) -> &G1Projective {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub(crate) fn sig2(&self) -> &G1Projective {
|
||||
&self.1
|
||||
}
|
||||
|
||||
pub fn randomise(&self, params: &GroupParameters) -> (Signature, Scalar) {
|
||||
let r = params.random_scalar();
|
||||
let r_prime = params.random_scalar();
|
||||
let h_prime = self.0 * r_prime;
|
||||
let s_prime = (self.1 * r_prime) + (h_prime * r);
|
||||
(Signature(h_prime, s_prime), r)
|
||||
}
|
||||
|
||||
pub fn to_bytes(self) -> [u8; 96] {
|
||||
let mut bytes = [0u8; 96];
|
||||
bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed());
|
||||
bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed());
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Signature> {
|
||||
Signature::try_from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for Signature {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.to_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
Signature::from_bytes(slice)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct BlindedSignature(pub(crate) G1Projective, pub(crate) G1Projective);
|
||||
|
||||
pub struct SignatureShare {
|
||||
signature: Signature,
|
||||
index: SignerIndex,
|
||||
}
|
||||
|
||||
impl SignatureShare {
|
||||
pub fn new(signature: Signature, index: SignerIndex) -> Self {
|
||||
SignatureShare { signature, index }
|
||||
}
|
||||
|
||||
pub fn signature(&self) -> &Signature {
|
||||
&self.signature
|
||||
}
|
||||
|
||||
pub fn index(&self) -> SignerIndex {
|
||||
self.index
|
||||
}
|
||||
|
||||
// pub fn aggregate(shares: &[Self]) -> Result<Signature> {
|
||||
// aggregate_signature_shares(shares)
|
||||
// }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::RngCore;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn polynomial_evaluation() {
|
||||
// y = 42 (it should be 42 regardless of x)
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![Scalar::from(42)],
|
||||
};
|
||||
|
||||
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(1)));
|
||||
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(0)));
|
||||
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(10)));
|
||||
|
||||
// y = x + 10, at x = 2 (exp: 12)
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![Scalar::from(10), Scalar::from(1)],
|
||||
};
|
||||
|
||||
assert_eq!(Scalar::from(12), poly.evaluate(&Scalar::from(2)));
|
||||
|
||||
// y = x^4 - 5x^2 + 2x - 3, at x = 3 (exp: 39)
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![
|
||||
(-Scalar::from(3)),
|
||||
Scalar::from(2),
|
||||
(-Scalar::from(5)),
|
||||
Scalar::zero(),
|
||||
Scalar::from(1),
|
||||
],
|
||||
};
|
||||
|
||||
assert_eq!(Scalar::from(39), poly.evaluate(&Scalar::from(3)));
|
||||
|
||||
// empty polynomial
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![],
|
||||
};
|
||||
|
||||
// should always be 0
|
||||
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(1)));
|
||||
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(0)));
|
||||
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(10)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn performing_lagrangian_scalar_interpolation_at_origin() {
|
||||
// x^2 + 3
|
||||
// x, f(x):
|
||||
// 1, 4,
|
||||
// 2, 7,
|
||||
// 3, 12,
|
||||
let points = vec![1, 2, 3];
|
||||
let values = vec![Scalar::from(4), Scalar::from(7), Scalar::from(12)];
|
||||
|
||||
assert_eq!(
|
||||
Scalar::from(3),
|
||||
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
|
||||
);
|
||||
|
||||
// x^3 + 3x^2 - 5x + 11
|
||||
// x, f(x):
|
||||
// 1, 10
|
||||
// 2, 21
|
||||
// 3, 50
|
||||
// 4, 103
|
||||
let points = vec![1, 2, 3, 4];
|
||||
let values = vec![
|
||||
Scalar::from(10),
|
||||
Scalar::from(21),
|
||||
Scalar::from(50),
|
||||
Scalar::from(103),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
Scalar::from(11),
|
||||
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
|
||||
);
|
||||
|
||||
// more points than it is required
|
||||
// x^2 + x + 10
|
||||
// x, f(x)
|
||||
// 1, 12
|
||||
// 2, 16
|
||||
// 3, 22
|
||||
// 4, 30
|
||||
// 5, 40
|
||||
let points = vec![1, 2, 3, 4, 5];
|
||||
let values = vec![
|
||||
Scalar::from(12),
|
||||
Scalar::from(16),
|
||||
Scalar::from(22),
|
||||
Scalar::from(30),
|
||||
Scalar::from(40),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
Scalar::from(10),
|
||||
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_g1_sanity_check() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut msg1 = [0u8; 1024];
|
||||
rng.fill_bytes(&mut msg1);
|
||||
let mut msg2 = [0u8; 1024];
|
||||
rng.fill_bytes(&mut msg2);
|
||||
|
||||
assert_eq!(hash_g1(msg1), hash_g1(msg1));
|
||||
assert_eq!(hash_g1(msg2), hash_g1(msg2));
|
||||
assert_ne!(hash_g1(msg1), hash_g1(msg2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_scalar_sanity_check() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut msg1 = [0u8; 1024];
|
||||
rng.fill_bytes(&mut msg1);
|
||||
let mut msg2 = [0u8; 1024];
|
||||
rng.fill_bytes(&mut msg2);
|
||||
|
||||
assert_eq!(hash_to_scalar(msg1), hash_to_scalar(msg1));
|
||||
assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2));
|
||||
assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "nym_offline_divisible_ecash"
|
||||
version = "0.1.0"
|
||||
authors = ["Ania Piotrowska <ania@nymtech.net>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
#bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch = "gt-serialisation", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
|
||||
bls12_381 = { path = "/Users/ania/Documents/Git/andrew_bls12_381", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
|
||||
itertools = "0.10"
|
||||
digest = "0.9"
|
||||
rand = "0.8"
|
||||
thiserror = "1.0"
|
||||
sha2 = "0.9"
|
||||
bs58 = "0.4.0"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
|
||||
[dependencies.ff]
|
||||
version = "0.11"
|
||||
default-features = false
|
||||
|
||||
[dependencies.group]
|
||||
version = "0.11"
|
||||
default-features = false
|
||||
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
@@ -0,0 +1,329 @@
|
||||
use std::collections::HashSet;
|
||||
use std::ops::Neg;
|
||||
use std::time::Duration;
|
||||
|
||||
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, multi_miller_loop, Scalar};
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use ff::Field;
|
||||
use group::{Curve, Group};
|
||||
use itertools::izip;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
|
||||
use nym_offline_divisible_ecash::{aggregate_verification_keys, aggregate_wallets,
|
||||
issue, issue_verify, PartialWallet,
|
||||
PayInfo, PublicKeyUser, SecretKeyUser, ttp_keygen_authorities, ttp_keygen_users, VerificationKeyAuth, withdrawal_request};
|
||||
use nym_offline_divisible_ecash::identification::{identify, IdentifyResult};
|
||||
use nym_offline_divisible_ecash::setup::{GroupParameters, Parameters};
|
||||
|
||||
#[allow(unused)]
|
||||
fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
|
||||
let gt1 = bls12_381::pairing(g11, g21);
|
||||
let gt2 = bls12_381::pairing(g12, g22);
|
||||
assert_eq!(gt1, gt2)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn single_pairing(g11: &G1Affine, g21: &G2Affine) {
|
||||
let gt1 = bls12_381::pairing(g11, g21);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn exponent_in_g1(g1: G1Projective, r: Scalar) {
|
||||
let g11 = (g1 * r);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn exponent_in_g2(g2: G2Projective, r: Scalar) {
|
||||
let g22 = (g2 * r);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn exponent_in_gt(gt: Gt, r: Scalar) {
|
||||
let gtt = (gt * r);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
|
||||
let miller_loop_result = multi_miller_loop(&[
|
||||
(g11, &G2Prepared::from(*g21)),
|
||||
(&g12.neg(), &G2Prepared::from(*g22)),
|
||||
]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_with_prepared(
|
||||
g11: &G1Affine,
|
||||
g21: &G2Prepared,
|
||||
g12: &G1Affine,
|
||||
g22: &G2Prepared,
|
||||
) {
|
||||
let miller_loop_result = multi_miller_loop(&[(g11, g21), (&g12.neg(), g22)]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
// the case of being able to prepare G2 generator
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_with_semi_prepared(
|
||||
g11: &G1Affine,
|
||||
g21: &G2Affine,
|
||||
g12: &G1Affine,
|
||||
g22: &G2Prepared,
|
||||
) {
|
||||
let miller_loop_result =
|
||||
multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), g22)]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn bench_pairings(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("benchmark-pairings");
|
||||
group.measurement_time(Duration::from_secs(200));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let g1 = G1Affine::generator();
|
||||
let g2 = G2Affine::generator();
|
||||
let r = Scalar::random(&mut rng);
|
||||
let s = Scalar::random(&mut rng);
|
||||
|
||||
let g11 = (g1 * r).to_affine();
|
||||
let g21 = (g2 * s).to_affine();
|
||||
let g21_prep = G2Prepared::from(g21);
|
||||
|
||||
let g12 = (g1 * s).to_affine();
|
||||
let g22 = (g2 * r).to_affine();
|
||||
let g22_prep = G2Prepared::from(g22);
|
||||
|
||||
let gt = bls12_381::pairing(&g11, &g21);
|
||||
let gen1 = G1Projective::generator();
|
||||
let gen2 = G2Projective::generator();
|
||||
|
||||
group.bench_function("exponent operation in G1", |b| {
|
||||
b.iter(|| exponent_in_g1(gen1, r))
|
||||
});
|
||||
|
||||
group.bench_function("exponent operation in G2", |b| {
|
||||
b.iter(|| exponent_in_g2(gen2, r))
|
||||
});
|
||||
|
||||
group.bench_function("exponent operation in Gt", |b| {
|
||||
b.iter(|| exponent_in_gt(gt, r))
|
||||
});
|
||||
|
||||
group.bench_function("single pairing", |b| {
|
||||
b.iter(|| single_pairing(&g11, &g21))
|
||||
});
|
||||
|
||||
group.bench_function("double pairing", |b| {
|
||||
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
|
||||
});
|
||||
|
||||
group.bench_function("multi miller in affine", |b| {
|
||||
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
|
||||
});
|
||||
|
||||
group.bench_function("multi miller with prepared g2", |b| {
|
||||
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
|
||||
});
|
||||
|
||||
group.bench_function("multi miller with semi-prepared g2", |b| {
|
||||
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
|
||||
});
|
||||
}
|
||||
|
||||
struct BenchCase {
|
||||
num_authorities: u64,
|
||||
threshold_p: f32,
|
||||
L: u64,
|
||||
spend_vv: u64,
|
||||
case_nr_pub_keys: u64,
|
||||
}
|
||||
|
||||
fn bench_divisible_ecash(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("benchmark-divisible-ecash");
|
||||
group.sample_size(300);
|
||||
group.measurement_time(Duration::from_secs(1500));
|
||||
|
||||
let case = BenchCase {
|
||||
num_authorities: 100,
|
||||
threshold_p: 0.7,
|
||||
L: 100,
|
||||
spend_vv: 10,
|
||||
case_nr_pub_keys: 99,
|
||||
};
|
||||
|
||||
// SETUP PHASE
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
|
||||
// KEY GENERATION FOR THE AUTHORITIES
|
||||
let threshold = (case.threshold_p * case.num_authorities as f32).round() as u64;
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, threshold, case.num_authorities).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let indices: Vec<u64> = (1..case.num_authorities + 1).collect();
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
|
||||
|
||||
// KEY GENERATION FOR THE USER
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = SecretKeyUser::public_key(&sk_user, &grp);
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut pk_all_users = HashSet::new();
|
||||
for i in 0..50 {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
pk_all_users.insert(pk_user);
|
||||
}
|
||||
pk_all_users.insert(pk_user.clone());
|
||||
|
||||
// WITHDRAWAL REQUEST
|
||||
let (withdrawal_req, req_info) = withdrawal_request(¶ms, &sk_user).unwrap();
|
||||
|
||||
// CLIENT BENCHMARK: prepare a single withdrawal request
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Client] withdrawal_request_{}_authorities_{}_L_{}_threshold",
|
||||
case.num_authorities, case.L, case.threshold_p,
|
||||
),
|
||||
|b| b.iter(|| withdrawal_request(¶ms, &sk_user).unwrap()),
|
||||
);
|
||||
|
||||
// ISSUE PARTIAL WALLETS
|
||||
// first one meaningful one just for benchmark
|
||||
let mut rng = rand::thread_rng();
|
||||
let keypair = authorities_keypairs.choose(&mut rng).unwrap();
|
||||
group.bench_function(
|
||||
&format!("[Issuing Authority] issue_partial_wallet_with_L_{}", case.L, ),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
issue(
|
||||
¶ms,
|
||||
&withdrawal_req,
|
||||
pk_user.clone(),
|
||||
&keypair.secret_key(),
|
||||
).unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
let mut wallet_blinded_signatures = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req,
|
||||
pk_user.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
).unwrap();
|
||||
wallet_blinded_signatures.push(blind_signature);
|
||||
}
|
||||
|
||||
// CLIENT BENCHMARK: verify the issued partial wallet
|
||||
let w = wallet_blinded_signatures.get(0).clone().unwrap();
|
||||
let vk = verification_keys_auth.get(0).clone().unwrap();
|
||||
group.bench_function(
|
||||
&format!("[Client] issue_verify_a_partial_wallet_with_L_{}", case.L, ),
|
||||
|b| b.iter(|| issue_verify(&grp, vk, &sk_user, w, &req_info).unwrap()),
|
||||
);
|
||||
|
||||
let partial_wallets: Vec<PartialWallet> = izip!(
|
||||
wallet_blinded_signatures.iter(),
|
||||
verification_keys_auth.iter()
|
||||
)
|
||||
.map(|(w, vk)| issue_verify(&grp, &vk, &sk_user, &w, &req_info).unwrap())
|
||||
.collect();
|
||||
|
||||
|
||||
// AGGREGATE WALLET
|
||||
let mut wallet = aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets).unwrap();
|
||||
|
||||
// CLIENT BENCHMARK: aggregating all partial wallets
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Client] aggregate_wallets_with_L_{}_threshold_{}",
|
||||
case.L, case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
let pay_info = PayInfo { info: [67u8; 32] };
|
||||
let (payment, wallet) = wallet.spend(¶ms, &verification_key, &sk_user, &pay_info, case.spend_vv, false).unwrap();
|
||||
|
||||
// CLIENT BENCHMARK: spend a single coin from the wallet
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Client] spend_a_single_coin_L_{}_threshold_{}",
|
||||
case.L, case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
wallet.spend(¶ms, &verification_key, &sk_user, &pay_info, case.spend_vv, true)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// MERCHANT BENCHMARK: verify whether the submitted payment is legit
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Merchant] spend_verify_of_a_single_payment_L_{}_threshold_{}",
|
||||
case.L, case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
payment.spend_verify(¶ms, &verification_key, &pay_info)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// BENCHMARK IDENTIFICATION
|
||||
// Let's generate a double spending payment
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = wallet.l();
|
||||
wallet.l.set(current_l - 1);
|
||||
|
||||
let pay_info2 = PayInfo { info: [52u8; 32] };
|
||||
let (payment2, wallet) = wallet.spend(¶ms, &verification_key, &sk_user, &pay_info2, 10, false).unwrap();
|
||||
|
||||
// MERCHANT BENCHMARK: identify double spending
|
||||
group.bench_function(
|
||||
&format!(
|
||||
"[Merchant] identify_L_{}_threshold_{}_spend_vv_{}_pks_{}",
|
||||
case.L, case.threshold_p, case.spend_vv, pk_all_users.len()
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
identify(¶ms, &verification_key, &pk_all_users, payment.clone(), payment2.clone(), pay_info, pay_info2).unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
let identify_result = identify(¶ms, &verification_key, &pk_all_users, payment.clone(), payment2.clone(), pay_info, pay_info2).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(pk_user));
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_divisible_ecash);
|
||||
criterion_main!(benches);
|
||||
@@ -0,0 +1,2 @@
|
||||
/// Max value of wallet
|
||||
pub(crate) const L: u64 = 100;
|
||||
@@ -0,0 +1,39 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, DivisibleEcashError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DivisibleEcashError {
|
||||
#[error("Setup error: {0}")]
|
||||
Setup(String),
|
||||
|
||||
#[error("Aggregation error: {0}")]
|
||||
Aggregation(String),
|
||||
|
||||
#[error("Withdrawal Request Verification related error: {0}")]
|
||||
WithdrawalRequestVerification(String),
|
||||
|
||||
#[error("Deserialization error: {0}")]
|
||||
Deserialization(String),
|
||||
|
||||
#[error("Interpolation error: {0}")]
|
||||
Interpolation(String),
|
||||
|
||||
#[error("Issuance Verification related error: {0}")]
|
||||
IssuanceVfy(String),
|
||||
|
||||
#[error("Spend Verification related error: {0}")]
|
||||
Spend(String),
|
||||
|
||||
#[error("Identify Verification related error: {0}")]
|
||||
Identify(String),
|
||||
|
||||
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {} or {modulus_target} % {modulus} == 0")]
|
||||
DeserializationInvalidLength {
|
||||
actual: usize,
|
||||
target: usize,
|
||||
modulus_target: usize,
|
||||
modulus: usize,
|
||||
object: String,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use bls12_381::{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(¶ms, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
let r = grp.random_scalar();
|
||||
let kappa = grp.gen2() * r
|
||||
+ verification_key.alpha
|
||||
+ attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
let r1 = grp.random_scalar();
|
||||
let r2 = grp.random_scalar();
|
||||
let phi = Phi(grp.gen1() * r1, params_u.get_ith_sigma(l as usize) * v + params_u.get_ith_eta(vv as usize) * r1);
|
||||
|
||||
let pay_info = PayInfo { info: [78u8; 32] };
|
||||
let rr = hash_to_scalar(pay_info.info);
|
||||
let varphi = VarPhi(grp.gen1() * r2, (grp.gen1() * rr) * sk + params_u.get_ith_theta(l as usize) * v + params_u.get_ith_eta(vv as usize) * r2);
|
||||
|
||||
// random value used to compute blinded bases
|
||||
let r_varsig1 = grp.random_scalar();
|
||||
let r_theta1 = grp.random_scalar();
|
||||
let r_varsig2 = grp.random_scalar();
|
||||
let r_theta2 = grp.random_scalar();
|
||||
let r_rr = grp.random_scalar();
|
||||
let r_ss = grp.random_scalar();
|
||||
let r_tt = grp.random_scalar();
|
||||
|
||||
|
||||
// compute blinded bases
|
||||
let psi_g1 = params_u.get_psi_g1();
|
||||
let psi_g2 = params_u.get_psi_g2();
|
||||
let varsig_prime1 = params_u.get_ith_sigma(l as usize) + (psi_g1 * r_varsig1);
|
||||
let theta_prime1 = params_u.get_ith_theta(l as usize) + (psi_g1 * r_theta1);
|
||||
let varsig_prime2 = params_u.get_ith_sigma(l as usize + vv as usize - 1) + (psi_g1 * r_varsig2);
|
||||
let theta_prime2 = params_u.get_ith_theta(l as usize + vv as usize - 1) + (psi_g1 * r_theta2);
|
||||
let rr_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).rr + (psi_g1 * r_rr);
|
||||
let ss_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).ss + (psi_g1 * r_ss);
|
||||
let tt_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).tt + (psi_g2 * r_tt);
|
||||
|
||||
|
||||
let rho1 = v.neg() * r_varsig1;
|
||||
let rho2 = v.neg() * r_theta1;
|
||||
let rho3 = r_rr * r_tt;
|
||||
|
||||
|
||||
let pg_varsigpr1_delta = pairing(&varsig_prime1.to_affine(), ¶ms_a.get_ith_delta((vv - 1) as usize).to_affine());
|
||||
let pg_psi0_delta = pairing(&psi_g1.to_affine(), ¶ms_a.get_ith_delta((vv - 1) as usize).to_affine());
|
||||
let pg_varsigpr2_gen2 = pairing(&varsig_prime2.to_affine(), grp.gen2());
|
||||
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
|
||||
let pg_thetapr1_delta = pairing(&theta_prime1.to_affine(), ¶ms_a.get_ith_delta((vv - 1) as usize).to_affine());
|
||||
let pg_thetapr2_gen2 = pairing(&theta_prime2.to_affine(), grp.gen2());
|
||||
let yy = params_u.get_sps_pk().get_yy();
|
||||
let pg_rrprime_yy = pairing(&rr_prime.to_affine(), &yy.to_affine());
|
||||
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
|
||||
let pg_ssprime_gen2 = pairing(&ss_prime.to_affine(), grp.gen2());
|
||||
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
|
||||
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
|
||||
let pg_varsigpr2_ww1 = pairing(&varsig_prime2.to_affine(), &ww1.to_affine());
|
||||
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
|
||||
let pg_thetapr2_ww2 = pairing(&theta_prime2.to_affine(), &ww2.to_affine());
|
||||
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
|
||||
let pg_gen1_zz = pairing(grp.gen1(), ¶ms_u.get_sps_pk().get_zz().to_affine());
|
||||
let pg_rr_tt = pairing(&rr_prime.to_affine(), &tt_prime.to_affine());
|
||||
let pg_rr_psi1 = pairing(&rr_prime.to_affine(), &psi_g2.to_affine());
|
||||
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &tt_prime.to_affine());
|
||||
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
|
||||
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
|
||||
|
||||
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
|
||||
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
|
||||
let pg_eq3 = pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 - pg_gen1_zz;
|
||||
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
|
||||
|
||||
|
||||
let instance = SpendInstance {
|
||||
kappa,
|
||||
phi,
|
||||
varphi,
|
||||
rr,
|
||||
rr_prime,
|
||||
ss_prime: ss_prime,
|
||||
tt_prime: tt_prime,
|
||||
varsig_prime1,
|
||||
theta_prime1,
|
||||
pg_eq1,
|
||||
pg_eq2,
|
||||
pg_eq3,
|
||||
pg_eq4,
|
||||
psi_g1: *psi_g1,
|
||||
psi_g2: *psi_g2,
|
||||
pg_psi0_delta,
|
||||
pg_psi0_gen2,
|
||||
pg_psi0_yy,
|
||||
pg_psi0_ww1,
|
||||
pg_psi0_ww2,
|
||||
pg_rr_psi1,
|
||||
pg_psi0_tt,
|
||||
pg_psi0_psi1,
|
||||
};
|
||||
|
||||
let witness = SpendWitness {
|
||||
sk_u: SecretKeyUser { sk },
|
||||
v,
|
||||
r,
|
||||
r1,
|
||||
r2,
|
||||
r_varsig1,
|
||||
r_theta1,
|
||||
r_varsig2,
|
||||
r_theta2,
|
||||
r_rr,
|
||||
r_ss,
|
||||
r_tt,
|
||||
rho1,
|
||||
rho2,
|
||||
rho3,
|
||||
};
|
||||
|
||||
// compute the zk proof
|
||||
let zk_proof = SpendProof::construct(¶ms, &instance, &witness, &verification_key, vv);
|
||||
assert!(zk_proof.verify(¶ms, &instance, &verification_key, vv));
|
||||
|
||||
// do a to and from bytes check
|
||||
let zk_proof_bytes = zk_proof.to_bytes();
|
||||
let zk_proof2 = SpendProof::try_from(&zk_proof_bytes[..]).unwrap();
|
||||
assert_eq!(zk_proof, zk_proof2);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,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(¶ms, &instance, &witness);
|
||||
assert!(zk_proof.verify(¶ms, &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(), ¶ms_a.get_ith_delta(k as usize).to_affine())
|
||||
+ pairing(&payment1.phi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment1.vv as usize, k as usize).to_affine());
|
||||
serial_numbers.insert(sn, k);
|
||||
}
|
||||
|
||||
// compute the serial numbers fo k2 in [0, V2-1]
|
||||
let mut k1 = 0;
|
||||
let mut k2 = 0;
|
||||
let mut duplicate_serial_numbers: Vec<(Gt, u64, u64)> = Default::default();
|
||||
for j in 0..payment2.vv {
|
||||
let sn = pairing(&payment2.phi.1.to_affine(), ¶ms_a.get_ith_delta(j as usize).to_affine())
|
||||
+ pairing(&payment2.phi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment2.vv as usize, j as usize).to_affine());
|
||||
if !serial_numbers.contains_key(&sn) {
|
||||
serial_numbers.insert(sn, j);
|
||||
} else {
|
||||
k1 = *serial_numbers.get(&sn).unwrap() as u64;
|
||||
k2 = j.clone();
|
||||
break;
|
||||
}
|
||||
return Ok(IdentifyResult::NotADuplicatePayment);
|
||||
}
|
||||
|
||||
if pay_info1 == pay_info2 {
|
||||
Ok(IdentifyResult::DuplicatePayInfo(pay_info1))
|
||||
} else {
|
||||
let delta_k1 = params_a.get_ith_delta(k1 as usize);
|
||||
let delta_k2 = params_a.get_ith_delta(k2 as usize);
|
||||
let tt1 = pairing(&payment1.varphi.1.to_affine(), &delta_k1.to_affine())
|
||||
+ pairing(&payment1.varphi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment1.vv as usize, k1 as usize).to_affine());
|
||||
let tt2 = pairing(&payment2.varphi.1.to_affine(), &delta_k2.to_affine())
|
||||
+ pairing(&payment2.varphi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment2.vv as usize, k2 as usize).to_affine());
|
||||
|
||||
|
||||
for pk_u in public_keys_u.iter() {
|
||||
let pg_pku_deltas = pairing(&pk_u.pk.to_affine(), &(delta_k1 * payment1.rr + delta_k2 * payment2.rr.neg()).to_affine());
|
||||
if tt1 - tt2 == pg_pku_deltas {
|
||||
return Ok(IdentifyResult::DoubleSpendingPublicKeys(pk_u.clone()));
|
||||
}
|
||||
}
|
||||
return Err(DivisibleEcashError::Identify(
|
||||
"A duplicate serial number was detected, the payinfo1 and payinfo2 are different, but we failed to identify the double-spending public key".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use bls12_381::pairing;
|
||||
use group::Curve;
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::scheme::{PayInfo, Payment};
|
||||
use crate::scheme::aggregation::{aggregate_verification_keys, aggregate_wallets};
|
||||
use crate::scheme::identification::{identify, IdentifyResult};
|
||||
use crate::scheme::keygen::{PublicKeyUser, SecretKeyUser, ttp_keygen_authorities, VerificationKeyAuth};
|
||||
use crate::scheme::setup::{GroupParameters, Parameters};
|
||||
use crate::scheme::withdrawal::{issue, issue_verify, withdrawal_request};
|
||||
use crate::utils::hash_g1;
|
||||
|
||||
#[test]
|
||||
fn duplicate_payments_with_the_same_pay_info() {
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
// KEY GENERATION FOR THE AUTHORITIES
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
// KEY GENERATION FOR THE USER1
|
||||
let sk1 = grp.random_scalar();
|
||||
let sk_user1 = SecretKeyUser { sk: sk1 };
|
||||
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
|
||||
|
||||
// KEY GENERATION FOR THE USER2
|
||||
let sk2 = grp.random_scalar();
|
||||
let sk_user2 = SecretKeyUser { sk: sk2 };
|
||||
let pk_user2 = SecretKeyUser::public_key(&sk_user2, &grp);
|
||||
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER1
|
||||
let (withdrawal_req1, req_info1) = withdrawal_request(¶ms, &sk_user1).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER1
|
||||
let mut partial_wallets1 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req1,
|
||||
pk_user1.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
).unwrap();
|
||||
let partial_wallet1 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user1, &blind_signature, &req_info1).unwrap();
|
||||
partial_wallets1.push(partial_wallet1);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER1
|
||||
let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
|
||||
|
||||
let pay_info1 = PayInfo { info: [67u8; 32] };
|
||||
let (payment1, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
|
||||
|
||||
// SPEND VERIFICATION for USER1
|
||||
assert!(payment1.spend_verify(¶ms, &verification_key, &pay_info1).unwrap());
|
||||
|
||||
let payment2 = payment1.clone();
|
||||
// SPEND VERIFICATION for the duplicate payment
|
||||
assert!(payment1.spend_verify(¶ms, &verification_key, &pay_info1).unwrap());
|
||||
|
||||
let pay_info2 = pay_info1.clone();
|
||||
|
||||
let public_keys = HashSet::from([pk_user1, pk_user2]);
|
||||
let identify_result = identify(¶ms, &verification_key, &public_keys, payment1, payment2, pay_info1, pay_info2).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::DuplicatePayInfo(pay_info1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_payments_with_one_repeating_serial_number_but_different_pay_info() {
|
||||
let rng = thread_rng();
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
// KEY GENERATION FOR THE AUTHORITIES
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
// KEY GENERATION FOR THE USER1
|
||||
let sk1 = grp.random_scalar();
|
||||
let sk_user1 = SecretKeyUser { sk: sk1 };
|
||||
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
|
||||
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut pk_all_users = HashSet::new();
|
||||
for i in 0..50 {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
pk_all_users.insert(pk_user);
|
||||
}
|
||||
pk_all_users.insert(pk_user1.clone());
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER1
|
||||
let (withdrawal_req1, req_info1) = withdrawal_request(¶ms, &sk_user1).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER1
|
||||
let mut partial_wallets1 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req1,
|
||||
pk_user1.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
).unwrap();
|
||||
let partial_wallet1 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user1, &blind_signature, &req_info1).unwrap();
|
||||
partial_wallets1.push(partial_wallet1);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER1
|
||||
let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
|
||||
|
||||
let pay_info1 = PayInfo { info: [67u8; 32] };
|
||||
let (payment1, new_wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = wallet1.l.get();
|
||||
wallet1.l.set(current_l - 1);
|
||||
|
||||
let pay_info2 = PayInfo { info: [52u8; 32] };
|
||||
let (payment2, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info2, 10, false).unwrap();
|
||||
|
||||
|
||||
let identify_result = identify(¶ms, &verification_key, &pk_all_users, payment1, payment2, pay_info1, pay_info2).unwrap();
|
||||
|
||||
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(pk_user1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_payments_with_multiple_repeating_serial_number_but_different_pay_info() {
|
||||
let rng = thread_rng();
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
// KEY GENERATION FOR THE AUTHORITIES
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
// KEY GENERATION FOR THE USER1
|
||||
let sk1 = grp.random_scalar();
|
||||
let sk_user1 = SecretKeyUser { sk: sk1 };
|
||||
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
|
||||
|
||||
// GENERATE KEYS FOR OTHER USERS
|
||||
let mut public_keys = HashSet::new();
|
||||
for i in 0..50 {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key(&grp);
|
||||
public_keys.insert(pk_user);
|
||||
}
|
||||
public_keys.insert(pk_user1.clone());
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER1
|
||||
let (withdrawal_req1, req_info1) = withdrawal_request(¶ms, &sk_user1).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER1
|
||||
let mut partial_wallets1 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req1,
|
||||
pk_user1.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
).unwrap();
|
||||
let partial_wallet1 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user1, &blind_signature, &req_info1).unwrap();
|
||||
partial_wallets1.push(partial_wallet1);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER1
|
||||
let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
|
||||
|
||||
let pay_info1 = PayInfo { info: [67u8; 32] };
|
||||
let (payment1, new_wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
|
||||
|
||||
// let's reverse the spending counter in the wallet to create a double spending payment
|
||||
let current_l = wallet1.l.get();
|
||||
wallet1.l.set(current_l - 7);
|
||||
|
||||
let pay_info2 = PayInfo { info: [52u8; 32] };
|
||||
let (payment2, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info2, 10, false).unwrap();
|
||||
|
||||
|
||||
let identify_result = identify(¶ms, &verification_key, &public_keys, payment1, payment2, pay_info1, pay_info2).unwrap();
|
||||
|
||||
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(pk_user1));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn ok_if_two_different_payments() {
|
||||
let rng = thread_rng();
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
// KEY GENERATION FOR THE AUTHORITIES
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
// KEY GENERATION FOR THE USER1
|
||||
let sk1 = grp.random_scalar();
|
||||
let sk_user1 = SecretKeyUser { sk: sk1 };
|
||||
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
|
||||
|
||||
// KEY GENERATION FOR THE USER2
|
||||
let sk2 = grp.random_scalar();
|
||||
let sk_user2 = SecretKeyUser { sk: sk2 };
|
||||
let pk_user2 = SecretKeyUser::public_key(&sk_user2, &grp);
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER1
|
||||
let (withdrawal_req1, req_info1) = withdrawal_request(¶ms, &sk_user1).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER1
|
||||
let mut partial_wallets1 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req1,
|
||||
pk_user1.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
).unwrap();
|
||||
let partial_wallet1 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user1, &blind_signature, &req_info1).unwrap();
|
||||
partial_wallets1.push(partial_wallet1);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER1
|
||||
let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
|
||||
|
||||
let pay_info1 = PayInfo { info: [67u8; 32] };
|
||||
let (payment1, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
|
||||
|
||||
// SPEND VERIFICATION for USER1
|
||||
assert!(payment1.spend_verify(¶ms, &verification_key, &pay_info1).unwrap());
|
||||
|
||||
// WITHDRAWAL REQUEST FOR USER2
|
||||
let (withdrawal_req2, req_info2) = withdrawal_request(¶ms, &sk_user2).unwrap();
|
||||
|
||||
// ISSUE PARTIAL WALLETS for USER2
|
||||
let mut partial_wallets2 = Vec::new();
|
||||
for auth_keypair in authorities_keypairs.clone() {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req2,
|
||||
pk_user2.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
).unwrap();
|
||||
let partial_wallet2 = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user2, &blind_signature, &req_info2).unwrap();
|
||||
partial_wallets2.push(partial_wallet2);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET FOR USER2
|
||||
let mut wallet2 = aggregate_wallets(&grp, &verification_key, &sk_user2, &partial_wallets2).unwrap();
|
||||
|
||||
let pay_info2 = PayInfo { info: [67u8; 32] };
|
||||
let (payment2, wallet2) = wallet2.spend(¶ms, &verification_key, &sk_user2, &pay_info2, 10, false).unwrap();
|
||||
|
||||
// SPEND VERIFICATION for USER2
|
||||
assert!(payment2.spend_verify(¶ms, &verification_key, &pay_info2).unwrap());
|
||||
|
||||
let public_keys = HashSet::from([pk_user1, pk_user2]);
|
||||
let identify_result = identify(¶ms, &verification_key, &public_keys, payment1, payment2, pay_info1, pay_info2).unwrap();
|
||||
assert_eq!(identify_result, IdentifyResult::NotADuplicatePayment);
|
||||
}
|
||||
}
|
||||
@@ -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(), ¶ms_a.get_ith_delta((vv - 1) as usize).to_affine());
|
||||
let pg_psi0_delta = pairing(&psi_g1.to_affine(), ¶ms_a.get_ith_delta((vv - 1) as usize).to_affine());
|
||||
let pg_varsigpr2_gen2 = pairing(&varsig_prime2.to_affine(), grp.gen2());
|
||||
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
|
||||
let pg_thetapr1_delta = pairing(&theta_prime1.to_affine(), ¶ms_a.get_ith_delta((vv - 1) as usize).to_affine());
|
||||
let pg_thetapr2_gen2 = pairing(&theta_prime2.to_affine(), grp.gen2());
|
||||
let yy = params_u.get_sps_pk().get_yy();
|
||||
let pg_rrprime_yy = pairing(&rr_prime.to_affine(), &yy.to_affine());
|
||||
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
|
||||
let pg_ssprime_gen2 = pairing(&ss_prime.to_affine(), grp.gen2());
|
||||
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
|
||||
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
|
||||
let pg_varsigpr2_ww1 = pairing(&varsig_prime2.to_affine(), &ww1.to_affine());
|
||||
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
|
||||
let pg_thetapr2_ww2 = pairing(&theta_prime2.to_affine(), &ww2.to_affine());
|
||||
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
|
||||
let pg_gen1_zz = pairing(grp.gen1(), ¶ms_u.get_sps_pk().get_zz().to_affine());
|
||||
let pg_rr_tt = pairing(&rr_prime.to_affine(), &tt_prime.to_affine());
|
||||
let pg_rr_psi1 = pairing(&rr_prime.to_affine(), &psi_g2.to_affine());
|
||||
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &tt_prime.to_affine());
|
||||
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
|
||||
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
|
||||
|
||||
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
|
||||
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
|
||||
let pg_eq3 = pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 + pg_gen1_zz.neg();
|
||||
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
|
||||
|
||||
let instance = SpendInstance {
|
||||
kappa,
|
||||
phi,
|
||||
varphi,
|
||||
rr,
|
||||
rr_prime,
|
||||
ss_prime,
|
||||
tt_prime,
|
||||
varsig_prime1,
|
||||
theta_prime1,
|
||||
pg_eq1,
|
||||
pg_eq2,
|
||||
pg_eq3,
|
||||
pg_eq4,
|
||||
psi_g1: *psi_g1,
|
||||
psi_g2: *psi_g2,
|
||||
pg_psi0_delta,
|
||||
pg_psi0_gen2,
|
||||
pg_psi0_yy,
|
||||
pg_psi0_ww1,
|
||||
pg_psi0_ww2,
|
||||
pg_rr_psi1,
|
||||
pg_psi0_tt,
|
||||
pg_psi0_psi1,
|
||||
};
|
||||
|
||||
let witness = SpendWitness {
|
||||
sk_u: sk_user.clone(),
|
||||
v: self.v,
|
||||
r: sign_blinding_factor,
|
||||
r1,
|
||||
r2,
|
||||
r_varsig1,
|
||||
r_theta1,
|
||||
r_varsig2,
|
||||
r_theta2,
|
||||
r_rr,
|
||||
r_ss,
|
||||
r_tt,
|
||||
rho1,
|
||||
rho2,
|
||||
rho3,
|
||||
};
|
||||
|
||||
// compute the zk proof
|
||||
let zk_proof = SpendProof::construct(params, &instance, &witness, &verification_key, vv);
|
||||
|
||||
// output pay and updated wallet
|
||||
let pay = Payment {
|
||||
kappa,
|
||||
sig: signature_prime,
|
||||
phi,
|
||||
varphi,
|
||||
varsig_prime1,
|
||||
varsig_prime2,
|
||||
theta_prime1,
|
||||
theta_prime2,
|
||||
rr_prime,
|
||||
ss_prime,
|
||||
tt_prime,
|
||||
rr,
|
||||
zk_proof,
|
||||
vv,
|
||||
};
|
||||
|
||||
// The number of samples collected by the benchmark process is way higher than the
|
||||
// MAX_WALLET_VALUE we ever consider. Thus, we would execute the spending too many times
|
||||
// and the initial condition at the top of this function will crush. Thus, we need a
|
||||
// benchmark flag to signal that we don't want to increase the spending couter but only
|
||||
// care about the function performance.
|
||||
if !bench_flag {
|
||||
let current_l = self.l();
|
||||
self.l.set(current_l + vv);
|
||||
}
|
||||
Ok((pay, self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Payment {
|
||||
pub fn spend_verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKeyAuth,
|
||||
pay_info: &PayInfo) -> Result<bool> {
|
||||
if bool::from(self.sig.0.is_identity()) {
|
||||
return Err(DivisibleEcashError::Spend(
|
||||
"The element h of the signature equals the identity".to_string(),
|
||||
));
|
||||
}
|
||||
let grp = params.get_grp();
|
||||
let params_a = params.get_params_a();
|
||||
let params_u = params.get_params_u();
|
||||
|
||||
if !check_bilinear_pairing(
|
||||
&self.sig.0.to_affine(),
|
||||
&G2Prepared::from(self.kappa.to_affine()),
|
||||
&self.sig.1.to_affine(),
|
||||
grp.prepared_miller_g2(),
|
||||
) {
|
||||
return Err(DivisibleEcashError::Spend(
|
||||
"The bilinear check for kappa failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if bool::from(self.sig.0.is_identity()) {
|
||||
return Err(DivisibleEcashError::Spend(
|
||||
"The element h of the signature on l equals the identity".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// verify integrity of R
|
||||
if !(self.rr == hash_to_scalar(pay_info.info)) {
|
||||
return Err(DivisibleEcashError::Spend(
|
||||
"Integrity of R does not hold".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
//TODO: verify whether payinfo contains merchent's identifier
|
||||
|
||||
let psi_g1 = params_u.get_psi_g1();
|
||||
let psi_g2 = params_u.get_psi_g2();
|
||||
let pg_varsigpr1_delta = pairing(&self.varsig_prime1.to_affine(), ¶ms_a.get_ith_delta((self.vv - 1) as usize).to_affine());
|
||||
let pg_psi0_delta = pairing(&psi_g1.to_affine(), ¶ms_a.get_ith_delta((self.vv - 1) as usize).to_affine());
|
||||
let pg_varsigpr2_gen2 = pairing(&self.varsig_prime2.to_affine(), grp.gen2());
|
||||
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
|
||||
let pg_thetapr1_delta = pairing(&self.theta_prime1.to_affine(), ¶ms_a.get_ith_delta((self.vv - 1) as usize).to_affine());
|
||||
let pg_thetapr2_gen2 = pairing(&self.theta_prime2.to_affine(), grp.gen2());
|
||||
let yy = params_u.get_sps_pk().get_yy();
|
||||
let pg_rrprime_yy = pairing(&self.rr_prime.to_affine(), &yy.to_affine());
|
||||
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
|
||||
let pg_ssprime_gen2 = pairing(&self.ss_prime.to_affine(), grp.gen2());
|
||||
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
|
||||
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
|
||||
let pg_varsigpr2_ww1 = pairing(&self.varsig_prime2.to_affine(), &ww1.to_affine());
|
||||
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
|
||||
let pg_thetapr2_ww2 = pairing(&self.theta_prime2.to_affine(), &ww2.to_affine());
|
||||
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
|
||||
let pg_gen1_zz = pairing(grp.gen1(), ¶ms_u.get_sps_pk().get_zz().to_affine());
|
||||
let pg_rr_tt = pairing(&self.rr_prime.to_affine(), &self.tt_prime.to_affine());
|
||||
let pg_rr_psi1 = pairing(&self.rr_prime.to_affine(), &psi_g2.to_affine());
|
||||
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &self.tt_prime.to_affine());
|
||||
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
|
||||
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
|
||||
|
||||
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
|
||||
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
|
||||
let pg_eq3 = pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 + pg_gen1_zz.neg();
|
||||
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
|
||||
|
||||
let instance = SpendInstance {
|
||||
kappa: self.kappa,
|
||||
phi: self.phi,
|
||||
varphi: self.varphi,
|
||||
rr: self.rr,
|
||||
rr_prime: self.rr_prime,
|
||||
ss_prime: self.ss_prime,
|
||||
tt_prime: self.tt_prime,
|
||||
varsig_prime1: self.varsig_prime1,
|
||||
theta_prime1: self.theta_prime1,
|
||||
pg_eq1,
|
||||
pg_eq2,
|
||||
pg_eq3,
|
||||
pg_eq4,
|
||||
psi_g1: *psi_g1,
|
||||
psi_g2: *psi_g2,
|
||||
pg_psi0_delta,
|
||||
pg_psi0_gen2,
|
||||
pg_psi0_yy,
|
||||
pg_psi0_ww1,
|
||||
pg_psi0_ww2,
|
||||
pg_rr_psi1,
|
||||
pg_psi0_tt,
|
||||
pg_psi0_psi1,
|
||||
};
|
||||
|
||||
Ok(self.zk_proof.verify(¶ms, &instance, &verification_key, self.vv))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::scheme::{PayInfo, Phi, VarPhi, Wallet};
|
||||
use crate::scheme::aggregation::aggregate_verification_keys;
|
||||
use crate::scheme::keygen::{PublicKeyUser, ttp_keygen_authorities, VerificationKeyAuth};
|
||||
use crate::scheme::setup::{GroupParameters, Parameters};
|
||||
use crate::utils::hash_g1;
|
||||
|
||||
#[test]
|
||||
fn phi_to_and_from_bytes() {
|
||||
let phi = Phi(hash_g1("Element 0 of Phi"), hash_g1("Element 1 of Phi"));
|
||||
let phi_bytes = phi.to_bytes();
|
||||
let phi_from_bytes = Phi::from_bytes(&phi_bytes).unwrap();
|
||||
assert_eq!(phi, phi_from_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn varphi_to_and_from_bytes() {
|
||||
let varphi = VarPhi(hash_g1("Element 0 of VarPhi"), hash_g1("Element 1 of VarPhi"));
|
||||
let varphi_bytes = varphi.to_bytes();
|
||||
let varphi_from_bytes = VarPhi::from_bytes(&varphi_bytes).unwrap();
|
||||
assert_eq!(varphi, varphi_from_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spend_verification_is_correct() {
|
||||
let rng = thread_rng();
|
||||
let grp = GroupParameters::new().unwrap();
|
||||
let params = Parameters::new(grp.clone());
|
||||
let params_u = params.get_params_u();
|
||||
let params_a = params.get_params_a();
|
||||
|
||||
let sk = grp.random_scalar();
|
||||
let pk_user = PublicKeyUser {
|
||||
pk: grp.gen1() * sk,
|
||||
};
|
||||
|
||||
let authorities_keypairs = ttp_keygen_authorities(¶ms, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -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(¶ms.get_grp()),
|
||||
};
|
||||
|
||||
let witness = WithdrawalReqWitness {
|
||||
attributes,
|
||||
com_opening,
|
||||
pc_coms_openings: pc_coms_openings.clone(),
|
||||
};
|
||||
|
||||
let zk_proof = WithdrawalReqProof::construct(¶ms, &instance, &witness);
|
||||
|
||||
let req = WithdrawalRequest {
|
||||
com_hash,
|
||||
com: commitment,
|
||||
pc_coms: pc_coms.clone(),
|
||||
zk_proof,
|
||||
};
|
||||
|
||||
let req_info = RequestInfo {
|
||||
com_hash,
|
||||
pc_coms_openings,
|
||||
v,
|
||||
};
|
||||
|
||||
Ok((req, req_info))
|
||||
}
|
||||
|
||||
pub fn issue(params: &Parameters, req: &WithdrawalRequest, pk_u: PublicKeyUser, sk_a: &SecretKeyAuth) -> Result<BlindedSignature> {
|
||||
let h = hash_g1(req.com.to_bytes());
|
||||
if !(h == req.com_hash) {
|
||||
return Err(DivisibleEcashError::WithdrawalRequestVerification(
|
||||
"Failed to verify the commitment hash".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// verify zk proof
|
||||
let instance = WithdrawalReqInstance {
|
||||
com: req.com,
|
||||
h: req.com_hash,
|
||||
pc_coms: req.pc_coms.clone(),
|
||||
pk_user: pk_u,
|
||||
};
|
||||
if !req.zk_proof.verify(¶ms, &instance) {
|
||||
return Err(DivisibleEcashError::WithdrawalRequestVerification(
|
||||
"Failed to verify the proof of knowledge".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let sig = req
|
||||
.pc_coms
|
||||
.iter()
|
||||
.zip(sk_a.ys.iter())
|
||||
.map(|(pc, yi)| pc * yi)
|
||||
.chain(std::iter::once(h * sk_a.x))
|
||||
.sum();
|
||||
|
||||
Ok(BlindedSignature(h, sig))
|
||||
}
|
||||
|
||||
pub fn issue_verify(
|
||||
params: &GroupParameters,
|
||||
vk_auth: &VerificationKeyAuth,
|
||||
sk_user: &SecretKeyUser,
|
||||
blind_signature: &BlindedSignature,
|
||||
req_info: &RequestInfo) -> Result<PartialWallet> {
|
||||
|
||||
// Parse the blinded signature
|
||||
let h = blind_signature.0;
|
||||
let c = blind_signature.1;
|
||||
|
||||
// Verify the integrity of the response from the authority
|
||||
if !(req_info.com_hash == h) {
|
||||
return Err(DivisibleEcashError::IssuanceVfy(
|
||||
"Integrity verification failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Unblind the blinded signature on the partial wallet
|
||||
let blinding_removers = vk_auth
|
||||
.beta_g1
|
||||
.iter()
|
||||
.zip(req_info.pc_coms_openings.iter())
|
||||
.map(|(beta, opening)| beta * opening)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let unblinded_c = c - blinding_removers;
|
||||
|
||||
let attr = vec![sk_user.sk, req_info.v];
|
||||
|
||||
let signed_attributes = attr
|
||||
.iter()
|
||||
.zip(vk_auth.beta_g2.iter())
|
||||
.map(|(attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
// Verify the signature correctness on the wallet share
|
||||
if !check_bilinear_pairing(
|
||||
&h.to_affine(),
|
||||
&G2Prepared::from((vk_auth.alpha + signed_attributes).to_affine()),
|
||||
&unblinded_c.to_affine(),
|
||||
params.prepared_miller_g2(),
|
||||
) {
|
||||
return Err(DivisibleEcashError::IssuanceVfy(
|
||||
"Verification of wallet share failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(PartialWallet {
|
||||
sig: Signature(h, unblinded_c),
|
||||
v: req_info.v,
|
||||
idx: None,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,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(¶ms, 2, 3).unwrap();
|
||||
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
let verification_key =
|
||||
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
|
||||
|
||||
// KEY GENERATION FOR THE USER
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = SecretKeyUser::public_key(&sk_user, &grp);
|
||||
|
||||
// WITHDRAWAL REQUEST
|
||||
let (withdrawal_req, req_info) = withdrawal_request(¶ms, &sk_user)?;
|
||||
|
||||
// ISSUE PARTIAL WALLETS
|
||||
let mut partial_wallets = Vec::new();
|
||||
for auth_keypair in authorities_keypairs {
|
||||
let blind_signature = issue(
|
||||
¶ms,
|
||||
&withdrawal_req,
|
||||
pk_user.clone(),
|
||||
&auth_keypair.secret_key(),
|
||||
)?;
|
||||
let partial_wallet = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user, &blind_signature, &req_info)?;
|
||||
partial_wallets.push(partial_wallet);
|
||||
}
|
||||
|
||||
// AGGREGATE WALLET
|
||||
let mut wallet = aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets)?;
|
||||
|
||||
let pay_info = PayInfo { info: [67u8; 32] };
|
||||
let (payment, wallet) = wallet.spend(¶ms, &verification_key, &sk_user, &pay_info, 10, false)?;
|
||||
|
||||
// SPEND VERIFICATION
|
||||
assert!(payment.spend_verify(¶ms, &verification_key, &pay_info).unwrap());
|
||||
let payment_bytes = payment.to_bytes();
|
||||
let payment2 = Payment::try_from(&payment_bytes[..]).unwrap();
|
||||
assert_eq!(payment, payment2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user