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
341 changed files with 14644 additions and 12400 deletions
Vendored
BIN
View File
Binary file not shown.
-21
View File
@@ -1,21 +0,0 @@
name: ci-cargo-deny
on: [workflow_dispatch]
jobs:
cargo-deny:
runs-on: ubuntu-22.04
strategy:
matrix:
checks:
# - advisories
- licenses
- bans sources
continue-on-error: ${{ matrix.checks == 'licenses' }}
steps:
- uses: actions/checkout@v3
- uses: EmbarkStudios/cargo-deny-action@v1
with:
log-level: warn
command: check ${{ matrix.checks }}
argument: --all-features
-1
View File
@@ -9,7 +9,6 @@
target
.env
.env.dev
envs/devnet.env
/.vscode/settings.json
validator/.vscode
sample-configs/validator-config.toml
Generated
+879 -594
View File
File diff suppressed because it is too large Load Diff
+23 -13
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"]
@@ -162,8 +173,7 @@ serde_json = "1.0.91"
tap = "1.0.1"
time = "0.3.30"
thiserror = "1.0.48"
tokio = "1.33.0"
tokio-util = "0.7.10"
tokio = "1.24.1"
tokio-tungstenite = "0.20.1"
tracing = "0.1.37"
tungstenite = { version = "0.20.1", default-features = false }
@@ -173,14 +183,6 @@ utoipa-swagger-ui = "3.1.5"
url = "2.4"
zeroize = "1.6.0"
# coconut/DKG related
# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork
# as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch ="feature/gt-serialization-0.8.0" }
group = "0.13.0"
ff = "0.13.0"
# cosmwasm-related
cosmwasm-derive = "=1.3.0"
cosmwasm-schema = "=1.3.0"
@@ -201,7 +203,6 @@ cw-controllers = { version = "=1.1.0" }
bip32 = "0.5.1"
cosmrs = "=0.15.0"
tendermint-rpc = "0.34" # same version as used by cosmrs
tendermint = "0.34" # same version as used by cosmrs
prost = "0.12"
# wasm-related dependencies
@@ -214,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 -1
View File
@@ -5,7 +5,6 @@ authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej St
description = "Implementation of the Nym Client"
edition = "2021"
rust-version = "1.65"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -39,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)
@@ -3,7 +3,6 @@ name = "nym-client-websocket-requests"
version = "0.1.0"
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-1
View File
@@ -5,7 +5,6 @@ authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
rust-version = "1.56"
license.workspace = true
[dependencies]
clap = { workspace = true, features = ["cargo", "derive"] }
@@ -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
@@ -2,7 +2,6 @@
name = "async-file-watcher"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+1 -2
View File
@@ -2,7 +2,6 @@
name = "nym-bandwidth-controller"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -11,9 +10,9 @@ bip39 = { workspace = true }
rand = "0.7.3"
thiserror = { workspace = true }
url = { workspace = true }
zeroize = { 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"] }
+33 -29
View File
@@ -2,49 +2,59 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::BandwidthControllerError;
use nym_coconut_interface::Base58;
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;
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;
use nym_validator_client::nyxd::Hash;
use rand::rngs::OsRng;
use state::State;
use state::{KeyPair, State};
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_key = identity::PrivateKey::new(&mut rng);
let encryption_key = encryption::PrivateKey::new(&mut rng);
let params = BandwidthVoucher::default_parameters();
let signing_keypair = KeyPair::from(identity::KeyPair::new(&mut rng));
let encryption_keypair = KeyPair::from(encryption::KeyPair::new(&mut rng));
let params = GroupParameters::new().unwrap();
let voucher_value = amount.amount.to_string();
let tx_hash = client
.deposit(
amount,
String::from(VOUCHER_INFO),
signing_key.public_key().to_base58_string(),
encryption_key.public_key().to_base58_string(),
String::from(ECASH_INFO),
signing_keypair.public_key.clone(),
encryption_keypair.public_key.clone(),
None,
)
.await?
.transaction_hash;
.transaction_hash
.to_string();
let voucher = BandwidthVoucher::new(
&params,
voucher_value,
VOUCHER_INFO.to_string(),
tx_hash,
signing_key,
encryption_key,
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 };
@@ -68,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,19 +1,44 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_coconut_interface::Parameters;
use nym_compact_ecash::setup::GroupParameters;
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use nym_crypto::asymmetric::{encryption, identity};
pub(crate) struct KeyPair {
pub public_key: String,
pub private_key: String,
}
impl From<identity::KeyPair> for KeyPair {
fn from(kp: identity::KeyPair) -> Self {
Self {
public_key: kp.public_key().to_base58_string(),
private_key: kp.private_key().to_base58_string(),
}
}
}
impl From<encryption::KeyPair> for KeyPair {
fn from(kp: encryption::KeyPair) -> Self {
Self {
public_key: kp.public_key().to_base58_string(),
private_key: kp.private_key().to_base58_string(),
}
}
}
pub struct State {
pub voucher: BandwidthVoucher,
pub params: Parameters,
pub params: GroupParameters,
}
impl State {
pub fn new(voucher: BandwidthVoucher) -> Self {
State {
voucher,
params: BandwidthVoucher::default_parameters(),
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 -43
View File
@@ -2,18 +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 zeroize::Zeroizing;
use {
nym_coconut_interface::Base58,
nym_credentials::coconut::{
bandwidth::prepare_for_spending, utils::obtain_aggregate_verification_key,
},
};
pub mod acquire;
pub mod error;
@@ -21,70 +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 = Zeroizing::new(nym_coconut_interface::Attribute::try_from_bs58(
bandwidth_credential.serial_number,
)?);
let binding_number = Zeroizing::new(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)))
}
@@ -99,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 -1
View File
@@ -4,7 +4,6 @@ version = "1.1.15"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
rust-version = "1.66"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -35,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")?;
@@ -127,9 +127,7 @@ impl ActionController {
.insert(frag_id, (Arc::new(pending_ack), None))
.is_some()
{
// This used to be a panic, however since we've seen this actually happen in the
// wild, let's not take the whole client (and possibly gateway) down because of it.
error!("Tried to insert duplicate pending ack! This should not be possible!")
panic!("Tried to insert duplicate pending ack")
}
}
}
@@ -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,
})
}
}
-1
View File
@@ -259,7 +259,6 @@ pub(super) fn get_specified_gateway(
gateways: &[gateway::Node],
must_use_tls: bool,
) -> Result<gateway::Node, ClientCoreError> {
log::debug!("Requesting specified gateway: {}", gateway_identity);
let user_gateway = identity::PublicKey::from_base58_string(gateway_identity)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
+1 -1
View File
@@ -212,7 +212,7 @@ where
D::StorageError: Send + Sync + 'static,
T: DeserializeOwned + Serialize + Send + Sync,
{
log::debug!("Setting up gateway");
log::trace!("Setting up gateway");
match setup {
GatewaySetup::MustLoad => use_loaded_gateway_details(key_store, details_store).await,
GatewaySetup::New {
+1 -1
View File
@@ -3,7 +3,6 @@ name = "nym-gateway-client"
version = "0.1.0"
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -20,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,
@@ -792,7 +793,6 @@ pub struct InitOnly;
impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
// for initialisation we do not need credential storage. Though it's still a bit weird we have to set the generic...
pub fn new_init(config: GatewayConfig, local_identity: Arc<identity::KeyPair>) -> Self {
log::trace!("Initialising gateway client");
use futures::channel::mpsc;
// note: this packet_router is completely invalid in normal circumstances, but "works"
+2 -3
View File
@@ -3,15 +3,14 @@ name = "nym-mixnet-client"
version = "0.1.0"
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
futures = { workspace = true }
log = { workspace = true }
tokio = { workspace = true, features = ["time", "net", "rt"] }
tokio-util = { workspace = true, features = ["codec"] }
tokio = { version = "1.24.1", features = ["time", "net", "rt"] }
tokio-util = { version = "0.7.4", features = ["codec"] }
# internal
nym-sphinx = { path = "../../nymsphinx" }
@@ -4,7 +4,6 @@ version = "0.1.0"
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
rust-version = "1.56"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -34,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>
@@ -5,12 +5,9 @@ use crate::nym_api::error::NymAPIError;
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::models::{
EpochCredentialsResponse, IssuedCredentialResponse, IssuedCredentialsResponse,
};
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody, VerifyCredentialBody,
VerifyCredentialResponse,
BlindSignRequestBody, BlindedSignatureResponse, EcashParametersResponse,
OfflineVerifyCredentialBody, OnlineVerifyCredentialBody, VerifyCredentialResponse,
};
use nym_api_requests::models::{
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
@@ -19,7 +16,6 @@ use nym_api_requests::models::{
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
};
use nym_coconut_dkg_common::types::EpochId;
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use nym_name_service_common::response::NamesListResponse;
@@ -387,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,
@@ -404,56 +400,32 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn epoch_credentials(
async fn verify_online_credential(
&self,
dkg_epoch: EpochId,
) -> Result<EpochCredentialsResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::COCONUT_EPOCH_CREDENTIALS,
&dkg_epoch.to_string(),
],
NO_PARAMS,
)
.await
}
async fn issued_credential(
&self,
credential_id: i64,
) -> Result<IssuedCredentialResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::COCONUT_ISSUED_CREDENTIAL,
&credential_id.to_string(),
],
NO_PARAMS,
)
.await
}
async fn issued_credentials(
&self,
credential_ids: Vec<i64>,
) -> Result<IssuedCredentialsResponse, NymAPIError> {
request_body: &OnlineVerifyCredentialBody,
) -> Result<VerifyCredentialResponse, NymAPIError> {
self.post_json(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::COCONUT_ISSUED_CREDENTIALS,
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,
&CredentialsRequestBody {
credential_ids,
pagination: None,
},
)
.await
}
@@ -16,10 +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 COCONUT_EPOCH_CREDENTIALS: &str = "epoch-credentials";
pub const COCONUT_ISSUED_CREDENTIAL: &str = "issued-credential";
pub const COCONUT_ISSUED_CREDENTIALS: &str = "issued-credentials";
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";
@@ -32,7 +32,7 @@ pub trait CoconutBandwidthSigningClient {
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let req = CoconutBandwidthExecuteMsg::DepositFunds {
data: DepositData::new(info, verification_key, encryption_key),
data: DepositData::new(info.to_string(), verification_key, encryption_key),
};
self.execute_coconut_bandwidth_contract(
fee,
@@ -6,8 +6,8 @@ use crate::nyxd::error::NyxdError;
use crate::nyxd::CosmWasmClient;
use async_trait::async_trait;
use cw3::{
ProposalListResponse, ProposalResponse, VoteListResponse, VoteResponse, VoterDetail,
VoterListResponse, VoterResponse,
ProposalListResponse, ProposalResponse, VoteListResponse, VoteResponse, VoterListResponse,
VoterResponse,
};
use cw_utils::ThresholdResponse;
use nym_multisig_contract_common::msg::QueryMsg as MultisigQueryMsg;
@@ -114,26 +114,6 @@ pub trait PagedMultisigQueryClient: MultisigQueryClient {
Ok(proposals)
}
async fn get_all_voters(&self) -> Result<Vec<VoterDetail>, NyxdError> {
let mut voters = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self.list_voters(start_after.take(), None).await?;
let last_voter = paged_response.voters.last().map(|prop| prop.addr.clone());
voters.append(&mut paged_response.voters);
if let Some(start_after_res) = last_voter {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(voters)
}
}
#[async_trait]
@@ -1,13 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nyxd::TxResponse;
pub fn find_tx_attribute(tx: &TxResponse, event_type: &str, attribute_key: &str) -> Option<String> {
let event = tx.tx_result.events.iter().find(|e| e.kind == event_type)?;
let attribute = event
.attributes
.iter()
.find(|attr| attr.key == attribute_key)?;
Some(attribute.value.clone())
}
@@ -47,10 +47,6 @@ pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::Gas;
pub use cosmrs::{bip32, AccountId, Denom};
pub use cosmwasm_std::Coin as CosmWasmCoin;
pub use cw2;
pub use cw3;
pub use cw4;
pub use cw_controllers;
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
pub use tendermint_rpc::{
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
@@ -71,7 +67,6 @@ pub mod contract_traits;
pub mod cosmwasm_client;
pub mod error;
pub mod fee;
pub mod helpers;
#[derive(Debug, Clone)]
pub struct Config {
@@ -381,11 +376,7 @@ where
})
}
pub async fn simulate<I, M>(
&self,
messages: I,
memo: impl Into<String> + Send + 'static,
) -> Result<SimulateResponse, NyxdError>
pub async fn simulate<I, M>(&self, messages: I) -> Result<SimulateResponse, NyxdError>
where
I: IntoIterator<Item = M> + Send,
M: Msg,
@@ -400,7 +391,7 @@ where
.map_err(|_| {
NyxdError::SerializationError("custom simulate messages".to_owned())
})?,
memo,
"simulating execution of transactions",
)
.await
}
-1
View File
@@ -3,7 +3,6 @@ name = "nym-coconut-interface"
version = "0.1.0"
edition = "2021"
description = "Crutch library until there is proper SerDe support for coconut structs"
license.workspace = true
[dependencies]
bs58 = "0.4.0"
+10 -12
View File
@@ -21,14 +21,10 @@ pub use nym_coconut::{
pub struct Credential {
#[getset(get = "pub")]
n_params: u32,
#[getset(get = "pub")]
theta: Theta,
voucher_value: u64,
voucher_info: String,
#[getset(get = "pub")]
epoch_id: u64,
}
@@ -68,12 +64,14 @@ impl Credential {
pub fn verify(&self, verification_key: &VerificationKey) -> bool {
let params = Parameters::new(self.n_params).unwrap();
let hashed_value = hash_to_scalar(self.voucher_value.to_string());
let hashed_info = hash_to_scalar(&self.voucher_info);
let public_attributes = &[&hashed_value, &hashed_info];
nym_coconut::verify_credential(&params, verification_key, &self.theta, public_attributes)
let public_attributes = [
self.voucher_value.to_string().as_bytes(),
self.voucher_info.as_bytes(),
]
.iter()
.map(hash_to_scalar)
.collect::<Vec<Attribute>>();
nym_coconut::verify_credential(&params, verification_key, &self.theta, &public_attributes)
}
pub fn as_bytes(&self) -> Vec<u8> {
@@ -182,8 +180,8 @@ mod tests {
&params,
&verification_key,
&signature,
&serial_number,
&binding_number,
serial_number,
binding_number,
)
.unwrap();
let credential = Credential::new(4, theta, voucher_value, voucher_info, 42);
-1
View File
@@ -3,7 +3,6 @@ name = "nym-cli-commands"
version = "1.0.0"
authors.workspace = true
edition = "2021"
license.workspace = true
[dependencies]
anyhow = { workspace = true }
@@ -26,10 +26,6 @@ pub struct Args {
}
pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
if args.amount == 0 {
bail!("did not specify credential amount")
}
let loaded = CommonConfigsWrapper::try_load(args.client_config)?;
if let Ok(id) = loaded.try_get_id() {
@@ -40,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()
@@ -49,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")
+1 -2
View File
@@ -3,7 +3,6 @@ name = "nym-config"
version = "0.1.0"
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -18,4 +17,4 @@ url = { workspace = true }
nym-network-defaults = { path = "../network-defaults" }
[features]
default = ["dirs"]
default = ["dirs"]
@@ -2,7 +2,6 @@
name = "nym-coconut-bandwidth-contract-common"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -13,4 +12,4 @@ cw2 = { workspace = true, optional = true }
nym-multisig-contract-common = { path = "../multisig-contract" }
[features]
schema = ["cw2"]
schema = ["cw2"]
@@ -4,9 +4,6 @@
// event types
pub const DEPOSITED_FUNDS_EVENT_TYPE: &str = "deposited-funds";
// a 'wasm-' prefix is added to all cosmwasm events
pub const COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE: &str = "wasm-deposited-funds";
// attributes that are used in multiple places
pub const DEPOSIT_VALUE: &str = "deposit-value";
pub const DEPOSIT_INFO: &str = "deposit-info";
@@ -2,7 +2,6 @@
name = "nym-coconut-dkg-common"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -15,4 +14,4 @@ contracts-common = { path = "../contracts-common", package = "nym-contracts-comm
nym-multisig-contract-common = { path = "../multisig-contract" }
[features]
schema = []
schema = []
@@ -174,19 +174,17 @@ impl Display for EpochState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
EpochState::PublicKeySubmission { resharing } => {
write!(f, "PublicKeySubmission (resharing: {resharing})")
}
EpochState::DealingExchange { resharing } => {
write!(f, "DealingExchange (resharing: {resharing})")
write!(f, "PublicKeySubmission with resharing {resharing}")
}
EpochState::DealingExchange { resharing } => write!(f, "DealingExchange {resharing}"),
EpochState::VerificationKeySubmission { resharing } => {
write!(f, "VerificationKeySubmission (resharing: {resharing})")
write!(f, "VerificationKeySubmission with resharing {resharing}")
}
EpochState::VerificationKeyValidation { resharing } => {
write!(f, "VerificationKeyValidation (resharing: {resharing})")
write!(f, "VerificationKeyValidation with resharing {resharing}")
}
EpochState::VerificationKeyFinalization { resharing } => {
write!(f, "VerificationKeyFinalization (resharing: {resharing})")
write!(f, "VerificationKeyFinalization with resharing {resharing}")
}
EpochState::InProgress => write!(f, "InProgress"),
}
@@ -2,7 +2,6 @@
name = "nym-ephemera-common"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -14,4 +13,4 @@ cw-utils = { workspace = true }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common" }
[features]
schema = []
schema = []
@@ -2,7 +2,6 @@
name = "nym-group-contract-common"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -2,7 +2,6 @@
name = "nym-multisig-contract-common"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -2,7 +2,6 @@
name = "nym-name-service-common"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -17,4 +16,4 @@ serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
[features]
schema = ["cw2"]
schema = ["cw2"]
@@ -2,7 +2,6 @@
name = "nym-service-provider-directory-common"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -16,4 +15,4 @@ nym-contracts-common = { path = "../contracts-common", version = "0.5.0" }
thiserror = { workspace = true }
[features]
schema = ["cw2"]
schema = ["cw2"]
-1
View File
@@ -2,7 +2,6 @@
name = "nym-credential-storage"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -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 -1
View File
@@ -2,7 +2,6 @@
name = "nym-credential-utils"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -17,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
+3 -4
View File
@@ -2,20 +2,19 @@
name = "nym-credentials"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bls12_381 = { workspace = true, default-features = false, features = ["pairings", "alloc", "experimental"] }
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
cosmrs = { workspace = true }
thiserror = { workspace = true }
log = { workspace = true }
zeroize = { workspace = true }
# I guess temporarily until we get serde support in coconut up and running
nym-coconut-interface = { path = "../coconut-interface" }
nym-crypto = { path = "../crypto", features = ["rand", "asymmetric"] }
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 }
+98 -242
View File
@@ -8,177 +8,136 @@
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 zeroize::{Zeroize, ZeroizeOnDrop};
use super::utils::prepare_credential_for_spending;
use crate::error::Error;
#[derive(Zeroize, ZeroizeOnDrop)]
pub const PUBLIC_ATTRIBUTES: u32 = 2;
pub const PRIVATE_ATTRIBUTES: u32 = 2;
pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES;
pub struct BandwidthVoucher {
// private attributes
/// 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,
// public atttributes:
/// the plain text value (e.g., bandwidth) encoded in this voucher
// TODO: in another PR change the value from `"1000"` to `"1000unym"`
// the plain text value (e.g., bandwidth) encoded in this voucher
voucher_value_plain: String,
/// the plain text information
// the plain text information
voucher_info_plain: String,
/// the precomputed value (e.g., bandwidth) encoded in this voucher
_voucher_value_prehashed: PublicAttribute,
/// the precomputed field with public information, e.g., type of voucher, interval etc.
_voucher_info_prehashed: PublicAttribute,
/// the hash of the deposit transaction
#[zeroize(skip)]
// the hash of the deposit transaction
tx_hash: Hash,
/// base58 encoded private key ensuring the depositer requested these attributes
// base58 encoded private key ensuring the depositer requested these attributes
signing_key: identity::PrivateKey,
/// base58 encoded private key ensuring only this client receives the signature share
unused_ed25519: encryption::PrivateKey,
pedersen_commitments_openings: Vec<Attribute>,
#[zeroize(skip)]
blind_sign_request: BlindSignRequest,
// base58 encoded private key ensuring only this client receives the signature share
encryption_key: encryption::PrivateKey,
ecash_keypair: KeyPairUser,
withdrawal_request_info: RequestInfo,
withdrawal_request: WithdrawalRequest,
}
impl BandwidthVoucher {
pub const PUBLIC_ATTRIBUTES: u32 = 2;
pub const PRIVATE_ATTRIBUTES: u32 = 2;
pub const ENCODED_ATTRIBUTES: u32 = 4;
pub fn default_parameters() -> Parameters {
// safety: the unwrap is fine here as Self::ENCODED_ATTRIBUTES is non-zero
Parameters::new(Self::ENCODED_ATTRIBUTES).unwrap()
}
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_prehashed = hash_to_scalar(voucher_value);
let _voucher_info_prehashed = hash_to_scalar(voucher_info);
let (pedersen_commitments_openings, blind_sign_request) = prepare_blind_sign(
params,
&[&serial_number, &binding_number],
&[&_voucher_value_prehashed, &_voucher_info_prehashed],
)
.unwrap();
let (withdrawal_request, withdrawal_request_info) =
withdrawal_request(params, &ecash_keypair.secret_key()).unwrap();
BandwidthVoucher {
serial_number,
binding_number,
_voucher_value_prehashed,
voucher_value_plain,
_voucher_info_prehashed,
voucher_info_plain,
tx_hash,
signing_key,
unused_ed25519: encryption_key,
pedersen_commitments_openings,
blind_sign_request,
encryption_key,
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.unused_ed25519.to_bytes();
let blind_sign_request_b = self.blind_sign_request.to_bytes();
let encryption_key_b = self.encryption_key.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",
@@ -190,81 +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_prehashed = 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_prehashed = 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_prehashed,
voucher_value_plain,
_voucher_info_prehashed,
voucher_info_plain,
tx_hash,
signing_key,
unused_ed25519: encryption_key,
pedersen_commitments_openings,
blind_sign_request,
encryption_key,
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_prehashed,
&self._voucher_info_prehashed,
]
}
pub fn identity_key(&self) -> &identity::PrivateKey {
&self.signing_key
pub fn tx_hash(&self) -> &Hash {
&self.tx_hash
}
pub fn encryption_key(&self) -> &encryption::PrivateKey {
&self.unused_ed25519
&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 {
@@ -278,54 +214,22 @@ impl BandwidthVoucher {
]
}
pub fn get_private_attributes(&self) -> Vec<&PrivateAttribute> {
vec![&self.serial_number, &self.binding_number]
}
pub fn signable_plaintext(request: &BlindSignRequest, tx_hash: Hash) -> Vec<u8> {
pub fn sign(&self, request: &WithdrawalRequest) -> identity::Signature {
let mut message = request.to_bytes();
message.extend_from_slice(tx_hash.as_bytes());
message
message.extend_from_slice(self.tx_hash.to_string().as_bytes());
self.signing_key.sign(&message)
}
pub fn sign(&self) -> identity::Signature {
let message = Self::signable_plaintext(&self.blind_sign_request, self.tx_hash);
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(BandwidthVoucher::ENCODED_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,
@@ -342,6 +246,7 @@ mod test {
&encryption::KeyPair::new(&mut rng).private_key().to_bytes(),
)
.unwrap(),
generate_keypair_user(&params),
)
}
@@ -350,8 +255,6 @@ 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_plain,
deserialized_voucher.voucher_value_plain
@@ -360,69 +263,22 @@ mod test {
voucher.voucher_info_plain,
deserialized_voucher.voucher_info_plain
);
assert_eq!(
voucher._voucher_value_prehashed,
deserialized_voucher._voucher_value_prehashed
);
assert_eq!(
voucher._voucher_info_prehashed,
deserialized_voucher._voucher_info_prehashed
);
assert_eq!(voucher.tx_hash, deserialized_voucher.tx_hash);
assert_eq!(
voucher.signing_key.to_string(),
deserialized_voucher.signing_key.to_string()
);
assert_eq!(
voucher.unused_ed25519.to_string(),
deserialized_voucher.unused_ed25519.to_string()
voucher.encryption_key.to_string(),
deserialized_voucher.encryption_key.to_string()
);
assert_eq!(
voucher.pedersen_commitments_openings,
deserialized_voucher.pedersen_commitments_openings
voucher.withdrawal_request_info.to_bytes(),
deserialized_voucher.withdrawal_request_info.to_bytes()
);
assert_eq!(
voucher.blind_sign_request.to_bs58(),
deserialized_voucher.blind_sign_request.to_bs58()
voucher.withdrawal_request.to_bs58(),
deserialized_voucher.withdrawal_request.to_bs58()
);
}
#[test]
fn voucher_consistency() {
let voucher = voucher_fixture();
assert!(!BandwidthVoucher::verify_against_plain(
&[],
&voucher.get_public_attributes_plain()
));
assert!(!BandwidthVoucher::verify_against_plain(
&voucher.get_public_attributes(),
&[],
));
assert!(!BandwidthVoucher::verify_against_plain(
&voucher.get_public_attributes(),
&[
voucher.get_public_attributes_plain()[0].clone(),
String::new()
]
));
assert!(!BandwidthVoucher::verify_against_plain(
&voucher.get_public_attributes(),
&[
String::new(),
voucher.get_public_attributes_plain()[1].clone()
]
));
assert!(!BandwidthVoucher::verify_against_plain(
&[voucher.get_public_attributes()[0], &Attribute::one()],
&voucher.get_public_attributes_plain()
));
assert!(!BandwidthVoucher::verify_against_plain(
&[&Attribute::one(), voucher.get_public_attributes()[1]],
&voucher.get_public_attributes_plain()
));
assert!(BandwidthVoucher::verify_against_plain(
&voucher.get_public_attributes(),
&voucher.get_public_attributes_plain()
));
}
}
+1
View File
@@ -2,4 +2,5 @@
// SPDX-License-Identifier: Apache-2.0
pub mod bandwidth;
pub mod params;
pub mod utils;
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_crypto::aes::Aes128;
use nym_crypto::blake3;
use nym_crypto::ctr;
type Aes128Ctr = ctr::Ctr64LE<Aes128>;
/// Hashing algorithm used during hkdf for ephemeral shared key generation per blinded signature
/// response encryption.
pub type NymApiCredentialHkdfAlgorithm = blake3::Hasher;
/// Encryption algorithm used for end-to-end encryption of blinded signature response
pub type NymApiCredentialEncryptionAlgorithm = Aes128Ctr;
+95 -72
View File
@@ -2,18 +2,25 @@
// SPDX-License-Identifier: Apache-2.0
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,
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;
use nym_crypto::symmetric::stream_cipher;
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);
}
@@ -31,62 +38,78 @@ pub async fn obtain_aggregate_verification_key(
}
async fn obtain_partial_credential(
params: &Parameters,
voucher: &BandwidthVoucher,
params: &GroupParameters,
attributes: &BandwidthVoucher,
client: &nym_validator_client::client::NymApiClient,
validator_vk: &VerificationKey,
) -> Result<Signature, Error> {
let public_attributes_plain = voucher.get_public_attributes_plain();
let blind_sign_request = voucher.blind_sign_request();
let request_signature = voucher.sign();
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 withdrawal_request = attributes.withdrawal_request();
let blind_sign_request_body = BlindSignRequestBody::new(
blind_sign_request.clone(),
voucher.tx_hash(),
request_signature,
public_attributes_plain,
withdrawal_request,
attributes.tx_hash().to_string(),
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;
let remote_key = PublicKey::from_bytes(&response.remote_key)?;
let blinded_signature = response.blinded_signature;
let encryption_key = recompute_shared_key::<
NymApiCredentialEncryptionAlgorithm,
NymApiCredentialHkdfAlgorithm,
>(&remote_key, attributes.encryption_key());
let zero_iv = stream_cipher::zero_iv::<NymApiCredentialEncryptionAlgorithm>();
let blinded_signature_bytes = stream_cipher::decrypt::<NymApiCredentialEncryptionAlgorithm>(
&encryption_key,
&zero_iv,
&encrypted_signature,
);
let public_attributes = voucher.get_public_attributes();
let private_attributes = voucher.get_private_attributes();
let blinded_signature = BlindedSignature::from_bytes(&blinded_signature_bytes)?;
let unblinded_signature = blinded_signature.unblind_and_verify(
let unblinded_signature = issue_verify(
params,
validator_vk,
&private_attributes,
&public_attributes,
&blind_sign_request.get_commitment_hash(),
voucher.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,
voucher: &BandwidthVoucher,
coconut_api_clients: &[CoconutApiClient],
params: &GroupParameters,
attributes: &BandwidthVoucher,
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 mut shares = Vec::with_capacity(coconut_api_clients.len());
let validators_partial_vks: Vec<_> = coconut_api_clients
//let public_attributes = attributes.get_public_attributes();
//let private_attributes = attributes.get_private_attributes();
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()
@@ -94,16 +117,13 @@ pub async fn obtain_aggregate_signature(
match obtain_partial_credential(
params,
voucher,
attributes,
&coconut_api_client.api_client,
&coconut_api_client.verification_key,
)
.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}",
@@ -112,46 +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 public_attributes = voucher.get_public_attributes();
let private_attributes = voucher.get_private_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);
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(
BandwidthVoucher::ENCODED_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),
@@ -201,17 +201,6 @@ impl<'a> From<&'a PrivateKey> for PublicKey {
}
impl PrivateKey {
#[cfg(feature = "rand")]
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let x25519_secret = x25519_dalek::StaticSecret::new(rng);
PrivateKey(x25519_secret)
}
pub fn public_key(&self) -> PublicKey {
self.into()
}
pub fn to_bytes(&self) -> [u8; PRIVATE_KEY_SIZE] {
self.0.to_bytes()
}
+3 -23
View File
@@ -5,14 +5,13 @@ pub use ed25519_dalek::ed25519::signature::Signature as SignatureTrait;
pub use ed25519_dalek::SignatureError;
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
#[cfg(feature = "sphinx")]
use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
use thiserror::Error;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[cfg(feature = "sphinx")]
use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
#[cfg(feature = "rand")]
use rand::{CryptoRng, RngCore};
#[cfg(feature = "serde")]
@@ -225,17 +224,6 @@ impl<'a> From<&'a PrivateKey> for PublicKey {
}
impl PrivateKey {
#[cfg(feature = "rand")]
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let ed25519_secret = ed25519_dalek::SecretKey::generate(rng);
PrivateKey(ed25519_secret)
}
pub fn public_key(&self) -> PublicKey {
self.into()
}
pub fn to_bytes(&self) -> [u8; SECRET_KEY_LENGTH] {
self.0.to_bytes()
}
@@ -307,7 +295,7 @@ impl PemStorableKey for PrivateKey {
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[derive(Copy, Clone, Debug)]
pub struct Signature(ed25519_dalek::Signature);
impl Signature {
@@ -331,14 +319,6 @@ impl Signature {
}
}
impl FromStr for Signature {
type Err = Ed25519RecoveryError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Signature::from_base58_string(s)
}
}
#[cfg(feature = "serde")]
impl Serialize for Signature {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+3 -4
View File
@@ -3,7 +3,6 @@ name = "nym-dkg"
version = "0.1.0"
edition = "2021"
resolver = "2"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -12,7 +11,7 @@ bitvec = "1.0.0"
# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork
# as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm
bls12_381 = { workspace = true, default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch ="gt-serialisation", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common", optional = true }
bs58 = "0.4"
@@ -30,11 +29,11 @@ zeroize = { workspace = true, features = ["zeroize_derive"] }
nym-pemstore = { path = "../pemstore" }
[dependencies.group]
workspace = true
version = "0.11"
default-features = false
[dependencies.ff]
workspace = true
version = "0.11"
default-features = false
[dev-dependencies]
-1
View File
@@ -2,7 +2,6 @@
name = "nym-execute"
version = "0.1.0"
edition = "2021"
license.workspace = true
[lib]
proc-macro = true
+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)];
+1 -2
View File
@@ -2,7 +2,6 @@
name = "ledger"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -11,4 +10,4 @@ bip32 = "0.5.1"
k256 = { workspace = true }
ledger-transport = "0.10.0"
ledger-transport-hid = "0.10.0"
thiserror = { workspace = true }
thiserror = { workspace = true }
+1 -2
View File
@@ -3,7 +3,6 @@ name = "nym-mixnode-common"
version = "0.1.0"
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -21,7 +20,7 @@ tokio = { version = "1.24.1", features = [
"net",
"io-util",
] }
tokio-util = { workspace = true, features = ["codec"] }
tokio-util = { version = "0.7.4", features = ["codec"] }
url = { workspace = true }
thiserror = { workspace = true }
+3 -3
View File
@@ -108,13 +108,13 @@ impl ConnectionHandler {
let reply_packet = match maybe_echo_packet {
Some(Ok(echo_packet)) => self.handle_echo_packet(echo_packet),
Some(Err(err)) => {
debug!(
error!(
"The socket connection got corrupted with error: {err}. Closing the socket",
);
return;
}
None => {
debug!("The socket connection got terminated by the remote!");
error!("The socket connection got terminated by the remote!");
return;
}
};
@@ -125,7 +125,7 @@ impl ConnectionHandler {
.write_all(reply_packet.to_bytes().as_ref())
.await
{
debug!(
error!(
"Failed to write reply packet back to the sender - {}. Closing the socket on our end",
err
);
+2
View File
@@ -434,6 +434,8 @@ pub const BANDWIDTH_VALUE: u64 = UTOKENS_TO_BURN * BYTES_PER_UTOKEN;
pub const VOUCHER_INFO: &str = "BandwidthVoucher";
pub const ECASH_INFO: &str = "TicketBook";
pub const ETH_MIN_BLOCK_DEPTH: usize = 7;
/// Defaults Cosmos Hub/ATOM path
-1
View File
@@ -2,7 +2,6 @@
name = "nym-node-tester-utils"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -3,7 +3,6 @@ name = "nym-nonexhaustive-delayqueue"
version = "0.1.0"
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -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(())
}

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