Compare commits

...

106 Commits

Author SHA1 Message Date
durch ce8718ae96 Rebase, clippy, fmt 2023-12-07 10:43:03 +01:00
Simon Wicky 07a86afb54 generate ecash key on client upgrade 2023-12-07 09:49:27 +01:00
Simon Wicky 3098eb8544 add config migration for clients 2023-12-07 09:49:27 +01:00
Simon Wicky f2ab3e0972 add small note on nym-api 2023-12-07 09:49:27 +01:00
Simon Wicky 785634b824 fix sql migrations 2023-12-07 09:49:27 +01:00
Simon Wicky f6d00ba196 increase api timeout for ecash 2023-12-07 09:49:27 +01:00
Simon Wicky 1dd208dd4c prevent shutdown to be dropped in online mode 2023-12-07 09:49:15 +01:00
Simon Wicky 49d22ab791 add serial number to payments 2023-12-07 09:49:15 +01:00
Simon Wicky d97244fb27 add value to payment 2023-12-07 09:49:13 +01:00
Simon Wicky 8093932c7b online mode on gateway 2023-12-07 09:48:29 +01:00
Simon Wicky e0f36537db add value to ecash wallets 2023-12-07 09:48:29 +01:00
Simon Wicky 2bb130c204 prepare new api endpoint 2023-12-07 09:48:29 +01:00
Simon Wicky 338613362d offline/online toggle for gateway 2023-12-07 09:48:29 +01:00
Simon Wicky 770324cf95 name change in preparation for online setup 2023-12-07 09:48:29 +01:00
Simon Wicky 9c6555663a persist pending credentials on reboot 2023-12-07 09:48:27 +01:00
Simon Wicky 9255e32d08 set ticker missed behavior 2023-12-07 09:48:12 +01:00
Simon Wicky f35f2649ef tweak to not try_send every time 2023-12-07 09:48:12 +01:00
Simon Wicky 7514fa3ca6 async credential sending 2023-12-07 09:48:12 +01:00
Simon Wicky 976b6d9ada bump up the bandwidth a bit 2023-12-07 09:46:00 +01:00
Simon Wicky 9d030bef94 fix sql migration 2023-12-07 09:46:00 +01:00
Simon Wicky ddfd503962 change parameters serialization so it's not random 2023-12-07 09:46:00 +01:00
Simon Wicky b8803376ff conversion to keypairauth elsewhere 2023-12-07 09:46:00 +01:00
Simon Wicky 3afb4c5041 use api ecash key for issuance 2023-12-07 09:46:00 +01:00
Simon Wicky f8a881af59 use stored ecash keypair for the api 2023-12-07 09:46:00 +01:00
Simon Wicky 14b974d2e1 add ecash key to api 2023-12-07 09:46:00 +01:00
Simon Wicky 3bf105301a fix route for coconut api 2023-12-07 09:46:00 +01:00
Simon Wicky 61cf4ea0c7 distribute parameters to all components 2023-12-07 09:46:00 +01:00
Simon Wicky d559d9a113 hard code ecash parameters 2023-12-07 09:45:43 +01:00
Simon Wicky 575282fe32 add consumed update to wallet 2023-12-07 09:45:39 +01:00
Simon Wicky 3e3c8a5467 add ecash params to api 2023-12-07 09:45:15 +01:00
Simon Wicky 0603273a49 remove secret key from credential storage 2023-12-07 09:45:15 +01:00
Simon Wicky dcfae92bab use stored ecash key when using credentials 2023-12-07 09:45:13 +01:00
Simon Wicky 9e925fd4ce config file template update 2023-12-07 09:45:00 +01:00
Simon Wicky 5792b89917 use ecash key for credential issuance 2023-12-07 09:45:00 +01:00
Simon Wicky d5bfa732c6 add ecash keypair to client 2023-12-07 09:45:00 +01:00
Simon Wicky 89644e5476 use identity key as provider pk 2023-12-07 09:44:57 +01:00
aniampio 7057170381 Move generate payinfo as implementation of the struct 2023-12-07 09:43:36 +01:00
aniampio ada02fe631 Improve function performance and readability 2023-12-07 09:43:07 +01:00
aniampio 7836f5771b Add function generating the payinfo 2023-12-07 09:42:23 +01:00
Simon Wicky 46c53eb1a5 credentials storage on gateway and api 2023-12-07 09:41:55 +01:00
Simon Wicky 506693a8a5 wip double spending detection 2023-12-07 09:41:53 +01:00
Simon Wicky cece0bb80e add traits to ecashcredential 2023-12-07 09:41:14 +01:00
Simon Wicky 56d36974fa add params in ecashcredential 2023-12-07 09:41:12 +01:00
Simon Wicky c0713a5a13 add payinfo to credential 2023-12-07 09:40:26 +01:00
Simon Wicky 74536467b6 impl payment flow 2023-12-07 09:40:23 +01:00
Simon Wicky c1c58a7476 update ecash credential 2023-12-07 09:40:00 +01:00
Simon Wicky bc9d4b4682 add secret-key for ecash credential storage 2023-12-07 09:40:00 +01:00
Simon Wicky e41796b157 WIP spending ecash 2023-12-07 09:40:00 +01:00
Simon Wicky 5a831b6482 swap VerificationKey for VerificationKeyAuth 2023-12-07 09:40:00 +01:00
Simon Wicky 4de642315d add get_next_ecash_cred for storage 2023-12-07 09:40:00 +01:00
Simon Wicky 1091f1341c issuance cli side 2023-12-07 09:39:58 +01:00
Simon Wicky ea0dbfea03 ecash issuance api side 2023-12-07 09:39:45 +01:00
Simon Wicky dd323ce493 update bls12_381 dependency 2023-12-07 09:39:45 +01:00
aniampio 954f76e241 Optimize the identification function 2023-12-07 09:39:19 +01:00
aniampio a09dc8e462 Add byte calculation for divisible ecash 2023-12-07 09:39:19 +01:00
aniampio f623bfed4c Add Wallet, Partial Wallet and Payment to and from bytes converstion 2023-12-07 09:39:19 +01:00
aniampio e6bcfb697c Increase benchmark duration 2023-12-07 09:39:19 +01:00
aniampio a70843b940 Update in divisible ecash benchmark 2023-12-07 09:39:19 +01:00
aniampio 8a21e4ae25 Benchmarks: change the number of public keys 2023-12-07 09:39:19 +01:00
aniampio d7f4590239 Update compact benchmarks 2023-12-07 09:39:19 +01:00
aniampio cd2f7a30d2 Remove spend verification from the identify function 2023-12-07 09:39:19 +01:00
aniampio 23b9e4d48a Update benchmarks 2023-12-07 09:39:19 +01:00
aniampio fecb67ded7 Update for compact e-cash - remove requirement for security tag 2023-12-07 09:39:19 +01:00
aniampio 3ffa367de1 Update benchmarks 2023-12-07 09:39:19 +01:00
aniampio 3691110db8 Add verification of the payments inside identify; speed up the identify checks 2023-12-07 09:39:17 +01:00
aniampio dd527e4295 Update the spend function for compact ecash - multi spend 2023-12-07 09:39:06 +01:00
aniampio 778f5bf5e5 Add duplicate payinfo check for identify 2023-12-07 09:39:06 +01:00
aniampio 761d09534f Update spend function in the compact ecash 2023-12-07 09:39:06 +01:00
aniampio 4a7e44b9c7 Add test for identification - case of no double spending 2023-12-07 09:39:06 +01:00
aniampio db69158b4b Add aggregation and e2e test 2023-12-07 09:39:06 +01:00
aniampio 40af8ebf47 Fix the eq 15 for the spend proof 2023-12-07 09:39:06 +01:00
aniampio f8e78c7fcc Update the verification function for structure preserving signature 2023-12-07 09:39:06 +01:00
aniampio 05ddbf2fba Add draft of signature verification function 2023-12-07 09:39:06 +01:00
aniampio 31e9c0004a Move the index for signature retrival 2023-12-07 09:39:06 +01:00
aniampio e4cfdc9888 3 out of 4 pairing tests in the zk proof pass 2023-12-07 09:39:06 +01:00
aniampio 0df62df780 Fix signatures tau_l 2023-12-07 09:39:06 +01:00
aniampio 3687600587 Shifting indices in setup - eq 4 test still passing 2023-12-07 09:39:06 +01:00
aniampio 64dd6c2b8c Add eq checks 2023-12-07 09:39:06 +01:00
aniampio aa018769c2 Add bilinear equations into the zk proof; the last eq passes the tests 2023-12-07 09:39:04 +01:00
aniampio 2ec2613a89 Define spend instance and witness structs 2023-12-07 09:38:54 +01:00
aniampio 15d10ab027 Add spend and spend verification functions; fix breaking test for proof of withdrawal 2023-12-07 09:38:54 +01:00
aniampio 0fc5b97dfb Add issuance and issuance verification - but one of the tests is failing 2023-12-07 09:38:54 +01:00
aniampio 7632e524c0 Add key generation functions 2023-12-07 09:38:54 +01:00
aniampio 49721177fc Update the structure preserving signature to work in the setup function as well 2023-12-07 09:38:50 +01:00
aniampio 7f0f2b056f Add benchmarks 2023-12-07 09:38:21 +01:00
aniampio cec35ee4d0 Update get range proof signature function to return an error 2023-12-07 09:37:43 +01:00
aniampio 5f545675e1 Add into the zk proof the proof that l is within the range 2023-12-07 09:36:20 +01:00
aniampio 49ac369ce2 Add struct for the divisible ecash crate 2023-12-07 09:36:17 +01:00
aniampio dfcc87167b Add test for succesfull identification 2023-12-07 09:35:54 +01:00
aniampio ee95596abf Fix the issue with two gamma equations 2023-12-07 09:35:54 +01:00
aniampio 23ce9de873 Add S and T into the zk proof for spend 2023-12-07 09:35:54 +01:00
aniampio 28081bd72c Move signature related structs and functions to utils 2023-12-07 09:35:54 +01:00
aniampio 5e146aa432 Move spend and spec vfy as functions associated with wallet and payment 2023-12-07 09:35:54 +01:00
aniampio 53a9d43c87 Add identify function 2023-12-07 09:35:54 +01:00
aniampio f487234007 Add spendVfy function 2023-12-07 09:35:54 +01:00
aniampio d2a7d26e5a Add verification for the spend zkproof and first tests 2023-12-07 09:35:54 +01:00
aniampio 7fcd246089 Start the spend function and zkproof for spend 2023-12-07 09:35:54 +01:00
aniampio f384338f0f Add new aggregation function and struct for the aggregated wallet 2023-12-07 09:35:54 +01:00
aniampio 0114743db9 Add aggregation into e2e tests 2023-12-07 09:35:54 +01:00
aniampio 9776bfb0b0 Copy and adjust aggregation of verification keys from coconut 2023-12-07 09:35:52 +01:00
aniampio 0526b3e6b4 Fix bug in the issuance verification 2023-12-07 09:35:37 +01:00
aniampio d00bef2ddf Fix a bug in the commitment computation 2023-12-07 09:35:37 +01:00
aniampio a0c81c982c Copy polynomial and ttpcode from Coconut; add first test; add keypair structures 2023-12-07 09:35:37 +01:00
aniampio 32ba1c7c18 Add zk proof for withdrawal request 2023-12-07 09:35:35 +01:00
aniampio bea8e4a881 Add initial functions 2023-12-07 09:34:41 +01:00
aniampio 9558c3deb2 Add template for the compact ecash crate 2023-12-07 09:33:00 +01:00
139 changed files with 12525 additions and 1184 deletions
Vendored
BIN
View File
Binary file not shown.
Generated
+816 -582
View File
File diff suppressed because it is too large Load Diff
+22 -2
View File
@@ -56,6 +56,9 @@ members = [
"common/node-tester-utils",
"common/nonexhaustive-delayqueue",
"common/nymcoconut",
"common/nym_offline_compact_ecash",
"common/nym_offline_divisible_ecash",
"common/nym_online_divisible_ecash",
"common/nymsphinx",
"common/nymsphinx/acknowledgements",
"common/nymsphinx/addressing",
@@ -108,7 +111,7 @@ members = [
"tools/nymvisor",
"tools/ts-rs-cli",
"wasm/client",
# "wasm/full-nym-wasm",
# "wasm/full-nym-wasm",
"wasm/mix-fetch",
"wasm/node-tester",
]
@@ -125,7 +128,15 @@ default-members = [
"explorer-api",
]
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-vpn/ui/src-tauri", "cpu-cycles"]
exclude = [
"explorer",
"contracts",
"nym-wallet",
"nym-connect/mobile/src-tauri",
"nym-connect/desktop",
"nym-vpn/ui/src-tauri",
"cpu-cycles",
]
[workspace.package]
authors = ["Nym Technologies SA"]
@@ -204,6 +215,15 @@ wasm-bindgen-futures = "0.4.37"
wasmtimer = "0.2.0"
web-sys = "0.3.63"
bls12_381 = { path = "/Users/drazen/nym/bls12_381", default-features = false, features = [
"alloc",
"pairings",
"experimental",
"zeroize",
] }
ff = { version = "0.13", default-features = false }
group = { version = "0.13", default-features = false }
# Profile settings for individual crates
[profile.release.package.nym-socks5-listener]
+1
View File
@@ -38,6 +38,7 @@ nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "cli"] }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" }
nym-config = { path = "../../common/config" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-credentials = { path = "../../common/credentials" }
@@ -5,8 +5,9 @@ use crate::client::config::old_config_v1_1_20_2::{
ClientPathsV1_1_20_2, ConfigV1_1_20_2, SocketTypeV1_1_20_2, SocketV1_1_20_2,
};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::{
ClientKeysPathsV1_1_20_2, CommonClientPathsV1_1_20_2,
};
use nym_client_core::config::old_config_v1_1_20::ConfigV1_1_20 as BaseConfigV1_1_20;
use nym_client_core::config::old_config_v1_1_20_2::{
ClientV1_1_20_2, ConfigV1_1_20_2 as BaseConfigV1_1_20_2,
@@ -60,7 +61,7 @@ impl From<ConfigV1_1_20> for ConfigV1_1_20_2 {
socket: value.socket.into(),
storage_paths: ClientPathsV1_1_20_2 {
common_paths: CommonClientPathsV1_1_20_2 {
keys: ClientKeysPaths {
keys: ClientKeysPathsV1_1_20_2 {
private_identity_key_file: value.base.client.private_identity_key_file,
public_identity_key_file: value.base.client.public_identity_key_file,
private_encryption_key_file: value.base.client.private_encryption_key_file,
@@ -50,6 +50,12 @@ keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key
# Path to file containing public encryption key.
keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}'
# Path to file containing private ecash key.
keys.private_ecash_key_file = '{{ storage_paths.keys.private_ecash_key_file }}'
# Path to file containing public ecash key.
keys.public_ecash_key_file = '{{ storage_paths.keys.public_ecash_key_file }}'
# A gateway specific, optional, base58 stringified shared key used for
# communication with particular gateway.
keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}'
+26
View File
@@ -18,6 +18,7 @@ use nym_client_core::client::base_client::storage::gateway_details::{
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::error::ClientCoreError;
use nym_compact_ecash::{generate_keypair_user, setup::GroupParameters};
use nym_config::OptionalSet;
use std::error::Error;
use std::net::IpAddr;
@@ -121,6 +122,28 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
)
}
fn init_ecash_keypair(config: &Config) -> Result<(), ClientError> {
let kp = generate_keypair_user(&GroupParameters::new().unwrap());
nym_pemstore::store_keypair(
&kp,
&nym_pemstore::KeyPairPath::new(
config
.storage_paths
.common_paths
.keys
.private_ecash_key_file
.clone(),
config
.storage_paths
.common_paths
.keys
.public_ecash_key_file
.clone(),
),
)?;
Ok(())
}
fn persist_gateway_details(
config: &Config,
details: GatewayEndpointConfig,
@@ -159,6 +182,7 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
let (updated, gateway_config) = updated_step2.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
init_ecash_keypair(&updated)?; //SW does that belong here?
updated.save_to_default_location()?;
Ok(true)
@@ -179,6 +203,7 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, ClientError> {
let updated_step1: ConfigV1_1_20_2 = old_config.into();
let (updated, gateway_config) = updated_step1.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
init_ecash_keypair(&updated)?; //SW does that belong here?
updated.save_to_default_location()?;
Ok(true)
@@ -196,6 +221,7 @@ fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, ClientError> {
let (updated, gateway_config) = old_config.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
init_ecash_keypair(&updated)?; //SW does that belong here?
updated.save_to_default_location()?;
Ok(true)
@@ -5,8 +5,9 @@ use crate::config::old_config_v1_1_20_2::{
ConfigV1_1_20_2, CoreConfigV1_1_20_2, SocksClientPathsV1_1_20_2,
};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::{
ClientKeysPathsV1_1_20_2, CommonClientPathsV1_1_20_2,
};
use nym_client_core::config::old_config_v1_1_20::ConfigV1_1_20 as BaseConfigV1_1_20;
use nym_client_core::config::old_config_v1_1_20_2::ClientV1_1_20_2;
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
@@ -50,7 +51,7 @@ impl From<ConfigV1_1_20> for ConfigV1_1_20_2 {
},
storage_paths: SocksClientPathsV1_1_20_2 {
common_paths: CommonClientPathsV1_1_20_2 {
keys: ClientKeysPaths {
keys: ClientKeysPathsV1_1_20_2 {
private_identity_key_file: value.base.client.private_identity_key_file,
public_identity_key_file: value.base.client.public_identity_key_file,
private_encryption_key_file: value.base.client.private_encryption_key_file,
+6
View File
@@ -50,6 +50,12 @@ keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key
# Path to file containing public encryption key.
keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}'
# Path to file containing private ecash key.
keys.private_ecash_key_file = '{{ storage_paths.keys.private_ecash_key_file }}'
# Path to file containing public ecash key.
keys.public_ecash_key_file = '{{ storage_paths.keys.public_ecash_key_file }}'
# A gateway specific, optional, base58 stringified shared key used for
# communication with particular gateway.
keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}'
+1
View File
@@ -12,6 +12,7 @@ thiserror = { workspace = true }
url = { workspace = true }
nym-coconut-interface = { path = "../coconut-interface" }
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
nym-credential-storage = { path = "../credential-storage" }
nym-credentials = { path = "../credentials" }
nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
+22 -21
View File
@@ -2,13 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::BandwidthControllerError;
use nym_coconut_interface::{Base58, Parameters};
use nym_compact_ecash::scheme::keygen::KeyPairUser;
use nym_compact_ecash::setup::GroupParameters;
use nym_compact_ecash::Base58;
use nym_credential_storage::storage::Storage;
use nym_credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use nym_credentials::coconut::utils::obtain_aggregate_signature;
use nym_crypto::asymmetric::{encryption, identity};
use nym_network_defaults::VOUCHER_INFO;
use nym_validator_client::coconut::all_coconut_api_clients;
use nym_network_defaults::ECASH_INFO;
use nym_validator_client::coconut::all_ecash_api_clients;
use nym_validator_client::nyxd::contract_traits::CoconutBandwidthSigningClient;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use nym_validator_client::nyxd::Coin;
@@ -19,20 +21,24 @@ use std::str::FromStr;
pub mod state;
pub async fn deposit<C>(client: &C, amount: Coin) -> Result<State, BandwidthControllerError>
pub async fn deposit<C>(
client: &C,
amount: Coin,
ecash_keypair: KeyPairUser,
) -> Result<State, BandwidthControllerError>
where
C: CoconutBandwidthSigningClient + Sync,
{
let mut rng = OsRng;
let signing_keypair = KeyPair::from(identity::KeyPair::new(&mut rng));
let encryption_keypair = KeyPair::from(encryption::KeyPair::new(&mut rng));
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
let params = GroupParameters::new().unwrap();
let voucher_value = amount.amount.to_string();
let tx_hash = client
.deposit(
amount,
String::from(VOUCHER_INFO),
String::from(ECASH_INFO),
signing_keypair.public_key.clone(),
encryption_keypair.public_key.clone(),
None,
@@ -44,10 +50,11 @@ where
let voucher = BandwidthVoucher::new(
&params,
voucher_value,
VOUCHER_INFO.to_string(),
ECASH_INFO.to_string(),
Hash::from_str(&tx_hash).map_err(|_| BandwidthControllerError::InvalidTxHash)?,
identity::PrivateKey::from_base58_string(&signing_keypair.private_key)?,
encryption::PrivateKey::from_base58_string(&encryption_keypair.private_key)?,
ecash_keypair,
);
let state = State { voucher, params };
@@ -71,22 +78,16 @@ where
.await?
.ok_or(BandwidthControllerError::NoThreshold)?;
let coconut_api_clients = all_coconut_api_clients(client, epoch_id).await?;
let ecash_api_clients = all_ecash_api_clients(client, epoch_id).await?;
let signature = obtain_aggregate_signature(
&state.params,
&state.voucher,
&coconut_api_clients,
threshold,
)
.await?;
let wallet =
obtain_aggregate_signature(&state.params, &state.voucher, &ecash_api_clients, threshold)
.await?;
storage
.insert_coconut_credential(
.insert_ecash_wallet(
ECASH_INFO.to_string(),
wallet.to_bs58(),
state.voucher.get_voucher_value(),
VOUCHER_INFO.to_string(),
state.voucher.get_private_attributes()[0].to_bs58(),
state.voucher.get_private_attributes()[1].to_bs58(),
signature.to_bs58(),
epoch_id.to_string(),
)
.await
@@ -1,8 +1,8 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_coconut_interface::Parameters;
use nym_credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
use nym_compact_ecash::setup::GroupParameters;
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use nym_crypto::asymmetric::{encryption, identity};
@@ -31,14 +31,14 @@ impl From<encryption::KeyPair> for KeyPair {
pub struct State {
pub voucher: BandwidthVoucher,
pub params: Parameters,
pub params: GroupParameters,
}
impl State {
pub fn new(voucher: BandwidthVoucher) -> Self {
State {
voucher,
params: Parameters::new(TOTAL_ATTRIBUTES).unwrap(),
params: GroupParameters::new().unwrap(),
}
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_coconut_interface::CoconutError;
use nym_compact_ecash::error::CompactEcashError;
use nym_credential_storage::error::StorageError;
use nym_credentials::error::Error as CredentialsError;
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
@@ -25,8 +25,8 @@ pub enum BandwidthControllerError {
#[error(transparent)]
StorageError(#[from] StorageError),
#[error("Coconut error - {0}")]
CoconutError(#[from] CoconutError),
#[error("Ecash error - {0}")]
EcashError(#[from] CompactEcashError),
#[error("Validator client error - {0}")]
ValidatorError(#[from] ValidatorClientError),
+62 -40
View File
@@ -2,17 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::BandwidthControllerError;
use nym_compact_ecash::scheme::keygen::KeyPairUser;
use nym_compact_ecash::scheme::{EcashCredential, Wallet};
use nym_compact_ecash::setup::{setup, Parameters};
use nym_compact_ecash::{Base58, PayInfo};
use nym_credential_storage::error::StorageError;
use nym_credential_storage::storage::Storage;
use nym_validator_client::coconut::all_coconut_api_clients;
use nym_credentials::obtain_aggregate_verification_key;
use nym_validator_client::coconut::all_ecash_api_clients;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use std::str::FromStr;
use {
nym_coconut_interface::Base58,
nym_credentials::coconut::{
bandwidth::prepare_for_spending, utils::obtain_aggregate_verification_key,
},
};
pub mod acquire;
pub mod error;
@@ -20,68 +19,89 @@ pub mod error;
pub struct BandwidthController<C, St> {
storage: St,
client: C,
ecash_keypair: KeyPairUser,
ecash_params: Parameters,
}
impl<C, St: Storage> BandwidthController<C, St> {
pub fn new(storage: St, client: C) -> Self {
BandwidthController { storage, client }
pub fn new(
storage: St,
client: C,
ecash_keypair: KeyPairUser,
ecash_params: Parameters,
) -> Self {
BandwidthController {
storage,
client,
ecash_keypair,
ecash_params,
}
}
pub fn storage(&self) -> &St {
&self.storage
}
pub async fn prepare_coconut_credential(
pub async fn prepare_ecash_credential(
&self,
) -> Result<(nym_coconut_interface::Credential, i64), BandwidthControllerError>
provider_pk: [u8; 32],
) -> Result<(EcashCredential, Wallet, i64), BandwidthControllerError>
where
C: DkgQueryClient + Sync + Send,
<St as Storage>::StorageError: Send + Sync + 'static,
{
let bandwidth_credential = self
let ecash_wallet = self
.storage
.get_next_coconut_credential()
.get_next_ecash_wallet()
.await
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?;
let voucher_value = u64::from_str(&bandwidth_credential.voucher_value)
.map_err(|_| StorageError::InconsistentData)?;
let voucher_info = bandwidth_credential.voucher_info.clone();
let serial_number =
nym_coconut_interface::Attribute::try_from_bs58(bandwidth_credential.serial_number)?;
let binding_number =
nym_coconut_interface::Attribute::try_from_bs58(bandwidth_credential.binding_number)?;
let signature =
nym_coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?;
let epoch_id = u64::from_str(&bandwidth_credential.epoch_id)
.map_err(|_| StorageError::InconsistentData)?;
let coconut_api_clients = all_coconut_api_clients(&self.client, epoch_id).await?;
let wallet = Wallet::try_from_bs58(ecash_wallet.wallet)?;
let epoch_id =
u64::from_str(&ecash_wallet.epoch_id).map_err(|_| StorageError::InconsistentData)?;
let verification_key = obtain_aggregate_verification_key(&coconut_api_clients).await?;
let ecash_api_clients = all_ecash_api_clients(&self.client, epoch_id).await?;
let verification_key = obtain_aggregate_verification_key(&ecash_api_clients).await?;
let sk_user = self.ecash_keypair.secret_key();
let pay_info = PayInfo::generate_payinfo(provider_pk);
let nb_tickets = 1u64; //SW: TEMPORARY VALUE, what should we put there?
let wallet_value = u64::from_str(&ecash_wallet.value)
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?;
let credential_value = nb_tickets * wallet_value / (self.ecash_params.ll());
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
Ok((
prepare_for_spending(
voucher_value,
voucher_info,
serial_number,
binding_number,
epoch_id,
&signature,
&verification_key,
)?,
bandwidth_credential.id,
))
let (payment, _) = wallet.spend(
&self.ecash_params,
&verification_key,
&sk_user,
&pay_info,
false,
nb_tickets,
)?;
let credential = EcashCredential::new(payment, credential_value, pay_info, epoch_id);
Ok((credential, wallet, ecash_wallet.id))
}
pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError>
pub async fn update_ecash_wallet(
&self,
wallet: Wallet,
id: i64,
) -> Result<(), BandwidthControllerError>
where
<St as Storage>::StorageError: Send + Sync + 'static,
{
// JS: shouldn't we send some contract/validator/gateway message here to actually, you know,
// consume it?
let consumed = wallet.l() >= setup(100).ll(); //temporary, depends on parameters distribution
let wallet_string = wallet.to_bs58();
self.storage
.consume_coconut_credential(id)
.update_ecash_wallet(wallet_string, id, consumed)
.await
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
}
@@ -96,6 +116,8 @@ where
BandwidthController {
storage: self.storage.clone(),
client: self.client.clone(),
ecash_keypair: self.ecash_keypair.clone(),
ecash_params: self.ecash_params.clone(),
}
}
}
+1
View File
@@ -34,6 +34,7 @@ zeroize = { workspace = true }
nym-bandwidth-controller = { path = "../bandwidth-controller" }
nym-config = { path = "../config" }
nym-crypto = { path = "../crypto" }
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
nym-gateway-client = { path = "../client-libs/gateway-client" }
nym-gateway-requests = { path = "../../gateway/gateway-requests" }
@@ -34,6 +34,7 @@ use crate::{config, spawn_future};
use futures::channel::mpsc;
use log::{debug, error, info};
use nym_bandwidth_controller::BandwidthController;
use nym_compact_ecash::setup::Parameters;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_crypto::asymmetric::encryption;
use nym_gateway_client::{
@@ -49,7 +50,10 @@ use nym_task::{TaskClient, TaskHandle};
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::HardcodedTopologyProvider;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::fmt::Debug;
use std::ops::Deref;
use std::path::Path;
use std::sync::Arc;
use url::Url;
@@ -556,6 +560,23 @@ where
setup_gateway(setup_method, key_store, details_store).await
}
async fn get_ecash_parameters(nym_api_urls: Vec<Url>) -> Result<Parameters, ClientCoreError> {
let nym_api = nym_api_urls
.choose(&mut thread_rng())
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
let validator_client = nym_validator_client::NymApiClient::new(nym_api.clone());
match validator_client.ecash_parameters().await {
Err(err) => {
error!(
"Failed to grab ecash parameters - {err}\n Plesae try again in a few minutes"
);
Err(ClientCoreError::ValidatorClientError(err))
}
Ok(response) => Ok(response.params),
}
}
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
where
S::ReplyStore: Send + Sync,
@@ -612,9 +633,16 @@ where
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
let bandwidth_controller = self
.dkg_query_client
.map(|client| BandwidthController::new(credential_store, client));
let ecash_parameters =
Self::get_ecash_parameters(self.config.get_nym_api_endpoints()).await?;
let bandwidth_controller = self.dkg_query_client.map(|client| {
BandwidthController::new(
credential_store,
client,
init_res.managed_keys.ecash_keypair().deref().clone(),
ecash_parameters,
)
});
let topology_provider = Self::setup_topology_provider(
self.custom_topology_provider.take(),
@@ -9,6 +9,8 @@ use crate::config::Config;
use crate::error::ClientCoreError;
use log::{error, info};
use nym_bandwidth_controller::BandwidthController;
use nym_compact_ecash::scheme::keygen::KeyPairUser;
use nym_compact_ecash::setup::Parameters;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_validator_client::nyxd;
use nym_validator_client::QueryHttpRpcNyxdClient;
@@ -101,25 +103,30 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
}
}
//SW Is this used anywhere?
pub fn create_bandwidth_controller<St: CredentialStorage>(
config: &Config,
storage: St,
ecash_keypair: KeyPairUser,
ecash_params: Parameters,
) -> BandwidthController<QueryHttpRpcNyxdClient, St> {
let nyxd_url = config
.get_validator_endpoints()
.pop()
.expect("No nyxd validator endpoint provided");
create_bandwidth_controller_with_urls(nyxd_url, storage)
create_bandwidth_controller_with_urls(nyxd_url, storage, ecash_keypair, ecash_params)
}
pub fn create_bandwidth_controller_with_urls<St: CredentialStorage>(
nyxd_url: Url,
storage: St,
ecash_keypair: KeyPairUser,
ecash_params: Parameters,
) -> BandwidthController<QueryHttpRpcNyxdClient, St> {
let client = default_query_dkg_client(nyxd_url);
BandwidthController::new(storage, client)
BandwidthController::new(storage, client, ecash_keypair, ecash_params)
}
pub fn default_query_dkg_client_from_config(config: &Config) -> QueryHttpRpcNyxdClient {
@@ -2,6 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::key_manager::persistence::KeyStore;
use nym_compact_ecash::scheme::keygen::KeyPairUser;
use nym_compact_ecash::setup::GroupParameters;
use nym_compact_ecash::{generate_keypair_user, PublicKeyUser};
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
@@ -87,6 +90,14 @@ impl ManagedKeys {
}
}
pub fn ecash_keypair(&self) -> Arc<KeyPairUser> {
match self {
ManagedKeys::Initial(keys) => keys.ecash_keypair(),
ManagedKeys::FullyDerived(keys) => keys.ecash_keypair(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn ack_key(&self) -> Arc<AckKey> {
match self {
ManagedKeys::Initial(keys) => keys.ack_key(),
@@ -124,6 +135,14 @@ impl ManagedKeys {
}
}
pub fn ecash_public_key(&self) -> PublicKeyUser {
match self {
ManagedKeys::Initial(keys) => keys.ecash_keypair().public_key(),
ManagedKeys::FullyDerived(keys) => keys.ecash_keypair().public_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn ensure_gateway_key(&self, gateway_shared_key: Option<Arc<SharedKeys>>) {
if let ManagedKeys::FullyDerived(key_manager) = &self {
if self.gateway_shared_key().is_none() && gateway_shared_key.is_none() {
@@ -185,6 +204,9 @@ pub struct KeyManagerBuilder {
/// key used for producing and processing acknowledgement packets.
ack_key: Arc<AckKey>,
/// key used for ecash wallet
ecash_keypair: Arc<KeyPairUser>,
}
impl KeyManagerBuilder {
@@ -197,6 +219,7 @@ impl KeyManagerBuilder {
identity_keypair: Arc::new(identity::KeyPair::new(rng)),
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
ack_key: Arc::new(AckKey::new(rng)),
ecash_keypair: Arc::new(generate_keypair_user(&GroupParameters::new().unwrap())),
}
}
@@ -207,6 +230,7 @@ impl KeyManagerBuilder {
KeyManager {
identity_keypair: self.identity_keypair,
encryption_keypair: self.encryption_keypair,
ecash_keypair: self.ecash_keypair,
gateway_shared_key,
ack_key: self.ack_key,
}
@@ -220,6 +244,10 @@ impl KeyManagerBuilder {
Arc::clone(&self.encryption_keypair)
}
pub fn ecash_keypair(&self) -> Arc<KeyPairUser> {
Arc::clone(&self.ecash_keypair)
}
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
}
@@ -240,6 +268,9 @@ pub struct KeyManager {
/// encryption key associated with the client instance.
encryption_keypair: Arc<encryption::KeyPair>,
/// ecash key associated with the client instance
ecash_keypair: Arc<KeyPairUser>,
/// shared key derived with the gateway during "registration handshake"
// I'm not a fan of how we broke the nice transition of `KeyManagerBuilder` -> `KeyManager`
// by making this field optional.
@@ -255,12 +286,14 @@ impl KeyManager {
pub fn from_keys(
id_keypair: identity::KeyPair,
enc_keypair: encryption::KeyPair,
ecash_keypair: KeyPairUser,
gateway_shared_key: Option<SharedKeys>,
ack_key: AckKey,
) -> Self {
Self {
identity_keypair: Arc::new(id_keypair),
encryption_keypair: Arc::new(enc_keypair),
ecash_keypair: Arc::new(ecash_keypair),
gateway_shared_key: gateway_shared_key.map(Arc::new),
ack_key: Arc::new(ack_key),
}
@@ -283,6 +316,12 @@ impl KeyManager {
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
Arc::clone(&self.encryption_keypair)
}
/// Gets an atomically reference counted pointer to [`encryption::KeyPair`].
pub fn ecash_keypair(&self) -> Arc<KeyPairUser> {
Arc::clone(&self.ecash_keypair)
}
/// Gets an atomically reference counted pointer to [`AckKey`].
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
@@ -310,6 +349,7 @@ impl KeyManager {
KeyManagerBuilder {
identity_keypair: self.identity_keypair,
encryption_keypair: self.encryption_keypair,
ecash_keypair: self.ecash_keypair,
ack_key: self.ack_key,
}
}
@@ -320,6 +360,7 @@ fn _assert_keys_zeroize_on_drop() {
_assert_zeroize_on_drop::<identity::KeyPair>();
_assert_zeroize_on_drop::<encryption::KeyPair>();
_assert_zeroize_on_drop::<KeyPairUser>();
_assert_zeroize_on_drop::<AckKey>();
_assert_zeroize_on_drop::<SharedKeys>();
}
@@ -3,6 +3,7 @@
use crate::client::key_manager::KeyManager;
use async_trait::async_trait;
use nym_compact_ecash::scheme::keygen::KeyPairUser;
use std::error::Error;
use tokio::sync::Mutex;
@@ -104,6 +105,12 @@ impl OnDiskKeys {
self.load_keypair(identity_paths, "identity")
}
#[doc(hidden)]
pub fn load_ecash_keypair(&self) -> Result<KeyPairUser, OnDiskKeysError> {
let ecash_paths = self.paths.ecash_key_pair_path();
self.load_keypair(ecash_paths, "ecash")
}
fn load_key<T: PemStorableKey>(
&self,
path: &std::path::Path,
@@ -159,6 +166,7 @@ impl OnDiskKeys {
fn load_keys(&self) -> Result<KeyManager, OnDiskKeysError> {
let identity_keypair = self.load_identity_keypair()?;
let encryption_keypair = self.load_encryption_keypair()?;
let ecash_keypair = self.load_ecash_keypair()?;
let ack_key: AckKey = self.load_key(self.paths.ack_key(), "ack key")?;
let gateway_shared_key: Option<SharedKeys> = self
@@ -168,6 +176,7 @@ impl OnDiskKeys {
Ok(KeyManager::from_keys(
identity_keypair,
encryption_keypair,
ecash_keypair,
gateway_shared_key,
ack_key,
))
@@ -178,6 +187,7 @@ impl OnDiskKeys {
let identity_paths = self.paths.identity_key_pair_path();
let encryption_paths = self.paths.encryption_key_pair_path();
let ecash_paths = self.paths.ecash_key_pair_path();
self.store_keypair(
keys.identity_keypair.as_ref(),
@@ -189,6 +199,7 @@ impl OnDiskKeys {
encryption_paths,
"encryption keys",
)?;
self.store_keypair(keys.ecash_keypair.as_ref(), ecash_paths, "ecash keys")?;
self.store_key(keys.ack_key.as_ref(), self.paths.ack_key(), "ack key")?;
@@ -8,6 +8,8 @@ pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem";
pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem";
pub const DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME: &str = "private_encryption.pem";
pub const DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME: &str = "public_encryption.pem";
pub const DEFAULT_PRIVATE_ECASH_KEY_FILENAME: &str = "private_ecash.pem";
pub const DEFAULT_PUBLIC_ECASH_KEY_FILENAME: &str = "public_ecash.pem";
pub const DEFAULT_GATEWAY_SHARED_KEY_FILENAME: &str = "gateway_shared.pem";
pub const DEFAULT_ACK_KEY_FILENAME: &str = "ack_key.pem";
@@ -25,6 +27,12 @@ pub struct ClientKeysPaths {
/// Path to file containing public encryption key.
pub public_encryption_key_file: PathBuf,
/// Path to file containing private ecash key.
pub private_ecash_key_file: PathBuf,
/// Path to file containing public ecash key.
pub public_ecash_key_file: PathBuf,
/// Path to file containing shared key derived with the specified gateway that is used
/// for all communication with it.
pub gateway_shared_key_file: PathBuf,
@@ -43,6 +51,8 @@ impl ClientKeysPaths {
public_identity_key_file: base_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME),
private_encryption_key_file: base_dir.join(DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME),
public_encryption_key_file: base_dir.join(DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME),
private_ecash_key_file: base_dir.join(DEFAULT_PRIVATE_ECASH_KEY_FILENAME),
public_ecash_key_file: base_dir.join(DEFAULT_PUBLIC_ECASH_KEY_FILENAME),
gateway_shared_key_file: base_dir.join(DEFAULT_GATEWAY_SHARED_KEY_FILENAME),
ack_key_file: base_dir.join(DEFAULT_ACK_KEY_FILENAME),
}
@@ -62,11 +72,20 @@ impl ClientKeysPaths {
)
}
pub fn ecash_key_pair_path(&self) -> nym_pemstore::KeyPairPath {
nym_pemstore::KeyPairPath::new(
self.private_ecash_key().to_path_buf(),
self.public_ecash_key().to_path_buf(),
)
}
pub fn any_file_exists(&self) -> bool {
matches!(self.public_identity_key_file.try_exists(), Ok(true))
|| matches!(self.private_identity_key_file.try_exists(), Ok(true))
|| matches!(self.public_encryption_key_file.try_exists(), Ok(true))
|| matches!(self.private_encryption_key_file.try_exists(), Ok(true))
|| matches!(self.public_ecash_key_file.try_exists(), Ok(true))
|| matches!(self.private_ecash_key_file.try_exists(), Ok(true))
|| matches!(self.gateway_shared_key_file.try_exists(), Ok(true))
|| matches!(self.ack_key_file.try_exists(), Ok(true))
}
@@ -76,6 +95,8 @@ impl ClientKeysPaths {
.or_else(|| file_exists(&self.private_identity_key_file))
.or_else(|| file_exists(&self.public_encryption_key_file))
.or_else(|| file_exists(&self.private_encryption_key_file))
.or_else(|| file_exists(&self.public_ecash_key_file))
.or_else(|| file_exists(&self.private_ecash_key_file))
.or_else(|| file_exists(&self.gateway_shared_key_file))
.or_else(|| file_exists(&self.ack_key_file))
}
@@ -100,6 +121,14 @@ impl ClientKeysPaths {
&self.public_encryption_key_file
}
pub fn private_ecash_key(&self) -> &Path {
&self.private_ecash_key_file
}
pub fn public_ecash_key(&self) -> &Path {
&self.public_ecash_key_file
}
pub fn gateway_shared_key(&self) -> &Path {
&self.gateway_shared_key_file
}
@@ -1,7 +1,9 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::disk_persistence::keys_paths::ClientKeysPaths;
use crate::config::disk_persistence::keys_paths::{
ClientKeysPaths, DEFAULT_PRIVATE_ECASH_KEY_FILENAME, DEFAULT_PUBLIC_ECASH_KEY_FILENAME,
};
use crate::config::disk_persistence::{CommonClientPaths, DEFAULT_GATEWAY_DETAILS_FILENAME};
use crate::error::ClientCoreError;
use serde::{Deserialize, Serialize};
@@ -10,7 +12,7 @@ use std::path::PathBuf;
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CommonClientPathsV1_1_20_2 {
pub keys: ClientKeysPaths,
pub keys: ClientKeysPathsV1_1_20_2,
pub credentials_database: PathBuf,
pub reply_surb_database: PathBuf,
}
@@ -23,10 +25,53 @@ impl CommonClientPathsV1_1_20_2 {
}
})?;
Ok(CommonClientPaths {
keys: self.keys,
keys: self.keys.upgrade_default()?,
gateway_details: data_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME),
credentials_database: self.credentials_database,
reply_surb_database: self.reply_surb_database,
})
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ClientKeysPathsV1_1_20_2 {
/// Path to file containing private identity key.
pub private_identity_key_file: PathBuf,
/// Path to file containing public identity key.
pub public_identity_key_file: PathBuf,
/// Path to file containing private encryption key.
pub private_encryption_key_file: PathBuf,
/// Path to file containing public encryption key.
pub public_encryption_key_file: PathBuf,
/// Path to file containing shared key derived with the specified gateway that is used
/// for all communication with it.
pub gateway_shared_key_file: PathBuf,
/// Path to file containing key used for encrypting and decrypting the content of an
/// acknowledgement so that nobody besides the client knows which packet it refers to.
pub ack_key_file: PathBuf,
}
impl ClientKeysPathsV1_1_20_2 {
pub fn upgrade_default(self) -> Result<ClientKeysPaths, ClientCoreError> {
let data_dir = self.gateway_shared_key_file.parent().ok_or_else(|| {
ClientCoreError::UnableToUpgradeConfigFile {
new_version: "1.1.20-2".to_string(),
}
})?;
Ok(ClientKeysPaths {
private_identity_key_file: self.private_identity_key_file,
public_identity_key_file: self.public_identity_key_file,
private_encryption_key_file: self.private_encryption_key_file,
public_encryption_key_file: self.public_encryption_key_file,
private_ecash_key_file: data_dir.join(DEFAULT_PRIVATE_ECASH_KEY_FILENAME),
public_ecash_key_file: data_dir.join(DEFAULT_PUBLIC_ECASH_KEY_FILENAME),
gateway_shared_key_file: self.gateway_shared_key_file,
ack_key_file: self.ack_key_file,
})
}
}
@@ -19,6 +19,7 @@ tokio = { version = "1.24.1", features = ["macros"] }
# internal
nym-bandwidth-controller = { path = "../../bandwidth-controller" }
nym-coconut-interface = { path = "../../coconut-interface" }
nym-compact-ecash = { path = "../../nym_offline_compact_ecash" }
nym-credential-storage = { path = "../../credential-storage" }
nym-crypto = { path = "../../crypto" }
nym-gateway-requests = { path = "../../../gateway/gateway-requests" }
@@ -12,7 +12,7 @@ use crate::{cleanup_socket_message, try_decrypt_binary_message};
use futures::{SinkExt, StreamExt};
use log::*;
use nym_bandwidth_controller::BandwidthController;
use nym_coconut_interface::Credential;
use nym_compact_ecash::scheme::EcashCredential;
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_crypto::asymmetric::identity;
@@ -513,14 +513,14 @@ impl<C, St> GatewayClient<C, St> {
}
}
async fn claim_coconut_bandwidth(
async fn claim_ecash_bandwidth(
&mut self,
credential: Credential,
credential: EcashCredential,
) -> Result<(), GatewayClientError> {
let mut rng = OsRng;
let iv = IV::new_random(&mut rng);
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential(
let msg = ClientControlRequest::new_enc_ecash_credential(
&credential,
self.shared_key.as_ref().unwrap(),
iv,
@@ -567,18 +567,18 @@ impl<C, St> GatewayClient<C, St> {
return self.try_claim_testnet_bandwidth().await;
}
let (credential, credential_id) = self
let (credential, new_wallet, wallet_id) = self
.bandwidth_controller
.as_ref()
.unwrap()
.prepare_coconut_credential()
.prepare_ecash_credential(self.gateway_identity.to_bytes())
.await?;
self.claim_coconut_bandwidth(credential).await?;
self.claim_ecash_bandwidth(credential).await?;
self.bandwidth_controller
.as_ref()
.unwrap()
.consume_credential(credential_id)
.update_ecash_wallet(new_wallet, wallet_id)
.await?;
Ok(())
@@ -671,6 +671,7 @@ impl<C, St> GatewayClient<C, St> {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
//SW NOTE : Logic to stop sending packet is there already. We only need to update bandwidth_remaining
if (mix_packet.packet().len() as i64) > self.bandwidth_remaining {
return Err(GatewayClientError::NotEnoughBandwidth(
mix_packet.packet().len() as i64,
@@ -33,6 +33,7 @@ futures = { workspace = true }
openssl = { version = "^0.10.55", features = ["vendored"], optional = true }
nym-coconut-interface = { path = "../../coconut-interface" }
nym-compact-ecash = { path = "../../nym_offline_compact_ecash" }
nym-network-defaults = { path = "../../network-defaults" }
nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
@@ -9,7 +9,8 @@ use crate::{
ReqwestRpcClient, ValidatorClientError,
};
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
BlindSignRequestBody, BlindedSignatureResponse, EcashParametersResponse,
OfflineVerifyCredentialBody, OnlineVerifyCredentialBody, VerifyCredentialResponse,
};
use nym_api_requests::models::{DescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::models::{
@@ -331,13 +332,21 @@ impl NymApiClient {
Ok(self.nym_api.blind_sign(request_body).await?)
}
pub async fn verify_bandwidth_credential(
pub async fn verify_offline_credential(
&self,
request_body: &VerifyCredentialBody,
request_body: &OfflineVerifyCredentialBody,
) -> Result<VerifyCredentialResponse, ValidatorClientError> {
Ok(self
.nym_api
.verify_bandwidth_credential(request_body)
.await?)
Ok(self.nym_api.verify_offline_credential(request_body).await?)
}
pub async fn verify_online_credential(
&self,
request_body: &OnlineVerifyCredentialBody,
) -> Result<VerifyCredentialResponse, ValidatorClientError> {
Ok(self.nym_api.verify_online_credential(request_body).await?)
}
pub async fn ecash_parameters(&self) -> Result<EcashParametersResponse, ValidatorClientError> {
Ok(self.nym_api.ecash_parameters().await?)
}
}
@@ -6,7 +6,8 @@ use crate::nyxd::error::NyxdError;
use crate::NymApiClient;
use nym_coconut_dkg_common::types::{EpochId, NodeIndex};
use nym_coconut_dkg_common::verification_key::ContractVKShare;
use nym_coconut_interface::{Base58, CoconutError, VerificationKey};
use nym_compact_ecash::error::CompactEcashError;
use nym_compact_ecash::{Base58, VerificationKeyAuth};
use thiserror::Error;
use url::Url;
@@ -14,7 +15,7 @@ use url::Url;
#[derive(Clone)]
pub struct CoconutApiClient {
pub api_client: NymApiClient,
pub verification_key: VerificationKey,
pub verification_key: VerificationKeyAuth,
pub node_id: NodeIndex,
pub cosmos_address: cosmrs::AccountId,
}
@@ -43,7 +44,7 @@ pub enum CoconutApiError {
#[error("the provided verification key is malformed: {source}")]
MalformedVerificationKey {
#[from]
source: CoconutError,
source: CompactEcashError,
},
#[error("the provided account address is malformed: {source}")]
@@ -65,14 +66,14 @@ impl TryFrom<ContractVKShare> for CoconutApiClient {
Ok(CoconutApiClient {
api_client: NymApiClient::new(url_address),
verification_key: VerificationKey::try_from_bs58(&share.share)?,
verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
node_id: share.node_index,
cosmos_address: share.owner.as_str().parse()?,
})
}
}
pub async fn all_coconut_api_clients<C>(
pub async fn all_ecash_api_clients<C>(
client: &C,
epoch_id: EpochId,
) -> Result<Vec<CoconutApiClient>, CoconutApiError>
@@ -6,7 +6,8 @@ use crate::nym_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
use async_trait::async_trait;
use http_api_client::{ApiClient, NO_PARAMS};
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
BlindSignRequestBody, BlindedSignatureResponse, EcashParametersResponse,
OfflineVerifyCredentialBody, OnlineVerifyCredentialBody, VerifyCredentialResponse,
};
use nym_api_requests::models::{
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
@@ -382,16 +383,16 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn verify_bandwidth_credential(
async fn verify_offline_credential(
&self,
request_body: &VerifyCredentialBody,
request_body: &OfflineVerifyCredentialBody,
) -> Result<VerifyCredentialResponse, NymAPIError> {
self.post_json(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::COCONUT_VERIFY_BANDWIDTH_CREDENTIAL,
routes::ECASH_VERIFY_OFFLINE_CREDENTIAL,
],
NO_PARAMS,
request_body,
@@ -399,6 +400,36 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn verify_online_credential(
&self,
request_body: &OnlineVerifyCredentialBody,
) -> Result<VerifyCredentialResponse, NymAPIError> {
self.post_json(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::ECASH_VERIFY_ONLINE_CREDENTIAL,
],
NO_PARAMS,
request_body,
)
.await
}
async fn ecash_parameters(&self) -> Result<EcashParametersResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::ECASH_PARAMETERS,
],
NO_PARAMS,
)
.await
}
async fn get_service_providers(&self) -> Result<ServicesListResponse, NymAPIError> {
log::trace!("Getting service providers");
self.get_json(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS)
@@ -16,7 +16,9 @@ pub const COCONUT_ROUTES: &str = "coconut";
pub const BANDWIDTH: &str = "bandwidth";
pub const COCONUT_BLIND_SIGN: &str = "blind-sign";
pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential";
pub const ECASH_VERIFY_OFFLINE_CREDENTIAL: &str = "verify-offline-credential";
pub const ECASH_VERIFY_ONLINE_CREDENTIAL: &str = "verify-online-credential";
pub const ECASH_PARAMETERS: &str = "ecash-parameters";
pub const STATUS_ROUTES: &str = "status";
pub const MIXNODE: &str = "mixnode";
@@ -36,6 +36,14 @@ pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
bail!("the loaded config does not have a credentials store information")
};
let Ok(ecash_key_path) = loaded.try_get_ecash_key() else {
bail!("the loaded config does not have an ecash key path information")
};
let Ok(ecash_keypair) = nym_pemstore::load_keypair(&ecash_key_path) else {
bail!("invalid secret key in the config path")
};
println!(
"using credentials store at '{}'",
credentials_store.display()
@@ -45,7 +53,14 @@ pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
let coin = Coin::new(args.amount as u128, denom);
let persistent_storage = initialise_persistent_storage(credentials_store).await;
utils::issue_credential(&client, coin, &persistent_storage, args.recovery_dir).await?;
utils::issue_credential(
&client,
coin,
ecash_keypair,
&persistent_storage,
args.recovery_dir,
)
.await?;
Ok(())
}
+27
View File
@@ -123,6 +123,18 @@ impl CommonConfigsWrapper {
}
}
pub(crate) fn try_get_ecash_key(&self) -> anyhow::Result<nym_pemstore::KeyPairPath> {
match self {
CommonConfigsWrapper::NymClients(cfg) => {
Ok(cfg.storage_paths.inner.keys.ecash_key_pair_path())
}
CommonConfigsWrapper::NymApi(cfg) => {
Ok(cfg.network_monitor.storage_paths.ecash_keypair_path())
}
CommonConfigsWrapper::Unknown(cfg) => cfg.try_get_ecash_key(),
}
}
pub(crate) fn try_get_credentials_store(&self) -> anyhow::Result<PathBuf> {
match self {
CommonConfigsWrapper::NymClients(cfg) => {
@@ -159,6 +171,17 @@ struct NymApiConfigNetworkMonitorLight {
#[derive(Deserialize, Debug)]
struct NetworkMonitorPaths {
credentials_database_path: PathBuf,
ecash_private_key_path: PathBuf,
ecash_public_key_path: PathBuf,
}
impl NetworkMonitorPaths {
fn ecash_keypair_path(&self) -> nym_pemstore::KeyPairPath {
nym_pemstore::KeyPairPath::new(
self.ecash_private_key_path.clone(),
self.ecash_public_key_path.clone(),
)
}
}
// a hacky way of reading common data from client configs (native, socks5, etc.)
@@ -215,6 +238,10 @@ impl UnknownConfigWrapper {
}
}
pub(crate) fn try_get_ecash_key(&self) -> anyhow::Result<nym_pemstore::KeyPairPath> {
todo!()
}
pub(crate) fn try_get_credentials_store(&self) -> anyhow::Result<PathBuf> {
let id_val = self
.find_value("credentials_database_path")
@@ -0,0 +1,14 @@
/*
* Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
CREATE TABLE ecash_wallets
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
voucher_info TEXT NOT NULL,
wallet TEXT NOT NULL UNIQUE,
value TEXT NOT NULL,
epoch_id TEXT NOT NULL,
consumed BOOLEAN NOT NULL
);
@@ -1,13 +1,14 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::CoconutCredential;
use crate::models::{CoconutCredential, EcashWallet};
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Clone)]
pub struct CoconutCredentialManager {
inner: Arc<RwLock<Vec<CoconutCredential>>>,
ecash: Arc<RwLock<Vec<EcashWallet>>>,
}
impl CoconutCredentialManager {
@@ -15,6 +16,7 @@ impl CoconutCredentialManager {
pub fn new() -> Self {
CoconutCredentialManager {
inner: Arc::new(RwLock::new(Vec::new())),
ecash: Arc::new(RwLock::new(Vec::new())),
}
}
@@ -50,6 +52,39 @@ impl CoconutCredentialManager {
});
}
/// Inserts provided signature into the database.
///
/// # Arguments
///
/// * `voucher_info`: What type of credential it is.
/// * `signature`: Ecash wallet credential in the form of a wallet.
/// * `value` : The value of the ecash wallet
/// * `epoch_id`: The epoch when it was signed.
pub async fn insert_ecash_wallet(
&self,
voucher_info: String,
wallet: String,
value: String,
epoch_id: String,
) {
let mut creds = self.ecash.write().await;
let id = creds.len() as i64;
creds.push(EcashWallet {
id,
voucher_info,
wallet,
value,
epoch_id,
consumed: false,
});
}
/// Tries to retrieve one of the stored, unused credentials.
pub async fn get_next_ecash_wallet(&self) -> Option<EcashWallet> {
let creds = self.ecash.read().await;
creds.iter().find(|c| !c.consumed).cloned()
}
/// Tries to retrieve one of the stored, unused credentials.
pub async fn get_next_coconut_credential(&self) -> Option<CoconutCredential> {
let creds = self.inner.read().await;
@@ -67,4 +102,12 @@ impl CoconutCredentialManager {
cred.consumed = true;
}
}
pub async fn update_ecash_wallet(&self, wallet: String, id: i64, consumed: bool) {
let mut creds = self.ecash.write().await;
if let Some(cred) = creds.get_mut(id as usize) {
cred.wallet = wallet;
cred.consumed = consumed;
}
}
}
@@ -1,7 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::CoconutCredential;
use crate::models::{CoconutCredential, EcashWallet};
#[derive(Clone)]
pub struct CoconutCredentialManager {
@@ -45,6 +45,41 @@ impl CoconutCredentialManager {
Ok(())
}
/// Inserts provided signature into the database.
///
/// # Arguments
///
/// * `voucher_info`: What type of credential it is.
/// * `signature`: Ecash wallet credential in the form of a wallet.
/// * `value` : The value of the ecash wallet
/// * `epoch_id`: The epoch when it was signed.
pub async fn insert_ecash_wallet(
&self,
voucher_info: String,
wallet: String,
value: String,
epoch_id: String,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"INSERT INTO ecash_wallets(voucher_info, wallet, value, epoch_id, consumed) VALUES (?, ?, ?, ?, ?)",
voucher_info, wallet, value, epoch_id, false
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
/// Tries to retrieve one of the stored, unused credentials.
pub async fn get_next_ecash_wallet(&self) -> Result<Option<EcashWallet>, sqlx::Error> {
sqlx::query_as!(
EcashWallet,
"SELECT * FROM ecash_wallets WHERE NOT consumed"
)
.fetch_optional(&self.connection_pool)
.await
}
/// Tries to retrieve one of the stored, unused credentials.
pub async fn get_next_coconut_credential(
&self,
@@ -71,4 +106,29 @@ impl CoconutCredentialManager {
.await?;
Ok(())
}
/// Consumes in the database the specified credential.
///
/// # Arguments
///
/// * `wallet` : New wallet string to update with
/// * `id`: Database id.
/// * `consumed` : If the wallet is entirely consumed
///
pub async fn update_ecash_wallet(
&self,
wallet: String,
id: i64,
consumed: bool,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"UPDATE ecash_wallets SET wallet = ?, consumed = ? WHERE id = ?",
wallet,
consumed,
id
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
}
@@ -3,7 +3,7 @@
use crate::backends::memory::CoconutCredentialManager;
use crate::error::StorageError;
use crate::models::CoconutCredential;
use crate::models::{CoconutCredential, EcashWallet};
use crate::storage::Storage;
use async_trait::async_trait;
@@ -50,6 +50,30 @@ impl Storage for EphemeralStorage {
Ok(())
}
async fn insert_ecash_wallet(
&self,
voucher_info: String,
wallet: String,
value: String,
epoch_id: String,
) -> Result<(), StorageError> {
self.coconut_credential_manager
.insert_ecash_wallet(voucher_info, wallet, value, epoch_id)
.await;
Ok(())
}
async fn get_next_ecash_wallet(&self) -> Result<EcashWallet, StorageError> {
let credential = self
.coconut_credential_manager
.get_next_ecash_wallet()
.await
.ok_or(StorageError::NoCredential)?;
Ok(credential)
}
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError> {
let credential = self
.coconut_credential_manager
@@ -67,4 +91,16 @@ impl Storage for EphemeralStorage {
Ok(())
}
async fn update_ecash_wallet(
&self,
wallet: String,
id: i64,
consumed: bool,
) -> Result<(), StorageError> {
self.coconut_credential_manager
.update_ecash_wallet(wallet, id, consumed)
.await;
Ok(())
}
}
+11
View File
@@ -13,3 +13,14 @@ pub struct CoconutCredential {
pub epoch_id: String,
pub consumed: bool,
}
#[derive(Clone)]
pub struct EcashWallet {
#[allow(dead_code)]
pub id: i64,
pub voucher_info: String,
pub wallet: String,
pub value: String,
pub epoch_id: String,
pub consumed: bool,
}
@@ -1,9 +1,9 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::backends::sqlite::CoconutCredentialManager;
use crate::error::StorageError;
use crate::storage::Storage;
use crate::{backends::sqlite::CoconutCredentialManager, models::EcashWallet};
use crate::models::CoconutCredential;
use async_trait::async_trait;
@@ -81,6 +81,30 @@ impl Storage for PersistentStorage {
Ok(())
}
async fn insert_ecash_wallet(
&self,
voucher_info: String,
wallet: String,
value: String,
epoch_id: String,
) -> Result<(), StorageError> {
self.coconut_credential_manager
.insert_ecash_wallet(voucher_info, wallet, value, epoch_id)
.await?;
Ok(())
}
async fn get_next_ecash_wallet(&self) -> Result<EcashWallet, StorageError> {
let credential = self
.coconut_credential_manager
.get_next_ecash_wallet()
.await?
.ok_or(StorageError::NoCredential)?;
Ok(credential)
}
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError> {
let credential = self
.coconut_credential_manager
@@ -98,4 +122,16 @@ impl Storage for PersistentStorage {
Ok(())
}
async fn update_ecash_wallet(
&self,
wallet: String,
id: i64,
consumed: bool,
) -> Result<(), StorageError> {
self.coconut_credential_manager
.update_ecash_wallet(wallet, id, consumed)
.await?;
Ok(())
}
}
+35 -1
View File
@@ -1,7 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::CoconutCredential;
use crate::models::{CoconutCredential, EcashWallet};
use async_trait::async_trait;
use std::error::Error;
@@ -29,6 +29,25 @@ pub trait Storage: Send + Sync {
epoch_id: String,
) -> Result<(), Self::StorageError>;
/// Inserts provided wallet into the database.
///
/// # Arguments
///
/// * `voucher_info`: What type of credential it is.
/// * `signature`: Ecash wallet credential in the form of a wallet.
/// * `value` : The value of the ecash wallet
/// * `epoch_id`: The epoch when it was signed.
async fn insert_ecash_wallet(
&self,
voucher_info: String,
signature: String,
value: String,
epoch_id: String,
) -> Result<(), Self::StorageError>;
/// Tries to retrieve one of the stored, unused credentials.
async fn get_next_ecash_wallet(&self) -> Result<EcashWallet, Self::StorageError>;
/// Tries to retrieve one of the stored, unused credentials.
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, Self::StorageError>;
@@ -38,4 +57,19 @@ pub trait Storage: Send + Sync {
///
/// * `id`: Id of the credential to be consumed.
async fn consume_coconut_credential(&self, id: i64) -> Result<(), Self::StorageError>;
/// Update in the database the specified credential.
///
/// # Arguments
///
/// * `wallet` : New Ecash wallet credential
/// * `id`: Id of the credential to be updated.
/// * `consumed`: if the credential is consumed or not
///
async fn update_ecash_wallet(
&self,
wallet: String,
id: i64,
consumed: bool,
) -> Result<(), Self::StorageError>;
}
+1
View File
@@ -16,3 +16,4 @@ nym-credential-storage = { path = "../../common/credential-storage" }
nym-validator-client = { path = "../../common/client-libs/validator-client" }
nym-config = { path = "../../common/config" }
nym-client-core = { path = "../../common/client-core" }
nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" }
+4 -1
View File
@@ -3,6 +3,7 @@ use crate::recovery_storage::RecoveryStorage;
use log::*;
use nym_bandwidth_controller::acquire::state::State;
use nym_client_core::config::disk_persistence::CommonClientPaths;
use nym_compact_ecash::scheme::keygen::KeyPairUser;
use nym_config::DEFAULT_DATA_DIR;
use nym_credential_storage::persistent_storage::PersistentStorage;
use nym_validator_client::nyxd::contract_traits::{CoconutBandwidthSigningClient, DkgQueryClient};
@@ -16,6 +17,7 @@ const SAFETY_BUFFER_SECS: u64 = 60; // 1 minute
pub async fn issue_credential<C>(
client: &C,
amount: Coin,
ecash_keypair: KeyPairUser,
persistent_storage: &PersistentStorage,
recovery_storage_path: PathBuf,
) -> Result<()>
@@ -39,7 +41,8 @@ where
}
};
let state = nym_bandwidth_controller::acquire::deposit(client, amount.clone()).await?;
let state =
nym_bandwidth_controller::acquire::deposit(client, amount.clone(), ecash_keypair).await?;
if nym_bandwidth_controller::acquire::get_credential(&state, client, persistent_storage)
.await
+1
View File
@@ -13,6 +13,7 @@ log = { workspace = true }
# I guess temporarily until we get serde support in coconut up and running
nym-coconut-interface = { path = "../coconut-interface" }
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "hashing"] }
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
+78 -180
View File
@@ -8,13 +8,16 @@
use cosmrs::tendermint::hash::Algorithm;
use cosmrs::tendermint::Hash;
use nym_coconut_interface::{
hash_to_scalar, prepare_blind_sign, Attribute, BlindSignRequest, Credential, Parameters,
PrivateAttribute, PublicAttribute, Signature, VerificationKey,
use nym_compact_ecash::{
scheme::{
keygen::KeyPairUser,
withdrawal::{RequestInfo, WithdrawalRequest},
},
setup::GroupParameters,
withdrawal_request,
};
use nym_crypto::asymmetric::{encryption, identity};
use super::utils::prepare_credential_for_spending;
use crate::error::Error;
pub const PUBLIC_ATTRIBUTES: u32 = 2;
@@ -22,16 +25,8 @@ pub const PRIVATE_ATTRIBUTES: u32 = 2;
pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES;
pub struct BandwidthVoucher {
// a random secret value generated by the client used for double-spending detection
serial_number: PrivateAttribute,
// a random secret value generated by the client used to bind multiple credentials together
binding_number: PrivateAttribute,
// the value (e.g., bandwidth) encoded in this voucher
voucher_value: PublicAttribute,
// the plain text value (e.g., bandwidth) encoded in this voucher
voucher_value_plain: String,
// a field with public information, e.g., type of voucher, interval etc.
voucher_info: PublicAttribute,
// the plain text information
voucher_info_plain: String,
// the hash of the deposit transaction
@@ -40,121 +35,109 @@ pub struct BandwidthVoucher {
signing_key: identity::PrivateKey,
// base58 encoded private key ensuring only this client receives the signature share
encryption_key: encryption::PrivateKey,
pedersen_commitments_openings: Vec<Attribute>,
blind_sign_request: BlindSignRequest,
ecash_keypair: KeyPairUser,
withdrawal_request_info: RequestInfo,
withdrawal_request: WithdrawalRequest,
}
impl BandwidthVoucher {
pub fn new(
params: &Parameters,
params: &GroupParameters,
voucher_value: String,
voucher_info: String,
tx_hash: Hash,
signing_key: identity::PrivateKey,
encryption_key: encryption::PrivateKey,
ecash_keypair: KeyPairUser,
) -> Self {
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let voucher_value_plain = voucher_value.clone();
let voucher_info_plain = voucher_info.clone();
let voucher_value = hash_to_scalar(voucher_value.as_bytes());
let voucher_info = hash_to_scalar(voucher_info.as_bytes());
let (pedersen_commitments_openings, blind_sign_request) = prepare_blind_sign(
params,
&[serial_number, binding_number],
&[voucher_value, voucher_info],
)
.unwrap();
let (withdrawal_request, withdrawal_request_info) =
withdrawal_request(params, &ecash_keypair.secret_key()).unwrap();
BandwidthVoucher {
serial_number,
binding_number,
voucher_value,
voucher_value_plain,
voucher_info,
voucher_info_plain,
tx_hash,
signing_key,
encryption_key,
pedersen_commitments_openings,
blind_sign_request,
ecash_keypair,
withdrawal_request_info,
withdrawal_request,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let serial_number_b = self.serial_number.to_bytes();
let binding_number_b = self.binding_number.to_bytes();
let voucher_value_plain_b = self.voucher_value_plain.as_bytes();
let voucher_info_plain_b = self.voucher_info_plain.as_bytes();
let tx_hash_b = self.tx_hash.as_bytes();
let signing_key_b = self.signing_key.to_bytes();
let encryption_key_b = self.encryption_key.to_bytes();
let blind_sign_request_b = self.blind_sign_request.to_bytes();
let ecash_key_b = self.ecash_keypair.to_bytes();
let withdrawal_request_b = self.withdrawal_request.to_bytes();
let withdrawal_request_info_b = self.withdrawal_request_info.to_bytes();
let mut ret = Vec::new();
ret.extend_from_slice(&serial_number_b);
ret.extend_from_slice(&binding_number_b);
ret.extend_from_slice(tx_hash_b);
ret.extend_from_slice(&signing_key_b);
ret.extend_from_slice(&encryption_key_b);
ret.extend_from_slice(&ecash_key_b);
ret.extend_from_slice(&(voucher_value_plain_b.len() as u64).to_be_bytes());
ret.extend_from_slice(&(voucher_info_plain_b.len() as u64).to_be_bytes());
ret.extend_from_slice(&(blind_sign_request_b.len() as u64).to_be_bytes());
ret.extend_from_slice(&(self.pedersen_commitments_openings.len() as u64).to_be_bytes());
ret.extend_from_slice(&(withdrawal_request_b.len() as u64).to_be_bytes());
ret.extend_from_slice(&(withdrawal_request_info_b.len() as u64).to_be_bytes());
ret.extend_from_slice(voucher_value_plain_b);
ret.extend_from_slice(voucher_info_plain_b);
ret.extend_from_slice(&blind_sign_request_b);
for commitment in self.pedersen_commitments_openings.iter() {
ret.extend_from_slice(&commitment.to_bytes());
}
ret.extend_from_slice(&withdrawal_request_b);
ret.extend_from_slice(&withdrawal_request_info_b);
ret
}
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 32 * 5 + 4 * 8 {
if bytes.len() < 32 * 3 + 80 + 4 * 8 {
return Err(Error::BandwidthVoucherDeserializationError(format!(
"Less then {} bytes needed",
32 * 5 + 4 * 8
32 * 3 + 80 + 4 * 8
)));
}
let mut buff = [0u8; 32];
let mut small_buff = [0u8; 8];
let scalar_err =
|| Error::BandwidthVoucherDeserializationError(String::from("Invalid Scalar"));
buff.copy_from_slice(&bytes[..32]);
let serial_number = Option::<PrivateAttribute>::from(PrivateAttribute::from_bytes(&buff))
.ok_or_else(scalar_err)?;
buff.copy_from_slice(&bytes[32..2 * 32]);
let binding_number = Option::<PrivateAttribute>::from(PrivateAttribute::from_bytes(&buff))
.ok_or_else(scalar_err)?;
buff.copy_from_slice(&bytes[2 * 32..3 * 32]);
buff.copy_from_slice(&bytes[0..32]);
let tx_hash = Hash::from_bytes(Algorithm::Sha256, &buff).map_err(|_| {
Error::BandwidthVoucherDeserializationError(String::from("Invalid transaction Hash"))
})?;
buff.copy_from_slice(&bytes[3 * 32..4 * 32]);
buff.copy_from_slice(&bytes[32..2 * 32]);
let signing_key = identity::PrivateKey::from_bytes(&buff).map_err(|_| {
Error::BandwidthVoucherDeserializationError(String::from("Invalid key"))
})?;
buff.copy_from_slice(&bytes[4 * 32..5 * 32]);
buff.copy_from_slice(&bytes[2 * 32..3 * 32]);
let encryption_key = encryption::PrivateKey::from_bytes(&buff).map_err(|_| {
Error::BandwidthVoucherDeserializationError(String::from("Invalid key"))
})?;
small_buff.copy_from_slice(&bytes[5 * 32..5 * 32 + 8]);
let voucher_value_plain_no = u64::from_be_bytes(small_buff) as usize;
small_buff.copy_from_slice(&bytes[5 * 32 + 8..5 * 32 + 2 * 8]);
let voucher_info_plain_no = u64::from_be_bytes(small_buff) as usize;
small_buff.copy_from_slice(&bytes[5 * 32 + 2 * 8..5 * 32 + 3 * 8]);
let blind_sign_request_no = u64::from_be_bytes(small_buff) as usize;
small_buff.copy_from_slice(&bytes[5 * 32 + 3 * 8..5 * 32 + 4 * 8]);
let pedersen_commitments_openings_no = u64::from_be_bytes(small_buff) as usize;
let total_length = 32 * 5
//ecash key
let ecash_keypair = KeyPairUser::from_bytes(&bytes[3 * 32..3 * 32 + 80])?;
small_buff.copy_from_slice(&bytes[3 * 32 + 80..3 * 32 + 80 + 8]);
let voucher_value_plain_no = u64::from_be_bytes(small_buff) as usize;
small_buff.copy_from_slice(&bytes[3 * 32 + 80 + 8..3 * 32 + 80 + 2 * 8]);
let voucher_info_plain_no = u64::from_be_bytes(small_buff) as usize;
small_buff.copy_from_slice(&bytes[3 * 32 + 80 + 2 * 8..3 * 32 + 80 + 3 * 8]);
let withdrawal_request_no = u64::from_be_bytes(small_buff) as usize;
small_buff.copy_from_slice(&bytes[3 * 32 + 80 + 3 * 8..3 * 32 + 80 + 4 * 8]);
let withdrawal_request_info_no = u64::from_be_bytes(small_buff) as usize;
let total_length = 32 * 3
+ 80
+ 4 * 8
+ voucher_value_plain_no
+ voucher_info_plain_no
+ blind_sign_request_no
+ pedersen_commitments_openings_no * 32;
+ withdrawal_request_no
+ withdrawal_request_info_no;
if bytes.len() != total_length {
return Err(Error::BandwidthVoucherDeserializationError(format!(
"Expected {total_length} bytes",
@@ -166,74 +149,58 @@ impl BandwidthVoucher {
"Invalid UTF8 string",
)))
};
let mut var_length_pointer = 5 * 32 + 4 * 8;
let mut var_length_pointer = 32 * 3 + 80 + 4 * 8;
let voucher_value_plain = String::from_utf8(
bytes[var_length_pointer..var_length_pointer + voucher_value_plain_no].to_vec(),
)
.or_else(utf_err)?;
let voucher_value = hash_to_scalar(&voucher_value_plain);
var_length_pointer += voucher_value_plain_no;
let voucher_info_plain = String::from_utf8(
bytes[var_length_pointer..var_length_pointer + voucher_info_plain_no].to_vec(),
)
.or_else(utf_err)?;
let voucher_info = hash_to_scalar(&voucher_info_plain);
var_length_pointer += voucher_info_plain_no;
let blind_sign_request = BlindSignRequest::from_bytes(
&bytes[var_length_pointer..var_length_pointer + blind_sign_request_no],
)?;
var_length_pointer += blind_sign_request_no;
let mut pedersen_commitments_openings = Vec::new();
for _ in 0..pedersen_commitments_openings_no {
buff.copy_from_slice(&bytes[var_length_pointer..var_length_pointer + 32]);
let commitment =
Option::<Attribute>::from(Attribute::from_bytes(&buff)).ok_or_else(scalar_err)?;
var_length_pointer += 32;
pedersen_commitments_openings.push(commitment);
}
let withdrawal_request = WithdrawalRequest::try_from(
&bytes[var_length_pointer..var_length_pointer + withdrawal_request_no],
)?;
var_length_pointer += withdrawal_request_no;
let withdrawal_request_info = RequestInfo::try_from(
&bytes[var_length_pointer..var_length_pointer + withdrawal_request_info_no],
)?;
Ok(Self {
serial_number,
binding_number,
voucher_value,
voucher_value_plain,
voucher_info,
voucher_info_plain,
tx_hash,
signing_key,
encryption_key,
pedersen_commitments_openings,
blind_sign_request,
ecash_keypair,
withdrawal_request,
withdrawal_request_info,
})
}
/// Check if the plain values correspond to the PublicAttributes
pub fn verify_against_plain(values: &[PublicAttribute], plain_values: &[String]) -> bool {
values.len() == 2
&& plain_values.len() == 2
&& values[0] == hash_to_scalar(&plain_values[0])
&& values[1] == hash_to_scalar(&plain_values[1])
}
pub fn tx_hash(&self) -> &Hash {
&self.tx_hash
}
pub fn get_public_attributes(&self) -> Vec<PublicAttribute> {
vec![self.voucher_value, self.voucher_info]
}
pub fn encryption_key(&self) -> &encryption::PrivateKey {
&self.encryption_key
}
pub fn pedersen_commitments_openings(&self) -> &Vec<Attribute> {
&self.pedersen_commitments_openings
pub fn ecash_keypair(&self) -> &KeyPairUser {
&self.ecash_keypair
}
pub fn blind_sign_request(&self) -> &BlindSignRequest {
&self.blind_sign_request
pub fn withdrawal_request(&self) -> &WithdrawalRequest {
&self.withdrawal_request
}
pub fn withdrawal_request_info(&self) -> &RequestInfo {
&self.withdrawal_request_info
}
pub fn get_voucher_value(&self) -> String {
@@ -247,49 +214,22 @@ impl BandwidthVoucher {
]
}
pub fn get_private_attributes(&self) -> Vec<PrivateAttribute> {
vec![self.serial_number, self.binding_number]
}
pub fn sign(&self, request: &BlindSignRequest) -> identity::Signature {
pub fn sign(&self, request: &WithdrawalRequest) -> identity::Signature {
let mut message = request.to_bytes();
message.extend_from_slice(self.tx_hash.to_string().as_bytes());
self.signing_key.sign(&message)
}
}
pub fn prepare_for_spending(
voucher_value: u64,
voucher_info: String,
serial_number: PrivateAttribute,
binding_number: PrivateAttribute,
epoch_id: u64,
signature: &Signature,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
let params = Parameters::new(TOTAL_ATTRIBUTES)?;
prepare_credential_for_spending(
&params,
voucher_value,
voucher_info,
serial_number,
binding_number,
epoch_id,
signature,
verification_key,
)
}
#[cfg(test)]
mod test {
use super::*;
use cosmrs::tendermint::hash::Algorithm;
use nym_coconut_interface::Base58;
use nym_compact_ecash::{generate_keypair_user, Base58};
use rand::rngs::OsRng;
fn voucher_fixture() -> BandwidthVoucher {
let params = Parameters::new(4).unwrap();
let params = GroupParameters::new().unwrap();
let mut rng = OsRng;
BandwidthVoucher::new(
&params,
@@ -306,6 +246,7 @@ mod test {
&encryption::KeyPair::new(&mut rng).private_key().to_bytes(),
)
.unwrap(),
generate_keypair_user(&params),
)
}
@@ -314,14 +255,10 @@ mod test {
let voucher = voucher_fixture();
let bytes = voucher.to_bytes();
let deserialized_voucher = BandwidthVoucher::try_from_bytes(&bytes).unwrap();
assert_eq!(voucher.serial_number, deserialized_voucher.serial_number);
assert_eq!(voucher.binding_number, deserialized_voucher.binding_number);
assert_eq!(voucher.voucher_value, deserialized_voucher.voucher_value);
assert_eq!(
voucher.voucher_value_plain,
deserialized_voucher.voucher_value_plain
);
assert_eq!(voucher.voucher_info, deserialized_voucher.voucher_info);
assert_eq!(
voucher.voucher_info_plain,
deserialized_voucher.voucher_info_plain
@@ -336,51 +273,12 @@ mod test {
deserialized_voucher.encryption_key.to_string()
);
assert_eq!(
voucher.pedersen_commitments_openings,
deserialized_voucher.pedersen_commitments_openings
voucher.withdrawal_request_info.to_bytes(),
deserialized_voucher.withdrawal_request_info.to_bytes()
);
assert_eq!(
voucher.blind_sign_request.to_bs58(),
deserialized_voucher.blind_sign_request.to_bs58()
voucher.withdrawal_request.to_bs58(),
deserialized_voucher.withdrawal_request.to_bs58()
);
}
#[test]
fn voucher_consistency() {
let voucher = voucher_fixture();
assert!(!BandwidthVoucher::verify_against_plain(
&[],
&voucher.get_public_attributes_plain()
));
assert!(!BandwidthVoucher::verify_against_plain(
&voucher.get_public_attributes(),
&[],
));
assert!(!BandwidthVoucher::verify_against_plain(
&voucher.get_public_attributes(),
&[
voucher.get_public_attributes_plain()[0].clone(),
String::new()
]
));
assert!(!BandwidthVoucher::verify_against_plain(
&voucher.get_public_attributes(),
&[
String::new(),
voucher.get_public_attributes_plain()[1].clone()
]
));
assert!(!BandwidthVoucher::verify_against_plain(
&[voucher.get_public_attributes()[0], Attribute::one()],
&voucher.get_public_attributes_plain()
));
assert!(!BandwidthVoucher::verify_against_plain(
&[Attribute::one(), voucher.get_public_attributes()[1]],
&voucher.get_public_attributes_plain()
));
assert!(BandwidthVoucher::verify_against_plain(
&voucher.get_public_attributes(),
&voucher.get_public_attributes_plain()
));
}
}
+73 -67
View File
@@ -1,14 +1,17 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES};
use crate::coconut::bandwidth::BandwidthVoucher;
use crate::coconut::params::{NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm};
use crate::error::Error;
use log::{debug, warn};
use nym_api_requests::coconut::BlindSignRequestBody;
use nym_coconut_interface::{
aggregate_signature_shares, aggregate_verification_keys, prove_bandwidth_credential, Attribute,
BlindedSignature, Credential, Parameters, Signature, SignatureShare, VerificationKey,
use nym_compact_ecash::scheme::Wallet;
use nym_compact_ecash::setup::GroupParameters;
use nym_compact_ecash::utils::BlindedSignature;
use nym_compact_ecash::{
aggregate_verification_keys, aggregate_wallets, issue_verify, PartialWallet,
VerificationKeyAuth,
};
use nym_crypto::asymmetric::encryption::PublicKey;
use nym_crypto::shared_key::recompute_shared_key;
@@ -17,7 +20,7 @@ use nym_validator_client::client::CoconutApiClient;
pub async fn obtain_aggregate_verification_key(
api_clients: &[CoconutApiClient],
) -> Result<VerificationKey, Error> {
) -> Result<VerificationKeyAuth, Error> {
if api_clients.is_empty() {
return Err(Error::NoValidatorsAvailable);
}
@@ -35,23 +38,24 @@ pub async fn obtain_aggregate_verification_key(
}
async fn obtain_partial_credential(
params: &Parameters,
params: &GroupParameters,
attributes: &BandwidthVoucher,
client: &nym_validator_client::client::NymApiClient,
validator_vk: &VerificationKey,
) -> Result<Signature, Error> {
let public_attributes = attributes.get_public_attributes();
validator_vk: &VerificationKeyAuth,
) -> Result<PartialWallet, Error> {
//let public_attributes = attributes.get_public_attributes();
let public_attributes_plain = attributes.get_public_attributes_plain();
let private_attributes = attributes.get_private_attributes();
let blind_sign_request = attributes.blind_sign_request();
//let private_attributes = attributes.get_private_attributes();
let withdrawal_request = attributes.withdrawal_request();
let blind_sign_request_body = BlindSignRequestBody::new(
blind_sign_request,
withdrawal_request,
attributes.tx_hash().to_string(),
attributes.sign(blind_sign_request).to_base58_string(),
&public_attributes,
public_attributes_plain,
(public_attributes.len() + private_attributes.len()) as u32,
attributes.sign(withdrawal_request).to_base58_string(),
attributes.ecash_keypair().public_key().to_base58_string(),
// &public_attributes,
public_attributes_plain.clone(),
(public_attributes_plain.len()) as u32,
);
let response = client.blind_sign(&blind_sign_request_body).await?;
let encrypted_signature = response.encrypted_signature;
@@ -70,43 +74,42 @@ async fn obtain_partial_credential(
let blinded_signature = BlindedSignature::from_bytes(&blinded_signature_bytes)?;
let unblinded_signature = blinded_signature.unblind(
let unblinded_signature = issue_verify(
params,
validator_vk,
&private_attributes,
&public_attributes,
&blind_sign_request.get_commitment_hash(),
attributes.pedersen_commitments_openings(),
&attributes.ecash_keypair().secret_key(),
&blinded_signature,
attributes.withdrawal_request_info(),
)?;
Ok(unblinded_signature)
}
pub async fn obtain_aggregate_signature(
params: &Parameters,
params: &GroupParameters,
attributes: &BandwidthVoucher,
coconut_api_clients: &[CoconutApiClient],
ecash_api_clients: &[CoconutApiClient],
threshold: u64,
) -> Result<Signature, Error> {
if coconut_api_clients.is_empty() {
) -> Result<Wallet, Error> {
if ecash_api_clients.is_empty() {
return Err(Error::NoValidatorsAvailable);
}
let public_attributes = attributes.get_public_attributes();
let private_attributes = attributes.get_private_attributes();
//let public_attributes = attributes.get_public_attributes();
//let private_attributes = attributes.get_private_attributes();
let mut shares = Vec::with_capacity(coconut_api_clients.len());
let validators_partial_vks: Vec<_> = coconut_api_clients
let mut wallets = Vec::with_capacity(ecash_api_clients.len());
let validators_partial_vks: Vec<_> = ecash_api_clients
.iter()
.map(|api_client| api_client.verification_key.clone())
.collect();
let indices: Vec<_> = coconut_api_clients
let indices: Vec<_> = ecash_api_clients
.iter()
.map(|api_client| api_client.node_id)
.collect();
let verification_key =
aggregate_verification_keys(&validators_partial_vks, Some(indices.as_ref()))?;
for coconut_api_client in coconut_api_clients.iter() {
for coconut_api_client in ecash_api_clients.iter() {
debug!(
"attempting to obtain partial credential from {}",
coconut_api_client.api_client.api_url()
@@ -120,10 +123,7 @@ pub async fn obtain_aggregate_signature(
)
.await
{
Ok(signature) => {
let share = SignatureShare::new(signature, coconut_api_client.node_id);
shares.push(share)
}
Ok(wallet) => wallets.push(wallet),
Err(err) => {
warn!(
"failed to obtain partial credential from {}: {err}",
@@ -132,43 +132,49 @@ pub async fn obtain_aggregate_signature(
}
};
}
if shares.len() < threshold as usize {
if wallets.len() < threshold as usize {
return Err(Error::NotEnoughShares);
}
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
attributes.extend_from_slice(&private_attributes);
attributes.extend_from_slice(&public_attributes);
// let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
// attributes.extend_from_slice(&private_attributes);
// attributes.extend_from_slice(&public_attributes);
aggregate_signature_shares(params, &verification_key, &attributes, &shares)
.map_err(Error::SignatureAggregationError)
aggregate_wallets(
params,
&verification_key,
&attributes.ecash_keypair().secret_key(),
&wallets,
attributes.withdrawal_request_info(),
)
.map_err(Error::CompactEcashError)
}
// TODO: better type flow
#[allow(clippy::too_many_arguments)]
pub fn prepare_credential_for_spending(
params: &Parameters,
voucher_value: u64,
voucher_info: String,
serial_number: Attribute,
binding_number: Attribute,
epoch_id: u64,
signature: &Signature,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
let theta = prove_bandwidth_credential(
params,
verification_key,
signature,
serial_number,
binding_number,
)?;
// #[allow(clippy::too_many_arguments)]
// pub fn prepare_credential_for_spending(
// params: &nym_coconut_interface::Parameters,
// voucher_value: u64,
// voucher_info: String,
// serial_number: nym_coconut_interface::Attribute,
// binding_number: nym_coconut_interface::Attribute,
// epoch_id: u64,
// signature: &nym_coconut_interface::Signature,
// verification_key: &nym_coconut_interface::VerificationKey,
// ) -> Result<nym_coconut_interface::Credential, Error> {
// let theta = nym_coconut_interface::prove_bandwidth_credential(
// params,
// verification_key,
// signature,
// serial_number,
// binding_number,
// )?;
Ok(Credential::new(
PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES,
theta,
voucher_value,
voucher_info,
epoch_id,
))
}
// Ok(nym_coconut_interface::Credential::new(
// PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES,
// theta,
// voucher_value,
// voucher_info,
// epoch_id,
// ))
// }
+4
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use nym_coconut_interface::CoconutError;
use nym_compact_ecash::error::CompactEcashError;
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
use nym_validator_client::ValidatorClientError;
@@ -21,6 +22,9 @@ pub enum Error {
#[error("Ran into a coconut error - {0}")]
CoconutError(#[from] CoconutError),
#[error("Ran into a Compact ecash error - {0}")]
CompactEcashError(#[from] CompactEcashError),
#[error("Ran into a validator client error - {0}")]
ValidatorClientError(#[from] ValidatorClientError),
+1 -1
View File
@@ -11,7 +11,7 @@ use thiserror::Error;
use tracing::warn;
use url::Url;
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(6);
pub type PathSegments<'a> = &'a [&'a str];
pub type Params<'a, K, V> = &'a [(K, V)];
+2
View File
@@ -434,6 +434,8 @@ pub const BANDWIDTH_VALUE: u64 = UTOKENS_TO_BURN * BYTES_PER_UTOKEN;
pub const VOUCHER_INFO: &str = "BandwidthVoucher";
pub const ECASH_INFO: &str = "TicketBook";
pub const ETH_MIN_BLOCK_DEPTH: usize = 7;
/// Defaults Cosmos Hub/ATOM path
@@ -0,0 +1,34 @@
[package]
name = "nym-compact-ecash"
version = "0.1.0"
authors = ["Ania Piotrowska <ania@nymtech.net>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bls12_381 = { workspace = true }
bs58 = "0.4.0"
chrono = "0.4.19"
digest = "0.9"
ff = { workspace = true }
getset = "0.1.1"
group = { workspace = true }
itertools = "0.10"
rand = "0.8"
serde = "1.0.189"
sha2 = "0.9"
thiserror = "1.0"
zeroize = { workspace = true }
# internal
nym-pemstore = { path = "../pemstore", version = "0.3.0" }
[dev-dependencies]
criterion = { version = "0.3", features = ["html_reports"] }
[[bench]]
name = "benchmarks"
harness = false
@@ -0,0 +1,374 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::ops::Neg;
use std::time::Duration;
use bls12_381::{
multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, Scalar,
};
use criterion::{criterion_group, criterion_main, Criterion};
use ff::Field;
use group::{Curve, Group};
use itertools::izip;
use rand::seq::SliceRandom;
use nym_compact_ecash::identify::{identify, IdentifyResult};
use nym_compact_ecash::setup::setup;
use nym_compact_ecash::{
aggregate_verification_keys, aggregate_wallets, generate_keypair_user, issue_verify,
issue_wallet, ttp_keygen, withdrawal_request, PartialWallet, PayInfo, PublicKeyUser,
SecretKeyUser, VerificationKeyAuth,
};
#[allow(unused)]
fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
let gt1 = bls12_381::pairing(g11, g21);
let gt2 = bls12_381::pairing(g12, g22);
assert_eq!(gt1, gt2)
}
#[allow(unused)]
fn single_pairing(g11: &G1Affine, g21: &G2Affine) {
let gt1 = bls12_381::pairing(g11, g21);
}
#[allow(unused)]
fn exponent_in_g1(g1: G1Projective, r: Scalar) {
let g11 = (g1 * r);
}
#[allow(unused)]
fn exponent_in_g2(g2: G2Projective, r: Scalar) {
let g22 = (g2 * r);
}
#[allow(unused)]
fn exponent_in_gt(gt: Gt, r: Scalar) {
let gtt = (gt * r);
}
#[allow(unused)]
fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
let miller_loop_result = multi_miller_loop(&[
(g11, &G2Prepared::from(*g21)),
(&g12.neg(), &G2Prepared::from(*g22)),
]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
#[allow(unused)]
fn multi_miller_pairing_with_prepared(
g11: &G1Affine,
g21: &G2Prepared,
g12: &G1Affine,
g22: &G2Prepared,
) {
let miller_loop_result = multi_miller_loop(&[(g11, g21), (&g12.neg(), g22)]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
// the case of being able to prepare G2 generator
#[allow(unused)]
fn multi_miller_pairing_with_semi_prepared(
g11: &G1Affine,
g21: &G2Affine,
g12: &G1Affine,
g22: &G2Prepared,
) {
let miller_loop_result =
multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), g22)]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
#[allow(unused)]
fn bench_pairings(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-pairings");
group.measurement_time(Duration::from_secs(200));
let mut rng = rand::thread_rng();
let g1 = G1Affine::generator();
let g2 = G2Affine::generator();
let r = Scalar::random(&mut rng);
let s = Scalar::random(&mut rng);
let g11 = (g1 * r).to_affine();
let g21 = (g2 * s).to_affine();
let g21_prep = G2Prepared::from(g21);
let g12 = (g1 * s).to_affine();
let g22 = (g2 * r).to_affine();
let g22_prep = G2Prepared::from(g22);
let gt = bls12_381::pairing(&g11, &g21);
let gen1 = G1Projective::generator();
let gen2 = G2Projective::generator();
group.bench_function("exponent operation in G1", |b| {
b.iter(|| exponent_in_g1(gen1, r))
});
group.bench_function("exponent operation in G2", |b| {
b.iter(|| exponent_in_g2(gen2, r))
});
group.bench_function("exponent operation in Gt", |b| {
b.iter(|| exponent_in_gt(gt, r))
});
group.bench_function("single pairing", |b| b.iter(|| single_pairing(&g11, &g21)));
group.bench_function("double pairing", |b| {
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
});
group.bench_function("multi miller in affine", |b| {
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
});
group.bench_function("multi miller with prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
});
group.bench_function("multi miller with semi-prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
});
}
struct BenchCase {
num_authorities: u64,
threshold_p: f32,
L: u64,
spend_vv: u64,
case_nr_pub_keys: u64,
}
fn bench_compact_ecash(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-compact-ecash");
// group.sample_size(300);
// group.measurement_time(Duration::from_secs(1500));
let case = BenchCase {
num_authorities: 100,
threshold_p: 0.7,
L: 100,
spend_vv: 1,
case_nr_pub_keys: 99,
};
let params = setup(case.L);
let grp = params.grp();
let user_keypair = generate_keypair_user(&grp);
let threshold = (case.threshold_p * case.num_authorities as f32).round() as u64;
let authorities_keypairs = ttp_keygen(&grp, threshold, case.num_authorities).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let indices: Vec<u64> = (1..case.num_authorities + 1).collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
// ISSUANCE PHASE
let (req, req_info) = withdrawal_request(grp, &user_keypair.secret_key()).unwrap();
// CLIENT BENCHMARK: prepare a single withdrawal request
// group.bench_function(
// &format!(
// "[Client] withdrawal_request_{}_authorities_{}_L_{}_threshold",
// case.num_authorities, case.L, case.threshold_p,
// ),
// |b| b.iter(|| withdrawal_request(grp, &user_keypair.secret_key()).unwrap()),
// );
// ISSUING AUTHRORITY BENCHMARK: Benchmark the issue_wallet function
// called by an authority to issue a blind signature on a partial wallet
let mut rng = rand::thread_rng();
let keypair = authorities_keypairs.choose(&mut rng).unwrap();
// group.bench_function(
// &format!("[Issuing Authority] issue_partial_wallet_with_L_{}", case.L, ),
// |b| {
// b.iter(|| {
// issue_wallet(
// &grp,
// keypair.secret_key(),
// user_keypair.public_key(),
// &req,
// ).unwrap()
// })
// },
// );
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue_wallet(
&grp,
auth_keypair.secret_key(),
user_keypair.public_key(),
&req,
);
wallet_blinded_signatures.push(blind_signature.unwrap());
}
// CLIENT BENCHMARK: verify the issued partial wallet
let w = wallet_blinded_signatures.get(0).clone().unwrap();
let vk = verification_keys_auth.get(0).clone().unwrap();
// group.bench_function(
// &format!("[Client] issue_verify_a_partial_wallet_with_L_{}", case.L, ),
// |b| b.iter(|| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap()),
// );
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
wallet_blinded_signatures.iter(),
verification_keys_auth.iter()
)
.map(|(w, vk)| issue_verify(&grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
.collect();
// CLIENT BENCHMARK: aggregating all partial wallets
// group.bench_function(
// &format!(
// "[Client] aggregate_wallets_with_L_{}_threshold_{}",
// case.L, case.threshold_p,
// ),
// |b| {
// b.iter(|| {
// aggregate_wallets(
// &grp,
// &verification_key,
// &user_keypair.secret_key(),
// &unblinded_wallet_shares,
// &req_info,
// )
// .unwrap()
// })
// },
// );
// Aggregate partial wallets
let aggr_wallet = aggregate_wallets(
&grp,
&verification_key,
&user_keypair.secret_key(),
&unblinded_wallet_shares,
&req_info,
)
.unwrap();
// SPENDING PHASE
let pay_info = PayInfo { payinfo: [6u8; 32] };
// CLIENT BENCHMARK: spend a single coin from the wallet
// group.bench_function(
// &format!(
// "[Client] spend_a_single_coin_L_{}_threshold_{}",
// case.L, case.threshold_p,
// ),
// |b| {
// b.iter(|| {
// aggr_wallet
// .spend(
// &params,
// &verification_key,
// &user_keypair.secret_key(),
// &pay_info,
// true,
// case.spend_vv,
// )
// .unwrap()
// })
// },
// );
let (payment, upd_wallet) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info,
false,
case.spend_vv,
)
.unwrap();
// MERCHANT BENCHMARK: verify whether the submitted payment is legit
// group.bench_function(
// &format!(
// "[Merchant] spend_verify_of_a_single_payment_L_{}_threshold_{}",
// case.L, case.threshold_p,
// ),
// |b| {
// b.iter(|| {
// payment
// .spend_verify(&params, &verification_key, &pay_info)
// .unwrap()
// })
// },
// );
// BENCHMARK IDENTIFICATION
// Let's generate a double spending payment
// let's reverse the spending counter in the wallet to create a double spending payment
let current_l = aggr_wallet.l.get();
aggr_wallet.l.set(current_l - case.spend_vv);
let pay_info2 = PayInfo { payinfo: [7u8; 32] };
let (payment2, _) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info2,
true,
case.spend_vv,
)
.unwrap();
// GENERATE KEYS FOR OTHER USERS
let mut public_keys: Vec<PublicKeyUser> = Default::default();
for i in 0..case.case_nr_pub_keys {
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = sk_user.public_key(&grp);
public_keys.push(pk_user);
}
public_keys.push(user_keypair.public_key());
// MERCHANT BENCHMARK: identify double spending
group.bench_function(
&format!(
"[Merchant] identify_L_{}_threshold_{}_spend_vv_{}_pks_{}",
case.L,
case.threshold_p,
case.spend_vv,
public_keys.len()
),
|b| {
b.iter(|| {
identify(
payment.clone(),
payment2.clone(),
pay_info.clone(),
pay_info2.clone(),
)
.unwrap()
})
},
);
let identify_result = identify(payment, payment2, pay_info.clone(), pay_info2.clone()).unwrap();
assert_eq!(
identify_result,
IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key())
);
}
criterion_group!(benches, bench_compact_ecash);
criterion_main!(benches);
@@ -0,0 +1,55 @@
use thiserror::Error;
pub type Result<T> = std::result::Result<T, CompactEcashError>;
#[derive(Error, Debug)]
pub enum CompactEcashError {
#[error("Setup error: {0}")]
Setup(String),
#[error("Aggregation error: {0}")]
Aggregation(String),
#[error("Withdrawal Request Verification related error: {0}")]
WithdrawalRequestVerification(String),
#[error("Deserialization error: {0}")]
Deserialization(String),
#[error("Interpolation error: {0}")]
Interpolation(String),
#[error("Issuance Verification related error: {0}")]
IssuanceVfy(String),
#[error("Spend Verification related error: {0}")]
Spend(String),
#[error("ZKP Proof related error: {0}")]
RangeProofOutOfBound(String),
#[error("Identify Verification related error: {0}")]
Identify(String),
#[error("Could not decode base 58 string - {0}")]
MalformedString(#[from] bs58::decode::Error),
#[error("Payment did not verify")]
PaymentVerification,
#[error(
"Deserailization error, expected at least {} bytes, got {}",
min,
actual
)]
DeserializationMinLength { min: usize, actual: usize },
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {target} or {modulus_target} % {modulus} == 0")]
DeserializationInvalidLength {
actual: usize,
target: usize,
modulus_target: usize,
modulus: usize,
object: String,
},
}
@@ -0,0 +1,18 @@
use crate::scheme::withdrawal::WithdrawalRequest;
use crate::scheme::EcashCredential;
use crate::setup::Parameters;
use crate::traits::Bytable;
macro_rules! impl_clone {
($struct:ident) => {
impl Clone for $struct {
fn clone(&self) -> Self {
Self::try_from_byte_slice(&self.to_byte_vec()).unwrap()
}
}
};
}
impl_clone!(WithdrawalRequest);
impl_clone!(EcashCredential);
impl_clone!(Parameters);
@@ -0,0 +1,2 @@
mod clone;
mod serde;
@@ -0,0 +1,53 @@
use crate::scheme::EcashCredential;
use crate::setup::Parameters;
use crate::traits::Base58;
use crate::VerificationKeyAuth;
use serde::de::Unexpected;
use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use crate::scheme::withdrawal::WithdrawalRequest;
macro_rules! impl_serde {
($struct:ident, $visitor:ident) => {
pub struct $visitor {}
impl Serialize for $struct {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_bs58())
}
}
impl<'de> Visitor<'de> for $visitor {
type Value = $struct;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "A base58 encoded struct")
}
fn visit_str<E: Error>(self, s: &str) -> Result<Self::Value, E> {
match $struct::try_from_bs58(s) {
Ok(x) => Ok(x),
Err(_) => Err(Error::invalid_value(Unexpected::Str(s), &self)),
}
}
}
impl<'de> Deserialize<'de> for $struct {
fn deserialize<D>(deserializer: D) -> Result<$struct, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str($visitor {})
}
}
};
}
impl_serde!(WithdrawalRequest, V1);
impl_serde!(EcashCredential, V2);
impl_serde!(VerificationKeyAuth, V3);
impl_serde!(Parameters, V4);
@@ -0,0 +1,41 @@
use std::convert::TryInto;
use bls12_381::Scalar;
pub use scheme::aggregation::aggregate_verification_keys;
pub use scheme::aggregation::aggregate_wallets;
pub use scheme::identify;
pub use scheme::keygen::generate_keypair_user;
pub use scheme::keygen::ttp_keygen;
pub use scheme::keygen::{PublicKeyUser, SecretKeyUser, VerificationKeyAuth};
pub use scheme::setup;
pub use scheme::withdrawal::issue_verify;
pub use scheme::withdrawal::issue_wallet;
pub use scheme::withdrawal::withdrawal_request;
pub use scheme::PartialWallet;
pub use scheme::PayInfo;
pub use traits::Base58;
use crate::error::CompactEcashError;
use crate::traits::Bytable;
pub mod error;
mod impls;
mod proofs;
pub mod scheme;
#[cfg(test)]
mod tests;
mod traits;
pub mod utils;
pub type Attribute = Scalar;
impl Bytable for Attribute {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CompactEcashError> {
Ok(Attribute::from_bytes(slice.try_into().unwrap()).unwrap())
}
}
@@ -0,0 +1,56 @@
use std::borrow::Borrow;
use bls12_381::Scalar;
use digest::generic_array::typenum::Unsigned;
use digest::Digest;
use sha2::Sha256;
pub mod proof_spend;
pub mod proof_withdrawal;
type ChallengeDigest = Sha256;
/// Generates a Scalar [or Fp] challenge by hashing a number of elliptic curve points.
fn compute_challenge<D, I, B>(iter: I) -> Scalar
where
D: Digest,
I: Iterator<Item = B>,
B: AsRef<[u8]>,
{
let mut h = D::new();
for point_representation in iter {
h.update(point_representation);
}
let digest = h.finalize();
// TODO: I don't like the 0 padding here (though it's what we've been using before,
// but we never had a security audit anyway...)
// instead we could maybe use the `from_bytes` variant and adding some suffix
// when computing the digest until we produce a valid scalar.
let mut bytes = [0u8; 64];
let pad_size = 64usize
.checked_sub(D::OutputSize::to_usize())
.unwrap_or_default();
bytes[pad_size..].copy_from_slice(&digest);
Scalar::from_bytes_wide(&bytes)
}
fn produce_response(witness_replacement: &Scalar, challenge: &Scalar, secret: &Scalar) -> Scalar {
witness_replacement - challenge * secret
}
// note: it's caller's responsibility to ensure witnesses.len() = secrets.len()
fn produce_responses<S>(witnesses: &[Scalar], challenge: &Scalar, secrets: &[S]) -> Vec<Scalar>
where
S: Borrow<Scalar>,
{
debug_assert_eq!(witnesses.len(), secrets.len());
witnesses
.iter()
.zip(secrets.iter())
.map(|(w, x)| produce_response(w, challenge, x.borrow()))
.collect()
}
@@ -0,0 +1,796 @@
use std::convert::{TryFrom, TryInto};
use bls12_381::{G1Projective, G2Projective, Scalar};
use group::{Curve, GroupEncoding};
use crate::error::{CompactEcashError, Result};
use crate::proofs::{compute_challenge, produce_response, produce_responses, ChallengeDigest};
use crate::scheme::keygen::VerificationKeyAuth;
use crate::scheme::setup::Parameters;
use crate::utils::{
try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
try_deserialize_scalar_vec,
};
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct SpendInstance {
pub kappa: G2Projective,
pub cc: G1Projective,
pub aa: Vec<G1Projective>,
pub ss: Vec<G1Projective>,
pub tt: Vec<G1Projective>,
pub kappa_k: Vec<G2Projective>,
}
impl TryFrom<&[u8]> for SpendInstance {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<SpendInstance> {
if bytes.len() < 48 * 5 + 2 * 96 || (bytes.len()) % 48 != 0 {
return Err(CompactEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len(),
target: 48 * 5 + 2 * 96,
modulus: 48,
object: "spend instance".to_string(),
});
}
let mut j = 0;
let kappa_bytes = bytes[j..j + 96].try_into().unwrap();
let kappa = try_deserialize_g2_projective(
&kappa_bytes,
CompactEcashError::Deserialization("Failed to deserialize kappa".to_string()),
)?;
j += 96;
let a_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < a_len as usize * 48 {
return Err(CompactEcashError::DeserializationMinLength {
min: a_len as usize * 48,
actual: bytes[j..].len(),
});
}
let mut aa = Vec::with_capacity(a_len as usize);
for i in 0..a_len as usize {
let start = j + i * 48;
let end = start + 48;
let aa_elem_bytes = bytes[start..end].try_into().unwrap();
let aa_elem = try_deserialize_g1_projective(
&aa_elem_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed A values".to_string(),
),
)?;
aa.push(aa_elem)
}
j += a_len as usize * 48;
let cc_bytes = bytes[j..j + 48].try_into().unwrap();
let cc = try_deserialize_g1_projective(
&cc_bytes,
CompactEcashError::Deserialization("Failed to deserialize C".to_string()),
)?;
j += 48;
let s_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < s_len as usize * 48 {
return Err(CompactEcashError::DeserializationMinLength {
min: s_len as usize * 48,
actual: bytes[j..].len(),
});
}
let mut ss = Vec::with_capacity(s_len as usize);
for i in 0..s_len as usize {
let start = j + i * 48;
let end = start + 48;
let ss_elem_bytes = bytes[start..end].try_into().unwrap();
let ss_elem = try_deserialize_g1_projective(
&ss_elem_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed S values".to_string(),
),
)?;
ss.push(ss_elem)
}
j += s_len as usize * 48;
let t_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < t_len as usize * 48 {
return Err(CompactEcashError::DeserializationMinLength {
min: t_len as usize * 48,
actual: bytes[j..].len(),
});
}
let mut tt = Vec::with_capacity(t_len as usize);
for i in 0..t_len as usize {
let start = j + i * 48;
let end = start + 48;
let tt_elem_bytes = bytes[start..end].try_into().unwrap();
let tt_elem = try_deserialize_g1_projective(
&tt_elem_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed T values".to_string(),
),
)?;
tt.push(tt_elem)
}
j += t_len as usize * 48;
let kappa_k_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < kappa_k_len as usize * 96 {
return Err(CompactEcashError::DeserializationMinLength {
min: kappa_k_len as usize * 96,
actual: bytes[j..].len(),
});
}
let mut kappa_k = Vec::with_capacity(kappa_k_len as usize);
for i in 0..kappa_k_len as usize {
let start = j + i * 48;
let end = start + 48;
let kappa_k_elem_bytes = bytes[start..end].try_into().unwrap();
let kappa_k_elem = try_deserialize_g2_projective(
&kappa_k_elem_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed kappa_k values".to_string(),
),
)?;
kappa_k.push(kappa_k_elem)
}
Ok(SpendInstance {
kappa,
aa,
cc,
ss,
tt,
kappa_k,
})
}
}
impl SpendInstance {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes: Vec<u8> = Default::default();
bytes.extend_from_slice(self.kappa.to_bytes().as_ref());
bytes.extend_from_slice(&self.aa.len().to_le_bytes());
for a in &self.aa {
bytes.extend_from_slice(&a.to_affine().to_compressed());
}
bytes.extend_from_slice(self.cc.to_bytes().as_ref());
bytes.extend_from_slice(&self.ss.len().to_le_bytes());
for s in &self.ss {
bytes.extend_from_slice(&s.to_affine().to_compressed());
}
bytes.extend_from_slice(&self.tt.len().to_le_bytes());
for t in &self.tt {
bytes.extend_from_slice(&t.to_affine().to_compressed());
}
bytes.extend_from_slice(&self.kappa_k.len().to_le_bytes());
for k in &self.kappa_k {
bytes.extend_from_slice(&k.to_affine().to_compressed());
}
bytes
}
}
pub struct SpendWitness {
// includes skUser, v, t
pub attributes: Vec<Scalar>,
// signature randomizing element
pub r: Scalar,
pub o_c: Scalar,
pub lk: Vec<Scalar>,
pub o_a: Vec<Scalar>,
pub mu: Vec<Scalar>,
pub o_mu: Vec<Scalar>,
pub r_k: Vec<Scalar>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SpendProof {
challenge: Scalar,
response_r: Scalar,
response_r_l: Vec<Scalar>,
response_l: Vec<Scalar>,
response_o_a: Vec<Scalar>,
response_o_c: Scalar,
response_mu: Vec<Scalar>,
response_o_mu: Vec<Scalar>,
response_attributes: Vec<Scalar>,
}
impl SpendProof {
pub fn construct(
params: &Parameters,
instance: &SpendInstance,
witness: &SpendWitness,
verification_key: &VerificationKeyAuth,
rr: &[Scalar],
) -> Self {
let grparams = params.grp();
// generate random values to replace each witness
let r_attributes = grparams.n_random_scalars(witness.attributes.len());
let r_sk = r_attributes[0];
let r_v = r_attributes[1];
let r_r = grparams.random_scalar();
let r_o_c = grparams.random_scalar();
let r_r_lk = grparams.n_random_scalars(witness.r_k.len());
let r_lk = grparams.n_random_scalars(witness.lk.len());
let r_o_a = grparams.n_random_scalars(witness.o_a.len());
let r_mu = grparams.n_random_scalars(witness.mu.len());
let r_o_mu = grparams.n_random_scalars(witness.o_mu.len());
let g1 = *grparams.gen1();
let gamma1 = *grparams.gamma1();
let beta2_bytes = verification_key
.beta_g2
.iter()
.map(|beta_i| beta_i.to_bytes())
.collect::<Vec<_>>();
// compute zkp commitment for each instance
let zkcm_kappa = grparams.gen2() * r_r
+ verification_key.alpha
+ r_attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
let zkcm_cc = g1 * r_o_c + gamma1 * r_v;
let zkcm_aa: Vec<G1Projective> = r_o_a
.iter()
.zip(r_lk.iter())
.map(|(r_o_a_k, r_l_k)| g1 * r_o_a_k + gamma1 * r_l_k)
.collect::<Vec<_>>();
let zkcm_aa_bytes = zkcm_aa.iter().map(|x| x.to_bytes()).collect::<Vec<_>>();
let zkcm_ss = r_mu
.iter()
.map(|r_mu_k| grparams.delta() * r_mu_k)
.collect::<Vec<_>>();
let zkcm_ss_bytes = zkcm_ss.iter().map(|x| x.to_bytes()).collect::<Vec<_>>();
let zkcm_tt = rr
.iter()
.zip(r_mu.iter())
.map(|(rr_k, r_mu_k)| g1 * r_sk + (g1 * rr_k) * r_mu_k)
.collect::<Vec<_>>();
let zkcm_tt_bytes = zkcm_tt.iter().map(|x| x.to_bytes()).collect::<Vec<_>>();
let zkcm_gamma11 = instance
.aa
.iter()
.zip(r_mu.iter())
.zip(r_o_mu.iter())
.map(|((aa_k, r_mu_k), r_o_mu_k)| {
(aa_k + instance.cc + gamma1) * r_mu_k + g1 * r_o_mu_k
})
.collect::<Vec<_>>();
let zkcm_gamma11_bytes = zkcm_gamma11
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
let zkcm_kappa_k = r_lk
.iter()
.zip(r_r_lk.iter())
.map(|(r_k, r_r_k)| {
params.pk_rp().alpha + params.pk_rp().beta * r_k + grparams.gen2() * r_r_k
})
.collect::<Vec<_>>();
let zkcm_kappa_k_bytes = zkcm_kappa_k
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
// compute the challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(grparams.gen1().to_bytes().as_ref())
.chain(std::iter::once(gamma1.to_bytes().as_ref()))
.chain(std::iter::once(verification_key.alpha.to_bytes().as_ref()))
.chain(beta2_bytes.iter().map(|b| b.as_ref()))
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_kappa.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_cc.to_bytes().as_ref()))
.chain(zkcm_aa_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_ss_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_kappa_k_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_tt_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_gamma11_bytes.iter().map(|x| x.as_ref())),
);
// compute response for each witness
let response_attributes = produce_responses(
&r_attributes,
&challenge,
&witness.attributes.iter().collect::<Vec<_>>(),
);
let response_r = produce_response(&r_r, &challenge, &witness.r);
let response_r_l = produce_responses(&r_r_lk, &challenge, &witness.r_k);
let response_l = produce_responses(&r_lk, &challenge, &witness.lk);
let response_o_a = produce_responses(&r_o_a, &challenge, &witness.o_a);
let response_o_c = produce_response(&r_o_c, &challenge, &witness.o_c);
let response_mu = produce_responses(&r_mu, &challenge, &witness.mu);
let response_o_mu = produce_responses(&r_o_mu, &challenge, &witness.o_mu);
SpendProof {
challenge,
response_r,
response_r_l,
response_l,
response_o_a,
response_o_c,
response_mu,
response_o_mu,
response_attributes,
}
}
pub fn verify(
&self,
params: &Parameters,
instance: &SpendInstance,
verification_key: &VerificationKeyAuth,
rr: &[Scalar],
) -> bool {
let grparams = params.grp();
let g1 = *grparams.gen1();
let gamma1 = *grparams.gamma1();
let beta2_bytes = verification_key
.beta_g2
.iter()
.map(|beta_i| beta_i.to_bytes())
.collect::<Vec<_>>();
// re-compute each zkp commitment
let zkcm_kappa = instance.kappa * self.challenge
+ grparams.gen2() * self.response_r
+ verification_key.alpha * (Scalar::one() - self.challenge)
+ self
.response_attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
let zkcm_aa = self
.response_o_a
.iter()
.zip(self.response_l.iter())
.zip(instance.aa.iter())
.map(|((resp_o_a_k, resp_l_k), aa_k)| {
g1 * resp_o_a_k + gamma1 * resp_l_k + aa_k * self.challenge
})
.collect::<Vec<_>>();
let zkcm_aa_bytes = zkcm_aa.iter().map(|x| x.to_bytes()).collect::<Vec<_>>();
let zkcm_cc = g1 * self.response_o_c
+ gamma1 * self.response_attributes[1]
+ instance.cc * self.challenge;
let zkcm_ss = self
.response_mu
.iter()
.zip(instance.ss.iter())
.map(|(resp_mu_k, ss_k)| grparams.delta() * resp_mu_k + ss_k * self.challenge)
.collect::<Vec<_>>();
let zkcm_ss_bytes = zkcm_ss.iter().map(|x| x.to_bytes()).collect::<Vec<_>>();
let zkcm_tt = self
.response_mu
.iter()
.zip(rr.iter())
.zip(instance.tt.iter())
.map(|((resp_mu_k, rr_k), tt_k)| {
g1 * self.response_attributes[0] + (g1 * rr_k) * resp_mu_k + tt_k * self.challenge
})
.collect::<Vec<_>>();
let zkcm_tt_bytes = zkcm_tt.iter().map(|x| x.to_bytes()).collect::<Vec<_>>();
let zkcm_gamma11 = instance
.aa
.iter()
.zip(self.response_mu.iter())
.zip(self.response_o_mu.iter())
.map(|((aa_k, resp_mu_k), resp_o_mu_k)| {
(aa_k + instance.cc + gamma1) * resp_mu_k
+ g1 * resp_o_mu_k
+ gamma1 * self.challenge
})
.collect::<Vec<_>>();
let zkcm_gamma11_bytes = zkcm_gamma11
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
let zkcm_kappa_k = instance
.kappa_k
.iter()
.zip(self.response_r_l.iter())
.zip(self.response_l.iter())
.map(|((kappa_k, resp_r_k), resp_r_l_k)| {
kappa_k * self.challenge
+ grparams.gen2() * resp_r_k
+ params.pk_rp().alpha * (Scalar::one() - self.challenge)
+ params.pk_rp().beta * resp_r_l_k
})
.collect::<Vec<_>>();
let zkcm_kappa_k_bytes = zkcm_kappa_k
.iter()
.map(|x| x.to_bytes())
.collect::<Vec<_>>();
// re-compute the challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(grparams.gen1().to_bytes().as_ref())
.chain(std::iter::once(gamma1.to_bytes().as_ref()))
.chain(std::iter::once(verification_key.alpha.to_bytes().as_ref()))
.chain(beta2_bytes.iter().map(|b| b.as_ref()))
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_kappa.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_cc.to_bytes().as_ref()))
.chain(zkcm_aa_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_ss_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_kappa_k_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_tt_bytes.iter().map(|x| x.as_ref()))
.chain(zkcm_gamma11_bytes.iter().map(|x| x.as_ref())),
);
challenge == self.challenge
}
pub fn to_bytes(&self) -> Vec<u8> {
let challenge_bytes = self.challenge.to_bytes();
let response_r_bytes = self.response_r.to_bytes();
let rrl_len = self.response_r_l.len();
let rrl_len_bytes = rrl_len.to_le_bytes();
let rl_len = self.response_l.len();
let rl_len_bytes = rl_len.to_le_bytes();
let roa_len = self.response_o_a.len();
let roa_len_bytes = roa_len.to_le_bytes();
let roc_bytes = self.response_o_c.to_bytes();
let rmu_len = self.response_mu.len();
let rmu_len_bytes = rmu_len.to_le_bytes();
let romu_len = self.response_o_mu.len();
let romu_len_bytes = romu_len.to_le_bytes();
let rattributes_len = self.response_attributes.len();
let rattributes_len_bytes = rattributes_len.to_le_bytes();
let mut bytes: Vec<u8> = Vec::with_capacity(
96 + (rrl_len + rl_len + roa_len + rmu_len + romu_len + rattributes_len) * 8
+ (rrl_len + rl_len + roa_len + rmu_len + romu_len + rattributes_len) * 32,
);
bytes.extend_from_slice(&challenge_bytes);
bytes.extend_from_slice(&response_r_bytes);
bytes.extend_from_slice(&roc_bytes);
bytes.extend_from_slice(&rrl_len_bytes);
for rrl in &self.response_r_l {
bytes.extend_from_slice(&rrl.to_bytes());
}
bytes.extend_from_slice(&rl_len_bytes);
for rl in &self.response_l {
bytes.extend_from_slice(&rl.to_bytes());
}
bytes.extend_from_slice(&roa_len_bytes);
for roa in &self.response_o_a {
bytes.extend_from_slice(&roa.to_bytes());
}
bytes.extend_from_slice(&rmu_len_bytes);
for rmu in &self.response_mu {
bytes.extend_from_slice(&rmu.to_bytes());
}
bytes.extend_from_slice(&romu_len_bytes);
for romu in &self.response_o_mu {
bytes.extend_from_slice(&romu.to_bytes());
}
bytes.extend_from_slice(&rattributes_len_bytes);
for rattr in &self.response_attributes {
bytes.extend_from_slice(&rattr.to_bytes());
}
bytes
}
}
impl TryFrom<&[u8]> for SpendProof {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<SpendProof> {
if bytes.len() < 336 || (bytes.len() - 96 - 48) % 32 != 0 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize proof of spending with bytes of invalid length".to_string(),
));
}
let mut idx = 0;
let challenge_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let response_r_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let response_o_c_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let challenge = try_deserialize_scalar(
&challenge_bytes,
CompactEcashError::Deserialization("Failed to deserialize challenge".to_string()),
)?;
let response_r = try_deserialize_scalar(
&response_r_bytes,
CompactEcashError::Deserialization("Failed to deserialize response_r".to_string()),
)?;
let response_o_c = try_deserialize_scalar(
&response_o_c_bytes,
CompactEcashError::Deserialization("Failed to deserialize response_o_c".to_string()),
)?;
let rrl_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
idx += 8;
if bytes[idx..].len() < rrl_len as usize * 32 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize response_r_l".to_string(),
));
}
let rrl_end = idx + rrl_len as usize * 32;
let response_r_l = try_deserialize_scalar_vec(
rrl_len,
&bytes[idx..rrl_end],
CompactEcashError::Deserialization("Failed to deserialize response_r_l".to_string()),
)?;
let rl_len = u64::from_le_bytes(bytes[rrl_end..rrl_end + 8].try_into().unwrap());
let response_l_start = rrl_end + 8;
if bytes[response_l_start..].len() < rl_len as usize * 32 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize response_l".to_string(),
));
}
let rl_end = response_l_start + rl_len as usize * 32;
let response_l = try_deserialize_scalar_vec(
rl_len,
&bytes[response_l_start..rl_end],
CompactEcashError::Deserialization("Failed to deserialize response_l".to_string()),
)?;
let roa_len = u64::from_le_bytes(bytes[rl_end..rl_end + 8].try_into().unwrap());
let roa_end = rl_end + 8;
if bytes[roa_end..].len() < roa_len as usize * 32 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize response_o_a".to_string(),
));
}
let roa_end = roa_end + roa_len as usize * 32;
let response_o_a = try_deserialize_scalar_vec(
roa_len,
&bytes[rl_end + 8..roa_end],
CompactEcashError::Deserialization("Failed to deserialize response_o_a".to_string()),
)?;
let response_mu_len = u64::from_le_bytes(bytes[roa_end..roa_end + 8].try_into().unwrap());
let response_mu_end = roa_end + 8;
if bytes[response_mu_end..].len() < response_mu_len as usize * 32 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize response_mu".to_string(),
));
}
let response_mu_end = response_mu_end + response_mu_len as usize * 32;
let response_mu = try_deserialize_scalar_vec(
response_mu_len,
&bytes[roa_end + 8..response_mu_end],
CompactEcashError::Deserialization("Failed to deserialize response_mu".to_string()),
)?;
let response_o_mu_len = u64::from_le_bytes(
bytes[response_mu_end..response_mu_end + 8]
.try_into()
.unwrap(),
);
let response_o_mu_end = response_mu_end + 8;
if bytes[response_o_mu_end..].len() < response_o_mu_len as usize * 32 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize response_o_mu".to_string(),
));
}
let response_o_mu_end = response_o_mu_end + response_o_mu_len as usize * 32;
let response_o_mu = try_deserialize_scalar_vec(
response_o_mu_len,
&bytes[response_mu_end + 8..response_o_mu_end],
CompactEcashError::Deserialization("Failed to deserialize response_o_mu".to_string()),
)?;
let response_attributes_len = u64::from_le_bytes(
bytes[response_o_mu_end..response_o_mu_end + 8]
.try_into()
.unwrap(),
);
let response_attributes_end = response_o_mu_end + 8;
if bytes[response_attributes_end..].len() < response_attributes_len as usize * 32 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize response_attributes".to_string(),
));
}
let response_attributes_end =
response_attributes_end + response_attributes_len as usize * 32;
let response_attributes = try_deserialize_scalar_vec(
response_attributes_len,
&bytes[response_o_mu_end + 8..response_attributes_end],
CompactEcashError::Deserialization(
"Failed to deserialize response_attributes".to_string(),
),
)?;
// Construct the SpendProof struct from the deserialized data
let spend_proof = SpendProof {
challenge,
response_r,
response_o_c,
response_r_l,
response_l,
response_o_a,
response_mu,
response_o_mu,
response_attributes,
};
Ok(spend_proof)
}
}
#[cfg(test)]
mod tests {
use bls12_381::{G2Projective, Scalar};
use rand::thread_rng;
use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::keygen::{ttp_keygen, PublicKeyUser, VerificationKeyAuth};
use crate::scheme::setup::setup;
use crate::scheme::PayInfo;
use crate::scheme::{pseudorandom_f_delta_v, pseudorandom_f_g_v};
use crate::utils::hash_to_scalar;
#[test]
fn spend_proof_construct_and_verify() {
let _rng = thread_rng();
let ll = 32;
let params = setup(ll);
let grparams = params.grp();
let sk = grparams.random_scalar();
let _pk_user = PublicKeyUser {
pk: grparams.gen1() * sk,
};
let authorities_keypairs = ttp_keygen(grparams, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
let v = grparams.random_scalar();
let t = grparams.random_scalar();
let attributes = vec![sk, v, t];
// the below value must be from range 0 to params.L()
let l = 5;
let gamma1 = *grparams.gamma1();
let g1 = *grparams.gen1();
let r = grparams.random_scalar();
let kappa = grparams.gen2() * r
+ verification_key.alpha
+ attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
.sum::<G2Projective>();
let o_a = grparams.random_scalar();
let o_c = grparams.random_scalar();
// compute commitments A, C, D
let aa = g1 * o_a + gamma1 * Scalar::from(l);
let cc = g1 * o_c + gamma1 * v;
// compute hash of the payment info
let pay_info = PayInfo {
payinfo: [37u8; 72],
};
let rr = hash_to_scalar(pay_info.payinfo);
// evaluate the pseudorandom functions
let ss = pseudorandom_f_delta_v(grparams, v, l);
let tt = g1 * sk + pseudorandom_f_g_v(grparams, v, l) * rr;
// compute values mu, o_mu, lambda, o_lambda
let mu: Scalar = (v + Scalar::from(l) + Scalar::from(1)).invert().unwrap();
let o_mu = ((o_a + o_c) * mu).neg();
// parse the signature associated with value l
let sign_l = params.get_sign_by_idx(l).unwrap();
// randomise the signature associated with value l
let (_sign_l_prime, r_l) = sign_l.randomise(grparams);
// compute kappa_l
let kappa_k =
grparams.gen2() * r_l + params.pk_rp().alpha + params.pk_rp().beta * Scalar::from(l);
let instance = SpendInstance {
kappa,
aa: vec![aa],
cc,
ss: vec![ss],
tt: vec![tt],
kappa_k: vec![kappa_k],
};
let witness = SpendWitness {
attributes,
r,
o_c,
lk: vec![Scalar::from(l)],
o_a: vec![o_a],
mu: vec![mu],
o_mu: vec![o_mu],
r_k: vec![r_l],
};
let zk_proof =
SpendProof::construct(&params, &instance, &witness, &verification_key, &[rr]);
assert!(zk_proof.verify(&params, &instance, &verification_key, &[rr]));
let zk_proof_bytes = zk_proof.to_bytes();
let zk_proof2 = SpendProof::try_from(zk_proof_bytes.as_slice()).unwrap();
assert_eq!(zk_proof, zk_proof2);
}
}
@@ -0,0 +1,410 @@
use std::convert::{TryFrom, TryInto};
use bls12_381::{G1Projective, Scalar};
use group::GroupEncoding;
use itertools::izip;
use crate::error::{CompactEcashError, Result};
use crate::proofs::{compute_challenge, produce_response, produce_responses, ChallengeDigest};
use crate::scheme::keygen::PublicKeyUser;
use crate::scheme::setup::GroupParameters;
use crate::utils::{
try_deserialize_g1_projective, try_deserialize_scalar, try_deserialize_scalar_vec,
};
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
// instance: g, gamma1, gamma2, gamma3, com, h, com1, com2, com3, pkUser
pub struct WithdrawalReqInstance {
// Joined commitment to all attributes
pub com: G1Projective,
// Hash of the joined commitment com
pub h: G1Projective,
// Pedersen commitments to each attribute
pub pc_coms: Vec<G1Projective>,
// Public key of a user
pub pk_user: PublicKeyUser,
}
impl TryFrom<&[u8]> for WithdrawalReqInstance {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<WithdrawalReqInstance> {
if bytes.len() < 48 * 4 + 8 || (bytes.len() - 8) % 48 != 0 {
return Err(CompactEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8,
target: 48 * 4 + 8,
modulus: 48,
object: "withdrawal request zkp instance".to_string(),
});
}
let com_bytes: [u8; 48] = bytes[..48].try_into().unwrap();
let com = try_deserialize_g1_projective(
&com_bytes,
CompactEcashError::Deserialization("Failed to deserialize com".to_string()),
)?;
let h_bytes: [u8; 48] = bytes[48..96].try_into().unwrap();
let h = try_deserialize_g1_projective(
&h_bytes,
CompactEcashError::Deserialization("Failed to deserialize h".to_string()),
)?;
let pc_coms_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
let actual_pc_coms_len = (bytes.len() - 152) / 48;
if pc_coms_len as usize != actual_pc_coms_len {
return Err(CompactEcashError::Deserialization(format!(
"Tried to deserialize pedersen commitments with inconsistent pc_coms_len (expected {}, got {})",
pc_coms_len, actual_pc_coms_len
)));
}
let mut pc_coms = Vec::new();
let mut pc_coms_end: usize = 0;
for i in 0..pc_coms_len {
let start = (104 + i * 48) as usize;
let end = start + 48;
let pc_i_bytes = bytes[start..end].try_into().unwrap();
let pc_i = try_deserialize_g1_projective(
&pc_i_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize pedersen commitment".to_string(),
),
)?;
pc_coms_end = end;
pc_coms.push(pc_i);
}
let pk_bytes = bytes[pc_coms_end..].try_into().unwrap();
let pk = try_deserialize_g1_projective(
&pk_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize user's public key".to_string(),
),
)?;
Ok(WithdrawalReqInstance {
com,
h,
pc_coms,
pk_user: PublicKeyUser { pk },
})
}
}
impl WithdrawalReqInstance {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let pc_coms_len = self.pc_coms.len();
let mut bytes = Vec::with_capacity(8 + (pc_coms_len + 3) * 48);
bytes.extend_from_slice(self.com.to_bytes().as_ref());
bytes.extend_from_slice(self.h.to_bytes().as_ref());
bytes.extend_from_slice(&pc_coms_len.to_le_bytes());
for pc in self.pc_coms.iter() {
bytes.extend_from_slice((pc.to_bytes()).as_ref());
}
bytes.extend_from_slice(self.pk_user.pk.to_bytes().as_ref());
bytes
}
#[allow(dead_code)]
pub fn from_bytes(bytes: &[u8]) -> Result<WithdrawalReqInstance> {
WithdrawalReqInstance::try_from(bytes)
}
}
// witness: m1, m2, m3, o, o1, o2, o3,
pub struct WithdrawalReqWitness {
pub attributes: Vec<Scalar>,
// Opening for the joined commitment com
pub com_opening: Scalar,
// Openings for the pedersen commitments
pub pc_coms_openings: Vec<Scalar>,
}
#[derive(Debug, PartialEq)]
pub struct WithdrawalReqProof {
challenge: Scalar,
response_opening: Scalar,
response_openings: Vec<Scalar>,
response_attributes: Vec<Scalar>,
}
impl WithdrawalReqProof {
pub(crate) fn construct(
params: &GroupParameters,
instance: &WithdrawalReqInstance,
witness: &WithdrawalReqWitness,
) -> Self {
// generate random values to replace the witnesses
let r_com_opening = params.random_scalar();
let r_pedcom_openings = params.n_random_scalars(witness.pc_coms_openings.len());
let r_attributes = params.n_random_scalars(witness.attributes.len());
// compute zkp commitments for each instance
let zkcm_com = params.gen1() * r_com_opening
+ r_attributes
.iter()
.zip(params.gammas().iter())
.map(|(rm_i, gamma_i)| gamma_i * rm_i)
.sum::<G1Projective>();
let zkcm_pedcom = r_pedcom_openings
.iter()
.zip(r_attributes.iter())
.map(|(o_j, m_j)| params.gen1() * o_j + instance.h * m_j)
.collect::<Vec<_>>();
let zkcm_user_sk = params.gen1() * r_attributes[0];
// covert to bytes
let gammas_bytes = params
.gammas()
.iter()
.map(|gamma| gamma.to_bytes())
.collect::<Vec<_>>();
let zkcm_pedcom_bytes = zkcm_pedcom
.iter()
.map(|cm| cm.to_bytes())
.collect::<Vec<_>>();
// compute zkp challenge using g1, gammas, c, h, c1, c2, c3, zk commitments
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(params.gen1().to_bytes().as_ref())
.chain(gammas_bytes.iter().map(|gamma| gamma.as_ref()))
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_com.to_bytes().as_ref()))
.chain(zkcm_pedcom_bytes.iter().map(|c| c.as_ref()))
.chain(std::iter::once(zkcm_user_sk.to_bytes().as_ref())),
);
// compute response
let response_opening = produce_response(&r_com_opening, &challenge, &witness.com_opening);
let response_openings = produce_responses(
&r_pedcom_openings,
&challenge,
&witness.pc_coms_openings.iter().collect::<Vec<_>>(),
);
let response_attributes = produce_responses(
&r_attributes,
&challenge,
&witness.attributes.iter().collect::<Vec<_>>(),
);
WithdrawalReqProof {
challenge,
response_opening,
response_openings,
response_attributes,
}
}
pub(crate) fn verify(
&self,
params: &GroupParameters,
instance: &WithdrawalReqInstance,
) -> bool {
// recompute zk commitments for each instance
let zkcm_com = instance.com * self.challenge
+ params.gen1() * self.response_opening
+ self
.response_attributes
.iter()
.zip(params.gammas().iter())
.map(|(m_i, gamma_i)| gamma_i * m_i)
.sum::<G1Projective>();
let zkcm_pedcom = izip!(
instance.pc_coms.iter(),
self.response_openings.iter(),
self.response_attributes.iter()
)
.map(|(cm_j, resp_o_j, resp_m_j)| {
cm_j * self.challenge + params.gen1() * resp_o_j + instance.h * resp_m_j
})
.collect::<Vec<_>>();
let zk_commitment_user_sk =
instance.pk_user.pk * self.challenge + params.gen1() * self.response_attributes[0];
// covert to bytes
let gammas_bytes = params
.gammas()
.iter()
.map(|gamma| gamma.to_bytes())
.collect::<Vec<_>>();
let zkcm_pedcom_bytes = zkcm_pedcom
.iter()
.map(|cm| cm.to_bytes())
.collect::<Vec<_>>();
// recompute zkp challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(params.gen1().to_bytes().as_ref())
.chain(gammas_bytes.iter().map(|hs| hs.as_ref()))
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_com.to_bytes().as_ref()))
.chain(zkcm_pedcom_bytes.iter().map(|c| c.as_ref()))
.chain(std::iter::once(zk_commitment_user_sk.to_bytes().as_ref())),
);
challenge == self.challenge
}
pub fn to_bytes(&self) -> Vec<u8> {
let challenge_bytes = self.challenge.to_bytes();
let response_opening_bytes = self.response_opening.to_bytes();
let ro_len = self.response_openings.len() as u64;
let ra_len = self.response_attributes.len() as u64;
let mut bytes =
Vec::with_capacity(32 + 32 + 8 + ro_len as usize * 32 + 8 + ra_len as usize * 32);
bytes.extend_from_slice(&challenge_bytes);
bytes.extend_from_slice(&response_opening_bytes);
bytes.extend_from_slice(&ro_len.to_le_bytes());
for ro in &self.response_openings {
bytes.extend_from_slice(&ro.to_bytes());
}
bytes.extend_from_slice(&ra_len.to_le_bytes());
for ra in &self.response_attributes {
bytes.extend_from_slice(&ra.to_bytes());
}
bytes
}
}
impl TryFrom<&[u8]> for WithdrawalReqProof {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<WithdrawalReqProof> {
if bytes.len() < 32 + 32 + 16 + 32 + 32 || (bytes.len() - 16) % 32 != 0 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize proof of withdrawal with bytes of invalid length".to_string(),
));
}
let mut idx = 0;
let challenge_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let response_opening_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let challenge = try_deserialize_scalar(
&challenge_bytes,
CompactEcashError::Deserialization("Failed to deserialize challenge".to_string()),
)?;
let response_opening = try_deserialize_scalar(
&response_opening_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize the response to the random".to_string(),
),
)?;
let ro_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
idx += 8;
if bytes[idx..].len() < ro_len as usize * 32 + 8 {
return Err(CompactEcashError::Deserialization(
"tried to deserialize response openings".to_string(),
));
}
let ro_end = idx + ro_len as usize * 32;
let response_openings = try_deserialize_scalar_vec(
ro_len,
&bytes[idx..ro_end],
CompactEcashError::Deserialization(
"Failed to deserialize openings response".to_string(),
),
)?;
let ra_len = u64::from_le_bytes(bytes[ro_end..ro_end + 8].try_into().unwrap());
let response_attributes = try_deserialize_scalar_vec(
ra_len,
&bytes[ro_end + 8..],
CompactEcashError::Deserialization(
"Failed to deserialize attributes response".to_string(),
),
)?;
Ok(WithdrawalReqProof {
challenge,
response_opening,
response_openings,
response_attributes,
})
}
}
#[cfg(test)]
mod tests {
use group::Group;
use rand::thread_rng;
use crate::utils::hash_g1;
use super::*;
#[test]
fn withdrawal_request_instance_roundtrip() {
let mut rng = thread_rng();
let params = GroupParameters::new().unwrap();
let instance = WithdrawalReqInstance {
com: G1Projective::random(&mut rng),
h: G1Projective::random(&mut rng),
pc_coms: vec![
G1Projective::random(&mut rng),
G1Projective::random(&mut rng),
G1Projective::random(&mut rng),
],
pk_user: PublicKeyUser {
pk: params.gen1() * params.random_scalar(),
},
};
let instance_bytes = instance.to_bytes();
let instance_p = WithdrawalReqInstance::from_bytes(&instance_bytes).unwrap();
assert_eq!(instance, instance_p)
}
#[test]
fn withdrawal_proof_construct_and_verify() {
let _rng = thread_rng();
let params = GroupParameters::new().unwrap();
let sk = params.random_scalar();
let pk_user = PublicKeyUser {
pk: params.gen1() * sk,
};
let v = params.random_scalar();
let t = params.random_scalar();
let attr = vec![sk, v, t];
let com_opening = params.random_scalar();
let com = params.gen1() * com_opening
+ attr
.iter()
.zip(params.gammas())
.map(|(&m, gamma)| gamma * m)
.sum::<G1Projective>();
let h = hash_g1(com.to_bytes());
let pc_openings = params.n_random_scalars(attr.len());
let pc_coms = pc_openings
.iter()
.zip(attr.iter())
.map(|(o_j, m_j)| params.gen1() * o_j + h * m_j)
.collect::<Vec<_>>();
let instance = WithdrawalReqInstance {
com,
h,
pc_coms,
pk_user,
};
let witness = WithdrawalReqWitness {
attributes: attr,
com_opening,
pc_coms_openings: pc_openings,
};
let zk_proof = WithdrawalReqProof::construct(&params, &instance, &witness);
assert!(zk_proof.verify(&params, &instance))
}
}
@@ -0,0 +1,168 @@
use core::iter::Sum;
use core::ops::Mul;
use std::cell::Cell;
use bls12_381::{G2Prepared, G2Projective, Scalar};
use group::Curve;
use itertools::Itertools;
use crate::error::{CompactEcashError, Result};
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::GroupParameters;
use crate::scheme::withdrawal::RequestInfo;
use crate::scheme::{PartialWallet, Wallet};
use crate::utils::{
check_bilinear_pairing, perform_lagrangian_interpolation_at_origin, PartialSignature,
Signature, SignatureShare, SignerIndex,
};
use crate::Attribute;
pub(crate) trait Aggregatable: Sized {
fn aggregate(aggregatable: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self>;
fn check_unique_indices(indices: &[SignerIndex]) -> bool {
// if aggregation is a threshold one, all indices should be unique
indices.iter().unique_by(|&index| index).count() == indices.len()
}
}
impl<T> Aggregatable for T
where
T: Sum,
for<'a> T: Sum<&'a T>,
for<'a> &'a T: Mul<Scalar, Output = T>,
{
fn aggregate(aggregatable: &[T], indices: Option<&[u64]>) -> Result<T> {
if aggregatable.is_empty() {
return Err(CompactEcashError::Aggregation(
"Empty set of values".to_string(),
));
}
if let Some(indices) = indices {
if !Self::check_unique_indices(indices) {
return Err(CompactEcashError::Aggregation(
"Non-unique indices".to_string(),
));
}
perform_lagrangian_interpolation_at_origin(indices, aggregatable)
} else {
// non-threshold
Ok(aggregatable.iter().sum())
}
}
}
impl Aggregatable for PartialSignature {
fn aggregate(sigs: &[PartialSignature], indices: Option<&[u64]>) -> Result<Signature> {
let h = sigs
.get(0)
.ok_or_else(|| CompactEcashError::Aggregation("Empty set of signatures".to_string()))?
.sig1();
// TODO: is it possible to avoid this allocation?
let sigmas = sigs.iter().map(|sig| *sig.sig2()).collect::<Vec<_>>();
let aggr_sigma = Aggregatable::aggregate(&sigmas, indices)?;
Ok(Signature(*h, aggr_sigma))
}
}
/// Ensures all provided verification keys were generated to verify the same number of attributes.
fn check_same_key_size(keys: &[VerificationKeyAuth]) -> bool {
keys.iter().map(|vk| vk.beta_g1.len()).all_equal()
&& keys.iter().map(|vk| vk.beta_g2.len()).all_equal()
}
pub fn aggregate_verification_keys(
keys: &[VerificationKeyAuth],
indices: Option<&[SignerIndex]>,
) -> Result<VerificationKeyAuth> {
if !check_same_key_size(keys) {
return Err(CompactEcashError::Aggregation(
"Verification keys are of different sizes".to_string(),
));
}
Aggregatable::aggregate(keys, indices)
}
pub fn aggregate_signature_shares(
params: &GroupParameters,
verification_key: &VerificationKeyAuth,
attributes: &[Attribute],
shares: &[SignatureShare],
) -> Result<Signature> {
let (signatures, indices): (Vec<_>, Vec<_>) = shares
.iter()
.map(|share| (*share.signature(), share.index()))
.unzip();
aggregate_signatures(
params,
verification_key,
attributes,
&signatures,
Some(&indices),
)
}
pub fn aggregate_signatures(
params: &GroupParameters,
verification_key: &VerificationKeyAuth,
attributes: &[Attribute],
signatures: &[PartialSignature],
indices: Option<&[SignerIndex]>,
) -> Result<Signature> {
// aggregate the signature
let signature = match Aggregatable::aggregate(signatures, indices) {
Ok(res) => res,
Err(err) => return Err(err),
};
// Verify the signature
let alpha = verification_key.alpha;
let tmp = attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
if !check_bilinear_pairing(
&signature.0.to_affine(),
&G2Prepared::from((alpha + tmp).to_affine()),
&signature.1.to_affine(),
params.prepared_miller_g2(),
) {
return Err(CompactEcashError::Aggregation(
"Verification of the aggregated signature failed".to_string(),
));
}
Ok(signature)
}
pub fn aggregate_wallets(
params: &GroupParameters,
verification_key: &VerificationKeyAuth,
sk_user: &SecretKeyUser,
wallets: &[PartialWallet],
req_info: &RequestInfo,
) -> Result<Wallet> {
// Aggregate partial wallets
let signature_shares: Vec<SignatureShare> = wallets
.iter()
.enumerate()
.map(|(idx, wallet)| SignatureShare::new(*wallet.signature(), (idx + 1) as u64))
.collect();
let attributes = vec![sk_user.sk, req_info.get_v()];
let aggregated_signature =
aggregate_signature_shares(params, verification_key, &attributes, &signature_shares)?;
Ok(Wallet {
sig: aggregated_signature,
v: req_info.get_v(),
l: Cell::new(0),
})
}
@@ -0,0 +1,464 @@
use crate::error::Result;
use crate::scheme::keygen::PublicKeyUser;
use crate::scheme::Payment;
use crate::PayInfo;
#[derive(Debug, Eq, PartialEq)]
pub enum IdentifyResult {
NotADuplicatePayment,
DuplicatePayInfo(PayInfo),
DoubleSpendingPublicKeys(PublicKeyUser),
}
pub fn identify(
payment1: Payment,
payment2: Payment,
pay_info1: PayInfo,
pay_info2: PayInfo,
) -> Result<IdentifyResult> {
let mut k = 0;
let mut j = 0;
for (id1, pay1_ss) in payment1.ss.iter().enumerate() {
for (id2, pay2_ss) in payment2.ss.iter().enumerate() {
if pay1_ss == pay2_ss {
k = id1;
j = id2;
break;
}
}
}
if payment1
.ss
.iter()
.any(|pay1_ss| payment2.ss.contains(pay1_ss))
{
if pay_info1 == pay_info2 {
Ok(IdentifyResult::DuplicatePayInfo(pay_info1))
} else {
let rr_diff = payment1.rr[k] - payment2.rr[j];
let pk = (payment2.tt[j] * payment1.rr[k] - payment1.tt[k] * payment2.rr[j])
* rr_diff.invert().unwrap();
let pk_user = PublicKeyUser { pk };
Ok(IdentifyResult::DoubleSpendingPublicKeys(pk_user))
}
} else {
Ok(IdentifyResult::NotADuplicatePayment)
}
}
#[cfg(test)]
mod tests {
use itertools::izip;
use crate::scheme::identify::{identify, IdentifyResult};
use crate::scheme::keygen::{PublicKeyUser, SecretKeyUser};
use crate::scheme::setup::setup;
use crate::{
aggregate_verification_keys, aggregate_wallets, generate_keypair_user, issue_verify,
issue_wallet, ttp_keygen, withdrawal_request, PartialWallet, PayInfo, VerificationKeyAuth,
};
#[test]
fn duplicate_payments_with_the_same_pay_info() {
let ll = 32;
let params = setup(ll);
let grparams = params.grp();
let user_keypair = generate_keypair_user(grparams);
let (req, req_info) = withdrawal_request(grparams, &user_keypair.secret_key()).unwrap();
let authorities_keypairs = ttp_keygen(grparams, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue_wallet(
grparams,
auth_keypair.secret_key(),
user_keypair.public_key(),
&req,
);
wallet_blinded_signatures.push(blind_signature.unwrap());
}
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
wallet_blinded_signatures.iter(),
verification_keys_auth.iter()
)
.map(|(w, vk)| {
issue_verify(grparams, vk, &user_keypair.secret_key(), w, &req_info).unwrap()
})
.collect();
// Aggregate partial wallets
let aggr_wallet = aggregate_wallets(
grparams,
&verification_key,
&user_keypair.secret_key(),
&unblinded_wallet_shares,
&req_info,
)
.unwrap();
// Let's try to spend some coins
let provider_keypair = generate_keypair_user(&grparams);
let pay_info1 = PayInfo::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
let spend_vv = 1;
let (payment1, _upd_wallet) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info1,
false,
spend_vv,
)
.unwrap();
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1)
.unwrap());
let payment2 = payment1.clone();
assert!(payment2
.spend_verify(&params, &verification_key, &pay_info1)
.unwrap());
let pay_info2 = pay_info1.clone();
let identify_result =
identify(payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
assert_eq!(
identify_result,
IdentifyResult::DuplicatePayInfo(pay_info1.clone())
);
}
#[test]
fn ok_if_two_different_payments() {
let ll = 32;
let params = setup(ll);
let grparams = params.grp();
let user_keypair = generate_keypair_user(grparams);
let (req, req_info) = withdrawal_request(grparams, &user_keypair.secret_key()).unwrap();
let authorities_keypairs = ttp_keygen(grparams, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue_wallet(
grparams,
auth_keypair.secret_key(),
user_keypair.public_key(),
&req,
);
wallet_blinded_signatures.push(blind_signature.unwrap());
}
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
wallet_blinded_signatures.iter(),
verification_keys_auth.iter()
)
.map(|(w, vk)| {
issue_verify(grparams, vk, &user_keypair.secret_key(), w, &req_info).unwrap()
})
.collect();
// Aggregate partial wallets
let aggr_wallet = aggregate_wallets(
grparams,
&verification_key,
&user_keypair.secret_key(),
&unblinded_wallet_shares,
&req_info,
)
.unwrap();
// Let's try to spend some coins
let provider_keypair = generate_keypair_user(&grparams);
let pay_info1 = PayInfo::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
let spend_vv = 1;
let (payment1, upd_wallet) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info1,
false,
spend_vv,
)
.unwrap();
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1)
.unwrap());
let pay_info2 = PayInfo::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
let (payment2, _) = upd_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info2,
false,
spend_vv,
)
.unwrap();
assert!(payment2
.spend_verify(&params, &verification_key, &pay_info2)
.unwrap());
let identify_result =
identify(payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
assert_eq!(identify_result, IdentifyResult::NotADuplicatePayment);
}
#[test]
fn two_payments_with_one_repeating_serial_number_but_different_pay_info() {
let ll = 32;
let params = setup(ll);
let grp = params.grp();
let user_keypair = generate_keypair_user(grp);
// GENERATE KEYS FOR OTHER USERS
let mut public_keys: Vec<PublicKeyUser> = Default::default();
for _i in 0..50 {
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = sk_user.public_key(grp);
public_keys.push(pk_user.clone());
}
public_keys.push(user_keypair.public_key().clone());
let (req, req_info) = withdrawal_request(grp, &user_keypair.secret_key()).unwrap();
let authorities_keypairs = ttp_keygen(grp, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue_wallet(
grp,
auth_keypair.secret_key(),
user_keypair.public_key(),
&req,
);
wallet_blinded_signatures.push(blind_signature.unwrap());
}
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
wallet_blinded_signatures.iter(),
verification_keys_auth.iter()
)
.map(|(w, vk)| issue_verify(grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
.collect();
// Aggregate partial wallets
let aggr_wallet = aggregate_wallets(
grp,
&verification_key,
&user_keypair.secret_key(),
&unblinded_wallet_shares,
&req_info,
)
.unwrap();
// Let's try to spend some coins
let provider_keypair = generate_keypair_user(&grp);
let pay_info1 = PayInfo::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
let spend_vv = 1;
let (payment1, _upd_wallet) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info1,
false,
spend_vv,
)
.unwrap();
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1)
.unwrap());
// let's reverse the spending counter in the wallet to create a double spending payment
let current_l = aggr_wallet.l.get();
aggr_wallet.l.set(current_l - 1);
let pay_info2 = PayInfo::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
let (payment2, _) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info2,
false,
spend_vv,
)
.unwrap();
assert!(payment2
.spend_verify(&params, &verification_key, &pay_info2)
.unwrap());
let identify_result =
identify(payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
assert_eq!(
identify_result,
IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key())
);
}
#[test]
fn two_payments_with_multiple_repeating_serial_numbers_but_different_pay_info() {
let ll = 32;
let params = setup(ll);
let grp = params.grp();
let user_keypair = generate_keypair_user(grp);
// GENERATE KEYS FOR OTHER USERS
let mut public_keys: Vec<PublicKeyUser> = Default::default();
for _ in 0..50 {
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = sk_user.public_key(grp);
public_keys.push(pk_user.clone());
}
public_keys.push(user_keypair.public_key().clone());
let (req, req_info) = withdrawal_request(grp, &user_keypair.secret_key()).unwrap();
let authorities_keypairs = ttp_keygen(grp, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue_wallet(
grp,
auth_keypair.secret_key(),
user_keypair.public_key(),
&req,
);
wallet_blinded_signatures.push(blind_signature.unwrap());
}
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
wallet_blinded_signatures.iter(),
verification_keys_auth.iter()
)
.map(|(w, vk)| issue_verify(grp, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
.collect();
// Aggregate partial wallets
let aggr_wallet = aggregate_wallets(
grp,
&verification_key,
&user_keypair.secret_key(),
&unblinded_wallet_shares,
&req_info,
)
.unwrap();
// Let's try to spend some coins
let provider_keypair = generate_keypair_user(&grp);
let pay_info1 = PayInfo::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
let spend_vv = 10;
let (payment1, _) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info1,
false,
spend_vv,
)
.unwrap();
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1)
.unwrap());
// let's reverse the spending counter in the wallet to create a double spending payment
let current_l = aggr_wallet.l.get();
aggr_wallet.l.set(current_l - 10);
let pay_info2 = PayInfo::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
let (payment2, _) = aggr_wallet
.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&pay_info2,
false,
spend_vv,
)
.unwrap();
let identify_result =
identify(payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
assert_eq!(
identify_result,
IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key())
);
}
}
@@ -0,0 +1,595 @@
use core::borrow::Borrow;
use core::iter::Sum;
use core::ops::{Add, Mul};
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
use std::convert::TryFrom;
use std::convert::TryInto;
use bls12_381::{G1Projective, G2Projective, Scalar};
use group::{Curve, GroupEncoding};
use crate::error::{CompactEcashError, Result};
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::setup::GroupParameters;
use crate::scheme::SignerIndex;
use crate::traits::Bytable;
use crate::utils::Polynomial;
use crate::utils::{
try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
try_deserialize_scalar_vec,
};
use crate::Base58;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Debug, PartialEq, Clone)]
pub struct SecretKeyAuth {
pub(crate) x: Scalar,
pub(crate) ys: Vec<Scalar>,
}
impl TryFrom<&[u8]> for SecretKeyAuth {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<SecretKeyAuth> {
// There should be x and at least one y
if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 {
return Err(CompactEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8,
target: 32 * 2 + 8,
modulus: 32,
object: "secret key".to_string(),
});
}
// this conversion will not fail as we are taking the same length of data
let x_bytes: [u8; 32] = bytes[..32].try_into().unwrap();
let ys_len = u64::from_le_bytes(bytes[32..40].try_into().unwrap());
let actual_ys_len = (bytes.len() - 40) / 32;
if ys_len as usize != actual_ys_len {
return Err(CompactEcashError::Deserialization(format!(
"Tried to deserialize secret key with inconsistent ys len (expected {}, got {})",
ys_len, actual_ys_len
)));
}
let x = try_deserialize_scalar(
&x_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize secret key scalar".to_string(),
),
)?;
let ys = try_deserialize_scalar_vec(
ys_len,
&bytes[40..],
CompactEcashError::Deserialization(
"Failed to deserialize secret key scalars".to_string(),
),
)?;
Ok(SecretKeyAuth { x, ys })
}
}
impl SecretKeyAuth {
pub fn verification_key(&self, params: &GroupParameters) -> VerificationKeyAuth {
let g1 = params.gen1();
let g2 = params.gen2();
VerificationKeyAuth {
alpha: g2 * self.x,
beta_g1: self.ys.iter().map(|y| g1 * y).collect(),
beta_g2: self.ys.iter().map(|y| g2 * y).collect(),
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let ys_len = self.ys.len();
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) * 32);
bytes.extend_from_slice(&self.x.to_bytes());
bytes.extend_from_slice(&ys_len.to_le_bytes());
for y in self.ys.iter() {
bytes.extend_from_slice(&y.to_bytes())
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<SecretKeyAuth> {
SecretKeyAuth::try_from(bytes)
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct VerificationKeyAuth {
pub(crate) alpha: G2Projective,
pub(crate) beta_g1: Vec<G1Projective>,
pub(crate) beta_g2: Vec<G2Projective>,
}
impl TryFrom<&[u8]> for VerificationKeyAuth {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<VerificationKeyAuth> {
// There should be at least alpha, one betaG1 and one betaG2 and their length
if bytes.len() < 96 * 2 + 48 + 8 || (bytes.len() - 8 - 96) % (96 + 48) != 0 {
return Err(CompactEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8 - 96,
target: 96 * 2 + 48 + 8,
modulus: 96 + 48,
object: "verification key".to_string(),
});
}
// this conversion will not fail as we are taking the same length of data
let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
let betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
let actual_betas_len = (bytes.len() - 104) / (96 + 48);
if betas_len as usize != actual_betas_len {
return Err(
CompactEcashError::Deserialization(
format!("Tried to deserialize verification key with inconsistent betas len (expected {}, got {})",
betas_len, actual_betas_len
)));
}
let alpha = try_deserialize_g2_projective(
&alpha_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize verification key G2 point (alpha)".to_string(),
),
)?;
let mut beta_g1 = Vec::with_capacity(betas_len as usize);
let mut beta_g1_end: u64 = 0;
for i in 0..betas_len {
let start = (104 + i * 48) as usize;
let end = start + 48;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g1_projective(
&beta_i_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize verification key G1 point (beta)".to_string(),
),
)?;
beta_g1_end = end as u64;
beta_g1.push(beta_i)
}
let mut beta_g2 = Vec::with_capacity(betas_len as usize);
for i in 0..betas_len {
let start = (beta_g1_end + i * 96) as usize;
let end = start + 96;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g2_projective(
&beta_i_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize verification key G2 point (beta)".to_string(),
),
)?;
beta_g2.push(beta_i)
}
Ok(VerificationKeyAuth {
alpha,
beta_g1,
beta_g2,
})
}
}
impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth {
type Output = VerificationKeyAuth;
#[inline]
fn add(self, rhs: &'b VerificationKeyAuth) -> VerificationKeyAuth {
// If you're trying to add two keys together that were created
// for different number of attributes, just panic as it's a
// nonsense operation.
assert_eq!(
self.beta_g1.len(),
rhs.beta_g1.len(),
"trying to add verification keys generated for different number of attributes [G1]"
);
assert_eq!(
self.beta_g2.len(),
rhs.beta_g2.len(),
"trying to add verification keys generated for different number of attributes [G2]"
);
assert_eq!(
self.beta_g1.len(),
self.beta_g2.len(),
"this key is incorrect - the number of elements G1 and G2 does not match"
);
assert_eq!(
rhs.beta_g1.len(),
rhs.beta_g2.len(),
"they key you want to add is incorrect - the number of elements G1 and G2 does not match"
);
VerificationKeyAuth {
alpha: self.alpha + rhs.alpha,
beta_g1: self
.beta_g1
.iter()
.zip(rhs.beta_g1.iter())
.map(|(self_beta_g1, rhs_beta_g1)| self_beta_g1 + rhs_beta_g1)
.collect(),
beta_g2: self
.beta_g2
.iter()
.zip(rhs.beta_g2.iter())
.map(|(self_beta_g2, rhs_beta_g2)| self_beta_g2 + rhs_beta_g2)
.collect(),
}
}
}
impl<'a> Mul<Scalar> for &'a VerificationKeyAuth {
type Output = VerificationKeyAuth;
#[inline]
fn mul(self, rhs: Scalar) -> Self::Output {
VerificationKeyAuth {
alpha: self.alpha * rhs,
beta_g1: self.beta_g1.iter().map(|b_i| b_i * rhs).collect(),
beta_g2: self.beta_g2.iter().map(|b_i| b_i * rhs).collect(),
}
}
}
impl<T> Sum<T> for VerificationKeyAuth
where
T: Borrow<VerificationKeyAuth>,
{
#[inline]
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = T>,
{
let mut peekable = iter.peekable();
let head_attributes = match peekable.peek() {
Some(head) => head.borrow().beta_g2.len(),
None => {
// TODO: this is a really weird edge case. You're trying to sum an EMPTY iterator
// of VerificationKey. So should it panic here or just return some nonsense value?
return VerificationKeyAuth::identity(0);
}
};
peekable.fold(
VerificationKeyAuth::identity(head_attributes),
|acc, item| acc + item.borrow(),
)
}
}
impl VerificationKeyAuth {
/// Create a (kinda) identity verification key using specified
/// number of 'beta' elements
pub(crate) fn identity(beta_size: usize) -> Self {
VerificationKeyAuth {
alpha: G2Projective::identity(),
beta_g1: vec![G1Projective::identity(); beta_size],
beta_g2: vec![G2Projective::identity(); beta_size],
}
}
pub fn aggregate(sigs: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self> {
aggregate_verification_keys(sigs, indices)
}
pub fn alpha(&self) -> &G2Projective {
&self.alpha
}
pub fn beta_g1(&self) -> &Vec<G1Projective> {
&self.beta_g1
}
pub fn beta_g2(&self) -> &Vec<G2Projective> {
&self.beta_g2
}
pub fn to_bytes(&self) -> Vec<u8> {
let beta_g1_len = self.beta_g1.len();
let beta_g2_len = self.beta_g2.len();
let mut bytes = Vec::with_capacity(96 + 8 + beta_g1_len * 48 + beta_g2_len * 96);
bytes.extend_from_slice(&self.alpha.to_affine().to_compressed());
bytes.extend_from_slice(&beta_g1_len.to_le_bytes());
for beta_g1 in self.beta_g1.iter() {
bytes.extend_from_slice(&beta_g1.to_affine().to_compressed())
}
for beta_g2 in self.beta_g2.iter() {
bytes.extend_from_slice(&beta_g2.to_affine().to_compressed())
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<VerificationKeyAuth> {
VerificationKeyAuth::try_from(bytes)
}
}
impl Bytable for VerificationKeyAuth {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> std::result::Result<Self, CompactEcashError> {
Self::from_bytes(slice)
}
}
impl Base58 for VerificationKeyAuth {}
#[derive(Debug, PartialEq, Clone, Zeroize, ZeroizeOnDrop)]
pub struct SecretKeyUser {
pub sk: Scalar,
}
impl SecretKeyUser {
pub fn public_key(&self, params: &GroupParameters) -> PublicKeyUser {
PublicKeyUser {
pk: params.gen1() * self.sk,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
self.sk.to_bytes().to_vec()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
let sk = Scalar::try_from_byte_slice(bytes)?;
Ok(SecretKeyUser { sk })
}
}
impl Bytable for SecretKeyUser {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> std::result::Result<Self, CompactEcashError> {
Self::from_bytes(slice)
}
}
impl Base58 for SecretKeyUser {}
impl PemStorableKey for SecretKeyUser {
type Error = CompactEcashError;
fn pem_type() -> &'static str {
"ECASH PRIVATE KEY"
}
fn to_bytes(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn from_bytes(bytes: &[u8]) -> Result<Self> {
Self::from_bytes(bytes)
}
}
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct PublicKeyUser {
pub(crate) pk: G1Projective,
}
impl PublicKeyUser {
pub fn to_base58_string(&self) -> String {
bs58::encode(&self.pk.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self> {
let bytes = bs58::decode(val)
.into_vec()
.map_err(|source| CompactEcashError::Deserialization(source.to_string()))?;
Self::from_bytes(&bytes)
}
pub fn to_bytes(&self) -> Vec<u8> {
self.pk.to_affine().to_compressed().to_vec()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() != 48 {
return Err(CompactEcashError::Deserialization(
"Failed to deserialize : Invalid length".to_string(),
));
}
let pk_bytes: &[u8; 48] = bytes[..48].try_into().unwrap();
let pk = try_deserialize_g1_projective(
pk_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize verification key G1 point".to_string(),
),
)?;
Ok(PublicKeyUser { pk })
}
}
impl PemStorableKey for PublicKeyUser {
type Error = CompactEcashError;
fn pem_type() -> &'static str {
"ECASH PUBLIC KEY"
}
fn to_bytes(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn from_bytes(bytes: &[u8]) -> Result<Self> {
Self::from_bytes(bytes)
}
}
pub struct KeyPairAuth {
secret_key: SecretKeyAuth,
verification_key: VerificationKeyAuth,
/// Optional index value specifying polynomial point used during threshold key generation.
pub index: Option<SignerIndex>,
}
impl KeyPairAuth {
pub fn new(
sk: SecretKeyAuth,
vk: VerificationKeyAuth,
index: Option<SignerIndex>,
) -> KeyPairAuth {
KeyPairAuth {
secret_key: sk,
verification_key: vk,
index,
}
}
pub fn secret_key(&self) -> SecretKeyAuth {
self.secret_key.clone()
}
pub fn verification_key(&self) -> VerificationKeyAuth {
self.verification_key.clone()
}
}
#[derive(Zeroize, ZeroizeOnDrop, Debug, Clone, PartialEq)]
pub struct KeyPairUser {
secret_key: SecretKeyUser,
#[zeroize(skip)]
public_key: PublicKeyUser,
}
impl KeyPairUser {
pub fn secret_key(&self) -> SecretKeyUser {
self.secret_key.clone()
}
pub fn public_key(&self) -> PublicKeyUser {
self.public_key.clone()
}
pub fn to_bytes(&self) -> Vec<u8> {
[self.secret_key.to_bytes(), self.public_key.to_bytes()].concat()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() != 32 + 48 {
return Err(CompactEcashError::Deserialization(
"Failed to deserialize keypair : Invalid length".to_string(),
));
}
let sk = SecretKeyUser::from_bytes(&bytes[..32])?;
let pk = PublicKeyUser::from_bytes(&bytes[32..32 + 48])?;
Ok(KeyPairUser {
secret_key: sk,
public_key: pk,
})
}
}
impl PemStorableKeyPair for KeyPairUser {
type PrivatePemKey = SecretKeyUser;
type PublicPemKey = PublicKeyUser;
fn private_key(&self) -> &Self::PrivatePemKey {
&self.secret_key
}
fn public_key(&self) -> &Self::PublicPemKey {
&self.public_key
}
fn from_keys(private_key: Self::PrivatePemKey, public_key: Self::PublicPemKey) -> Self {
KeyPairUser {
secret_key: private_key,
public_key,
}
}
}
pub fn generate_keypair_user(params: &GroupParameters) -> KeyPairUser {
let sk_user = SecretKeyUser {
sk: params.random_scalar(),
};
let pk_user = PublicKeyUser {
pk: params.gen1() * sk_user.sk,
};
KeyPairUser {
secret_key: sk_user,
public_key: pk_user,
}
}
pub fn ttp_keygen(
params: &GroupParameters,
threshold: u64,
num_authorities: u64,
) -> Result<Vec<KeyPairAuth>> {
if threshold == 0 {
return Err(CompactEcashError::Setup(
"Tried to generate threshold keys with a 0 threshold value".to_string(),
));
}
if threshold > num_authorities {
return Err(
CompactEcashError::Setup(
"Tried to generate threshold keys for threshold value being higher than number of the signing authorities".to_string(),
));
}
let attributes = params.gammas().len();
// generate polynomials
let v = Polynomial::new_random(params, threshold - 1);
let ws = (0..attributes)
.map(|_| Polynomial::new_random(params, threshold - 1))
.collect::<Vec<_>>();
// TODO: potentially if we had some known authority identifier we could use that instead
// of the increasing (1,2,3,...) sequence
let polynomial_indices = (1..=num_authorities).collect::<Vec<_>>();
// generate polynomial shares
let x = polynomial_indices
.iter()
.map(|&id| v.evaluate(&Scalar::from(id)));
let ys = polynomial_indices.iter().map(|&id| {
ws.iter()
.map(|w| w.evaluate(&Scalar::from(id)))
.collect::<Vec<_>>()
});
// finally set the keys
let secret_keys = x.zip(ys).map(|(x, ys)| SecretKeyAuth { x, ys });
let keypairs = secret_keys
.zip(polynomial_indices.iter())
.map(|(secret_key, index)| {
let verification_key = secret_key.verification_key(params);
KeyPairAuth {
secret_key,
verification_key,
index: Some(*index),
}
})
.collect();
Ok(keypairs)
}
@@ -0,0 +1,810 @@
use std::cell::Cell;
use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar};
use group::Curve;
use getset::{CopyGetters, Getters};
use crate::error::{CompactEcashError, Result};
use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::traits::Bytable;
use crate::utils::{
check_bilinear_pairing, hash_to_scalar, try_deserialize_g1_projective,
try_deserialize_g2_projective, try_deserialize_scalar, Signature, SignerIndex,
};
use crate::{Attribute, Base58};
use chrono::Utc;
use rand::{thread_rng, Rng};
pub mod aggregation;
pub mod identify;
pub mod keygen;
pub mod setup;
pub mod withdrawal;
#[derive(Debug, Clone, PartialEq)]
pub struct PartialWallet {
sig: Signature,
v: Scalar,
idx: Option<SignerIndex>,
}
impl PartialWallet {
pub fn signature(&self) -> &Signature {
&self.sig
}
pub fn v(&self) -> Scalar {
self.v
}
pub fn index(&self) -> Option<SignerIndex> {
self.idx
}
pub fn to_bytes(&self) -> [u8; 136] {
let mut bytes = [0u8; 136];
bytes[0..96].copy_from_slice(&self.sig.to_bytes());
bytes[96..128].copy_from_slice(&self.v.to_bytes());
// Check if idx is Some and copy its bytes if it exists
if let Some(idx) = &self.idx {
bytes[128..136].copy_from_slice(&idx.to_le_bytes());
}
bytes
}
}
impl TryFrom<&[u8]> for PartialWallet {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<PartialWallet> {
if bytes.len() != 136 {
return Err(CompactEcashError::Deserialization(format!(
"PartialWallet should be exactly 136 bytes, got {}",
bytes.len()
)));
}
let sig_bytes: &[u8; 96] = &bytes[..96].try_into().expect("Slice size != 96");
let v_bytes: &[u8; 32] = &bytes[96..128].try_into().expect("Slice size != 32");
let idx_bytes: &[u8; 8] = &bytes[128..136].try_into().expect("Slice size != 8");
let sig = Signature::try_from(sig_bytes.as_slice()).unwrap();
let v = Scalar::from_bytes(v_bytes).unwrap();
let mut idx = None;
if !idx_bytes.iter().all(|&x| x == 0) {
idx = Some(u64::from_le_bytes(*idx_bytes));
}
Ok(PartialWallet { sig, v, idx })
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Wallet {
sig: Signature,
v: Scalar,
pub l: Cell<u64>,
}
impl Wallet {
pub fn signature(&self) -> &Signature {
&self.sig
}
pub fn v(&self) -> Scalar {
self.v
}
pub fn l(&self) -> u64 {
self.l.get()
}
pub fn to_bytes(&self) -> [u8; 136] {
let mut bytes = [0u8; 136];
bytes[0..96].copy_from_slice(&self.sig.to_bytes());
bytes[96..128].copy_from_slice(&self.v.to_bytes());
bytes[128..136].copy_from_slice(&self.l.get().to_le_bytes());
bytes
}
#[allow(dead_code)]
fn up(&self) {
self.l.set(self.l.get() + 1);
}
pub fn spend(
&self,
params: &Parameters,
verification_key: &VerificationKeyAuth,
sk_user: &SecretKeyUser,
pay_info: &PayInfo,
bench_flag: bool,
spend_vv: u64,
) -> Result<(Payment, &Self)> {
if self.l() + spend_vv > params.ll() {
return Err(CompactEcashError::Spend(
"The counter l is higher than max L".to_string(),
));
}
let grparams = params.grp();
// randomize signature in the wallet
let (signature_prime, sign_blinding_factor) = self.signature().randomise(grparams);
// construct kappa i.e., blinded attributes for show
let attributes = vec![sk_user.sk, self.v()];
// compute kappa
let kappa = compute_kappa(
grparams,
verification_key,
&attributes,
sign_blinding_factor,
);
// pick random openings o_c
let o_c = grparams.random_scalar();
// compute commitments C
let cc = grparams.gen1() * o_c + grparams.gamma1() * self.v();
let mut aa: Vec<G1Projective> = Default::default();
let mut ss: Vec<G1Projective> = Default::default();
let mut tt: Vec<G1Projective> = Default::default();
let mut rr: Vec<Scalar> = Default::default();
let mut o_a: Vec<Scalar> = Default::default();
let mut o_mu: Vec<Scalar> = Default::default();
let mut mu: Vec<Scalar> = Default::default();
let mut r_k_vec: Vec<Scalar> = Default::default();
let mut kappa_k_vec: Vec<G2Projective> = Default::default();
let mut sign_lk_prime_vec: Vec<Signature> = Default::default();
let mut lk: Vec<Scalar> = Default::default();
for k in 0..spend_vv {
lk.push(Scalar::from(self.l() + k));
// compute hashes R_k of the payment info
let rr_k = hash_to_scalar(pay_info.payinfo);
rr.push(rr_k);
let o_a_k = grparams.random_scalar();
o_a.push(o_a_k);
let aa_k = grparams.gen1() * o_a_k + grparams.gamma1() * Scalar::from(self.l() + k);
aa.push(aa_k);
// evaluate the pseudorandom functions
let ss_k = pseudorandom_f_delta_v(grparams, self.v(), self.l() + k);
ss.push(ss_k);
let tt_k = grparams.gen1() * sk_user.sk
+ pseudorandom_f_g_v(grparams, self.v(), self.l() + k) * rr_k;
tt.push(tt_k);
// compute values mu, o_mu, lambda, o_lambda
let mu_k: Scalar = (self.v() + Scalar::from(self.l() + k) + Scalar::from(1))
.invert()
.unwrap();
mu.push(mu_k);
let o_mu_k = ((o_a_k + o_c) * mu_k).neg();
o_mu.push(o_mu_k);
// parse the signature associated with value l+k
let sign_lk = params.get_sign_by_idx(self.l() + k)?;
// randomise the signature associated with value l+k
let (sign_lk_prime, r_k) = sign_lk.randomise(grparams);
sign_lk_prime_vec.push(sign_lk_prime);
r_k_vec.push(r_k);
// compute kappa_k
let kappa_k = grparams.gen2() * r_k
+ params.pk_rp().alpha
+ params.pk_rp().beta * Scalar::from(self.l() + k);
kappa_k_vec.push(kappa_k);
}
// construct the zkp proof
let spend_instance = SpendInstance {
kappa,
cc,
aa: aa.clone(),
ss: ss.clone(),
tt: tt.clone(),
kappa_k: kappa_k_vec.clone(),
};
let spend_witness = SpendWitness {
attributes,
r: sign_blinding_factor,
o_c,
lk,
o_a,
mu,
o_mu,
r_k: r_k_vec,
};
let zk_proof = SpendProof::construct(
params,
&spend_instance,
&spend_witness,
verification_key,
&rr,
);
// output pay and updated wallet
let pay = Payment {
kappa,
sig: signature_prime,
ss: ss.clone(),
tt: tt.clone(),
aa: aa.clone(),
rr: rr.clone(),
kappa_k: kappa_k_vec.clone(),
sig_lk: sign_lk_prime_vec,
cc,
zk_proof,
vv: spend_vv,
};
// The number of samples collected by the benchmark process is way higher than the
// MAX_WALLET_VALUE we ever consider. Thus, we would execute the spending too many times
// and the initial condition at the top of this function will crush. Thus, we need a
// benchmark flag to signal that we don't want to increase the spending couter but only
// care about the function performance.
if !bench_flag {
let current_l = self.l();
self.l.set(current_l + spend_vv);
}
Ok((pay, self))
}
}
impl TryFrom<&[u8]> for Wallet {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<Wallet> {
if bytes.len() != 136 {
return Err(CompactEcashError::Deserialization(format!(
"Wallet should be exactly 136 bytes, got {}",
bytes.len()
)));
}
let sig_bytes: &[u8; 96] = &bytes[..96].try_into().expect("Slice size != 96");
let v_bytes: &[u8; 32] = &bytes[96..128].try_into().expect("Slice size != 32");
let l_bytes: &[u8; 8] = &bytes[128..136].try_into().expect("Slice size != 8");
let sig = Signature::try_from(sig_bytes.as_slice()).unwrap();
let v = Scalar::from_bytes(v_bytes).unwrap();
let l = Cell::new(u64::from_le_bytes(*l_bytes));
Ok(Wallet { sig, v, l })
}
}
impl Bytable for Wallet {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> std::result::Result<Self, CompactEcashError> {
Wallet::try_from(slice)
}
}
impl Base58 for Wallet {}
pub fn pseudorandom_f_delta_v(params: &GroupParameters, v: Scalar, l: u64) -> G1Projective {
let pow = (v + Scalar::from(l) + Scalar::from(1)).invert().unwrap();
params.delta() * pow
}
pub fn pseudorandom_f_g_v(params: &GroupParameters, v: Scalar, l: u64) -> G1Projective {
let pow = (v + Scalar::from(l) + Scalar::from(1)).invert().unwrap();
params.gen1() * pow
}
pub fn compute_kappa(
params: &GroupParameters,
verification_key: &VerificationKeyAuth,
attributes: &[Attribute],
blinding_factor: Scalar,
) -> G2Projective {
params.gen2() * blinding_factor
+ verification_key.alpha
+ attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
.sum::<G2Projective>()
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct PayInfo {
pub payinfo: [u8; 72],
}
impl PayInfo {
pub fn generate_payinfo(provider_pk: [u8; 32]) -> PayInfo {
let mut payinfo = [0u8; 72];
// Generating random bytes
thread_rng().fill(&mut payinfo[..32]);
// Adding timestamp bytes
let timestamp = Utc::now().timestamp();
payinfo[32..40].copy_from_slice(&timestamp.to_be_bytes());
// Adding provider public key bytes
payinfo[40..].copy_from_slice(&provider_pk);
PayInfo { payinfo }
}
pub fn timestamp(&self) -> i64 {
i64::from_be_bytes(self.payinfo[32..40].try_into().unwrap())
}
pub fn pk(&self) -> [u8; 32] {
self.payinfo[40..].try_into().unwrap()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Payment {
pub kappa: G2Projective,
pub sig: Signature,
pub ss: Vec<G1Projective>,
pub tt: Vec<G1Projective>,
pub aa: Vec<G1Projective>,
pub rr: Vec<Scalar>,
pub kappa_k: Vec<G2Projective>,
pub sig_lk: Vec<Signature>,
pub cc: G1Projective,
pub zk_proof: SpendProof,
pub vv: u64,
}
impl Payment {
pub fn spend_verify(
&self,
params: &Parameters,
verification_key: &VerificationKeyAuth,
pay_info: &PayInfo,
) -> Result<bool> {
if bool::from(self.sig.0.is_identity()) {
return Err(CompactEcashError::Spend(
"The element h of the signature equals the identity".to_string(),
));
}
if !check_bilinear_pairing(
&self.sig.0.to_affine(),
&G2Prepared::from(self.kappa.to_affine()),
&self.sig.1.to_affine(),
params.grp().prepared_miller_g2(),
) {
return Err(CompactEcashError::Spend(
"The bilinear check for kappa failed".to_string(),
));
}
for k in 0..self.vv {
if bool::from(self.sig_lk[k as usize].0.is_identity()) {
return Err(CompactEcashError::Spend(
"The element h of the signature on l equals the identity".to_string(),
));
}
if !check_bilinear_pairing(
&self.sig_lk[k as usize].0.to_affine(),
&G2Prepared::from(self.kappa_k[k as usize].to_affine()),
&self.sig_lk[k as usize].1.to_affine(),
params.grp().prepared_miller_g2(),
) {
return Err(CompactEcashError::Spend(
"The bilinear check for kappa_l failed".to_string(),
));
}
// verify integrity of R_k
if !(self.rr[k as usize] == hash_to_scalar(pay_info.payinfo)) {
return Err(CompactEcashError::Spend(
"Integrity of R_k does not hold".to_string(),
));
}
}
//TODO: verify whether payinfo contains merchent's identifier
// verify the zk proof
let instance = SpendInstance {
kappa: self.kappa,
aa: self.aa.clone(),
cc: self.cc,
ss: self.ss.clone(),
tt: self.tt.clone(),
kappa_k: self.kappa_k.clone(),
};
if !self
.zk_proof
.verify(params, &instance, verification_key, &self.rr)
{
return Err(CompactEcashError::Spend(
"ZkProof verification failed".to_string(),
));
}
Ok(true)
}
pub fn serial_number_bs58(&self) -> String {
SerialNumber {
inner: self.ss.clone(),
}
.to_bs58()
}
pub fn has_serial_number(&self, serial_number_bs58: &str) -> Result<bool> {
let serial_number = SerialNumber::try_from_bs58(serial_number_bs58)?;
let ret = self.ss.eq(&serial_number.inner);
Ok(ret)
}
pub fn to_bytes(&self) -> Vec<u8> {
let kappa_bytes = self.kappa.to_affine().to_compressed();
let sig_bytes = self.sig.to_bytes();
let cc_bytes = self.cc.to_affine().to_compressed();
let vv_bytes: [u8; 8] = self.vv.to_le_bytes();
let ss_len = self.ss.len() as u64;
let tt_len = self.tt.len() as u64;
let aa_len = self.aa.len() as u64;
let rr_len = self.rr.len() as u64;
let kappa_k_len = self.kappa_k.len() as u64;
let sig_lk_len = self.sig_lk.len() as u64;
let zk_proof_bytes = self.zk_proof.to_bytes();
let zk_proof_bytes_len = self.zk_proof.to_bytes().len() as u64;
let mut bytes: Vec<u8> = Vec::with_capacity(
(96 + 96
+ 48
+ 8
+ ss_len * 48
+ 8
+ tt_len * 48
+ 8
+ aa_len * 48
+ 8
+ rr_len * 32
+ 8
+ kappa_k_len * 96
+ 8
+ sig_lk_len * 96
+ zk_proof_bytes_len) as usize,
);
bytes.extend_from_slice(&kappa_bytes);
bytes.extend_from_slice(&sig_bytes);
bytes.extend_from_slice(&cc_bytes);
bytes.extend_from_slice(&vv_bytes);
let ss_len_bytes = ss_len.to_le_bytes();
bytes.extend_from_slice(&ss_len_bytes);
for s in &self.ss {
bytes.extend_from_slice(&s.to_affine().to_compressed());
}
let tt_len_bytes = tt_len.to_le_bytes();
bytes.extend_from_slice(&tt_len_bytes);
for t in &self.tt {
bytes.extend_from_slice(&t.to_affine().to_compressed());
}
let aa_len_bytes = aa_len.to_le_bytes();
bytes.extend_from_slice(&aa_len_bytes);
for a in &self.aa {
bytes.extend_from_slice(&a.to_affine().to_compressed());
}
let rr_len_bytes = rr_len.to_le_bytes();
bytes.extend_from_slice(&rr_len_bytes);
for r in &self.rr {
bytes.extend_from_slice(&r.to_bytes());
}
let kappa_k_len_bytes = kappa_k_len.to_le_bytes();
bytes.extend_from_slice(&kappa_k_len_bytes);
for kk in &self.kappa_k {
bytes.extend_from_slice(&kk.to_affine().to_compressed());
}
let sig_lk_len_bytes = sig_lk_len.to_le_bytes();
bytes.extend_from_slice(&sig_lk_len_bytes);
for sig in &self.sig_lk {
bytes.extend_from_slice(&sig.to_bytes());
}
bytes.extend_from_slice(&zk_proof_bytes);
bytes
}
}
impl TryFrom<&[u8]> for Payment {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<Payment> {
if bytes.len() < 656 {
return Err(CompactEcashError::Deserialization(
"Invalid byte array for Payment deserialization".to_string(),
));
}
let kappa_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
let sig_bytes: [u8; 96] = bytes[96..192].try_into().unwrap();
let cc_bytes: [u8; 48] = bytes[192..240].try_into().unwrap();
let vv_bytes: [u8; 8] = bytes[240..248].try_into().unwrap();
let ss_len = u64::from_le_bytes(bytes[248..256].try_into().unwrap()) as usize;
// Convert the byte arrays back into their respective types
let kappa = try_deserialize_g2_projective(
&kappa_bytes,
CompactEcashError::Deserialization("Failed to deserialize kappa".to_string()),
)?;
let sig = Signature::try_from(sig_bytes.as_slice())?;
let cc = try_deserialize_g1_projective(
&cc_bytes,
CompactEcashError::Deserialization("Failed to deserialize cc".to_string()),
)?;
let vv = u64::from_le_bytes(vv_bytes);
let mut idx = 256;
let mut ss = Vec::with_capacity(ss_len);
for _ in 0..ss_len {
let ss_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
let ss_elem = try_deserialize_g1_projective(
&ss_bytes,
CompactEcashError::Deserialization("Failed to deserialize ss element".to_string()),
)?;
ss.push(ss_elem);
idx += 48;
}
let tt_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap()) as usize;
idx += 8;
let mut tt = Vec::with_capacity(tt_len);
for _ in 0..tt_len {
let tt_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
let tt_elem = try_deserialize_g1_projective(
&tt_bytes,
CompactEcashError::Deserialization("Failed to deserialize tt element".to_string()),
)?;
tt.push(tt_elem);
idx += 48;
}
let aa_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap()) as usize;
idx += 8;
let mut aa = Vec::with_capacity(aa_len);
for _ in 0..aa_len {
let aa_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
let aa_elem = try_deserialize_g1_projective(
&aa_bytes,
CompactEcashError::Deserialization("Failed to deserialize aa element".to_string()),
)?;
aa.push(aa_elem);
idx += 48;
}
let rr_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap()) as usize;
idx += 8;
let mut rr = Vec::with_capacity(rr_len);
for _ in 0..rr_len {
let rr_bytes: [u8; 32] = bytes[idx..idx + 32].try_into().unwrap();
let rr_elem = try_deserialize_scalar(
&rr_bytes,
CompactEcashError::Deserialization("Failed to deserialize rr element".to_string()),
)?;
rr.push(rr_elem);
idx += 32;
}
let kappa_k_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap()) as usize;
idx += 8;
let mut kappa_k = Vec::with_capacity(kappa_k_len);
for _ in 0..kappa_k_len {
let kappa_k_bytes: [u8; 96] = bytes[idx..idx + 96].try_into().unwrap();
let kappa_k_elem = try_deserialize_g2_projective(
&kappa_k_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize kappa_k element".to_string(),
),
)?;
kappa_k.push(kappa_k_elem);
idx += 96;
}
// sig_lk
let sig_lk_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap()) as usize;
idx += 8;
let mut sig_lk = Vec::with_capacity(sig_lk_len);
for _ in 0..sig_lk_len {
let sig_lk_bytes: [u8; 96] = bytes[idx..idx + 96].try_into().unwrap();
let sig_lk_elem = Signature::try_from(sig_lk_bytes.as_slice())?;
sig_lk.push(sig_lk_elem);
idx += 96;
}
// Deserialize the SpendProof struct
let zk_proof_bytes = &bytes[idx..];
let zk_proof = SpendProof::try_from(zk_proof_bytes)?;
// Construct the Payment struct from the deserialized data
let payment = Payment {
kappa,
sig,
ss,
tt,
aa,
rr,
kappa_k,
sig_lk,
cc,
zk_proof,
vv,
};
Ok(payment)
}
}
pub struct SerialNumber {
pub(crate) inner: Vec<G1Projective>,
}
impl SerialNumber {
pub fn to_bytes(&self) -> Vec<u8> {
let ss_len = self.inner.len();
let mut bytes: Vec<u8> = Vec::with_capacity(ss_len * 48);
for s in &self.inner {
bytes.extend_from_slice(&s.to_affine().to_compressed());
}
bytes
}
}
impl TryFrom<&[u8]> for SerialNumber {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<Self> {
if bytes.len() % 48 != 0 {
return Err(
CompactEcashError::Deserialization(
format!("Tried to deserialize blinded serial number with incorrect number of bytes, expected a multiple of 48, got {}", bytes.len()),
));
}
let inner_len = bytes.len() / 48;
let mut inner = Vec::with_capacity(inner_len);
let mut idx = 0;
for _ in 0..inner_len {
let ss_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
let ss_elem = try_deserialize_g1_projective(
&ss_bytes,
CompactEcashError::Deserialization("Failed to deserialize ss element".to_string()),
)?;
inner.push(ss_elem);
idx += 48;
}
Ok(SerialNumber { inner })
}
}
impl Bytable for SerialNumber {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
Self::try_from(slice)
}
}
impl Base58 for SerialNumber {}
#[derive(Getters, CopyGetters)]
pub struct EcashCredential {
#[getset(get = "pub")]
payment: Payment,
value: u64,
#[getset(get = "pub")]
pay_info: PayInfo,
#[getset(get = "pub")]
epoch_id: u64,
}
impl EcashCredential {
pub fn new(payment: Payment, value: u64, pay_info: PayInfo, epoch_id: u64) -> Self {
EcashCredential {
payment,
value,
pay_info,
epoch_id,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let payment_bytes = self.payment.to_bytes();
let mut bytes = Vec::with_capacity(payment_bytes.len() + 72 + 8 + 8 + 8);
bytes.extend_from_slice(&(payment_bytes.len() as u64).to_be_bytes());
bytes.extend_from_slice(&self.payment.to_bytes());
bytes.extend_from_slice(&self.value.to_be_bytes());
bytes.extend_from_slice(&self.pay_info.payinfo);
bytes.extend_from_slice(&self.epoch_id.to_be_bytes());
bytes
}
pub fn value(&self) -> u64 {
self.value
}
pub fn serial_number(&self) -> String {
self.payment.serial_number_bs58()
}
pub fn has_serial_number(&self, serial_number_bs58: &str) -> Result<bool> {
self.payment.has_serial_number(serial_number_bs58)
}
}
impl TryFrom<&[u8]> for EcashCredential {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 72 + 8 + 8 + 8 {
return Err(CompactEcashError::Deserialization(
"Invalid byte array for EcashCredential deserialization".to_string(),
));
}
let mut index = 0;
let payment_len = u64::from_be_bytes(bytes[index..index + 8].try_into().unwrap()) as usize;
index += 8;
if bytes[index..].len() < payment_len {
return Err(CompactEcashError::Deserialization(
"Invalid byte array for EcashCredential deserialization".to_string(),
));
}
let payment = Payment::try_from(&bytes[index..index + payment_len])?;
index += payment_len;
if bytes[index..].len() != 72 + 8 + 8 {
return Err(CompactEcashError::Deserialization(
"Invalid byte array for EcashCredential deserialization".to_string(),
));
}
let value = u64::from_be_bytes(bytes[index..index + 8].try_into().unwrap());
index += 8;
let pay_info = PayInfo {
payinfo: bytes[index..index + 72].try_into().unwrap(),
};
index += 72;
let epoch_id = u64::from_be_bytes(bytes[index..index + 8].try_into().unwrap());
Ok(EcashCredential {
payment,
value,
pay_info,
epoch_id,
})
}
}
impl Bytable for EcashCredential {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> std::result::Result<Self, CompactEcashError> {
Self::try_from(slice)
}
}
impl Base58 for EcashCredential {}
@@ -0,0 +1,249 @@
use std::collections::HashMap;
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar};
use ff::Field;
use group::Curve;
use rand::thread_rng;
use crate::error::{CompactEcashError, Result};
use crate::traits::Bytable;
use crate::utils::{hash_g1, try_deserialize_g2_projective, Signature};
use crate::Base58;
const ATTRIBUTES_LEN: usize = 3;
#[derive(Debug)]
pub struct GroupParameters {
/// Generator of the G1 group
g1: G1Affine,
/// Generator of the G2 group
g2: G2Affine,
/// Additional generators of the G1 group
gammas: Vec<G1Projective>,
// Additional generator of the G1 group
delta: G1Projective,
/// Precomputed G2 generator used for the miller loop
_g2_prepared_miller: G2Prepared,
}
impl GroupParameters {
pub fn new() -> Result<GroupParameters> {
let gammas = (1..=ATTRIBUTES_LEN)
.map(|i| hash_g1(format!("gamma{}", i)))
.collect();
let delta = hash_g1("delta");
Ok(GroupParameters {
g1: G1Affine::generator(),
g2: G2Affine::generator(),
gammas,
delta,
_g2_prepared_miller: G2Prepared::from(G2Affine::generator()),
})
}
pub(crate) fn gen1(&self) -> &G1Affine {
&self.g1
}
pub(crate) fn gen2(&self) -> &G2Affine {
&self.g2
}
pub(crate) fn gammas(&self) -> &Vec<G1Projective> {
&self.gammas
}
pub(crate) fn gamma1(&self) -> &G1Projective {
&self.gammas[0]
}
#[allow(dead_code)]
pub(crate) fn gamma2(&self) -> Option<&G1Projective> {
self.gammas.get(2)
}
pub(crate) fn delta(&self) -> &G1Projective {
&self.delta
}
pub fn random_scalar(&self) -> Scalar {
// lazily-initialized thread-local random number generator, seeded by the system
let mut rng = thread_rng();
Scalar::random(&mut rng)
}
pub fn n_random_scalars(&self, n: usize) -> Vec<Scalar> {
(0..n).map(|_| self.random_scalar()).collect()
}
pub(crate) fn prepared_miller_g2(&self) -> &G2Prepared {
&self._g2_prepared_miller
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct SecretKeyRP {
pub(crate) x: Scalar,
pub(crate) y: Scalar,
}
impl SecretKeyRP {
pub fn public_key(&self, params: &GroupParameters) -> PublicKeyRP {
PublicKeyRP {
alpha: params.gen2() * self.x,
beta: params.gen2() * self.y,
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct PublicKeyRP {
pub(crate) alpha: G2Projective,
pub(crate) beta: G2Projective,
}
#[derive(Debug)]
pub struct Parameters {
/// group parameters
grp: GroupParameters,
/// Public Key for range proof verification
pk_rp: PublicKeyRP,
/// Max value of wallet
ll: u64,
/// list of signatures for values l in [0, L]
signs: HashMap<u64, Signature>,
}
impl Parameters {
pub fn grp(&self) -> &GroupParameters {
&self.grp
}
pub fn pk_rp(&self) -> &PublicKeyRP {
&self.pk_rp
}
pub fn ll(&self) -> u64 {
self.ll
}
pub fn signs(&self) -> &HashMap<u64, Signature> {
&self.signs
}
pub fn get_sign_by_idx(&self, idx: u64) -> Result<&Signature> {
match self.signs.get(&idx) {
Some(val) => Ok(val),
None => Err(CompactEcashError::RangeProofOutOfBound(
"Cannot find the range proof signature for the given value. \
Check if the requested value is within the bound 0..L"
.to_string(),
)),
}
}
pub fn to_bytes(&self) -> Vec<u8> {
//we omit grp as it is fixed
let pk_rp_alpha_bytes = self.pk_rp.alpha.to_affine().to_compressed();
let pk_rp_beta_bytes = self.pk_rp.beta.to_affine().to_compressed();
let l_bytes = self.ll.to_be_bytes();
let mut signs_bytes: Vec<u8> = Vec::with_capacity((self.ll * 96) as usize);
for l in 0..self.ll {
let sign = self.signs[&l];
signs_bytes.extend_from_slice(&sign.to_bytes());
}
let mut bytes = Vec::with_capacity(96 + 96 + 8 + signs_bytes.len());
bytes.extend_from_slice(&pk_rp_alpha_bytes);
bytes.extend_from_slice(&pk_rp_beta_bytes);
bytes.extend_from_slice(&l_bytes);
bytes.extend_from_slice(&signs_bytes);
bytes
}
}
impl TryFrom<&[u8]> for Parameters {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 96 + 96 + 8 {
return Err(CompactEcashError::Deserialization(
"Invalid byte array for Parameters deserialization".to_string(),
));
}
let pk_rp_alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
let pk_rp_beta_bytes: [u8; 96] = bytes[96..192].try_into().unwrap();
let ll = u64::from_be_bytes(bytes[192..200].try_into().unwrap());
let mut index = 200;
if bytes[index..].len() != ll as usize * 96 {
return Err(CompactEcashError::Deserialization(
"Invalid byte array for Parameters signatures deserialization".to_string(),
));
}
let pk_rp_alpha = try_deserialize_g2_projective(
&pk_rp_alpha_bytes,
CompactEcashError::Deserialization("Failed to deserialize pk_rp_alpha".to_string()),
)?;
let pk_rp_beta = try_deserialize_g2_projective(
&pk_rp_beta_bytes,
CompactEcashError::Deserialization("Failed to deserialize pk_rp_beta".to_string()),
)?;
let grp_params = GroupParameters::new()?;
let pk_rp = PublicKeyRP {
alpha: pk_rp_alpha,
beta: pk_rp_beta,
};
let mut signs = HashMap::new();
for l in 0..ll {
let sign = Signature::try_from(&bytes[index..index + 96])?;
signs.insert(l, sign);
index += 96;
}
Ok(Self {
grp: grp_params,
pk_rp,
ll,
signs,
})
}
}
impl Bytable for Parameters {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> std::result::Result<Self, CompactEcashError> {
Self::try_from(slice)
}
}
impl Base58 for Parameters {}
pub fn setup(ll: u64) -> Parameters {
let grp = GroupParameters::new().unwrap();
let x = grp.random_scalar();
let y = grp.random_scalar();
let sk_rp = SecretKeyRP { x, y };
let pk_rp = sk_rp.public_key(&grp);
let mut signs = HashMap::new();
for l in 0..ll {
let r = grp.random_scalar();
let h = grp.gen1() * r;
signs.insert(l, Signature(h, h * (x + y * Scalar::from(l))));
}
Parameters {
grp,
pk_rp,
ll,
signs,
}
}
@@ -0,0 +1,402 @@
use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar};
use group::{Curve, GroupEncoding};
use crate::error::{CompactEcashError, Result};
use crate::proofs::proof_withdrawal::{
WithdrawalReqInstance, WithdrawalReqProof, WithdrawalReqWitness,
};
use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::GroupParameters;
use crate::scheme::PartialWallet;
use crate::traits::Bytable;
use crate::utils::{
check_bilinear_pairing, hash_g1, try_deserialize_g1_projective, try_deserialize_scalar,
};
use crate::utils::{BlindedSignature, Signature};
use crate::Base58;
#[derive(Debug, PartialEq)]
pub struct WithdrawalRequest {
com_hash: G1Projective,
com: G1Projective,
pc_coms: Vec<G1Projective>,
zk_proof: WithdrawalReqProof,
}
impl WithdrawalRequest {
pub fn to_bytes(&self) -> Vec<u8> {
let com_hash_bytes = self.com_hash.to_affine().to_compressed();
let com_bytes = self.com.to_affine().to_compressed();
let pr_coms_len = self.pc_coms.len() as u64;
let zk_proof_bytes = self.zk_proof.to_bytes();
let mut bytes =
Vec::with_capacity(48 + 48 + 8 + pr_coms_len as usize * 48 + zk_proof_bytes.len());
bytes.extend_from_slice(&com_hash_bytes);
bytes.extend_from_slice(&com_bytes);
bytes.extend_from_slice(&pr_coms_len.to_le_bytes());
for c in &self.pc_coms {
bytes.extend_from_slice(&c.to_affine().to_compressed());
}
bytes.extend_from_slice(&zk_proof_bytes);
bytes
}
}
impl TryFrom<&[u8]> for WithdrawalRequest {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<WithdrawalRequest> {
if bytes.len() < 48 + 48 + 8 + 48 {
return Err(CompactEcashError::DeserializationMinLength {
min: 48 + 48 + 8 + 48,
actual: bytes.len(),
});
}
let mut j = 0;
let commitment_hash_bytes_len = 48;
let commitment_bytes_len = 48;
let com_hash_bytes = bytes[..j + commitment_hash_bytes_len].try_into().unwrap();
let com_hash = try_deserialize_g1_projective(
&com_hash_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed commitment hash".to_string(),
),
)?;
j += commitment_hash_bytes_len;
let com_bytes = bytes[j..j + commitment_bytes_len].try_into().unwrap();
let com = try_deserialize_g1_projective(
&com_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed commitment".to_string(),
),
)?;
j += commitment_bytes_len;
let pc_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < pc_len as usize * 48 {
return Err(CompactEcashError::DeserializationMinLength {
min: pc_len as usize * 48,
actual: bytes[56..].len(),
});
}
let mut pc_coms = Vec::with_capacity(pc_len as usize);
for i in 0..pc_len as usize {
let start = j + i * 48;
let end = start + 48;
let pc_com_bytes = bytes[start..end].try_into().unwrap();
let pc_com = try_deserialize_g1_projective(
&pc_com_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed Pedersen commitment".to_string(),
),
)?;
pc_coms.push(pc_com)
}
let zk_proof = WithdrawalReqProof::try_from(&bytes[j + pc_len as usize * 48..])?;
Ok(WithdrawalRequest {
com_hash,
com,
pc_coms,
zk_proof,
})
}
}
impl Bytable for WithdrawalRequest {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
WithdrawalRequest::try_from(slice)
}
}
impl Base58 for WithdrawalRequest {}
pub struct RequestInfo {
com_hash: G1Projective,
com_opening: Scalar,
pc_coms_openings: Vec<Scalar>,
v: Scalar,
}
impl RequestInfo {
pub fn get_com(&self) -> G1Projective {
self.com_hash
}
pub fn get_com_openings(&self) -> Scalar {
self.com_opening
}
pub fn get_pc_coms_openings(&self) -> &Vec<Scalar> {
&self.pc_coms_openings
}
pub fn get_v(&self) -> Scalar {
self.v
}
pub fn to_bytes(&self) -> Vec<u8> {
let com_hash_bytes = self.com_hash.to_affine().to_compressed();
let com_opening_bytes = self.com_opening.to_bytes();
let pr_coms_openings_len = self.pc_coms_openings.len() as u64;
let v_bytes = self.v.to_bytes();
let mut bytes = Vec::with_capacity(48 + 32 + 8 + pr_coms_openings_len as usize * 32 + 32);
bytes.extend_from_slice(&com_hash_bytes);
bytes.extend_from_slice(&com_opening_bytes);
bytes.extend_from_slice(&pr_coms_openings_len.to_le_bytes());
for c in &self.pc_coms_openings {
bytes.extend_from_slice(&c.to_bytes());
}
bytes.extend_from_slice(&v_bytes);
bytes
}
}
impl TryFrom<&[u8]> for RequestInfo {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<RequestInfo> {
if bytes.len() < 48 + 32 + 8 + 32 {
return Err(CompactEcashError::DeserializationMinLength {
min: 48 + 32 + 8 + 32,
actual: bytes.len(),
});
}
let mut j = 0;
let commitment_hash_bytes_len = 48;
let com_hash_bytes = bytes[j..j + commitment_hash_bytes_len].try_into().unwrap();
let com_hash = try_deserialize_g1_projective(
&com_hash_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed commitment hash".to_string(),
),
)?;
j += commitment_hash_bytes_len;
let com_opening_bytes_len = 32;
let com_opening_bytes = bytes[j..j + com_opening_bytes_len].try_into().unwrap();
let com_opening = try_deserialize_scalar(
&com_opening_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize commitment opening".to_string(),
),
)?;
j += com_opening_bytes_len;
let pc_coms_openings_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < pc_coms_openings_len as usize * 32 {
return Err(CompactEcashError::DeserializationMinLength {
min: pc_coms_openings_len as usize * 32,
actual: bytes[j..].len(),
});
}
let mut pc_coms_openings = Vec::with_capacity(pc_coms_openings_len as usize);
for i in 0..pc_coms_openings_len as usize {
let start = j + i * 32;
let end = start + 32;
let pc_com_opening_bytes = bytes[start..end].try_into().unwrap();
let pc_com_opening = try_deserialize_scalar(
&pc_com_opening_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed Pedersen commitment opening".to_string(),
),
)?;
pc_coms_openings.push(pc_com_opening)
}
j += pc_coms_openings_len as usize * 32;
let v_len = 32;
if bytes[j..].len() != v_len {
return Err(CompactEcashError::DeserializationMinLength {
min: v_len,
actual: bytes[j..].len(),
});
}
let v_bytes = bytes[j..j + v_len].try_into().unwrap();
let v = try_deserialize_scalar(
v_bytes,
CompactEcashError::Deserialization("Failed to deserialize v".to_string()),
)?;
Ok(RequestInfo {
com_hash,
com_opening,
pc_coms_openings,
v,
})
}
}
pub fn withdrawal_request(
params: &GroupParameters,
sk_user: &SecretKeyUser,
) -> Result<(WithdrawalRequest, RequestInfo)> {
let v = params.random_scalar();
let attributes = vec![sk_user.sk, v];
let gammas = params.gammas();
let com_opening = params.random_scalar();
let com = params.gen1() * com_opening
+ attributes
.iter()
.zip(gammas)
.map(|(&m, gamma)| gamma * m)
.sum::<G1Projective>();
// Value h in the paper
let com_hash = hash_g1(com.to_bytes());
// For each private attribute we compute a pedersen commitment
let pc_coms_openings = params.n_random_scalars(attributes.len());
// Compute Pedersen commitment for each attribute
let pc_coms = pc_coms_openings
.iter()
.zip(attributes.iter())
.map(|(o_j, m_j)| params.gen1() * o_j + com_hash * m_j)
.collect::<Vec<_>>();
// construct a zk proof of knowledge proving possession of m1, m2, m3, o, o1, o2, o3
let instance = WithdrawalReqInstance {
com,
h: com_hash,
pc_coms: pc_coms.clone(),
pk_user: PublicKeyUser {
pk: params.gen1() * sk_user.sk,
},
};
let witness = WithdrawalReqWitness {
attributes,
com_opening,
pc_coms_openings: pc_coms_openings.clone(),
};
let zk_proof = WithdrawalReqProof::construct(params, &instance, &witness);
let req = WithdrawalRequest {
com_hash,
com,
pc_coms: pc_coms.clone(),
zk_proof,
};
let req_info = RequestInfo {
com_hash,
com_opening,
pc_coms_openings: pc_coms_openings.clone(),
v,
};
Ok((req, req_info))
}
pub fn issue_wallet(
params: &GroupParameters,
sk_auth: SecretKeyAuth,
pk_user: PublicKeyUser,
withdrawal_req: &WithdrawalRequest,
) -> Result<BlindedSignature> {
let h = hash_g1(withdrawal_req.com.to_bytes());
if !(h == withdrawal_req.com_hash) {
return Err(CompactEcashError::WithdrawalRequestVerification(
"Failed to verify the commitment hash".to_string(),
));
}
// verify zk proof
let instance = WithdrawalReqInstance {
com: withdrawal_req.com,
h: withdrawal_req.com_hash,
pc_coms: withdrawal_req.pc_coms.clone(),
pk_user,
};
if !withdrawal_req.zk_proof.verify(params, &instance) {
return Err(CompactEcashError::WithdrawalRequestVerification(
"Failed to verify the proof of knowledge".to_string(),
));
}
let sig = withdrawal_req
.pc_coms
.iter()
.zip(sk_auth.ys.iter())
.map(|(pc, yi)| pc * yi)
.chain(std::iter::once(h * sk_auth.x))
.sum();
Ok(BlindedSignature(h, sig))
}
pub fn issue_verify(
params: &GroupParameters,
vk_auth: &VerificationKeyAuth,
sk_user: &SecretKeyUser,
blind_signature: &BlindedSignature,
req_info: &RequestInfo,
) -> Result<PartialWallet> {
// Parse the blinded signature
let h = blind_signature.0;
let c = blind_signature.1;
// Verify the integrity of the response from the authority
if !(req_info.com_hash == h) {
return Err(CompactEcashError::IssuanceVfy(
"Integrity verification failed".to_string(),
));
}
// Unblind the blinded signature on the partial wallet
let blinding_removers = vk_auth
.beta_g1
.iter()
.zip(req_info.pc_coms_openings.iter())
.map(|(beta, opening)| beta * opening)
.sum::<G1Projective>();
let unblinded_c = c - blinding_removers;
let attr = [sk_user.sk, req_info.v];
let signed_attributes = attr
.iter()
.zip(vk_auth.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
// Verify the signature correctness on the wallet share
if !check_bilinear_pairing(
&h.to_affine(),
&G2Prepared::from((vk_auth.alpha + signed_attributes).to_affine()),
&unblinded_c.to_affine(),
params.prepared_miller_g2(),
) {
return Err(CompactEcashError::IssuanceVfy(
"Verification of wallet share failed".to_string(),
));
}
Ok(PartialWallet {
sig: Signature(h, unblinded_c),
v: req_info.v,
idx: None,
})
}
@@ -0,0 +1,97 @@
use itertools::izip;
use crate::error::CompactEcashError;
use crate::scheme::aggregation::{aggregate_verification_keys, aggregate_wallets};
use crate::scheme::keygen::{generate_keypair_user, ttp_keygen, VerificationKeyAuth};
use crate::scheme::setup::setup;
use crate::scheme::withdrawal::{
issue_verify, issue_wallet, withdrawal_request, WithdrawalRequest,
};
use crate::scheme::PayInfo;
use crate::scheme::{PartialWallet, Payment, Wallet};
#[test]
fn main() -> Result<(), CompactEcashError> {
let L = 32;
let params = setup(L);
let grparams = params.grp();
let user_keypair = generate_keypair_user(&grparams);
let (req, req_info) = withdrawal_request(grparams, &user_keypair.secret_key()).unwrap();
let req_bytes = req.to_bytes();
let req2 = WithdrawalRequest::try_from(req_bytes.as_slice()).unwrap();
assert_eq!(req, req2);
let authorities_keypairs = ttp_keygen(&grparams, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key = aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3]))?;
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue_wallet(
&grparams,
auth_keypair.secret_key(),
user_keypair.public_key(),
&req,
);
wallet_blinded_signatures.push(blind_signature.unwrap());
}
let unblinded_wallet_shares: Vec<PartialWallet> = izip!(
wallet_blinded_signatures.iter(),
verification_keys_auth.iter()
)
.map(|(w, vk)| issue_verify(&grparams, vk, &user_keypair.secret_key(), w, &req_info).unwrap())
.collect();
let partial_wallet = unblinded_wallet_shares.get(0).unwrap().clone();
let partial_wallet_bytes = partial_wallet.to_bytes();
let partial_wallet2 = PartialWallet::try_from(&partial_wallet_bytes[..]).unwrap();
assert_eq!(partial_wallet, partial_wallet2);
// Aggregate partial wallets
let aggr_wallet = aggregate_wallets(
&grparams,
&verification_key,
&user_keypair.secret_key(),
&unblinded_wallet_shares,
&req_info,
)?;
let wallet_bytes = aggr_wallet.to_bytes();
let wallet = Wallet::try_from(&wallet_bytes[..]).unwrap();
assert_eq!(aggr_wallet, wallet);
// Let's try to spend some coins
let provider_keypair = generate_keypair_user(&grparams);
let payinfo = PayInfo::generate_payinfo(
provider_keypair.public_key().to_bytes()[..32]
.try_into()
.unwrap(),
);
let spend_vv = 1;
let (payment, _) = aggr_wallet.spend(
&params,
&verification_key,
&user_keypair.secret_key(),
&payinfo,
false,
spend_vv,
)?;
assert!(payment
.spend_verify(&params, &verification_key, &payinfo)
.unwrap());
let payment_bytes = payment.to_bytes();
let payment2 = Payment::try_from(&payment_bytes[..]).unwrap();
assert_eq!(payment, payment2);
Ok(())
}
@@ -0,0 +1 @@
mod e2e;
@@ -0,0 +1,22 @@
use crate::CompactEcashError;
pub trait Bytable
where
Self: Sized,
{
fn to_byte_vec(&self) -> Vec<u8>;
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CompactEcashError>;
}
pub trait Base58
where
Self: Bytable,
{
fn try_from_bs58<S: AsRef<str>>(x: S) -> Result<Self, CompactEcashError> {
Self::try_from_byte_slice(&bs58::decode(x.as_ref()).into_vec().unwrap())
}
fn to_bs58(&self) -> String {
bs58::encode(self.to_byte_vec()).into_string()
}
}
@@ -0,0 +1,477 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use core::iter::Sum;
use core::ops::Mul;
use std::convert::{TryFrom, TryInto};
use std::ops::Neg;
use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField};
use bls12_381::{
multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar,
};
use ff::Field;
use group::{Curve, Group};
use crate::error::{CompactEcashError, Result};
use crate::scheme::setup::GroupParameters;
use crate::traits::Bytable;
pub struct Polynomial {
coefficients: Vec<Scalar>,
}
impl Polynomial {
// for polynomial of degree n, we generate n+1 values
// (for example for degree 1, like y = x + 2, we need [2,1])
pub fn new_random(params: &GroupParameters, degree: u64) -> Self {
Polynomial {
coefficients: params.n_random_scalars((degree + 1) as usize),
}
}
/// Evaluates the polynomial at point x.
pub fn evaluate(&self, x: &Scalar) -> Scalar {
if self.coefficients.is_empty() {
Scalar::zero()
// if x is zero then we can ignore most of the expensive computation and
// just return the last term of the polynomial
} else if x.is_zero().unwrap_u8() == 1 {
// we checked that coefficients are not empty so unwrap here is fine
*self.coefficients.first().unwrap()
} else {
self.coefficients
.iter()
.enumerate()
// coefficient[n] * x ^ n
.map(|(i, coefficient)| coefficient * x.pow(&[i as u64, 0, 0, 0]))
.sum()
}
}
}
#[inline]
fn generate_lagrangian_coefficients_at_origin(points: &[u64]) -> Vec<Scalar> {
let x = Scalar::zero();
points
.iter()
.enumerate()
.map(|(i, point_i)| {
let mut numerator = Scalar::one();
let mut denominator = Scalar::one();
let xi = Scalar::from(*point_i);
for (j, point_j) in points.iter().enumerate() {
if j != i {
let xj = Scalar::from(*point_j);
// numerator = (x - xs[0]) * ... * (x - xs[j]), j != i
numerator *= x - xj;
// denominator = (xs[i] - x[0]) * ... * (xs[i] - x[j]), j != i
denominator *= xi - xj;
}
}
// numerator / denominator
numerator * denominator.invert().unwrap()
})
.collect()
}
/// Performs a Lagrange interpolation at the origin for a polynomial defined by `points` and `values`.
/// It can be used for Scalars, G1 and G2 points.
pub(crate) fn perform_lagrangian_interpolation_at_origin<T>(
points: &[SignerIndex],
values: &[T],
) -> Result<T>
where
T: Sum,
for<'a> &'a T: Mul<Scalar, Output = T>,
{
if points.is_empty() || values.is_empty() {
return Err(CompactEcashError::Interpolation(
"Tried to perform lagrangian interpolation for an empty set of coordinates".to_string(),
));
}
if points.len() != values.len() {
return Err(CompactEcashError::Interpolation(
"Tried to perform lagrangian interpolation for an incomplete set of coordinates"
.to_string(),
));
}
let coefficients = generate_lagrangian_coefficients_at_origin(points);
Ok(coefficients
.into_iter()
.zip(values.iter())
.map(|(coeff, val)| val * coeff)
.sum())
}
// A temporary way of hashing particular message into G1.
// Implementation idea was taken from `threshold_crypto`:
// https://github.com/poanetwork/threshold_crypto/blob/7709462f2df487ada3bb3243060504b5881f2628/src/lib.rs#L691
// Eventually it should get replaced by, most likely, the osswu map
// method once ideally it's implemented inside the pairing crate.
// note: I have absolutely no idea what are the correct domains for those. I just used whatever
// was given in the test vectors of `Hashing to Elliptic Curves draft-irtf-cfrg-hash-to-curve-11`
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.9.1
const G1_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_";
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-K.1
const SCALAR_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-expander";
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
}
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
let mut output = vec![Scalar::zero()];
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
msg.as_ref(),
SCALAR_HASH_DOMAIN,
&mut output,
);
output[0]
}
pub fn try_deserialize_scalar_vec(
expected_len: u64,
bytes: &[u8],
err: CompactEcashError,
) -> Result<Vec<Scalar>> {
if bytes.len() != expected_len as usize * 32 {
return Err(err);
}
let mut out = Vec::with_capacity(expected_len as usize);
for i in 0..expected_len as usize {
let s_bytes = bytes[i * 32..(i + 1) * 32].try_into().unwrap();
let s = match Into::<Option<Scalar>>::into(Scalar::from_bytes(&s_bytes)) {
None => return Err(err),
Some(scalar) => scalar,
};
out.push(s)
}
Ok(out)
}
pub fn try_deserialize_scalar(bytes: &[u8; 32], err: CompactEcashError) -> Result<Scalar> {
Into::<Option<Scalar>>::into(Scalar::from_bytes(bytes)).ok_or(err)
}
pub fn try_deserialize_g1_projective(
bytes: &[u8; 48],
err: CompactEcashError,
) -> Result<G1Projective> {
Into::<Option<G1Affine>>::into(G1Affine::from_compressed(bytes))
.ok_or(err)
.map(G1Projective::from)
}
pub fn try_deserialize_g2_projective(
bytes: &[u8; 96],
err: CompactEcashError,
) -> Result<G2Projective> {
Into::<Option<G2Affine>>::into(G2Affine::from_compressed(bytes))
.ok_or(err)
.map(G2Projective::from)
}
/// Checks whether e(P, Q) * e(-R, S) == id
pub fn check_bilinear_pairing(p: &G1Affine, q: &G2Prepared, r: &G1Affine, s: &G2Prepared) -> bool {
// checking e(P, Q) * e(-R, S) == id
// is equivalent to checking e(P, Q) == e(R, S)
// but requires only a single final exponentiation rather than two of them
// and therefore, as seen via benchmarks.rs, is almost 50% faster
// (1.47ms vs 2.45ms, tested on R9 5900X)
let multi_miller = multi_miller_loop(&[(p, q), (&r.neg(), s)]);
multi_miller.final_exponentiation().is_identity().into()
}
pub type SignerIndex = u64;
#[derive(Debug, Clone, Copy, PartialEq)]
// #[cfg_attr(test, derive(PartialEq))]
pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective);
pub type PartialSignature = Signature;
impl TryFrom<&[u8]> for Signature {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<Signature> {
if bytes.len() != 96 {
return Err(CompactEcashError::Deserialization(format!(
"Signature must be exactly 96 bytes, got {}",
bytes.len()
)));
}
let sig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
let sig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
let sig1 = try_deserialize_g1_projective(
sig1_bytes,
CompactEcashError::Deserialization("Failed to deserialize compressed sig1".to_string()),
)?;
let sig2 = try_deserialize_g1_projective(
sig2_bytes,
CompactEcashError::Deserialization("Failed to deserialize compressed sig2".to_string()),
)?;
Ok(Signature(sig1, sig2))
}
}
impl Signature {
pub(crate) fn sig1(&self) -> &G1Projective {
&self.0
}
pub(crate) fn sig2(&self) -> &G1Projective {
&self.1
}
pub fn randomise(&self, params: &GroupParameters) -> (Signature, Scalar) {
let r = params.random_scalar();
let r_prime = params.random_scalar();
let h_prime = self.0 * r_prime;
let s_prime = (self.1 * r_prime) + (h_prime * r);
(Signature(h_prime, s_prime), r)
}
pub fn to_bytes(self) -> [u8; 96] {
let mut bytes = [0u8; 96];
bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed());
bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Signature> {
Signature::try_from(bytes)
}
}
impl Bytable for Signature {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
Signature::from_bytes(slice)
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct BlindedSignature(pub(crate) G1Projective, pub(crate) G1Projective);
impl TryFrom<&[u8]> for BlindedSignature {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<BlindedSignature> {
if bytes.len() != 96 {
return Err(CompactEcashError::Deserialization(format!(
"BlindedSignature must be exactly 96 bytes, got {}",
bytes.len()
)));
}
let bsig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
let bsig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
let bsig1 = try_deserialize_g1_projective(
bsig1_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed bsig1".to_string(),
),
)?;
let bsig2 = try_deserialize_g1_projective(
bsig2_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize compressed bsig2".to_string(),
),
)?;
Ok(BlindedSignature(bsig1, bsig2))
}
}
impl BlindedSignature {
pub fn to_bytes(&self) -> [u8; 96] {
let mut bytes = [0u8; 96];
bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed());
bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<BlindedSignature> {
BlindedSignature::try_from(bytes)
}
}
pub struct SignatureShare {
signature: Signature,
index: SignerIndex,
}
impl SignatureShare {
pub fn new(signature: Signature, index: SignerIndex) -> Self {
SignatureShare { signature, index }
}
pub fn signature(&self) -> &Signature {
&self.signature
}
pub fn index(&self) -> SignerIndex {
self.index
}
// pub fn aggregate(shares: &[Self]) -> Result<Signature> {
// aggregate_signature_shares(shares)
// }
}
#[cfg(test)]
mod tests {
use rand::RngCore;
use super::*;
#[test]
fn polynomial_evaluation() {
// y = 42 (it should be 42 regardless of x)
let poly = Polynomial {
coefficients: vec![Scalar::from(42)],
};
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(1)));
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(0)));
assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(10)));
// y = x + 10, at x = 2 (exp: 12)
let poly = Polynomial {
coefficients: vec![Scalar::from(10), Scalar::from(1)],
};
assert_eq!(Scalar::from(12), poly.evaluate(&Scalar::from(2)));
// y = x^4 - 5x^2 + 2x - 3, at x = 3 (exp: 39)
let poly = Polynomial {
coefficients: vec![
(-Scalar::from(3)),
Scalar::from(2),
(-Scalar::from(5)),
Scalar::zero(),
Scalar::from(1),
],
};
assert_eq!(Scalar::from(39), poly.evaluate(&Scalar::from(3)));
// empty polynomial
let poly = Polynomial {
coefficients: vec![],
};
// should always be 0
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(1)));
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(0)));
assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(10)));
}
#[test]
fn performing_lagrangian_scalar_interpolation_at_origin() {
// x^2 + 3
// x, f(x):
// 1, 4,
// 2, 7,
// 3, 12,
let points = vec![1, 2, 3];
let values = vec![Scalar::from(4), Scalar::from(7), Scalar::from(12)];
assert_eq!(
Scalar::from(3),
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
);
// x^3 + 3x^2 - 5x + 11
// x, f(x):
// 1, 10
// 2, 21
// 3, 50
// 4, 103
let points = vec![1, 2, 3, 4];
let values = vec![
Scalar::from(10),
Scalar::from(21),
Scalar::from(50),
Scalar::from(103),
];
assert_eq!(
Scalar::from(11),
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
);
// more points than it is required
// x^2 + x + 10
// x, f(x)
// 1, 12
// 2, 16
// 3, 22
// 4, 30
// 5, 40
let points = vec![1, 2, 3, 4, 5];
let values = vec![
Scalar::from(12),
Scalar::from(16),
Scalar::from(22),
Scalar::from(30),
Scalar::from(40),
];
assert_eq!(
Scalar::from(10),
perform_lagrangian_interpolation_at_origin(&points, &values).unwrap()
);
}
#[test]
fn hash_g1_sanity_check() {
let mut rng = rand::thread_rng();
let mut msg1 = [0u8; 1024];
rng.fill_bytes(&mut msg1);
let mut msg2 = [0u8; 1024];
rng.fill_bytes(&mut msg2);
assert_eq!(hash_g1(msg1), hash_g1(msg1));
assert_eq!(hash_g1(msg2), hash_g1(msg2));
assert_ne!(hash_g1(msg1), hash_g1(msg2));
}
#[test]
fn hash_scalar_sanity_check() {
let mut rng = rand::thread_rng();
let mut msg1 = [0u8; 1024];
rng.fill_bytes(&mut msg1);
let mut msg2 = [0u8; 1024];
rng.fill_bytes(&mut msg2);
assert_eq!(hash_to_scalar(msg1), hash_to_scalar(msg1));
assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2));
assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2));
}
}
@@ -0,0 +1,26 @@
[package]
name = "nym_offline_divisible_ecash"
version = "0.1.0"
authors = ["Ania Piotrowska <ania@nymtech.net>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
#bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch = "gt-serialisation", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
bls12_381 = { workspace = true }
ff = { workspace = true }
group = { workspace = true }
itertools = "0.10"
digest = "0.9"
rand = "0.8"
thiserror = "1.0"
sha2 = "0.9"
bs58 = "0.4.0"
[dev-dependencies]
criterion = { version = "0.3", features = ["html_reports"] }
[[bench]]
name = "benchmarks"
harness = false
@@ -0,0 +1,375 @@
use std::collections::HashSet;
use std::ops::Neg;
use std::time::Duration;
use bls12_381::{
multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, Scalar,
};
use criterion::{criterion_group, criterion_main, Criterion};
use ff::Field;
use group::{Curve, Group};
use itertools::izip;
use rand::seq::SliceRandom;
use rand::thread_rng;
use nym_offline_divisible_ecash::identification::{identify, IdentifyResult};
use nym_offline_divisible_ecash::setup::{GroupParameters, Parameters};
use nym_offline_divisible_ecash::{
aggregate_verification_keys, aggregate_wallets, issue, issue_verify, ttp_keygen_authorities,
ttp_keygen_users, withdrawal_request, PartialWallet, PayInfo, PublicKeyUser, SecretKeyUser,
VerificationKeyAuth,
};
#[allow(unused)]
fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
let gt1 = bls12_381::pairing(g11, g21);
let gt2 = bls12_381::pairing(g12, g22);
assert_eq!(gt1, gt2)
}
#[allow(unused)]
fn single_pairing(g11: &G1Affine, g21: &G2Affine) {
let gt1 = bls12_381::pairing(g11, g21);
}
#[allow(unused)]
fn exponent_in_g1(g1: G1Projective, r: Scalar) {
let g11 = (g1 * r);
}
#[allow(unused)]
fn exponent_in_g2(g2: G2Projective, r: Scalar) {
let g22 = (g2 * r);
}
#[allow(unused)]
fn exponent_in_gt(gt: Gt, r: Scalar) {
let gtt = (gt * r);
}
#[allow(unused)]
fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
let miller_loop_result = multi_miller_loop(&[
(g11, &G2Prepared::from(*g21)),
(&g12.neg(), &G2Prepared::from(*g22)),
]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
#[allow(unused)]
fn multi_miller_pairing_with_prepared(
g11: &G1Affine,
g21: &G2Prepared,
g12: &G1Affine,
g22: &G2Prepared,
) {
let miller_loop_result = multi_miller_loop(&[(g11, g21), (&g12.neg(), g22)]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
// the case of being able to prepare G2 generator
#[allow(unused)]
fn multi_miller_pairing_with_semi_prepared(
g11: &G1Affine,
g21: &G2Affine,
g12: &G1Affine,
g22: &G2Prepared,
) {
let miller_loop_result =
multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), g22)]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
#[allow(unused)]
fn bench_pairings(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-pairings");
group.measurement_time(Duration::from_secs(200));
let mut rng = rand::thread_rng();
let g1 = G1Affine::generator();
let g2 = G2Affine::generator();
let r = Scalar::random(&mut rng);
let s = Scalar::random(&mut rng);
let g11 = (g1 * r).to_affine();
let g21 = (g2 * s).to_affine();
let g21_prep = G2Prepared::from(g21);
let g12 = (g1 * s).to_affine();
let g22 = (g2 * r).to_affine();
let g22_prep = G2Prepared::from(g22);
let gt = bls12_381::pairing(&g11, &g21);
let gen1 = G1Projective::generator();
let gen2 = G2Projective::generator();
group.bench_function("exponent operation in G1", |b| {
b.iter(|| exponent_in_g1(gen1, r))
});
group.bench_function("exponent operation in G2", |b| {
b.iter(|| exponent_in_g2(gen2, r))
});
group.bench_function("exponent operation in Gt", |b| {
b.iter(|| exponent_in_gt(gt, r))
});
group.bench_function("single pairing", |b| b.iter(|| single_pairing(&g11, &g21)));
group.bench_function("double pairing", |b| {
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
});
group.bench_function("multi miller in affine", |b| {
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
});
group.bench_function("multi miller with prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
});
group.bench_function("multi miller with semi-prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
});
}
struct BenchCase {
num_authorities: u64,
threshold_p: f32,
L: u64,
spend_vv: u64,
case_nr_pub_keys: u64,
}
fn bench_divisible_ecash(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-divisible-ecash");
group.sample_size(300);
group.measurement_time(Duration::from_secs(1500));
let case = BenchCase {
num_authorities: 100,
threshold_p: 0.7,
L: 100,
spend_vv: 10,
case_nr_pub_keys: 99,
};
// SETUP PHASE
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
// KEY GENERATION FOR THE AUTHORITIES
let threshold = (case.threshold_p * case.num_authorities as f32).round() as u64;
let authorities_keypairs =
ttp_keygen_authorities(&params, threshold, case.num_authorities).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let indices: Vec<u64> = (1..case.num_authorities + 1).collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
// KEY GENERATION FOR THE USER
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = SecretKeyUser::public_key(&sk_user, &grp);
// GENERATE KEYS FOR OTHER USERS
let mut pk_all_users = HashSet::new();
for i in 0..50 {
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = sk_user.public_key(&grp);
pk_all_users.insert(pk_user);
}
pk_all_users.insert(pk_user.clone());
// WITHDRAWAL REQUEST
let (withdrawal_req, req_info) = withdrawal_request(&params, &sk_user).unwrap();
// CLIENT BENCHMARK: prepare a single withdrawal request
group.bench_function(
&format!(
"[Client] withdrawal_request_{}_authorities_{}_L_{}_threshold",
case.num_authorities, case.L, case.threshold_p,
),
|b| b.iter(|| withdrawal_request(&params, &sk_user).unwrap()),
);
// ISSUE PARTIAL WALLETS
// first one meaningful one just for benchmark
let mut rng = rand::thread_rng();
let keypair = authorities_keypairs.choose(&mut rng).unwrap();
group.bench_function(
&format!("[Issuing Authority] issue_partial_wallet_with_L_{}", case.L,),
|b| {
b.iter(|| {
issue(
&params,
&withdrawal_req,
pk_user.clone(),
&keypair.secret_key(),
)
.unwrap()
})
},
);
let mut wallet_blinded_signatures = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue(
&params,
&withdrawal_req,
pk_user.clone(),
&auth_keypair.secret_key(),
)
.unwrap();
wallet_blinded_signatures.push(blind_signature);
}
// CLIENT BENCHMARK: verify the issued partial wallet
let w = wallet_blinded_signatures.get(0).clone().unwrap();
let vk = verification_keys_auth.get(0).clone().unwrap();
group.bench_function(
&format!("[Client] issue_verify_a_partial_wallet_with_L_{}", case.L,),
|b| b.iter(|| issue_verify(&grp, vk, &sk_user, w, &req_info).unwrap()),
);
let partial_wallets: Vec<PartialWallet> = izip!(
wallet_blinded_signatures.iter(),
verification_keys_auth.iter()
)
.map(|(w, vk)| issue_verify(&grp, &vk, &sk_user, &w, &req_info).unwrap())
.collect();
// AGGREGATE WALLET
let mut wallet =
aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets).unwrap();
// CLIENT BENCHMARK: aggregating all partial wallets
group.bench_function(
&format!(
"[Client] aggregate_wallets_with_L_{}_threshold_{}",
case.L, case.threshold_p,
),
|b| {
b.iter(|| {
aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets).unwrap()
})
},
);
let pay_info = PayInfo { info: [67u8; 32] };
let (payment, wallet) = wallet
.spend(
&params,
&verification_key,
&sk_user,
&pay_info,
case.spend_vv,
false,
)
.unwrap();
// CLIENT BENCHMARK: spend a single coin from the wallet
group.bench_function(
&format!(
"[Client] spend_a_single_coin_L_{}_threshold_{}",
case.L, case.threshold_p,
),
|b| {
b.iter(|| {
wallet
.spend(
&params,
&verification_key,
&sk_user,
&pay_info,
case.spend_vv,
true,
)
.unwrap()
})
},
);
// MERCHANT BENCHMARK: verify whether the submitted payment is legit
group.bench_function(
&format!(
"[Merchant] spend_verify_of_a_single_payment_L_{}_threshold_{}",
case.L, case.threshold_p,
),
|b| {
b.iter(|| {
payment
.spend_verify(&params, &verification_key, &pay_info)
.unwrap()
})
},
);
// BENCHMARK IDENTIFICATION
// Let's generate a double spending payment
// let's reverse the spending counter in the wallet to create a double spending payment
let current_l = wallet.l();
wallet.l.set(current_l - 1);
let pay_info2 = PayInfo { info: [52u8; 32] };
let (payment2, wallet) = wallet
.spend(&params, &verification_key, &sk_user, &pay_info2, 10, false)
.unwrap();
// MERCHANT BENCHMARK: identify double spending
group.bench_function(
&format!(
"[Merchant] identify_L_{}_threshold_{}_spend_vv_{}_pks_{}",
case.L,
case.threshold_p,
case.spend_vv,
pk_all_users.len()
),
|b| {
b.iter(|| {
identify(
&params,
&verification_key,
&pk_all_users,
payment.clone(),
payment2.clone(),
pay_info,
pay_info2,
)
.unwrap()
})
},
);
let identify_result = identify(
&params,
&verification_key,
&pk_all_users,
payment.clone(),
payment2.clone(),
pay_info,
pay_info2,
)
.unwrap();
assert_eq!(
identify_result,
IdentifyResult::DoubleSpendingPublicKeys(pk_user)
);
}
criterion_group!(benches, bench_divisible_ecash);
criterion_main!(benches);
@@ -0,0 +1,2 @@
/// Max value of wallet
pub(crate) const L: u64 = 100;
@@ -0,0 +1,39 @@
use thiserror::Error;
pub type Result<T> = std::result::Result<T, DivisibleEcashError>;
#[derive(Error, Debug)]
pub enum DivisibleEcashError {
#[error("Setup error: {0}")]
Setup(String),
#[error("Aggregation error: {0}")]
Aggregation(String),
#[error("Withdrawal Request Verification related error: {0}")]
WithdrawalRequestVerification(String),
#[error("Deserialization error: {0}")]
Deserialization(String),
#[error("Interpolation error: {0}")]
Interpolation(String),
#[error("Issuance Verification related error: {0}")]
IssuanceVfy(String),
#[error("Spend Verification related error: {0}")]
Spend(String),
#[error("Identify Verification related error: {0}")]
Identify(String),
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {} or {modulus_target} % {modulus} == 0")]
DeserializationInvalidLength {
actual: usize,
target: usize,
modulus_target: usize,
modulus: usize,
object: String,
},
}
@@ -0,0 +1,29 @@
use bls12_381::{pairing, G1Projective, G2Prepared, G2Projective, Scalar};
pub use scheme::aggregation::aggregate_verification_keys;
pub use scheme::aggregation::aggregate_wallets;
pub use scheme::identification;
pub use scheme::keygen::ttp_keygen_authorities;
pub use scheme::keygen::ttp_keygen_users;
pub use scheme::keygen::{PublicKeyUser, SecretKeyUser, VerificationKeyAuth};
pub use scheme::setup;
pub use scheme::withdrawal::issue;
pub use scheme::withdrawal::issue_verify;
pub use scheme::withdrawal::withdrawal_request;
pub use scheme::PartialWallet;
pub use scheme::PayInfo;
pub use traits::Base58;
use crate::error::DivisibleEcashError;
use crate::traits::Bytable;
mod constants;
mod error;
mod proofs;
mod scheme;
#[cfg(test)]
mod tests;
mod traits;
mod utils;
pub type Attribute = Scalar;
@@ -0,0 +1,64 @@
use std::borrow::Borrow;
use std::convert::TryFrom;
use std::convert::TryInto;
use bls12_381::{G1Affine, G1Projective, Scalar};
use digest::generic_array::typenum::Unsigned;
use digest::Digest;
use group::GroupEncoding;
use sha2::Sha256;
use crate::error::{DivisibleEcashError, Result};
use crate::scheme::keygen::PublicKeyUser;
use crate::scheme::setup::GroupParameters;
use crate::utils::try_deserialize_g1_projective;
pub mod proof_spend;
pub mod proof_withdrawal;
type ChallengeDigest = Sha256;
/// Generates a Scalar [or Fp] challenge by hashing a number of elliptic curve points.
fn compute_challenge<D, I, B>(iter: I) -> Scalar
where
D: Digest,
I: Iterator<Item = B>,
B: AsRef<[u8]>,
{
let mut h = D::new();
for point_representation in iter {
h.update(point_representation);
}
let digest = h.finalize();
// TODO: I don't like the 0 padding here (though it's what we've been using before,
// but we never had a security audit anyway...)
// instead we could maybe use the `from_bytes` variant and adding some suffix
// when computing the digest until we produce a valid scalar.
let mut bytes = [0u8; 64];
let pad_size = 64usize
.checked_sub(D::OutputSize::to_usize())
.unwrap_or_default();
bytes[pad_size..].copy_from_slice(&digest);
Scalar::from_bytes_wide(&bytes)
}
fn produce_response(witness_replacement: &Scalar, challenge: &Scalar, secret: &Scalar) -> Scalar {
witness_replacement - challenge * secret
}
// note: it's caller's responsibility to ensure witnesses.len() = secrets.len()
fn produce_responses<S>(witnesses: &[Scalar], challenge: &Scalar, secrets: &[S]) -> Vec<Scalar>
where
S: Borrow<Scalar>,
{
debug_assert_eq!(witnesses.len(), secrets.len());
witnesses
.iter()
.zip(secrets.iter())
.map(|(w, x)| produce_response(w, challenge, x.borrow()))
.collect()
}
@@ -0,0 +1,655 @@
use std::convert::TryFrom;
use std::ops::Neg;
use bls12_381::{G1Projective, G2Projective, Gt, Scalar};
use group::GroupEncoding;
use crate::error::{DivisibleEcashError, Result};
use crate::proofs::{compute_challenge, produce_response, produce_responses, ChallengeDigest};
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::Parameters;
use crate::scheme::{Phi, VarPhi, Wallet};
use crate::utils::try_deserialize_scalar;
pub struct SpendInstance {
pub kappa: G2Projective,
pub phi: Phi,
pub varphi: VarPhi,
pub rr: Scalar,
pub rr_prime: G1Projective,
pub ss_prime: G1Projective,
pub tt_prime: G2Projective,
pub varsig_prime1: G1Projective,
pub theta_prime1: G1Projective,
pub pg_eq1: Gt,
pub pg_eq2: Gt,
pub pg_eq3: Gt,
pub pg_eq4: Gt,
pub psi_g1: G1Projective,
pub psi_g2: G2Projective,
pub pg_psi0_delta: Gt,
pub pg_psi0_gen2: Gt,
pub pg_psi0_yy: Gt,
pub pg_psi0_ww1: Gt,
pub pg_psi0_ww2: Gt,
pub pg_rr_psi1: Gt,
pub pg_psi0_tt: Gt,
pub pg_psi0_psi1: Gt,
}
impl SpendInstance {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(96 + 96 + 3 * 96 + 5 * 48 + 12 * 288);
bytes.extend_from_slice(self.kappa.to_bytes().as_ref());
bytes.extend_from_slice(self.phi.to_bytes().as_ref());
bytes.extend_from_slice(self.varphi.to_bytes().as_ref());
bytes.extend_from_slice(self.rr.to_bytes().as_ref());
bytes.extend_from_slice(self.rr_prime.to_bytes().as_ref());
bytes.extend_from_slice(self.ss_prime.to_bytes().as_ref());
bytes.extend_from_slice(self.tt_prime.to_bytes().as_ref());
bytes.extend_from_slice(self.varsig_prime1.to_bytes().as_ref());
bytes.extend_from_slice(self.theta_prime1.to_bytes().as_ref());
bytes.extend_from_slice(self.pg_eq1.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_eq2.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_eq3.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_eq4.to_compressed().as_ref());
bytes.extend_from_slice(self.psi_g1.to_bytes().as_ref());
bytes.extend_from_slice(self.psi_g2.to_bytes().as_ref());
bytes.extend_from_slice(self.pg_psi0_delta.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_psi0_gen2.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_psi0_yy.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_psi0_ww1.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_psi0_ww2.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_rr_psi1.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_psi0_tt.to_compressed().as_ref());
bytes.extend_from_slice(self.pg_psi0_psi1.to_compressed().as_ref());
bytes
}
}
pub struct SpendWitness {
pub sk_u: SecretKeyUser,
pub v: Scalar,
pub r: Scalar,
pub r1: Scalar,
pub r2: Scalar,
pub r_varsig1: Scalar,
pub r_theta1: Scalar,
pub r_varsig2: Scalar,
pub r_theta2: Scalar,
pub r_rr: Scalar,
pub r_ss: Scalar,
pub r_tt: Scalar,
pub rho1: Scalar,
pub rho2: Scalar,
pub rho3: Scalar,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SpendProof {
challenge: Scalar,
response_r: Scalar,
response_r_sk_u: Scalar,
response_r_v: Scalar,
response_r_r: Scalar,
response_r_r1: Scalar,
response_r_r2: Scalar,
response_r_varsig1: Scalar,
response_r_theta1: Scalar,
response_r_varsig2: Scalar,
response_r_theta2: Scalar,
response_r_rr: Scalar,
response_r_ss: Scalar,
response_r_tt: Scalar,
response_r_rho1: Scalar,
response_r_rho2: Scalar,
response_r_rho3: Scalar,
}
impl SpendProof {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes: Vec<u8> = Vec::with_capacity(32 * 17); // 17 fields, each 32 bytes
bytes.extend_from_slice(&self.challenge.to_bytes());
bytes.extend_from_slice(&self.response_r.to_bytes());
bytes.extend_from_slice(&self.response_r_sk_u.to_bytes());
bytes.extend_from_slice(&self.response_r_v.to_bytes());
bytes.extend_from_slice(&self.response_r_r.to_bytes());
bytes.extend_from_slice(&self.response_r_r1.to_bytes());
bytes.extend_from_slice(&self.response_r_r2.to_bytes());
bytes.extend_from_slice(&self.response_r_varsig1.to_bytes());
bytes.extend_from_slice(&self.response_r_theta1.to_bytes());
bytes.extend_from_slice(&self.response_r_varsig2.to_bytes());
bytes.extend_from_slice(&self.response_r_theta2.to_bytes());
bytes.extend_from_slice(&self.response_r_rr.to_bytes());
bytes.extend_from_slice(&self.response_r_ss.to_bytes());
bytes.extend_from_slice(&self.response_r_tt.to_bytes());
bytes.extend_from_slice(&self.response_r_rho1.to_bytes());
bytes.extend_from_slice(&self.response_r_rho2.to_bytes());
bytes.extend_from_slice(&self.response_r_rho3.to_bytes());
bytes
}
pub fn construct(
params: &Parameters,
instance: &SpendInstance,
witness: &SpendWitness,
verification_key: &VerificationKeyAuth,
vv: u64,
) -> Self {
let grp = params.get_grp();
let params_u = params.get_params_u();
let params_a = params.get_params_a();
// generate random values to replace each witness
let r_attributes = grp.n_random_scalars(2);
let r_sk_u = r_attributes[0];
let r_v = r_attributes[1];
let r_r = grp.random_scalar();
let r_r1 = grp.random_scalar();
let r_r2 = grp.random_scalar();
let r_r_varsig1 = grp.random_scalar();
let r_r_theta1 = grp.random_scalar();
let r_r_varsig2 = grp.random_scalar();
let r_r_theta2 = grp.random_scalar();
let r_r_rr = grp.random_scalar();
let r_r_ss = grp.random_scalar();
let r_r_tt = grp.random_scalar();
let r_rho1 = grp.random_scalar();
let r_rho2 = grp.random_scalar();
let r_rho3 = grp.random_scalar();
let g1 = grp.gen1();
// compute zkp commitment for each instance
let zkcm_kappa = grp.gen2() * r_r
+ verification_key.alpha
+ r_attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
let zkcm_phi0 = g1 * r_r1;
let zkcm_phi1 = instance.varsig_prime1 * r_v
+ instance.psi_g1 * r_rho1
+ params_u.get_ith_eta(vv as usize) * r_r1;
let zkcm_varphi0 = g1 * r_r2;
let zkcm_varphi1 = (g1 * instance.rr) * r_sk_u
+ instance.theta_prime1 * r_v
+ instance.psi_g1 * r_rho2
+ params_u.get_ith_eta(vv as usize) * r_r2;
let zkcm_pg_eq1 =
instance.pg_psi0_delta * r_r_varsig1 + instance.pg_psi0_gen2 * r_r_varsig2.neg();
let zkcm_pg_eq2 =
instance.pg_psi0_delta * r_r_theta1 + instance.pg_psi0_gen2 * r_r_theta2.neg();
let zkcm_pg_eq3 = instance.pg_psi0_yy * r_r_rr
+ instance.pg_psi0_gen2 * r_r_ss
+ instance.pg_psi0_ww1 * r_r_varsig2
+ instance.pg_psi0_ww2 * r_r_theta2;
let zkcm_pg_eq4 = instance.pg_rr_psi1 * r_r_tt
+ instance.pg_psi0_tt * r_r_rr
+ instance.pg_psi0_psi1 * r_rho3.neg();
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(g1.to_bytes().as_ref())
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_kappa.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_phi0.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_phi1.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_varphi0.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_varphi1.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_pg_eq1.to_compressed().as_ref()))
.chain(std::iter::once(zkcm_pg_eq2.to_compressed().as_ref()))
.chain(std::iter::once(zkcm_pg_eq3.to_compressed().as_ref()))
.chain(std::iter::once(zkcm_pg_eq4.to_compressed().as_ref())),
);
// compute response for each witness
let response_r = produce_response(&r_r, &challenge, &witness.r);
let response_r_sk_u = produce_response(&r_sk_u, &challenge, &witness.sk_u.sk);
let response_r_v = produce_response(&r_v, &challenge, &witness.v);
let response_r_r = produce_response(&r_r, &challenge, &witness.r);
let response_r_r1 = produce_response(&r_r1, &challenge, &witness.r1);
let response_r_r2 = produce_response(&r_r2, &challenge, &witness.r2);
let response_r_varsig1 = produce_response(&r_r_varsig1, &challenge, &witness.r_varsig1);
let response_r_theta1 = produce_response(&r_r_theta1, &challenge, &witness.r_theta1);
let response_r_varsig2 = produce_response(&r_r_varsig2, &challenge, &witness.r_varsig2);
let response_r_theta2 = produce_response(&r_r_theta2, &challenge, &witness.r_theta2);
let response_r_rr = produce_response(&r_r_rr, &challenge, &witness.r_rr);
let response_r_ss = produce_response(&r_r_ss, &challenge, &witness.r_ss);
let response_r_tt = produce_response(&r_r_tt, &challenge, &witness.r_tt);
let response_r_rho1 = produce_response(&r_rho1, &challenge, &witness.rho1);
let response_r_rho2 = produce_response(&r_rho2, &challenge, &witness.rho2);
let response_r_rho3 = produce_response(&r_rho3, &challenge, &witness.rho3);
SpendProof {
challenge,
response_r,
response_r_sk_u,
response_r_v,
response_r_r,
response_r_r1,
response_r_r2,
response_r_varsig1,
response_r_theta1,
response_r_varsig2,
response_r_theta2,
response_r_rr,
response_r_ss,
response_r_tt,
response_r_rho1,
response_r_rho2,
response_r_rho3,
}
}
pub fn verify(
&self,
params: &Parameters,
instance: &SpendInstance,
verification_key: &VerificationKeyAuth,
vv: u64,
) -> bool {
let grp = params.get_grp();
let params_u = params.get_params_u();
let params_a = params.get_params_a();
let g1 = grp.gen1();
// re-compute each zkp commitment
let zkcm_kappa = instance.kappa * self.challenge
+ grp.gen2() * self.response_r
+ verification_key.alpha * (Scalar::one() - self.challenge)
+ [self.response_r_sk_u, self.response_r_v]
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
let zkcm_phi0 = g1 * self.response_r_r1 + instance.phi.0 * self.challenge;
let zkcm_phi1 = instance.varsig_prime1 * self.response_r_v
+ instance.psi_g1 * self.response_r_rho1
+ params_u.get_ith_eta(vv as usize) * self.response_r_r1
+ instance.phi.1 * self.challenge;
let zkcm_varphi0 = g1 * self.response_r_r2 + instance.varphi.0 * self.challenge;
let zkcm_varphi1 = (g1 * instance.rr) * self.response_r_sk_u
+ instance.theta_prime1 * self.response_r_v
+ instance.psi_g1 * self.response_r_rho2
+ params_u.get_ith_eta(vv as usize) * self.response_r_r2
+ instance.varphi.1 * self.challenge;
let zkcm_pg_eq1 = instance.pg_psi0_delta * self.response_r_varsig1
+ instance.pg_psi0_gen2 * self.response_r_varsig2.neg()
+ instance.pg_eq1 * self.challenge;
let zkcm_pg_eq2 = instance.pg_psi0_delta * self.response_r_theta1
+ instance.pg_psi0_gen2 * self.response_r_theta2.neg()
+ instance.pg_eq2 * self.challenge;
let zkcm_pg_eq3 = instance.pg_psi0_yy * self.response_r_rr
+ instance.pg_psi0_gen2 * self.response_r_ss
+ instance.pg_psi0_ww1 * self.response_r_varsig2
+ instance.pg_psi0_ww2 * self.response_r_theta2
+ instance.pg_eq3 * self.challenge;
let zkcm_pg_eq4 = instance.pg_rr_psi1 * self.response_r_tt
+ instance.pg_psi0_tt * self.response_r_rr
+ instance.pg_psi0_psi1 * self.response_r_rho3.neg()
+ instance.pg_eq4 * self.challenge;
// re-compute the challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(g1.to_bytes().as_ref())
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_kappa.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_phi0.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_phi1.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_varphi0.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_varphi1.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_pg_eq1.to_compressed().as_ref()))
.chain(std::iter::once(zkcm_pg_eq2.to_compressed().as_ref()))
.chain(std::iter::once(zkcm_pg_eq3.to_compressed().as_ref()))
.chain(std::iter::once(zkcm_pg_eq4.to_compressed().as_ref())),
);
challenge == self.challenge
}
}
impl TryFrom<&[u8]> for SpendProof {
type Error = DivisibleEcashError;
fn try_from(bytes: &[u8]) -> Result<Self> {
const FIELD_SIZE: usize = 32; // Each Scalar field is 32 bytes
if bytes.len() != FIELD_SIZE * 17 {
return Err(DivisibleEcashError::Deserialization(
"Invalid byte array for SpendProof deserialization".to_string(),
));
}
let challenge_bytes = bytes[0..FIELD_SIZE].try_into().unwrap();
let challenge = try_deserialize_scalar(
&challenge_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize challenge".to_string()),
)?;
let response_r_bytes = bytes[FIELD_SIZE..FIELD_SIZE * 2].try_into().unwrap();
let response_r = try_deserialize_scalar(
&response_r_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r".to_string()),
)?;
let response_r_sk_u_bytes = bytes[FIELD_SIZE * 2..FIELD_SIZE * 3].try_into().unwrap();
let response_r_sk_u = try_deserialize_scalar(
&response_r_sk_u_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize response_r_sk_u".to_string(),
),
)?;
let response_r_v_bytes = bytes[FIELD_SIZE * 3..FIELD_SIZE * 4].try_into().unwrap();
let response_r_v = try_deserialize_scalar(
&response_r_v_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_v".to_string()),
)?;
let response_r_r_bytes = bytes[FIELD_SIZE * 4..FIELD_SIZE * 5].try_into().unwrap();
let response_r_r = try_deserialize_scalar(
&response_r_r_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_r".to_string()),
)?;
let response_r_r1_bytes = bytes[FIELD_SIZE * 5..FIELD_SIZE * 6].try_into().unwrap();
let response_r_r1 = try_deserialize_scalar(
&response_r_r1_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_r1".to_string()),
)?;
let response_r_r2_bytes = bytes[FIELD_SIZE * 6..FIELD_SIZE * 7].try_into().unwrap();
let response_r_r2 = try_deserialize_scalar(
&response_r_r2_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_r2".to_string()),
)?;
let response_r_varsig1_bytes = bytes[FIELD_SIZE * 7..FIELD_SIZE * 8].try_into().unwrap();
let response_r_varsig1 = try_deserialize_scalar(
response_r_varsig1_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize response_r_varsig1".to_string(),
),
)?;
let response_r_theta1_bytes = bytes[FIELD_SIZE * 8..FIELD_SIZE * 9].try_into().unwrap();
let response_r_theta1 = try_deserialize_scalar(
&response_r_theta1_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize response_r_theta1".to_string(),
),
)?;
let response_r_varsig2_bytes = bytes[FIELD_SIZE * 9..FIELD_SIZE * 10].try_into().unwrap();
let response_r_varsig2 = try_deserialize_scalar(
&response_r_varsig2_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize response_r_varsig2".to_string(),
),
)?;
let response_r_theta2_bytes = bytes[FIELD_SIZE * 10..FIELD_SIZE * 11].try_into().unwrap();
let response_r_theta2 = try_deserialize_scalar(
&response_r_theta2_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize response_r_theta2".to_string(),
),
)?;
let response_r_rr_bytes = bytes[FIELD_SIZE * 11..FIELD_SIZE * 12].try_into().unwrap();
let response_r_rr = try_deserialize_scalar(
&response_r_rr_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_rr".to_string()),
)?;
let response_r_ss_bytes = bytes[FIELD_SIZE * 12..FIELD_SIZE * 13].try_into().unwrap();
let response_r_ss = try_deserialize_scalar(
&response_r_ss_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_ss".to_string()),
)?;
let response_r_tt_bytes = bytes[FIELD_SIZE * 13..FIELD_SIZE * 14].try_into().unwrap();
let response_r_tt = try_deserialize_scalar(
&response_r_tt_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize response_r_tt".to_string()),
)?;
let response_r_rho1_bytes = bytes[FIELD_SIZE * 14..FIELD_SIZE * 15].try_into().unwrap();
let response_r_rho1 = try_deserialize_scalar(
&response_r_rho1_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize response_r_rho1".to_string(),
),
)?;
let response_r_rho2_bytes = bytes[FIELD_SIZE * 15..FIELD_SIZE * 16].try_into().unwrap();
let response_r_rho2 = try_deserialize_scalar(
&response_r_rho2_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize response_r_rho2".to_string(),
),
)?;
let response_r_rho3_bytes = bytes[FIELD_SIZE * 16..FIELD_SIZE * 17].try_into().unwrap();
let response_r_rho3 = try_deserialize_scalar(
&response_r_rho3_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize response_r_rho3".to_string(),
),
)?;
Ok(SpendProof {
challenge,
response_r,
response_r_sk_u,
response_r_v,
response_r_r,
response_r_r1,
response_r_r2,
response_r_varsig1,
response_r_theta1,
response_r_varsig2,
response_r_theta2,
response_r_rr,
response_r_ss,
response_r_tt,
response_r_rho1,
response_r_rho2,
response_r_rho3,
})
}
}
#[cfg(test)]
mod tests {
use std::ops::Neg;
use bls12_381::{pairing, G2Projective};
use group::Curve;
use rand::thread_rng;
use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::keygen::{
ttp_keygen_authorities, PublicKeyUser, SecretKeyUser, VerificationKeyAuth,
};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::scheme::{PayInfo, Phi, VarPhi};
use crate::utils::hash_to_scalar;
#[test]
fn spend_proof_construct_and_verify() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
let params_u = params.get_params_u();
let params_a = params.get_params_a();
let sk = grp.random_scalar();
let pk_user = PublicKeyUser {
pk: grp.gen1() * sk,
};
let v = grp.random_scalar();
let attributes = vec![sk, v];
let l: usize = 10;
let vv: u64 = 20;
let authorities_keypairs = ttp_keygen_authorities(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
let r = grp.random_scalar();
let kappa = grp.gen2() * r
+ verification_key.alpha
+ attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
.sum::<G2Projective>();
let r1 = grp.random_scalar();
let r2 = grp.random_scalar();
let phi = Phi(
grp.gen1() * r1,
params_u.get_ith_sigma(l as usize) * v + params_u.get_ith_eta(vv as usize) * r1,
);
let pay_info = PayInfo { info: [78u8; 32] };
let rr = hash_to_scalar(pay_info.info);
let varphi = VarPhi(
grp.gen1() * r2,
(grp.gen1() * rr) * sk
+ params_u.get_ith_theta(l as usize) * v
+ params_u.get_ith_eta(vv as usize) * r2,
);
// random value used to compute blinded bases
let r_varsig1 = grp.random_scalar();
let r_theta1 = grp.random_scalar();
let r_varsig2 = grp.random_scalar();
let r_theta2 = grp.random_scalar();
let r_rr = grp.random_scalar();
let r_ss = grp.random_scalar();
let r_tt = grp.random_scalar();
// compute blinded bases
let psi_g1 = params_u.get_psi_g1();
let psi_g2 = params_u.get_psi_g2();
let varsig_prime1 = params_u.get_ith_sigma(l as usize) + (psi_g1 * r_varsig1);
let theta_prime1 = params_u.get_ith_theta(l as usize) + (psi_g1 * r_theta1);
let varsig_prime2 =
params_u.get_ith_sigma(l as usize + vv as usize - 1) + (psi_g1 * r_varsig2);
let theta_prime2 =
params_u.get_ith_theta(l as usize + vv as usize - 1) + (psi_g1 * r_theta2);
let rr_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).rr + (psi_g1 * r_rr);
let ss_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).ss + (psi_g1 * r_ss);
let tt_prime = params_u.get_ith_sps_sign(l as usize + vv as usize - 1).tt + (psi_g2 * r_tt);
let rho1 = v.neg() * r_varsig1;
let rho2 = v.neg() * r_theta1;
let rho3 = r_rr * r_tt;
let pg_varsigpr1_delta = pairing(
&varsig_prime1.to_affine(),
&params_a.get_ith_delta((vv - 1) as usize).to_affine(),
);
let pg_psi0_delta = pairing(
&psi_g1.to_affine(),
&params_a.get_ith_delta((vv - 1) as usize).to_affine(),
);
let pg_varsigpr2_gen2 = pairing(&varsig_prime2.to_affine(), grp.gen2());
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
let pg_thetapr1_delta = pairing(
&theta_prime1.to_affine(),
&params_a.get_ith_delta((vv - 1) as usize).to_affine(),
);
let pg_thetapr2_gen2 = pairing(&theta_prime2.to_affine(), grp.gen2());
let yy = params_u.get_sps_pk().get_yy();
let pg_rrprime_yy = pairing(&rr_prime.to_affine(), &yy.to_affine());
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
let pg_ssprime_gen2 = pairing(&ss_prime.to_affine(), grp.gen2());
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
let pg_varsigpr2_ww1 = pairing(&varsig_prime2.to_affine(), &ww1.to_affine());
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
let pg_thetapr2_ww2 = pairing(&theta_prime2.to_affine(), &ww2.to_affine());
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
let pg_gen1_zz = pairing(grp.gen1(), &params_u.get_sps_pk().get_zz().to_affine());
let pg_rr_tt = pairing(&rr_prime.to_affine(), &tt_prime.to_affine());
let pg_rr_psi1 = pairing(&rr_prime.to_affine(), &psi_g2.to_affine());
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &tt_prime.to_affine());
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
let pg_eq3 =
pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 - pg_gen1_zz;
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
let instance = SpendInstance {
kappa,
phi,
varphi,
rr,
rr_prime,
ss_prime: ss_prime,
tt_prime: tt_prime,
varsig_prime1,
theta_prime1,
pg_eq1,
pg_eq2,
pg_eq3,
pg_eq4,
psi_g1: *psi_g1,
psi_g2: *psi_g2,
pg_psi0_delta,
pg_psi0_gen2,
pg_psi0_yy,
pg_psi0_ww1,
pg_psi0_ww2,
pg_rr_psi1,
pg_psi0_tt,
pg_psi0_psi1,
};
let witness = SpendWitness {
sk_u: SecretKeyUser { sk },
v,
r,
r1,
r2,
r_varsig1,
r_theta1,
r_varsig2,
r_theta2,
r_rr,
r_ss,
r_tt,
rho1,
rho2,
rho3,
};
// compute the zk proof
let zk_proof = SpendProof::construct(&params, &instance, &witness, &verification_key, vv);
assert!(zk_proof.verify(&params, &instance, &verification_key, vv));
// do a to and from bytes check
let zk_proof_bytes = zk_proof.to_bytes();
let zk_proof2 = SpendProof::try_from(&zk_proof_bytes[..]).unwrap();
assert_eq!(zk_proof, zk_proof2);
}
}
@@ -0,0 +1,330 @@
use std::convert::{TryFrom, TryInto};
use bls12_381::{G1Projective, Scalar};
use group::GroupEncoding;
use itertools::izip;
use crate::error::{DivisibleEcashError, Result};
use crate::proofs::{compute_challenge, produce_response, produce_responses, ChallengeDigest};
use crate::scheme::keygen::PublicKeyUser;
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::utils::try_deserialize_g1_projective;
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
// instance: g, gamma1, gamma2, gamma3, com, h, com1, com2, com3, pkUser
pub struct WithdrawalReqInstance {
// Joined commitment to all attributes
pub com: G1Projective,
// Hash of the joined commitment com
pub h: G1Projective,
// Pedersen commitments to each attribute
pub pc_coms: Vec<G1Projective>,
// Public key of a user
pub pk_user: PublicKeyUser,
}
impl TryFrom<&[u8]> for WithdrawalReqInstance {
type Error = DivisibleEcashError;
fn try_from(bytes: &[u8]) -> Result<WithdrawalReqInstance> {
if bytes.len() < 48 * 4 + 8 || (bytes.len() - 8) % 48 != 0 {
return Err(DivisibleEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8,
target: 48 * 4 + 8,
modulus: 48,
object: "withdrawal request zkp instance".to_string(),
});
}
let com_bytes: [u8; 48] = bytes[..48].try_into().unwrap();
let com = try_deserialize_g1_projective(
&com_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize com".to_string()),
)?;
let h_bytes: [u8; 48] = bytes[48..96].try_into().unwrap();
let h = try_deserialize_g1_projective(
&h_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize h".to_string()),
)?;
let pc_coms_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
let actual_pc_coms_len = (bytes.len() - 152) / 48;
if pc_coms_len as usize != actual_pc_coms_len {
return Err(DivisibleEcashError::Deserialization(format!(
"Tried to deserialize pedersen commitments with inconsistent pc_coms_len (expected {}, got {})",
pc_coms_len, actual_pc_coms_len
)));
}
let mut pc_coms = Vec::new();
let mut pc_coms_end: usize = 0;
for i in 0..pc_coms_len {
let start = (104 + i * 48) as usize;
let end = (start + 48) as usize;
let pc_i_bytes = bytes[start..end].try_into().unwrap();
let pc_i = try_deserialize_g1_projective(
&pc_i_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize pedersen commitment".to_string(),
),
)?;
pc_coms_end = end;
pc_coms.push(pc_i);
}
let pk_bytes = bytes[pc_coms_end..].try_into().unwrap();
let pk = try_deserialize_g1_projective(
&pk_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize user's public key".to_string(),
),
)?;
Ok(WithdrawalReqInstance {
com,
h,
pc_coms,
pk_user: PublicKeyUser { pk },
})
}
}
impl WithdrawalReqInstance {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let pc_coms_len = self.pc_coms.len();
let mut bytes = Vec::with_capacity(8 + (pc_coms_len + 3) as usize * 48);
bytes.extend_from_slice(self.com.to_bytes().as_ref());
bytes.extend_from_slice(self.h.to_bytes().as_ref());
bytes.extend_from_slice(&pc_coms_len.to_le_bytes());
for pc in self.pc_coms.iter() {
bytes.extend_from_slice((pc.to_bytes()).as_ref());
}
bytes.extend_from_slice(self.pk_user.pk.to_bytes().as_ref());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<WithdrawalReqInstance> {
WithdrawalReqInstance::try_from(bytes)
}
}
// witness: m1, m2, m3, o, o1, o2, o3,
pub struct WithdrawalReqWitness {
pub attributes: Vec<Scalar>,
// Opening for the joined commitment com
pub com_opening: Scalar,
// Openings for the pedersen commitments
pub pc_coms_openings: Vec<Scalar>,
}
pub struct WithdrawalReqProof {
challenge: Scalar,
response_opening: Scalar,
response_openings: Vec<Scalar>,
response_attributes: Vec<Scalar>,
}
impl WithdrawalReqProof {
pub(crate) fn construct(
params: &Parameters,
instance: &WithdrawalReqInstance,
witness: &WithdrawalReqWitness,
) -> Self {
let grp = params.get_grp();
let g1 = grp.gen1();
let params_u = params.get_params_u();
// generate random values to replace the witnesses
let r_com_opening = grp.random_scalar();
let r_pedcom_openings = grp.n_random_scalars(witness.pc_coms_openings.len());
let r_attributes = grp.n_random_scalars(witness.attributes.len());
// compute zkp commitments for each instance
let zkcm_com = g1 * r_com_opening
+ r_attributes
.iter()
.zip(params_u.get_gammas().iter())
.map(|(rm_i, gamma_i)| gamma_i * rm_i)
.sum::<G1Projective>();
let zkcm_pedcom = r_pedcom_openings
.iter()
.zip(r_attributes.iter())
.map(|(o_j, m_j)| g1 * o_j + instance.h * m_j)
.collect::<Vec<_>>();
let zkcm_user_sk = g1 * r_attributes[0];
// covert to bytes
let gammas_bytes = params_u
.get_gammas()
.iter()
.map(|gamma| gamma.to_bytes())
.collect::<Vec<_>>();
let zkcm_pedcom_bytes = zkcm_pedcom
.iter()
.map(|cm| cm.to_bytes())
.collect::<Vec<_>>();
// compute zkp challenge using g1, gammas, c, h, c1, c2, c3, zk commitments
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(g1.to_bytes().as_ref())
.chain(gammas_bytes.iter().map(|gamma| gamma.as_ref()))
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_com.to_bytes().as_ref()))
.chain(zkcm_pedcom_bytes.iter().map(|c| c.as_ref()))
.chain(std::iter::once(zkcm_user_sk.to_bytes().as_ref())),
);
// compute response
let response_opening = produce_response(&r_com_opening, &challenge, &witness.com_opening);
let response_openings = produce_responses(
&r_pedcom_openings,
&challenge,
&witness.pc_coms_openings.iter().collect::<Vec<_>>(),
);
let response_attributes = produce_responses(
&r_attributes,
&challenge,
&witness.attributes.iter().collect::<Vec<_>>(),
);
WithdrawalReqProof {
challenge,
response_opening,
response_openings,
response_attributes,
}
}
pub(crate) fn verify(&self, params: &Parameters, instance: &WithdrawalReqInstance) -> bool {
let grp = params.get_grp();
let g1 = grp.gen1();
let params_u = params.get_params_u();
// recompute zk commitments for each instance
let zkcm_com = instance.com * self.challenge
+ g1 * self.response_opening
+ self
.response_attributes
.iter()
.zip(params_u.get_gammas().iter())
.map(|(m_i, gamma_i)| gamma_i * m_i)
.sum::<G1Projective>();
let zkcm_pedcom = izip!(
instance.pc_coms.iter(),
self.response_openings.iter(),
self.response_attributes.iter()
)
.map(|(cm_j, resp_o_j, resp_m_j)| {
cm_j * self.challenge + g1 * resp_o_j + instance.h * resp_m_j
})
.collect::<Vec<_>>();
let zk_commitment_user_sk =
instance.pk_user.pk * self.challenge + g1 * self.response_attributes[0];
// covert to bytes
let gammas_bytes = params_u
.get_gammas()
.iter()
.map(|gamma| gamma.to_bytes())
.collect::<Vec<_>>();
let zkcm_pedcom_bytes = zkcm_pedcom
.iter()
.map(|cm| cm.to_bytes())
.collect::<Vec<_>>();
// recompute zkp challenge
let challenge = compute_challenge::<ChallengeDigest, _, _>(
std::iter::once(g1.to_bytes().as_ref())
.chain(gammas_bytes.iter().map(|hs| hs.as_ref()))
.chain(std::iter::once(instance.to_bytes().as_ref()))
.chain(std::iter::once(zkcm_com.to_bytes().as_ref()))
.chain(zkcm_pedcom_bytes.iter().map(|c| c.as_ref()))
.chain(std::iter::once(zk_commitment_user_sk.to_bytes().as_ref())),
);
challenge == self.challenge
}
}
#[cfg(test)]
mod tests {
use group::Group;
use rand::thread_rng;
use crate::scheme::setup::Parameters;
use crate::utils::hash_g1;
use super::*;
#[test]
fn withdrawal_request_instance_roundtrip() {
let mut rng = thread_rng();
let params = GroupParameters::new().unwrap();
let instance = WithdrawalReqInstance {
com: G1Projective::random(&mut rng),
h: G1Projective::random(&mut rng),
pc_coms: vec![
G1Projective::random(&mut rng),
G1Projective::random(&mut rng),
G1Projective::random(&mut rng),
],
pk_user: PublicKeyUser {
pk: params.gen1() * params.random_scalar(),
},
};
let instance_bytes = instance.to_bytes();
let instance_p = WithdrawalReqInstance::from_bytes(&instance_bytes).unwrap();
assert_eq!(instance, instance_p)
}
#[test]
fn withdrawal_proof_construct_and_verify() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
let sk = grp.random_scalar();
let pk_user = PublicKeyUser {
pk: grp.gen1() * sk,
};
let v = grp.random_scalar();
let t = grp.random_scalar();
let attr = vec![sk, v, t];
let com_opening = grp.random_scalar();
let com = grp.gen1() * com_opening
+ attr
.iter()
.zip(params.get_params_u().get_gammas())
.map(|(&m, gamma)| gamma * m)
.sum::<G1Projective>();
let h = hash_g1(com.to_bytes());
let pc_openings = grp.n_random_scalars(attr.len());
let pc_coms = pc_openings
.iter()
.zip(attr.iter())
.map(|(o_j, m_j)| grp.gen1() * o_j + h * m_j)
.collect::<Vec<_>>();
let instance = WithdrawalReqInstance {
com,
h,
pc_coms,
pk_user,
};
let witness = WithdrawalReqWitness {
attributes: attr,
com_opening,
pc_coms_openings: pc_openings,
};
let zk_proof = WithdrawalReqProof::construct(&params, &instance, &witness);
assert!(zk_proof.verify(&params, &instance))
}
}
@@ -0,0 +1,166 @@
use core::iter::Sum;
use core::ops::Mul;
use std::cell::Cell;
use bls12_381::{pairing, G2Prepared, G2Projective, Scalar};
use group::Curve;
use itertools::Itertools;
use crate::error::{DivisibleEcashError, Result};
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::GroupParameters;
use crate::scheme::{PartialWallet, Wallet};
use crate::utils::{
check_bilinear_pairing, perform_lagrangian_interpolation_at_origin, PartialSignature,
Signature, SignatureShare, SignerIndex,
};
use crate::Attribute;
pub(crate) trait Aggregatable: Sized {
fn aggregate(aggregatable: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self>;
fn check_unique_indices(indices: &[SignerIndex]) -> bool {
// if aggregation is a threshold one, all indices should be unique
indices.iter().unique_by(|&index| index).count() == indices.len()
}
}
impl<T> Aggregatable for T
where
T: Sum,
for<'a> T: Sum<&'a T>,
for<'a> &'a T: Mul<Scalar, Output = T>,
{
fn aggregate(aggregatable: &[T], indices: Option<&[u64]>) -> Result<T> {
if aggregatable.is_empty() {
return Err(DivisibleEcashError::Aggregation(
"Empty set of values".to_string(),
));
}
if let Some(indices) = indices {
if !Self::check_unique_indices(indices) {
return Err(DivisibleEcashError::Aggregation(
"Non-unique indices".to_string(),
));
}
perform_lagrangian_interpolation_at_origin(indices, aggregatable)
} else {
// non-threshold
Ok(aggregatable.iter().sum())
}
}
}
impl Aggregatable for PartialSignature {
fn aggregate(sigs: &[PartialSignature], indices: Option<&[u64]>) -> Result<Signature> {
let h = sigs
.get(0)
.ok_or_else(|| DivisibleEcashError::Aggregation("Empty set of signatures".to_string()))?
.sig1();
// TODO: is it possible to avoid this allocation?
let sigmas = sigs.iter().map(|sig| *sig.sig2()).collect::<Vec<_>>();
let aggr_sigma = Aggregatable::aggregate(&sigmas, indices)?;
Ok(Signature(*h, aggr_sigma))
}
}
/// Ensures all provided verification keys were generated to verify the same number of attributes.
fn check_same_key_size(keys: &[VerificationKeyAuth]) -> bool {
keys.iter().map(|vk| vk.beta_g1.len()).all_equal()
&& keys.iter().map(|vk| vk.beta_g2.len()).all_equal()
}
pub fn aggregate_verification_keys(
keys: &[VerificationKeyAuth],
indices: Option<&[SignerIndex]>,
) -> Result<VerificationKeyAuth> {
if !check_same_key_size(keys) {
return Err(DivisibleEcashError::Aggregation(
"Verification keys are of different sizes".to_string(),
));
}
Aggregatable::aggregate(keys, indices)
}
pub fn aggregate_signature_shares(
params: &GroupParameters,
verification_key: &VerificationKeyAuth,
attributes: &[Attribute],
shares: &[SignatureShare],
) -> Result<Signature> {
let (signatures, indices): (Vec<_>, Vec<_>) = shares
.iter()
.map(|share| (*share.signature(), share.index()))
.unzip();
aggregate_signatures(
params,
verification_key,
attributes,
&signatures,
Some(&indices),
)
}
pub fn aggregate_signatures(
params: &GroupParameters,
verification_key: &VerificationKeyAuth,
attributes: &[Attribute],
signatures: &[PartialSignature],
indices: Option<&[SignerIndex]>,
) -> Result<Signature> {
// aggregate the signature
let signature = match Aggregatable::aggregate(signatures, indices) {
Ok(res) => res,
Err(err) => return Err(err),
};
// Verify the signature
let alpha = verification_key.alpha;
let tmp = attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
if !check_bilinear_pairing(
&signature.0.to_affine(),
&G2Prepared::from((alpha + tmp).to_affine()),
&signature.1.to_affine(),
params.prepared_miller_g2(),
) {
return Err(DivisibleEcashError::Aggregation(
"Verification of the aggregated signature failed".to_string(),
));
}
Ok(signature)
}
pub fn aggregate_wallets(
grp: &GroupParameters,
verification_key: &VerificationKeyAuth,
sk_user: &SecretKeyUser,
wallets: &[PartialWallet],
) -> Result<Wallet> {
let signature_shares: Vec<SignatureShare> = wallets
.iter()
.enumerate()
.map(|(idx, wallet)| SignatureShare::new(*wallet.signature(), (idx + 1) as u64))
.collect();
let v = wallets.get(0).unwrap().v;
let attributes = vec![sk_user.sk, v];
let aggregated_signature =
aggregate_signature_shares(&grp, &verification_key, &attributes, &signature_shares)?;
Ok(Wallet {
sig: aggregated_signature,
v,
l: Cell::new(1),
})
}
@@ -0,0 +1,514 @@
use std::collections::{HashMap, HashSet};
use std::ops::Neg;
use bls12_381::{pairing, Gt, Scalar};
use group::Curve;
use crate::error::{DivisibleEcashError, Result};
use crate::scheme::identification::IdentifyResult::DoubleSpendingPublicKeys;
use crate::scheme::keygen::{PublicKeyUser, VerificationKeyAuth};
use crate::scheme::setup::Parameters;
use crate::scheme::{PayInfo, Payment};
#[derive(Debug, Eq, PartialEq)]
pub enum IdentifyResult {
NotADuplicatePayment,
DuplicatePayInfo(PayInfo),
DoubleSpendingPublicKeys(PublicKeyUser),
Whatever,
}
// how do we get the list of all pkU ?
pub fn identify(
params: &Parameters,
verification_key: &VerificationKeyAuth,
public_keys_u: &HashSet<PublicKeyUser>,
payment1: Payment,
payment2: Payment,
pay_info1: PayInfo,
pay_info2: PayInfo,
) -> Result<IdentifyResult> {
let params_a = params.get_params_a();
// compute the serial numbers for k1 in [0, V1-1]
let mut serial_numbers = HashMap::new();
for k in 0..payment1.vv {
let sn = pairing(
&payment1.phi.1.to_affine(),
&params_a.get_ith_delta(k as usize).to_affine(),
) + pairing(
&payment1.phi.0.to_affine(),
&params_a
.get_etas_ith_jth_elem(payment1.vv as usize, k as usize)
.to_affine(),
);
serial_numbers.insert(sn, k);
}
// compute the serial numbers fo k2 in [0, V2-1]
let mut k1 = 0;
let mut k2 = 0;
let mut duplicate_serial_numbers: Vec<(Gt, u64, u64)> = Default::default();
for j in 0..payment2.vv {
let sn = pairing(
&payment2.phi.1.to_affine(),
&params_a.get_ith_delta(j as usize).to_affine(),
) + pairing(
&payment2.phi.0.to_affine(),
&params_a
.get_etas_ith_jth_elem(payment2.vv as usize, j as usize)
.to_affine(),
);
if !serial_numbers.contains_key(&sn) {
serial_numbers.insert(sn, j);
} else {
k1 = *serial_numbers.get(&sn).unwrap() as u64;
k2 = j.clone();
break;
}
return Ok(IdentifyResult::NotADuplicatePayment);
}
if pay_info1 == pay_info2 {
Ok(IdentifyResult::DuplicatePayInfo(pay_info1))
} else {
let delta_k1 = params_a.get_ith_delta(k1 as usize);
let delta_k2 = params_a.get_ith_delta(k2 as usize);
let tt1 = pairing(&payment1.varphi.1.to_affine(), &delta_k1.to_affine())
+ pairing(
&payment1.varphi.0.to_affine(),
&params_a
.get_etas_ith_jth_elem(payment1.vv as usize, k1 as usize)
.to_affine(),
);
let tt2 = pairing(&payment2.varphi.1.to_affine(), &delta_k2.to_affine())
+ pairing(
&payment2.varphi.0.to_affine(),
&params_a
.get_etas_ith_jth_elem(payment2.vv as usize, k2 as usize)
.to_affine(),
);
for pk_u in public_keys_u.iter() {
let pg_pku_deltas = pairing(
&pk_u.pk.to_affine(),
&(delta_k1 * payment1.rr + delta_k2 * payment2.rr.neg()).to_affine(),
);
if tt1 - tt2 == pg_pku_deltas {
return Ok(IdentifyResult::DoubleSpendingPublicKeys(pk_u.clone()));
}
}
return Err(DivisibleEcashError::Identify(
"A duplicate serial number was detected, the payinfo1 and payinfo2 are different, but we failed to identify the double-spending public key".to_string(),
));
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use bls12_381::pairing;
use group::Curve;
use rand::thread_rng;
use crate::scheme::aggregation::{aggregate_verification_keys, aggregate_wallets};
use crate::scheme::identification::{identify, IdentifyResult};
use crate::scheme::keygen::{
ttp_keygen_authorities, PublicKeyUser, SecretKeyUser, VerificationKeyAuth,
};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::scheme::withdrawal::{issue, issue_verify, withdrawal_request};
use crate::scheme::{PayInfo, Payment};
use crate::utils::hash_g1;
#[test]
fn duplicate_payments_with_the_same_pay_info() {
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
let params_u = params.get_params_u();
let params_a = params.get_params_a();
// KEY GENERATION FOR THE AUTHORITIES
let authorities_keypairs = ttp_keygen_authorities(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
// KEY GENERATION FOR THE USER1
let sk1 = grp.random_scalar();
let sk_user1 = SecretKeyUser { sk: sk1 };
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
// KEY GENERATION FOR THE USER2
let sk2 = grp.random_scalar();
let sk_user2 = SecretKeyUser { sk: sk2 };
let pk_user2 = SecretKeyUser::public_key(&sk_user2, &grp);
// WITHDRAWAL REQUEST FOR USER1
let (withdrawal_req1, req_info1) = withdrawal_request(&params, &sk_user1).unwrap();
// ISSUE PARTIAL WALLETS for USER1
let mut partial_wallets1 = Vec::new();
for auth_keypair in authorities_keypairs.clone() {
let blind_signature = issue(
&params,
&withdrawal_req1,
pk_user1.clone(),
&auth_keypair.secret_key(),
)
.unwrap();
let partial_wallet1 = issue_verify(
&grp,
&auth_keypair.verification_key(),
&sk_user1,
&blind_signature,
&req_info1,
)
.unwrap();
partial_wallets1.push(partial_wallet1);
}
// AGGREGATE WALLET FOR USER1
let mut wallet1 =
aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
let pay_info1 = PayInfo { info: [67u8; 32] };
let (payment1, wallet1) = wallet1
.spend(&params, &verification_key, &sk_user1, &pay_info1, 10, false)
.unwrap();
// SPEND VERIFICATION for USER1
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1)
.unwrap());
let payment2 = payment1.clone();
// SPEND VERIFICATION for the duplicate payment
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1)
.unwrap());
let pay_info2 = pay_info1.clone();
let public_keys = HashSet::from([pk_user1, pk_user2]);
let identify_result = identify(
&params,
&verification_key,
&public_keys,
payment1,
payment2,
pay_info1,
pay_info2,
)
.unwrap();
assert_eq!(identify_result, IdentifyResult::DuplicatePayInfo(pay_info1));
}
#[test]
fn two_payments_with_one_repeating_serial_number_but_different_pay_info() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
let params_u = params.get_params_u();
let params_a = params.get_params_a();
// KEY GENERATION FOR THE AUTHORITIES
let authorities_keypairs = ttp_keygen_authorities(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
// KEY GENERATION FOR THE USER1
let sk1 = grp.random_scalar();
let sk_user1 = SecretKeyUser { sk: sk1 };
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
// GENERATE KEYS FOR OTHER USERS
let mut pk_all_users = HashSet::new();
for i in 0..50 {
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = sk_user.public_key(&grp);
pk_all_users.insert(pk_user);
}
pk_all_users.insert(pk_user1.clone());
// WITHDRAWAL REQUEST FOR USER1
let (withdrawal_req1, req_info1) = withdrawal_request(&params, &sk_user1).unwrap();
// ISSUE PARTIAL WALLETS for USER1
let mut partial_wallets1 = Vec::new();
for auth_keypair in authorities_keypairs.clone() {
let blind_signature = issue(
&params,
&withdrawal_req1,
pk_user1.clone(),
&auth_keypair.secret_key(),
)
.unwrap();
let partial_wallet1 = issue_verify(
&grp,
&auth_keypair.verification_key(),
&sk_user1,
&blind_signature,
&req_info1,
)
.unwrap();
partial_wallets1.push(partial_wallet1);
}
// AGGREGATE WALLET FOR USER1
let mut wallet1 =
aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
let pay_info1 = PayInfo { info: [67u8; 32] };
let (payment1, new_wallet1) = wallet1
.spend(&params, &verification_key, &sk_user1, &pay_info1, 10, false)
.unwrap();
// let's reverse the spending counter in the wallet to create a double spending payment
let current_l = wallet1.l.get();
wallet1.l.set(current_l - 1);
let pay_info2 = PayInfo { info: [52u8; 32] };
let (payment2, wallet1) = wallet1
.spend(&params, &verification_key, &sk_user1, &pay_info2, 10, false)
.unwrap();
let identify_result = identify(
&params,
&verification_key,
&pk_all_users,
payment1,
payment2,
pay_info1,
pay_info2,
)
.unwrap();
assert_eq!(
identify_result,
IdentifyResult::DoubleSpendingPublicKeys(pk_user1)
);
}
#[test]
fn two_payments_with_multiple_repeating_serial_number_but_different_pay_info() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
let params_u = params.get_params_u();
let params_a = params.get_params_a();
// KEY GENERATION FOR THE AUTHORITIES
let authorities_keypairs = ttp_keygen_authorities(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
// KEY GENERATION FOR THE USER1
let sk1 = grp.random_scalar();
let sk_user1 = SecretKeyUser { sk: sk1 };
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
// GENERATE KEYS FOR OTHER USERS
let mut public_keys = HashSet::new();
for i in 0..50 {
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = sk_user.public_key(&grp);
public_keys.insert(pk_user);
}
public_keys.insert(pk_user1.clone());
// WITHDRAWAL REQUEST FOR USER1
let (withdrawal_req1, req_info1) = withdrawal_request(&params, &sk_user1).unwrap();
// ISSUE PARTIAL WALLETS for USER1
let mut partial_wallets1 = Vec::new();
for auth_keypair in authorities_keypairs.clone() {
let blind_signature = issue(
&params,
&withdrawal_req1,
pk_user1.clone(),
&auth_keypair.secret_key(),
)
.unwrap();
let partial_wallet1 = issue_verify(
&grp,
&auth_keypair.verification_key(),
&sk_user1,
&blind_signature,
&req_info1,
)
.unwrap();
partial_wallets1.push(partial_wallet1);
}
// AGGREGATE WALLET FOR USER1
let mut wallet1 =
aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
let pay_info1 = PayInfo { info: [67u8; 32] };
let (payment1, new_wallet1) = wallet1
.spend(&params, &verification_key, &sk_user1, &pay_info1, 10, false)
.unwrap();
// let's reverse the spending counter in the wallet to create a double spending payment
let current_l = wallet1.l.get();
wallet1.l.set(current_l - 7);
let pay_info2 = PayInfo { info: [52u8; 32] };
let (payment2, wallet1) = wallet1
.spend(&params, &verification_key, &sk_user1, &pay_info2, 10, false)
.unwrap();
let identify_result = identify(
&params,
&verification_key,
&public_keys,
payment1,
payment2,
pay_info1,
pay_info2,
)
.unwrap();
assert_eq!(
identify_result,
IdentifyResult::DoubleSpendingPublicKeys(pk_user1)
);
}
#[test]
fn ok_if_two_different_payments() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
let params_u = params.get_params_u();
let params_a = params.get_params_a();
// KEY GENERATION FOR THE AUTHORITIES
let authorities_keypairs = ttp_keygen_authorities(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
// KEY GENERATION FOR THE USER1
let sk1 = grp.random_scalar();
let sk_user1 = SecretKeyUser { sk: sk1 };
let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp);
// KEY GENERATION FOR THE USER2
let sk2 = grp.random_scalar();
let sk_user2 = SecretKeyUser { sk: sk2 };
let pk_user2 = SecretKeyUser::public_key(&sk_user2, &grp);
// WITHDRAWAL REQUEST FOR USER1
let (withdrawal_req1, req_info1) = withdrawal_request(&params, &sk_user1).unwrap();
// ISSUE PARTIAL WALLETS for USER1
let mut partial_wallets1 = Vec::new();
for auth_keypair in authorities_keypairs.clone() {
let blind_signature = issue(
&params,
&withdrawal_req1,
pk_user1.clone(),
&auth_keypair.secret_key(),
)
.unwrap();
let partial_wallet1 = issue_verify(
&grp,
&auth_keypair.verification_key(),
&sk_user1,
&blind_signature,
&req_info1,
)
.unwrap();
partial_wallets1.push(partial_wallet1);
}
// AGGREGATE WALLET FOR USER1
let mut wallet1 =
aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap();
let pay_info1 = PayInfo { info: [67u8; 32] };
let (payment1, wallet1) = wallet1
.spend(&params, &verification_key, &sk_user1, &pay_info1, 10, false)
.unwrap();
// SPEND VERIFICATION for USER1
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1)
.unwrap());
// WITHDRAWAL REQUEST FOR USER2
let (withdrawal_req2, req_info2) = withdrawal_request(&params, &sk_user2).unwrap();
// ISSUE PARTIAL WALLETS for USER2
let mut partial_wallets2 = Vec::new();
for auth_keypair in authorities_keypairs.clone() {
let blind_signature = issue(
&params,
&withdrawal_req2,
pk_user2.clone(),
&auth_keypair.secret_key(),
)
.unwrap();
let partial_wallet2 = issue_verify(
&grp,
&auth_keypair.verification_key(),
&sk_user2,
&blind_signature,
&req_info2,
)
.unwrap();
partial_wallets2.push(partial_wallet2);
}
// AGGREGATE WALLET FOR USER2
let mut wallet2 =
aggregate_wallets(&grp, &verification_key, &sk_user2, &partial_wallets2).unwrap();
let pay_info2 = PayInfo { info: [67u8; 32] };
let (payment2, wallet2) = wallet2
.spend(&params, &verification_key, &sk_user2, &pay_info2, 10, false)
.unwrap();
// SPEND VERIFICATION for USER2
assert!(payment2
.spend_verify(&params, &verification_key, &pay_info2)
.unwrap());
let public_keys = HashSet::from([pk_user1, pk_user2]);
let identify_result = identify(
&params,
&verification_key,
&public_keys,
payment1,
payment2,
pay_info1,
pay_info2,
)
.unwrap();
assert_eq!(identify_result, IdentifyResult::NotADuplicatePayment);
}
}
@@ -0,0 +1,438 @@
use std::borrow::Borrow;
use std::convert::{TryFrom, TryInto};
use std::iter::Sum;
use std::ops::{Add, Mul};
use bls12_381::{G1Projective, G2Projective, Scalar};
use group::Curve;
use crate::error::{DivisibleEcashError, Result};
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::utils::{
try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
try_deserialize_scalar_vec, Polynomial, SignerIndex,
};
#[derive(Eq, Debug, PartialEq, Clone)]
pub struct SecretKeyAuth {
pub(crate) x: Scalar,
pub(crate) ys: Vec<Scalar>,
}
impl TryFrom<&[u8]> for SecretKeyAuth {
type Error = DivisibleEcashError;
fn try_from(bytes: &[u8]) -> Result<SecretKeyAuth> {
// There should be x and at least one y
if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 {
return Err(DivisibleEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8,
target: 32 * 2 + 8,
modulus: 32,
object: "secret key".to_string(),
});
}
// this conversion will not fail as we are taking the same length of data
let x_bytes: [u8; 32] = bytes[..32].try_into().unwrap();
let ys_len = u64::from_le_bytes(bytes[32..40].try_into().unwrap());
let actual_ys_len = (bytes.len() - 40) / 32;
if ys_len as usize != actual_ys_len {
return Err(DivisibleEcashError::Deserialization(format!(
"Tried to deserialize secret key with inconsistent ys len (expected {}, got {})",
ys_len, actual_ys_len
)));
}
let x = try_deserialize_scalar(
&x_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize secret key scalar".to_string(),
),
)?;
let ys = try_deserialize_scalar_vec(
ys_len,
&bytes[40..],
DivisibleEcashError::Deserialization(
"Failed to deserialize secret key scalars".to_string(),
),
)?;
Ok(SecretKeyAuth { x, ys })
}
}
impl SecretKeyAuth {
pub fn verification_key(&self, grp: &GroupParameters) -> VerificationKeyAuth {
let g1 = grp.gen1();
let g2 = grp.gen2();
VerificationKeyAuth {
alpha: g2 * self.x,
beta_g1: self.ys.iter().map(|y| g1 * y).collect(),
beta_g2: self.ys.iter().map(|y| g2 * y).collect(),
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let ys_len = self.ys.len();
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) as usize * 32);
bytes.extend_from_slice(&self.x.to_bytes());
bytes.extend_from_slice(&ys_len.to_le_bytes());
for y in self.ys.iter() {
bytes.extend_from_slice(&y.to_bytes())
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<SecretKeyAuth> {
SecretKeyAuth::try_from(bytes)
}
}
#[derive(Eq, Debug, PartialEq, Clone)]
pub struct VerificationKeyAuth {
pub(crate) alpha: G2Projective,
pub(crate) beta_g1: Vec<G1Projective>,
pub(crate) beta_g2: Vec<G2Projective>,
}
impl TryFrom<&[u8]> for VerificationKeyAuth {
type Error = DivisibleEcashError;
fn try_from(bytes: &[u8]) -> Result<VerificationKeyAuth> {
// There should be at least alpha, one betaG1 and one betaG2 and their length
if bytes.len() < 96 * 2 + 48 + 8 || (bytes.len() - 8 - 96) % (96 + 48) != 0 {
return Err(DivisibleEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8 - 96,
target: 96 * 2 + 48 + 8,
modulus: 96 + 48,
object: "verification key".to_string(),
});
}
// this conversion will not fail as we are taking the same length of data
let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
let betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
let actual_betas_len = (bytes.len() - 104) / (96 + 48);
if betas_len as usize != actual_betas_len {
return Err(
DivisibleEcashError::Deserialization(
format!("Tried to deserialize verification key with inconsistent betas len (expected {}, got {})",
betas_len, actual_betas_len
)));
}
let alpha = try_deserialize_g2_projective(
&alpha_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize verification key G2 point (alpha)".to_string(),
),
)?;
let mut beta_g1 = Vec::with_capacity(betas_len as usize);
let mut beta_g1_end: u64 = 0;
for i in 0..betas_len {
let start = (104 + i * 48) as usize;
let end = (start + 48) as usize;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g1_projective(
&beta_i_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize verification key G1 point (beta)".to_string(),
),
)?;
beta_g1_end = end as u64;
beta_g1.push(beta_i)
}
let mut beta_g2 = Vec::with_capacity(betas_len as usize);
for i in 0..betas_len {
let start = (beta_g1_end + i * 96) as usize;
let end = (start + 96) as usize;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g2_projective(
&beta_i_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize verification key G2 point (beta)".to_string(),
),
)?;
beta_g2.push(beta_i)
}
Ok(VerificationKeyAuth {
alpha,
beta_g1,
beta_g2,
})
}
}
impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth {
type Output = VerificationKeyAuth;
#[inline]
fn add(self, rhs: &'b VerificationKeyAuth) -> VerificationKeyAuth {
// If you're trying to add two keys together that were created
// for different number of attributes, just panic as it's a
// nonsense operation.
assert_eq!(
self.beta_g1.len(),
rhs.beta_g1.len(),
"trying to add verification keys generated for different number of attributes [G1]"
);
assert_eq!(
self.beta_g2.len(),
rhs.beta_g2.len(),
"trying to add verification keys generated for different number of attributes [G2]"
);
assert_eq!(
self.beta_g1.len(),
self.beta_g2.len(),
"this key is incorrect - the number of elements G1 and G2 does not match"
);
assert_eq!(
rhs.beta_g1.len(),
rhs.beta_g2.len(),
"they key you want to add is incorrect - the number of elements G1 and G2 does not match"
);
VerificationKeyAuth {
alpha: self.alpha + rhs.alpha,
beta_g1: self
.beta_g1
.iter()
.zip(rhs.beta_g1.iter())
.map(|(self_beta_g1, rhs_beta_g1)| self_beta_g1 + rhs_beta_g1)
.collect(),
beta_g2: self
.beta_g2
.iter()
.zip(rhs.beta_g2.iter())
.map(|(self_beta_g2, rhs_beta_g2)| self_beta_g2 + rhs_beta_g2)
.collect(),
}
}
}
impl<'a> Mul<Scalar> for &'a VerificationKeyAuth {
type Output = VerificationKeyAuth;
#[inline]
fn mul(self, rhs: Scalar) -> Self::Output {
VerificationKeyAuth {
alpha: self.alpha * rhs,
beta_g1: self.beta_g1.iter().map(|b_i| b_i * rhs).collect(),
beta_g2: self.beta_g2.iter().map(|b_i| b_i * rhs).collect(),
}
}
}
impl<T> Sum<T> for VerificationKeyAuth
where
T: Borrow<VerificationKeyAuth>,
{
#[inline]
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = T>,
{
let mut peekable = iter.peekable();
let head_attributes = match peekable.peek() {
Some(head) => head.borrow().beta_g2.len(),
None => {
// TODO: this is a really weird edge case. You're trying to sum an EMPTY iterator
// of VerificationKey. So should it panic here or just return some nonsense value?
return VerificationKeyAuth::identity(0);
}
};
peekable.fold(
VerificationKeyAuth::identity(head_attributes),
|acc, item| acc + item.borrow(),
)
}
}
impl VerificationKeyAuth {
/// Create a (kinda) identity verification key using specified
/// number of 'beta' elements
pub(crate) fn identity(beta_size: usize) -> Self {
VerificationKeyAuth {
alpha: G2Projective::identity(),
beta_g1: vec![G1Projective::identity(); beta_size],
beta_g2: vec![G2Projective::identity(); beta_size],
}
}
pub fn aggregate(sigs: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self> {
aggregate_verification_keys(sigs, indices)
}
pub fn alpha(&self) -> &G2Projective {
&self.alpha
}
pub fn beta_g1(&self) -> &Vec<G1Projective> {
&self.beta_g1
}
pub fn beta_g2(&self) -> &Vec<G2Projective> {
&self.beta_g2
}
pub fn to_bytes(&self) -> Vec<u8> {
let beta_g1_len = self.beta_g1.len();
let beta_g2_len = self.beta_g2.len();
let mut bytes = Vec::with_capacity(96 + 8 + beta_g1_len * 48 + beta_g2_len * 96);
bytes.extend_from_slice(&self.alpha.to_affine().to_compressed());
bytes.extend_from_slice(&beta_g1_len.to_le_bytes());
for beta_g1 in self.beta_g1.iter() {
bytes.extend_from_slice(&beta_g1.to_affine().to_compressed())
}
for beta_g2 in self.beta_g2.iter() {
bytes.extend_from_slice(&beta_g2.to_affine().to_compressed())
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<VerificationKeyAuth> {
VerificationKeyAuth::try_from(bytes)
}
}
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct KeyPairUser {
secret_key: SecretKeyUser,
public_key: PublicKeyUser,
}
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct KeyPairAuth {
secret_key: SecretKeyAuth,
verification_key: VerificationKeyAuth,
/// Optional index value specifying polynomial point used during threshold key generation.
pub index: Option<SignerIndex>,
}
impl KeyPairAuth {
pub fn secret_key(&self) -> SecretKeyAuth {
self.secret_key.clone()
}
pub fn verification_key(&self) -> VerificationKeyAuth {
self.verification_key.clone()
}
}
#[derive(Eq, Debug, PartialEq, Clone)]
pub struct SecretKeyUser {
pub sk: Scalar,
}
impl SecretKeyUser {
pub fn public_key(&self, params: &GroupParameters) -> PublicKeyUser {
PublicKeyUser {
pk: params.gen1() * self.sk,
}
}
}
#[derive(Hash, Eq, Debug, PartialEq, Clone)]
pub struct PublicKeyUser {
pub(crate) pk: G1Projective,
}
impl KeyPairUser {
pub fn secret_key(&self) -> SecretKeyUser {
self.secret_key.clone()
}
pub fn public_key(&self) -> PublicKeyUser {
self.public_key.clone()
}
}
pub fn ttp_keygen_authorities(
params: &Parameters,
threshold: u64,
num_authorities: u64,
) -> Result<Vec<KeyPairAuth>> {
if threshold == 0 {
return Err(DivisibleEcashError::Setup(
"Tried to generate threshold keys with a 0 threshold value".to_string(),
));
}
if threshold > num_authorities {
return Err(
DivisibleEcashError::Setup(
"Tried to generate threshold keys for threshold value being higher than number of the signing authorities".to_string(),
));
}
let grp = params.get_grp();
// generate polynomials
let v = Polynomial::new_random(&grp, threshold - 1);
let ws = (0..2)
.map(|_| Polynomial::new_random(&grp, threshold - 1))
.collect::<Vec<_>>();
let polynomial_indices = (1..=num_authorities).collect::<Vec<_>>();
// generate polynomial shares
let x = polynomial_indices
.iter()
.map(|&id| v.evaluate(&Scalar::from(id)));
let ys = polynomial_indices.iter().map(|&id| {
ws.iter()
.map(|w| w.evaluate(&Scalar::from(id)))
.collect::<Vec<_>>()
});
let secret_keys = x.zip(ys).map(|(x, ys)| SecretKeyAuth { x, ys });
let keypairs = secret_keys
.zip(polynomial_indices.iter())
.map(|(secret_key, index)| {
let verification_key = secret_key.verification_key(&grp);
KeyPairAuth {
secret_key,
verification_key,
index: Some(*index),
}
})
.collect();
Ok(keypairs)
}
pub fn ttp_keygen_users(params: &Parameters) -> KeyPairUser {
let grp = params.get_grp();
let sk_user = SecretKeyUser {
sk: grp.random_scalar(),
};
let pk_user = PublicKeyUser {
pk: grp.gen1() * sk_user.sk,
};
KeyPairUser {
secret_key: sk_user,
public_key: pk_user,
}
}
@@ -0,0 +1,729 @@
use std::cell::Cell;
use std::convert::{TryFrom, TryInto};
use std::ops::Neg;
use bls12_381::{pairing, G1Projective, G2Prepared, G2Projective, Scalar};
use group::{Curve, GroupEncoding};
use crate::constants::L;
use crate::error::{DivisibleEcashError, Result};
use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::utils::{
check_bilinear_pairing, hash_to_scalar, try_deserialize_g1_projective,
try_deserialize_g2_projective, try_deserialize_scalar, Signature, SignerIndex,
};
use crate::Attribute;
pub mod aggregation;
pub mod identification;
pub mod keygen;
pub mod setup;
pub mod structure_preserving_signature;
pub mod withdrawal;
pub fn compute_kappa(
params: &GroupParameters,
verification_key: &VerificationKeyAuth,
attributes: &[Attribute],
blinding_factor: Scalar,
) -> G2Projective {
params.gen2() * blinding_factor
+ verification_key.alpha
+ attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
.sum::<G2Projective>()
}
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
pub struct Phi(pub(crate) G1Projective, pub(crate) G1Projective);
impl Phi {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(48 + 48);
bytes.extend_from_slice(self.0.to_bytes().as_ref());
bytes.extend_from_slice(self.1.to_bytes().as_ref());
bytes
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 48 * 2 || (bytes.len()) % 48 != 0 {
return Err(DivisibleEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len(),
target: 48 * 2,
modulus: 48,
object: "phi".to_string(),
});
}
let elem_0_bytes = bytes[0..48].try_into().unwrap();
let elem_0 = try_deserialize_g1_projective(
elem_0_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize element 0 of Phi".to_string(),
),
)?;
let elem_1_bytes = bytes[48..96].try_into().unwrap();
let elem_1 = try_deserialize_g1_projective(
elem_1_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize element 1 of Phi".to_string(),
),
)?;
Ok(Phi(elem_0, elem_1))
}
}
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
pub struct VarPhi(pub(crate) G1Projective, pub(crate) G1Projective);
impl VarPhi {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(48 + 48);
bytes.extend_from_slice(self.0.to_bytes().as_ref());
bytes.extend_from_slice(self.1.to_bytes().as_ref());
bytes
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 48 * 2 || (bytes.len()) % 48 != 0 {
return Err(DivisibleEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len(),
target: 48 * 2,
modulus: 48,
object: "varphi".to_string(),
});
}
let elem_0_bytes = bytes[0..48].try_into().unwrap();
let elem_0 = try_deserialize_g1_projective(
elem_0_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize element 0 of VarPhi".to_string(),
),
)?;
let elem_1_bytes = bytes[48..96].try_into().unwrap();
let elem_1 = try_deserialize_g1_projective(
elem_1_bytes,
DivisibleEcashError::Deserialization(
"Failed to deserialize element 1 of VarPhi".to_string(),
),
)?;
Ok(VarPhi(elem_0, elem_1))
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct PayInfo {
pub info: [u8; 32],
}
#[derive(Debug, Clone, PartialEq)]
pub struct Payment {
pub kappa: G2Projective,
pub sig: Signature,
pub phi: Phi,
pub varphi: VarPhi,
pub varsig_prime1: G1Projective,
pub varsig_prime2: G1Projective,
pub theta_prime1: G1Projective,
pub theta_prime2: G1Projective,
pub rr_prime: G1Projective,
pub ss_prime: G1Projective,
pub tt_prime: G2Projective,
pub rr: Scalar,
pub zk_proof: SpendProof,
pub vv: u64,
}
impl Payment {
pub fn get_kappa(&self) -> G2Projective {
self.kappa
}
pub fn get_sig(&self) -> Signature {
self.sig
}
pub fn get_phi(&self) -> Phi {
self.phi
}
pub fn get_varphi(&self) -> VarPhi {
self.varphi
}
pub fn get_varsig_prime1(&self) -> G1Projective {
self.varsig_prime1
}
pub fn get_varsig_prime2(&self) -> G1Projective {
self.varsig_prime2
}
pub fn get_theta_prime1(&self) -> G1Projective {
self.theta_prime1
}
pub fn get_theta_prime2(&self) -> G1Projective {
self.theta_prime2
}
pub fn get_rr_prime(&self) -> G1Projective {
self.rr_prime
}
pub fn get_ss_prime(&self) -> G1Projective {
self.ss_prime
}
pub fn get_tt_prime(&self) -> G2Projective {
self.tt_prime
}
pub fn get_rr(&self) -> Scalar {
self.rr
}
pub fn get_zk_proof(&self) -> SpendProof {
self.zk_proof.clone()
}
pub fn get_vv(&self) -> u64 {
self.vv
}
pub fn to_bytes(&self) -> Vec<u8> {
let kappa_bytes = self.kappa.to_affine().to_compressed();
let sig_bytes = self.sig.to_bytes();
let phi_bytes = self.phi.to_bytes();
let varphi_bytes = self.varphi.to_bytes();
let varsig_prime1_bytes = self.varsig_prime1.to_affine().to_compressed();
let varsig_prime2_bytes = self.varsig_prime2.to_affine().to_compressed();
let theta_prime1_bytes = self.theta_prime1.to_affine().to_compressed();
let theta_prime2 = self.theta_prime2.to_affine().to_compressed();
let rr_prime_bytes = self.rr_prime.to_affine().to_compressed();
let ss_prime_bytes = self.ss_prime.to_affine().to_compressed();
let tt_prime_bytes = self.tt_prime.to_affine().to_compressed();
let rr_bytes = self.rr.to_bytes();
let zk_proof_bytes = self.zk_proof.to_bytes();
let vv_bytes = self.vv.to_le_bytes();
let mut bytes: Vec<u8> = Vec::with_capacity(760);
bytes.extend_from_slice(&kappa_bytes);
bytes.extend_from_slice(&sig_bytes);
bytes.extend_from_slice(&phi_bytes);
bytes.extend_from_slice(&varphi_bytes);
bytes.extend_from_slice(&varsig_prime1_bytes);
bytes.extend_from_slice(&varsig_prime2_bytes);
bytes.extend_from_slice(&theta_prime1_bytes);
bytes.extend_from_slice(&theta_prime2);
bytes.extend_from_slice(&rr_prime_bytes);
bytes.extend_from_slice(&ss_prime_bytes);
bytes.extend_from_slice(&tt_prime_bytes);
bytes.extend_from_slice(&rr_bytes);
bytes.extend_from_slice(&vv_bytes);
bytes.extend_from_slice(&zk_proof_bytes);
bytes
}
}
impl TryFrom<&[u8]> for Payment {
type Error = DivisibleEcashError;
fn try_from(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 760 {
return Err(DivisibleEcashError::Deserialization(
"Invalid byte array for Payment deserialization".to_string(),
));
}
let mut idx = 0;
let kappa_bytes: [u8; 96] = bytes[idx..idx + 96].try_into().unwrap();
let kappa = try_deserialize_g2_projective(
&kappa_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize kappa".to_string()),
)?;
idx += 96;
let sig_bytes: [u8; 96] = bytes[idx..idx + 96].try_into().unwrap();
let sig = Signature::try_from(sig_bytes.as_slice())?;
idx += 96;
let phi_bytes: [u8; 96] = bytes[idx..idx + 96].try_into().unwrap();
let phi = Phi::from_bytes(&phi_bytes).unwrap();
idx += 96;
let varphi_bytes: [u8; 96] = bytes[idx..idx + 96].try_into().unwrap();
let varphi = VarPhi::from_bytes(&varphi_bytes).unwrap();
idx += 96;
let varsig_prime1_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
let varsig_prime1 = try_deserialize_g1_projective(
&varsig_prime1_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize varsig_prime1".to_string()),
)?;
idx += 48;
let varsig_prime2_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
let varsig_prime2 = try_deserialize_g1_projective(
&varsig_prime2_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize varsig_prime2".to_string()),
)?;
idx += 48;
let theta_prime1_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
let theta_prime1 = try_deserialize_g1_projective(
&theta_prime1_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize theta_prime1".to_string()),
)?;
idx += 48;
let theta_prime2_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
let theta_prime2 = try_deserialize_g1_projective(
&theta_prime2_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize theta_prime2".to_string()),
)?;
idx += 48;
let rr_prime_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
let rr_prime = try_deserialize_g1_projective(
&rr_prime_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize rr_prime".to_string()),
)?;
idx += 48;
let ss_prime_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap();
let ss_prime = try_deserialize_g1_projective(
&ss_prime_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize ss_prime".to_string()),
)?;
idx += 48;
let tt_prime_bytes: [u8; 96] = bytes[idx..idx + 96].try_into().unwrap();
let tt_prime = try_deserialize_g2_projective(
&tt_prime_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize tt_prime".to_string()),
)?;
idx += 96;
let rr_bytes: [u8; 32] = bytes[idx..idx + 32].try_into().unwrap();
let rr = try_deserialize_scalar(
&rr_bytes,
DivisibleEcashError::Deserialization("Failed to deserialize rr element".to_string()),
)?;
idx += 32;
let vv = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
idx += 8;
// Deserialize the SpendProof struct
let zk_proof_bytes = &bytes[idx..];
let zk_proof = SpendProof::try_from(zk_proof_bytes)?;
Ok(Payment {
kappa,
sig,
phi,
varphi,
varsig_prime1,
varsig_prime2,
theta_prime1,
theta_prime2,
rr_prime,
ss_prime,
tt_prime,
rr,
zk_proof,
vv,
})
}
}
pub struct PartialWallet {
sig: Signature,
v: Scalar,
idx: Option<SignerIndex>,
}
impl PartialWallet {
pub fn signature(&self) -> &Signature {
&self.sig
}
pub fn v(&self) -> Scalar {
self.v
}
pub fn index(&self) -> Option<SignerIndex> {
self.idx
}
}
pub struct Wallet {
sig: Signature,
v: Scalar,
pub l: Cell<u64>,
}
impl Wallet {
pub fn signature(&self) -> &Signature {
&self.sig
}
pub fn v(&self) -> Scalar {
self.v
}
pub fn l(&self) -> u64 {
self.l.get()
}
pub fn spend(
&self,
params: &Parameters,
verification_key: &VerificationKeyAuth,
sk_user: &SecretKeyUser,
pay_info: &PayInfo,
vv: u64,
bench_flag: bool,
) -> Result<(Payment, &Self)> {
if self.l() + vv >= L {
return Err(DivisibleEcashError::Spend(
"The counter l is higher than max L".to_string(),
));
}
let grp = params.get_grp();
let params_u = params.get_params_u();
let params_a = params.get_params_a();
// randomize signature in the wallet
let (signature_prime, sign_blinding_factor) = self.signature().randomise(grp);
// construct kappa i.e., blinded attributes for show
let attributes = vec![sk_user.sk, self.v()];
// compute kappa
let kappa = compute_kappa(&grp, &verification_key, &attributes, sign_blinding_factor);
let r1 = grp.random_scalar();
let r2 = grp.random_scalar();
let phi = Phi(
grp.gen1() * r1,
params_u.get_ith_sigma(self.l() as usize) * self.v
+ params_u.get_ith_eta(vv as usize) * r1,
);
// compute hash of the payment info
let rr = hash_to_scalar(pay_info.info);
let varphi = VarPhi(
grp.gen1() * r2,
(grp.gen1() * rr) * sk_user.sk
+ params_u.get_ith_theta(self.l() as usize) * self.v
+ params_u.get_ith_eta(vv as usize) * r2,
);
// random value used to compute blinded bases
let r_varsig1 = grp.random_scalar();
let r_theta1 = grp.random_scalar();
let r_varsig2 = grp.random_scalar();
let r_theta2 = grp.random_scalar();
let r_rr = grp.random_scalar();
let r_ss = grp.random_scalar();
let r_tt = grp.random_scalar();
// compute blinded bases
let psi_g1 = params_u.get_psi_g1();
let psi_g2 = params_u.get_psi_g2();
let varsig_prime1 = params_u.get_ith_sigma(self.l() as usize) + (psi_g1 * r_varsig1);
let theta_prime1 = params_u.get_ith_theta(self.l() as usize) + (psi_g1 * r_theta1);
let varsig_prime2 =
params_u.get_ith_sigma(self.l() as usize + vv as usize - 1) + (psi_g1 * r_varsig2);
let theta_prime2 =
params_u.get_ith_theta(self.l() as usize + vv as usize - 1) + (psi_g1 * r_theta2);
let tau_l_vv = params_u.get_ith_sps_sign(self.l() as usize + vv as usize - 1);
let rr_prime = tau_l_vv.rr + (psi_g1 * r_rr);
let ss_prime = tau_l_vv.ss + (psi_g1 * r_ss);
let tt_prime = tau_l_vv.tt + (psi_g2 * r_tt);
let rho1 = self.v.neg() * r_varsig1;
let rho2 = self.v.neg() * r_theta1;
let rho3 = r_rr * r_tt;
let pg_varsigpr1_delta = pairing(
&varsig_prime1.to_affine(),
&params_a.get_ith_delta((vv - 1) as usize).to_affine(),
);
let pg_psi0_delta = pairing(
&psi_g1.to_affine(),
&params_a.get_ith_delta((vv - 1) as usize).to_affine(),
);
let pg_varsigpr2_gen2 = pairing(&varsig_prime2.to_affine(), grp.gen2());
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
let pg_thetapr1_delta = pairing(
&theta_prime1.to_affine(),
&params_a.get_ith_delta((vv - 1) as usize).to_affine(),
);
let pg_thetapr2_gen2 = pairing(&theta_prime2.to_affine(), grp.gen2());
let yy = params_u.get_sps_pk().get_yy();
let pg_rrprime_yy = pairing(&rr_prime.to_affine(), &yy.to_affine());
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
let pg_ssprime_gen2 = pairing(&ss_prime.to_affine(), grp.gen2());
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
let pg_varsigpr2_ww1 = pairing(&varsig_prime2.to_affine(), &ww1.to_affine());
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
let pg_thetapr2_ww2 = pairing(&theta_prime2.to_affine(), &ww2.to_affine());
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
let pg_gen1_zz = pairing(grp.gen1(), &params_u.get_sps_pk().get_zz().to_affine());
let pg_rr_tt = pairing(&rr_prime.to_affine(), &tt_prime.to_affine());
let pg_rr_psi1 = pairing(&rr_prime.to_affine(), &psi_g2.to_affine());
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &tt_prime.to_affine());
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
let pg_eq3 =
pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 + pg_gen1_zz.neg();
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
let instance = SpendInstance {
kappa,
phi,
varphi,
rr,
rr_prime,
ss_prime,
tt_prime,
varsig_prime1,
theta_prime1,
pg_eq1,
pg_eq2,
pg_eq3,
pg_eq4,
psi_g1: *psi_g1,
psi_g2: *psi_g2,
pg_psi0_delta,
pg_psi0_gen2,
pg_psi0_yy,
pg_psi0_ww1,
pg_psi0_ww2,
pg_rr_psi1,
pg_psi0_tt,
pg_psi0_psi1,
};
let witness = SpendWitness {
sk_u: sk_user.clone(),
v: self.v,
r: sign_blinding_factor,
r1,
r2,
r_varsig1,
r_theta1,
r_varsig2,
r_theta2,
r_rr,
r_ss,
r_tt,
rho1,
rho2,
rho3,
};
// compute the zk proof
let zk_proof = SpendProof::construct(params, &instance, &witness, &verification_key, vv);
// output pay and updated wallet
let pay = Payment {
kappa,
sig: signature_prime,
phi,
varphi,
varsig_prime1,
varsig_prime2,
theta_prime1,
theta_prime2,
rr_prime,
ss_prime,
tt_prime,
rr,
zk_proof,
vv,
};
// The number of samples collected by the benchmark process is way higher than the
// MAX_WALLET_VALUE we ever consider. Thus, we would execute the spending too many times
// and the initial condition at the top of this function will crush. Thus, we need a
// benchmark flag to signal that we don't want to increase the spending couter but only
// care about the function performance.
if !bench_flag {
let current_l = self.l();
self.l.set(current_l + vv);
}
Ok((pay, self))
}
}
impl Payment {
pub fn spend_verify(
&self,
params: &Parameters,
verification_key: &VerificationKeyAuth,
pay_info: &PayInfo,
) -> Result<bool> {
if bool::from(self.sig.0.is_identity()) {
return Err(DivisibleEcashError::Spend(
"The element h of the signature equals the identity".to_string(),
));
}
let grp = params.get_grp();
let params_a = params.get_params_a();
let params_u = params.get_params_u();
if !check_bilinear_pairing(
&self.sig.0.to_affine(),
&G2Prepared::from(self.kappa.to_affine()),
&self.sig.1.to_affine(),
grp.prepared_miller_g2(),
) {
return Err(DivisibleEcashError::Spend(
"The bilinear check for kappa failed".to_string(),
));
}
if bool::from(self.sig.0.is_identity()) {
return Err(DivisibleEcashError::Spend(
"The element h of the signature on l equals the identity".to_string(),
));
}
// verify integrity of R
if !(self.rr == hash_to_scalar(pay_info.info)) {
return Err(DivisibleEcashError::Spend(
"Integrity of R does not hold".to_string(),
));
}
//TODO: verify whether payinfo contains merchent's identifier
let psi_g1 = params_u.get_psi_g1();
let psi_g2 = params_u.get_psi_g2();
let pg_varsigpr1_delta = pairing(
&self.varsig_prime1.to_affine(),
&params_a.get_ith_delta((self.vv - 1) as usize).to_affine(),
);
let pg_psi0_delta = pairing(
&psi_g1.to_affine(),
&params_a.get_ith_delta((self.vv - 1) as usize).to_affine(),
);
let pg_varsigpr2_gen2 = pairing(&self.varsig_prime2.to_affine(), grp.gen2());
let pg_psi0_gen2 = pairing(&psi_g1.to_affine(), grp.gen2());
let pg_thetapr1_delta = pairing(
&self.theta_prime1.to_affine(),
&params_a.get_ith_delta((self.vv - 1) as usize).to_affine(),
);
let pg_thetapr2_gen2 = pairing(&self.theta_prime2.to_affine(), grp.gen2());
let yy = params_u.get_sps_pk().get_yy();
let pg_rrprime_yy = pairing(&self.rr_prime.to_affine(), &yy.to_affine());
let pg_psi0_yy = pairing(&psi_g1.to_affine(), &yy.to_affine());
let pg_ssprime_gen2 = pairing(&self.ss_prime.to_affine(), grp.gen2());
let ww1 = params_u.get_sps_pk().get_ith_ww(0);
let ww2 = params_u.get_sps_pk().get_ith_ww(1);
let pg_varsigpr2_ww1 = pairing(&self.varsig_prime2.to_affine(), &ww1.to_affine());
let pg_psi0_ww1 = pairing(&psi_g1.to_affine(), &ww1.to_affine());
let pg_thetapr2_ww2 = pairing(&self.theta_prime2.to_affine(), &ww2.to_affine());
let pg_psi0_ww2 = pairing(&psi_g1.to_affine(), &ww2.to_affine());
let pg_gen1_zz = pairing(grp.gen1(), &params_u.get_sps_pk().get_zz().to_affine());
let pg_rr_tt = pairing(&self.rr_prime.to_affine(), &self.tt_prime.to_affine());
let pg_rr_psi1 = pairing(&self.rr_prime.to_affine(), &psi_g2.to_affine());
let pg_psi0_tt = pairing(&psi_g1.to_affine(), &self.tt_prime.to_affine());
let pg_psi0_psi1 = pairing(&psi_g1.to_affine(), &psi_g2.to_affine());
let pg_gen1_gen2 = pairing(grp.gen1(), grp.gen2());
let pg_eq1 = pg_varsigpr1_delta - pg_varsigpr2_gen2;
let pg_eq2 = pg_thetapr1_delta - pg_thetapr2_gen2;
let pg_eq3 =
pg_rrprime_yy + pg_ssprime_gen2 + pg_varsigpr2_ww1 + pg_thetapr2_ww2 + pg_gen1_zz.neg();
let pg_eq4 = pg_rr_tt - pg_gen1_gen2;
let instance = SpendInstance {
kappa: self.kappa,
phi: self.phi,
varphi: self.varphi,
rr: self.rr,
rr_prime: self.rr_prime,
ss_prime: self.ss_prime,
tt_prime: self.tt_prime,
varsig_prime1: self.varsig_prime1,
theta_prime1: self.theta_prime1,
pg_eq1,
pg_eq2,
pg_eq3,
pg_eq4,
psi_g1: *psi_g1,
psi_g2: *psi_g2,
pg_psi0_delta,
pg_psi0_gen2,
pg_psi0_yy,
pg_psi0_ww1,
pg_psi0_ww2,
pg_rr_psi1,
pg_psi0_tt,
pg_psi0_psi1,
};
Ok(self
.zk_proof
.verify(&params, &instance, &verification_key, self.vv))
}
}
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
use rand::thread_rng;
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::keygen::{ttp_keygen_authorities, PublicKeyUser, VerificationKeyAuth};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::scheme::{PayInfo, Phi, VarPhi, Wallet};
use crate::utils::hash_g1;
#[test]
fn phi_to_and_from_bytes() {
let phi = Phi(hash_g1("Element 0 of Phi"), hash_g1("Element 1 of Phi"));
let phi_bytes = phi.to_bytes();
let phi_from_bytes = Phi::from_bytes(&phi_bytes).unwrap();
assert_eq!(phi, phi_from_bytes);
}
#[test]
fn varphi_to_and_from_bytes() {
let varphi = VarPhi(
hash_g1("Element 0 of VarPhi"),
hash_g1("Element 1 of VarPhi"),
);
let varphi_bytes = varphi.to_bytes();
let varphi_from_bytes = VarPhi::from_bytes(&varphi_bytes).unwrap();
assert_eq!(varphi, varphi_from_bytes);
}
#[test]
fn spend_verification_is_correct() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
let params_u = params.get_params_u();
let params_a = params.get_params_a();
let sk = grp.random_scalar();
let pk_user = PublicKeyUser {
pk: grp.gen1() * sk,
};
let authorities_keypairs = ttp_keygen_authorities(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
}
}
@@ -0,0 +1,258 @@
use std::convert::TryFrom;
use std::net::ToSocketAddrs;
use bls12_381::{pairing, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar};
use ff::Field;
use group::Curve;
use rand::thread_rng;
use crate::constants::L;
use crate::error::Result;
use crate::scheme::structure_preserving_signature::{SPSKeyPair, SPSSignature, SPSVerificationKey};
use crate::utils::{hash_g1, hash_g2};
#[derive(Debug, Clone)]
pub struct GroupParameters {
/// Generator of the G1 group
g1: G1Affine,
/// Generator of the G2 group
g2: G2Affine,
/// Precomputed G2 generator used for the miller loop
_g2_prepared_miller: G2Prepared,
}
impl GroupParameters {
pub fn new() -> Result<GroupParameters> {
Ok(GroupParameters {
g1: G1Affine::generator(),
g2: G2Affine::generator(),
_g2_prepared_miller: G2Prepared::from(G2Affine::generator()),
})
}
pub(crate) fn gen1(&self) -> &G1Affine {
&self.g1
}
pub(crate) fn gen2(&self) -> &G2Affine {
&self.g2
}
pub(crate) fn prepared_miller_g2(&self) -> &G2Prepared {
&self._g2_prepared_miller
}
pub fn random_scalar(&self) -> Scalar {
// lazily-initialized thread-local random number generator, seeded by the system
let mut rng = thread_rng();
Scalar::random(&mut rng)
}
pub(crate) fn n_random_scalars(&self, n: usize) -> Vec<Scalar> {
(0..n).map(|_| self.random_scalar()).collect()
}
}
#[derive(Debug, Clone)]
pub struct Parameters {
grp: GroupParameters,
params_u: ParametersUser,
params_a: ParametersAuthority,
tmp_sigma: G1Projective,
pub y: Scalar,
}
impl Parameters {
pub(crate) fn get_grp(&self) -> &GroupParameters {
&self.grp
}
pub(crate) fn get_params_u(&self) -> &ParametersUser {
&self.params_u
}
pub(crate) fn get_params_a(&self) -> &ParametersAuthority {
&self.params_a
}
pub fn new(grp: GroupParameters) -> Parameters {
let g1 = grp.gen1();
let g2 = grp.gen2();
let psi_g1 = hash_g1("psi1");
let psi_g2 = hash_g2("psi2");
let gamma1 = hash_g1("gamma1");
let gamma2 = hash_g1("gamma2");
let eta = hash_g1("eta");
let z = grp.random_scalar();
let y = grp.random_scalar();
let vec_a = grp.n_random_scalars(L as usize);
let sigma = g1 * z;
let theta = eta * z;
let sigmas_u: Vec<G1Projective> = (1..=L)
.map(|i| sigma * (y.pow(&[i as u64, 0, 0, 0])))
.collect();
let thetas_u: Vec<G1Projective> = (1..=L)
.map(|i| theta * (y.pow(&[i as u64, 0, 0, 0])))
.collect();
let deltas_a: Vec<G2Projective> = (0..=L - 1)
.map(|i| g2 * (y.pow(&[i as u64, 0, 0, 0])))
.collect();
let etas_u: Vec<G1Projective> = vec_a.iter().map(|x| g1 * x).collect();
let mut etas_a: Vec<Vec<G2Projective>> = Default::default();
for l in 1..=L {
let mut etas_a_l: Vec<G2Projective> = Default::default();
for k in 0..=l - 1 {
etas_a_l.push(g2 * (vec_a[l as usize - 1].neg() * (y.pow(&[k as u64, 0, 0, 0]))));
}
etas_a.push(etas_a_l);
}
let sps_keypair = SPSKeyPair::new(grp.clone(), 2, 0);
let sps_signatures: Vec<SPSSignature> = sigmas_u
.iter()
.zip(thetas_u.iter())
.map(|(sigma, theta)| {
sps_keypair
.sps_sk
.sign(&grp, Some(&vec![*sigma, *theta]), None)
})
.collect();
// Compute signature for each pair sigma, theta
let params_u = ParametersUser {
gammas: vec![gamma1, gamma2],
psi_g1,
psi_g2,
eta,
etas: etas_u,
sigmas: sigmas_u,
thetas: thetas_u,
sps_signatures,
sps_pk: sps_keypair.sps_vk,
};
let params_a = ParametersAuthority {
deltas: deltas_a,
etas: etas_a,
};
return Parameters {
grp,
params_u,
params_a,
tmp_sigma: sigma,
y,
};
}
}
impl Parameters {
pub(crate) fn get_sigma(&self) -> &G1Projective {
&self.tmp_sigma
}
}
#[derive(Debug, Clone)]
pub struct ParametersUser {
gammas: Vec<G1Projective>,
psi_g1: G1Projective,
psi_g2: G2Projective,
eta: G1Projective,
etas: Vec<G1Projective>,
sigmas: Vec<G1Projective>,
thetas: Vec<G1Projective>,
sps_signatures: Vec<SPSSignature>,
sps_pk: SPSVerificationKey,
}
impl ParametersUser {
pub(crate) fn get_gammas(&self) -> &Vec<G1Projective> {
&self.gammas
}
pub(crate) fn get_psi_g1(&self) -> &G1Projective {
&self.psi_g1
}
pub(crate) fn get_psi_g2(&self) -> &G2Projective {
&self.psi_g2
}
pub(crate) fn get_eta(&self) -> &G1Projective {
&self.eta
}
pub(crate) fn get_etas(&self) -> &[G1Projective] {
&self.etas
}
pub(crate) fn get_ith_eta(&self, idx: usize) -> &G1Projective {
self.etas.get(idx - 1).unwrap()
}
pub(crate) fn get_sigmas(&self) -> &[G1Projective] {
&self.sigmas
}
pub(crate) fn get_ith_sigma(&self, idx: usize) -> &G1Projective {
self.sigmas.get(idx - 1).unwrap()
}
pub(crate) fn get_thetas(&self) -> &[G1Projective] {
&self.thetas
}
pub(crate) fn get_ith_theta(&self, idx: usize) -> &G1Projective {
self.thetas.get(idx - 1).unwrap()
}
pub(crate) fn get_sps_signs(&self) -> &[SPSSignature] {
&self.sps_signatures
}
pub(crate) fn get_ith_sps_sign(&self, idx: usize) -> &SPSSignature {
&self.sps_signatures.get(idx - 1).unwrap()
}
pub(crate) fn get_sps_pk(&self) -> &SPSVerificationKey {
&self.sps_pk
}
}
#[derive(Debug, Clone)]
pub struct ParametersAuthority {
deltas: Vec<G2Projective>,
etas: Vec<Vec<G2Projective>>,
}
impl ParametersAuthority {
pub(crate) fn get_deltas(&self) -> &[G2Projective] {
&self.deltas
}
pub(crate) fn get_ith_delta(&self, idx: usize) -> &G2Projective {
self.deltas.get(idx).unwrap()
}
pub(crate) fn get_etas(&self) -> &Vec<Vec<G2Projective>> {
&self.etas
}
pub(crate) fn get_eta_ith_row(&self, idx: usize) -> &[G2Projective] {
self.etas.get(idx).unwrap()
}
pub(crate) fn get_etas_ith_jth_elem(&self, row: usize, column: usize) -> &G2Projective {
self.etas.get(row - 1).unwrap().get(column).unwrap()
}
}
@@ -0,0 +1,245 @@
use std::fmt::Debug;
use std::ops::Neg;
use bls12_381::{pairing, G1Projective, G2Projective, Gt, Scalar};
use group::Curve;
use crate::scheme::setup::GroupParameters;
#[derive(Debug, Clone)]
pub struct SPSVerificationKey {
pub grp: GroupParameters,
pub uus: Vec<G1Projective>,
pub wws: Vec<G2Projective>,
pub yy: G2Projective,
pub zz: G2Projective,
}
pub struct SPSSecretKey {
sps_vk: SPSVerificationKey,
us: Vec<Scalar>,
ws: Vec<Scalar>,
y: Scalar,
z: Scalar,
}
impl SPSSecretKey {
pub fn z(&self) -> Scalar {
self.z
}
pub fn y(&self) -> Scalar {
self.y
}
pub fn sign(
&self,
grp: &GroupParameters,
messages_a: Option<&[G1Projective]>,
messages_b: Option<&[G2Projective]>,
) -> SPSSignature {
let r = grp.random_scalar();
let rr = grp.gen1() * r;
let ss: G1Projective = match messages_a {
Some(msgs_a) => {
let prod_s: Vec<G1Projective> = msgs_a
.iter()
.zip(self.ws.iter())
.map(|(m_i, w_i)| m_i * w_i.neg())
.collect();
grp.gen1() * (self.z() - r * self.y())
+ prod_s
.iter()
.fold(G1Projective::identity(), |acc, elem| acc + elem)
}
None => grp.gen1() * (self.z() - r * self.y()),
};
let tt = match messages_b {
Some(msgs_b) => {
let prod_t: Vec<G2Projective> = msgs_b
.iter()
.zip(self.us.iter())
.map(|(m_i, u_i)| m_i * u_i.neg())
.collect();
(grp.gen2()
+ prod_t
.iter()
.fold(G2Projective::identity(), |acc, elem| acc + elem))
* r.invert().unwrap()
}
None => grp.gen2() * r.invert().unwrap(),
};
SPSSignature { rr, ss, tt }
}
}
impl SPSVerificationKey {
pub fn verify(
&self,
grp: &GroupParameters,
signature: SPSSignature,
messages_a: &[G1Projective],
messages_b: Option<&[G2Projective]>,
) -> bool {
let pg_rr_yy = pairing(&signature.rr.to_affine(), &self.yy.to_affine());
let pg_ss_g2 = pairing(&signature.ss.to_affine(), grp.gen2());
let pg_g1_zz = pairing(grp.gen1(), &self.zz.to_affine());
let pg_ma_ww: Vec<Gt> = messages_a
.iter()
.zip(self.wws.iter())
.map(|(ma, ww)| pairing(&ma.to_affine(), &ww.to_affine()))
.collect();
let mut prod_pg_ma_ww = Gt::identity();
for elem in pg_ma_ww.iter() {
prod_pg_ma_ww = prod_pg_ma_ww + elem;
}
// let prod_pg_ma_ww = pg_ma_ww.iter().fold(Gt::identity() | acc, elem | acc + elem);
assert_eq!(pg_rr_yy + pg_ss_g2 + prod_pg_ma_ww, pg_g1_zz);
let result = match messages_b {
Some(msgs_b) => {
let pg_rr_tt = pairing(&signature.rr.to_affine(), &signature.tt.to_affine());
let pg_g1_g2 = pairing(grp.gen1(), grp.gen2());
let pg_uu_mb: Vec<Gt> = self
.uus
.iter()
.zip(msgs_b.iter())
.map(|(uu, mb)| pairing(&uu.to_affine(), &mb.to_affine()))
.collect();
let mut prod_pg_uu_mb = Gt::identity();
for elem in pg_uu_mb.iter() {
prod_pg_uu_mb = prod_pg_uu_mb + elem;
}
// let prod_pg_uu_mb = pg_uu_mb.iter().fold(Gt::identity() | acc, elem | acc + elem);
if pg_rr_tt + prod_pg_uu_mb == pg_g1_g2 {
true
} else {
false
}
}
None => {
let pg_sign_rr_yy = pairing(&signature.rr.to_affine(), &self.yy.to_affine());
let pg_sign_ss_gen2 = pairing(&signature.ss.to_affine(), &grp.gen2());
let pg_ma_wws: Vec<Gt> = messages_a
.iter()
.zip(self.wws.iter())
.map(|(ma, ww)| pairing(&ma.to_affine(), &ww.to_affine()))
.collect();
let mut prod_pg_ma_wws = Gt::identity();
for elem in pg_ma_wws.iter() {
prod_pg_ma_wws = prod_pg_ma_wws + elem;
}
let pg_gen1_zz = pairing(&grp.gen1(), &self.zz.to_affine());
let pg_rr_tt = pairing(&signature.rr.to_affine(), &signature.tt.to_affine());
let pg_gen1_gen2 = pairing(&grp.gen1(), &grp.gen2());
assert_eq!(pg_sign_rr_yy + pg_sign_ss_gen2 + prod_pg_ma_wws, pg_gen1_zz);
assert_eq!(pg_rr_tt, pg_gen1_gen2);
if pg_sign_rr_yy + pg_sign_ss_gen2 + prod_pg_ma_wws == pg_gen1_zz
&& pg_rr_tt == pg_gen1_gen2
{
true
} else {
false
}
}
};
return result;
}
pub fn get_ith_ww(&self, idx: usize) -> &G2Projective {
return self.wws.get(idx).unwrap();
}
pub fn get_zz(&self) -> &G2Projective {
return &self.zz;
}
pub fn get_yy(&self) -> &G2Projective {
return &self.yy;
}
}
pub struct SPSKeyPair {
pub sps_sk: SPSSecretKey,
pub sps_vk: SPSVerificationKey,
}
impl SPSKeyPair {
pub fn new(grp: GroupParameters, a: usize, b: usize) -> SPSKeyPair {
let us = grp.n_random_scalars(b);
let ws = grp.n_random_scalars(a);
let y = grp.random_scalar();
let z = grp.random_scalar();
let uus: Vec<G1Projective> = us.iter().map(|u| grp.gen1() * u).collect();
let yy = grp.gen2() * y;
let wws: Vec<G2Projective> = ws.iter().map(|w| grp.gen2() * w).collect();
let zz = grp.gen2() * z;
let sps_vk = SPSVerificationKey {
grp: grp.clone(),
uus,
wws,
yy,
zz,
};
let sps_sk = SPSSecretKey {
sps_vk: sps_vk.clone(),
us,
ws,
y,
z,
};
SPSKeyPair { sps_sk, sps_vk }
}
}
#[derive(Debug, Clone)]
pub struct SPSSignature {
pub rr: G1Projective,
pub ss: G1Projective,
pub tt: G2Projective,
}
#[cfg(test)]
mod tests {
use rand::thread_rng;
use crate::scheme::setup::GroupParameters;
use crate::scheme::structure_preserving_signature::SPSKeyPair;
use crate::utils::{hash_g1, hash_g2};
#[test]
fn sign_and_verify_for_two_msg_in_G1_and_two_msgs_in_G2() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let sps_keypair = SPSKeyPair::new(grp.clone(), 2, 2);
let msgs_a = vec![hash_g1("messageA1"), hash_g1("messageA2")];
let msgs_b = vec![hash_g2("messageB1"), hash_g2("messageB2")];
let signature = sps_keypair.sps_sk.sign(&grp, Some(&msgs_a), Some(&msgs_b));
assert!(sps_keypair
.sps_vk
.verify(&grp, signature, &msgs_a, Some(&msgs_b)));
}
#[test]
fn sign_and_verify_for_two_msg_in_G1_and_no_msgs_in_G2() {
let rng = thread_rng();
let grp = GroupParameters::new().unwrap();
let sps_keypair = SPSKeyPair::new(grp.clone(), 2, 2);
let msgs_a = vec![hash_g1("messageA1"), hash_g1("messageA2")];
let signature = sps_keypair.sps_sk.sign(&grp, Some(&msgs_a), None);
assert!(sps_keypair.sps_vk.verify(&grp, signature, &msgs_a, None));
}
}
@@ -0,0 +1,175 @@
use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar};
use group::{Curve, GroupEncoding};
use crate::error::{DivisibleEcashError, Result};
use crate::proofs::proof_withdrawal::{
WithdrawalReqInstance, WithdrawalReqProof, WithdrawalReqWitness,
};
use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::scheme::PartialWallet;
use crate::utils::{check_bilinear_pairing, hash_g1, BlindedSignature, Signature};
pub struct WithdrawalRequest {
com_hash: G1Projective,
com: G1Projective,
pc_coms: Vec<G1Projective>,
zk_proof: WithdrawalReqProof,
}
pub struct RequestInfo {
com_hash: G1Projective,
pc_coms_openings: Vec<Scalar>,
v: Scalar,
}
pub fn withdrawal_request(
params: &Parameters,
sk_user: &SecretKeyUser,
) -> Result<(WithdrawalRequest, RequestInfo)> {
let grp = params.get_grp();
let g1 = grp.gen1();
let params_u = params.get_params_u();
let v = grp.random_scalar();
let attributes = vec![sk_user.sk, v];
let com_opening = grp.random_scalar();
let commitment = g1 * com_opening
+ attributes
.iter()
.zip(params_u.get_gammas())
.map(|(&m, gamma)| gamma * m)
.sum::<G1Projective>();
// Value h in the paper
let com_hash = hash_g1(commitment.to_bytes());
let pc_coms_openings = grp.n_random_scalars(attributes.len());
// Compute Pedersen commitment for each attribute
let pc_coms = pc_coms_openings
.iter()
.zip(attributes.iter())
.map(|(o_j, m_j)| g1 * o_j + com_hash * m_j)
.collect::<Vec<_>>();
// construct a zk proof of knowledge proving possession of m1, m2, o, o1, o2
let instance = WithdrawalReqInstance {
com: commitment,
h: com_hash,
pc_coms: pc_coms.clone(),
pk_user: sk_user.public_key(&params.get_grp()),
};
let witness = WithdrawalReqWitness {
attributes,
com_opening,
pc_coms_openings: pc_coms_openings.clone(),
};
let zk_proof = WithdrawalReqProof::construct(&params, &instance, &witness);
let req = WithdrawalRequest {
com_hash,
com: commitment,
pc_coms: pc_coms.clone(),
zk_proof,
};
let req_info = RequestInfo {
com_hash,
pc_coms_openings,
v,
};
Ok((req, req_info))
}
pub fn issue(
params: &Parameters,
req: &WithdrawalRequest,
pk_u: PublicKeyUser,
sk_a: &SecretKeyAuth,
) -> Result<BlindedSignature> {
let h = hash_g1(req.com.to_bytes());
if !(h == req.com_hash) {
return Err(DivisibleEcashError::WithdrawalRequestVerification(
"Failed to verify the commitment hash".to_string(),
));
}
// verify zk proof
let instance = WithdrawalReqInstance {
com: req.com,
h: req.com_hash,
pc_coms: req.pc_coms.clone(),
pk_user: pk_u,
};
if !req.zk_proof.verify(&params, &instance) {
return Err(DivisibleEcashError::WithdrawalRequestVerification(
"Failed to verify the proof of knowledge".to_string(),
));
}
let sig = req
.pc_coms
.iter()
.zip(sk_a.ys.iter())
.map(|(pc, yi)| pc * yi)
.chain(std::iter::once(h * sk_a.x))
.sum();
Ok(BlindedSignature(h, sig))
}
pub fn issue_verify(
params: &GroupParameters,
vk_auth: &VerificationKeyAuth,
sk_user: &SecretKeyUser,
blind_signature: &BlindedSignature,
req_info: &RequestInfo,
) -> Result<PartialWallet> {
// Parse the blinded signature
let h = blind_signature.0;
let c = blind_signature.1;
// Verify the integrity of the response from the authority
if !(req_info.com_hash == h) {
return Err(DivisibleEcashError::IssuanceVfy(
"Integrity verification failed".to_string(),
));
}
// Unblind the blinded signature on the partial wallet
let blinding_removers = vk_auth
.beta_g1
.iter()
.zip(req_info.pc_coms_openings.iter())
.map(|(beta, opening)| beta * opening)
.sum::<G1Projective>();
let unblinded_c = c - blinding_removers;
let attr = vec![sk_user.sk, req_info.v];
let signed_attributes = attr
.iter()
.zip(vk_auth.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
// Verify the signature correctness on the wallet share
if !check_bilinear_pairing(
&h.to_affine(),
&G2Prepared::from((vk_auth.alpha + signed_attributes).to_affine()),
&unblinded_c.to_affine(),
params.prepared_miller_g2(),
) {
return Err(DivisibleEcashError::IssuanceVfy(
"Verification of wallet share failed".to_string(),
));
}
Ok(PartialWallet {
sig: Signature(h, unblinded_c),
v: req_info.v,
idx: None,
})
}
@@ -0,0 +1,76 @@
use rand::thread_rng;
use crate::error::DivisibleEcashError;
use crate::scheme::aggregation::{
aggregate_signatures, aggregate_verification_keys, aggregate_wallets,
};
use crate::scheme::identification::identify;
use crate::scheme::keygen::{
ttp_keygen_authorities, PublicKeyUser, SecretKeyUser, VerificationKeyAuth,
};
use crate::scheme::setup::{GroupParameters, Parameters};
use crate::scheme::withdrawal::{issue, issue_verify, withdrawal_request};
use crate::scheme::{PayInfo, Payment};
#[test]
// Test wa full end to end flow of withdrawal request, issuance,
// and spending.
fn main() -> Result<(), DivisibleEcashError> {
// SETUP PHASE
let grp = GroupParameters::new().unwrap();
let params = Parameters::new(grp.clone());
// KEY GENERATION FOR THE AUTHORITIES
let authorities_keypairs = ttp_keygen_authorities(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap();
// KEY GENERATION FOR THE USER
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = SecretKeyUser::public_key(&sk_user, &grp);
// WITHDRAWAL REQUEST
let (withdrawal_req, req_info) = withdrawal_request(&params, &sk_user)?;
// ISSUE PARTIAL WALLETS
let mut partial_wallets = Vec::new();
for auth_keypair in authorities_keypairs {
let blind_signature = issue(
&params,
&withdrawal_req,
pk_user.clone(),
&auth_keypair.secret_key(),
)?;
let partial_wallet = issue_verify(
&grp,
&auth_keypair.verification_key(),
&sk_user,
&blind_signature,
&req_info,
)?;
partial_wallets.push(partial_wallet);
}
// AGGREGATE WALLET
let mut wallet = aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets)?;
let pay_info = PayInfo { info: [67u8; 32] };
let (payment, wallet) =
wallet.spend(&params, &verification_key, &sk_user, &pay_info, 10, false)?;
// SPEND VERIFICATION
assert!(payment
.spend_verify(&params, &verification_key, &pay_info)
.unwrap());
let payment_bytes = payment.to_bytes();
let payment2 = Payment::try_from(&payment_bytes[..]).unwrap();
assert_eq!(payment, payment2);
Ok(())
}
@@ -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::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField};
use bls12_381::{
multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar,
};
use ff::Field;
use group::{Curve, Group};
use crate::error::{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,23 @@
[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 = { workspace = true }
bs58 = "0.4.0"
digest = "0.9"
ff = { workspace = true }
group = { workspace = true }
itertools = "0.10"
rand = "0.8"
sha2 = "0.9"
thiserror = "1.0"
[dev-dependencies]
criterion = { version = "0.3", features = ["html_reports"] }
@@ -0,0 +1 @@
@@ -0,0 +1,3 @@
mod error;
mod traits;
mod utils;
@@ -0,0 +1 @@
mod e2e;
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+41 -35
View File
@@ -15,6 +15,12 @@ use rand::seq::SliceRandom;
use std::ops::Neg;
use std::time::Duration;
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,
};
#[allow(unused)]
fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
let gt1 = bls12_381::pairing(g11, g21);
@@ -33,6 +39,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,
@@ -125,43 +165,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,
+1 -1
View File
@@ -39,7 +39,7 @@ mod proofs;
mod scheme;
pub mod tests;
mod traits;
mod utils;
pub mod utils;
pub type Attribute = Scalar;
pub type PrivateAttribute = Attribute;
+5 -11
View File
@@ -122,7 +122,7 @@ const G1_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_R
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-K.1
const SCALAR_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-expander";
pub(crate) fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
}
@@ -137,7 +137,7 @@ pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
output[0]
}
pub(crate) fn try_deserialize_scalar_vec(
pub fn try_deserialize_scalar_vec(
expected_len: u64,
bytes: &[u8],
err: CoconutError,
@@ -159,23 +159,17 @@ pub(crate) fn try_deserialize_scalar_vec(
Ok(out)
}
pub(crate) fn try_deserialize_scalar(bytes: &[u8; 32], err: CoconutError) -> Result<Scalar> {
pub fn try_deserialize_scalar(bytes: &[u8; 32], err: CoconutError) -> Result<Scalar> {
Into::<Option<Scalar>>::into(Scalar::from_bytes(bytes)).ok_or(err)
}
pub(crate) fn try_deserialize_g1_projective(
bytes: &[u8; 48],
err: CoconutError,
) -> Result<G1Projective> {
pub fn try_deserialize_g1_projective(bytes: &[u8; 48], err: CoconutError) -> Result<G1Projective> {
Into::<Option<G1Affine>>::into(G1Affine::from_compressed(bytes))
.ok_or(err)
.map(G1Projective::from)
}
pub(crate) fn try_deserialize_g2_projective(
bytes: &[u8; 96],
err: CoconutError,
) -> Result<G2Projective> {
pub fn try_deserialize_g2_projective(bytes: &[u8; 96], err: CoconutError) -> Result<G2Projective> {
Into::<Option<G2Affine>>::into(G2Affine::from_compressed(bytes))
.ok_or(err)
.map(G2Projective::from)
+2
View File
@@ -22,6 +22,7 @@ atty = "0.2"
bip39 = { workspace = true }
bs58 = "0.4.0"
clap = { workspace = true, features = ["cargo", "derive"] }
chrono = "0.4.19"
colored = "2.0"
dashmap = { workspace = true }
dirs = "4.0"
@@ -63,6 +64,7 @@ nym-node = { path = "../nym-node" }
nym-api-requests = { path = "../nym-api/nym-api-requests" }
nym-bin-common = { path = "../common/bin-common", features = ["output_format"] }
nym-coconut-interface = { path = "../common/coconut-interface" }
nym-compact-ecash = { path = "../common/nym_offline_compact_ecash" }
nym-config = { path = "../common/config" }
nym-credentials = { path = "../common/credentials" }
nym-crypto = { path = "../common/crypto" }
+1
View File
@@ -25,6 +25,7 @@ nym-pemstore = { path = "../../common/pemstore" }
nym-sphinx = { path = "../../common/nymsphinx" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-compact-ecash = {path = "../../common/nym_offline_compact_ecash" }
nym-credentials = { path = "../../common/credentials" }
[dependencies.tungstenite]
+29
View File
@@ -7,6 +7,7 @@ use crate::registration::handshake::SharedKeys;
use crate::{GatewayMacSize, PROTOCOL_VERSION};
use log::error;
use nym_coconut_interface::Credential;
use nym_compact_ecash::scheme::EcashCredential;
use nym_crypto::generic_array::typenum::Unsigned;
use nym_crypto::hmac::recompute_keyed_hmac_and_verify_tag;
use nym_crypto::symmetric::stream_cipher;
@@ -137,6 +138,10 @@ pub enum ClientControlRequest {
enc_credential: Vec<u8>,
iv: Vec<u8>,
},
EcashCredential {
enc_credential: Vec<u8>,
iv: Vec<u8>,
},
ClaimFreeTestnetBandwidth,
}
@@ -177,6 +182,30 @@ impl ClientControlRequest {
Credential::from_bytes(&credential_bytes)
.map_err(|_| GatewayRequestsError::MalformedEncryption)
}
pub fn new_enc_ecash_credential(
credential: &EcashCredential,
shared_key: &SharedKeys,
iv: IV,
) -> Self {
let serialized_credential = credential.to_bytes();
let enc_credential = shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner()));
ClientControlRequest::EcashCredential {
enc_credential,
iv: iv.to_bytes(),
}
}
pub fn try_from_enc_ecash_credential(
enc_credential: Vec<u8>,
shared_key: &SharedKeys,
iv: IV,
) -> Result<EcashCredential, GatewayRequestsError> {
let credential_bytes = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?;
EcashCredential::try_from(credential_bytes.as_slice())
.map_err(|_| GatewayRequestsError::MalformedEncryption)
}
}
impl From<ClientControlRequest> for Message {
@@ -0,0 +1,19 @@
/*
* Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
CREATE TABLE credentials
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
credentials TEXT NOT NULL
);
CREATE TABLE pending
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
credential TEXT NOT NULL,
address TEXT NOT NULL,
api_url TEXT NOT NULL
);
+5
View File
@@ -40,6 +40,7 @@ pub(crate) struct OverrideConfig {
pub(crate) mnemonic: Option<bip39::Mnemonic>,
pub(crate) nyxd_urls: Option<Vec<url::Url>>,
pub(crate) only_coconut_credentials: Option<bool>,
pub(crate) offline_credential_verification: Option<bool>,
pub(crate) with_network_requester: Option<bool>,
pub(crate) with_ip_packet_router: Option<bool>,
}
@@ -76,6 +77,10 @@ impl OverrideConfig {
Config::with_only_coconut_credentials,
self.only_coconut_credentials,
)
.with_optional(
Config::with_offline_credential_verification,
self.offline_credential_verification,
)
.with_optional(
Config::with_enabled_network_requester,
self.with_network_requester,
+6
View File
@@ -73,6 +73,10 @@ pub struct Init {
#[clap(long, hide = true)]
only_coconut_credentials: Option<bool>,
/// Set this gateway to use offline credentials verification
#[clap(long, hide = true)]
offline_credential_verification: Option<bool>,
/// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server
#[clap(long)]
enabled_statistics: Option<bool>,
@@ -160,6 +164,7 @@ impl From<Init> for OverrideConfig {
nyxd_urls: init_config.nyxd_urls,
only_coconut_credentials: init_config.only_coconut_credentials,
offline_credential_verification: init_config.offline_credential_verification,
with_network_requester: Some(init_config.with_network_requester),
with_ip_packet_router: Some(init_config.with_ip_packet_router),
}
@@ -292,6 +297,7 @@ mod tests {
enabled_statistics: None,
nyxd_urls: None,
only_coconut_credentials: None,
offline_credential_verification: None,
output: Default::default(),
with_network_requester: false,
with_ip_packet_router: false,

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