ability to obtain bandwidth voucher from wasm via vpn-api
This commit is contained in:
Generated
+3234
-1120
File diff suppressed because it is too large
Load Diff
+8
-6
@@ -122,6 +122,7 @@ members = [
|
||||
# "wasm/full-nym-wasm",
|
||||
"wasm/mix-fetch",
|
||||
"wasm/node-tester",
|
||||
"wasm/zknym-lib",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -174,6 +175,7 @@ generic-array = "0.14.7"
|
||||
getrandom = "0.2.10"
|
||||
headers = "0.4.0"
|
||||
humantime-serde = "1.1.1"
|
||||
http = "1"
|
||||
hyper = "1.3.1"
|
||||
k256 = "0.13"
|
||||
lazy_static = "1.4.0"
|
||||
@@ -237,14 +239,14 @@ tendermint-rpc = "0.34" # same version as used by cosmrs
|
||||
prost = "0.12"
|
||||
|
||||
# wasm-related dependencies
|
||||
gloo-utils = "0.1.7"
|
||||
js-sys = "0.3.63"
|
||||
serde-wasm-bindgen = "0.5.0"
|
||||
gloo-utils = "0.2.0"
|
||||
js-sys = "0.3.69"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
tsify = "0.4.5"
|
||||
wasm-bindgen = "0.2.86"
|
||||
wasm-bindgen-futures = "0.4.37"
|
||||
wasm-bindgen = "0.2.92"
|
||||
wasm-bindgen-futures = "0.4.39"
|
||||
wasmtimer = "0.2.0"
|
||||
web-sys = "0.3.63"
|
||||
web-sys = "0.3.69"
|
||||
|
||||
# Profile settings for individual crates
|
||||
|
||||
|
||||
@@ -155,11 +155,6 @@ impl IssuedBandwidthCredential {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn randomise_signature(&mut self) {
|
||||
let signature_prime = self.signature.randomise(bandwidth_credential_params());
|
||||
self.signature = signature_prime.0
|
||||
}
|
||||
|
||||
pub fn default_parameters() -> Parameters {
|
||||
IssuanceBandwidthCredential::default_parameters()
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ impl<'a> From<&'a BandwidthVoucherIssuanceData> for BandwidthVoucherIssuedData {
|
||||
}
|
||||
|
||||
impl BandwidthVoucherIssuedData {
|
||||
pub fn new(value: Coin) -> Self {
|
||||
BandwidthVoucherIssuedData { value }
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &Coin {
|
||||
&self.value
|
||||
}
|
||||
|
||||
@@ -200,6 +200,14 @@ impl<'a> From<&'a PrivateKey> for PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PrivateKey {
|
||||
type Err = KeyRecoveryError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
PrivateKey::from_base58_string(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrivateKey {
|
||||
#[cfg(feature = "rand")]
|
||||
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
|
||||
|
||||
@@ -227,6 +227,14 @@ impl<'a> From<&'a PrivateKey> for PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PrivateKey {
|
||||
type Err = Ed25519RecoveryError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
PrivateKey::from_base58_string(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrivateKey {
|
||||
#[cfg(feature = "rand")]
|
||||
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
|
||||
|
||||
@@ -13,6 +13,7 @@ license.workspace = true
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
http.workspace = true
|
||||
url = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use reqwest::{IntoUrl, Response, StatusCode};
|
||||
use reqwest::header::HeaderValue;
|
||||
use reqwest::{RequestBuilder, Response, StatusCode};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
@@ -11,6 +12,8 @@ use thiserror::Error;
|
||||
use tracing::warn;
|
||||
use url::Url;
|
||||
|
||||
pub use reqwest::IntoUrl;
|
||||
|
||||
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
pub type PathSegments<'a> = &'a [&'a str];
|
||||
@@ -52,6 +55,88 @@ pub enum HttpClientError<E: Display = String> {
|
||||
RequestTimeout,
|
||||
}
|
||||
|
||||
pub struct ClientBuilder {
|
||||
url: Url,
|
||||
timeout: Option<Duration>,
|
||||
custom_user_agent: bool,
|
||||
reqwest_client_builder: reqwest::ClientBuilder,
|
||||
}
|
||||
|
||||
impl ClientBuilder {
|
||||
pub fn new<U, E>(url: U) -> Result<Self, HttpClientError<E>>
|
||||
where
|
||||
U: IntoUrl,
|
||||
E: Display,
|
||||
{
|
||||
// a naive check: if the provided URL does not start with http(s), add that scheme
|
||||
let str_url = url.as_str();
|
||||
|
||||
if !str_url.starts_with("http") {
|
||||
let alt = format!("http://{str_url}");
|
||||
warn!("the provided url ('{str_url}') does not contain scheme information. Changing it to '{alt}' ...");
|
||||
// TODO: or should we maybe default to https?
|
||||
Self::new(alt)
|
||||
} else {
|
||||
Ok(ClientBuilder {
|
||||
url: url.into_url()?,
|
||||
timeout: None,
|
||||
custom_user_agent: false,
|
||||
reqwest_client_builder: reqwest::ClientBuilder::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_timeout(mut self, timeout: Duration) -> Self {
|
||||
self.timeout = Some(timeout);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_reqwest_builder(mut self, reqwest_builder: reqwest::ClientBuilder) -> Self {
|
||||
self.reqwest_client_builder = reqwest_builder;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_user_agent<V>(mut self, value: V) -> Self
|
||||
where
|
||||
V: TryInto<HeaderValue>,
|
||||
V::Error: Into<http::Error>,
|
||||
{
|
||||
self.custom_user_agent = true;
|
||||
self.reqwest_client_builder = self.reqwest_client_builder.user_agent(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build<E>(self) -> Result<Client, HttpClientError<E>>
|
||||
where
|
||||
E: Display,
|
||||
{
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let reqwest_client = self.reqwest_client_builder.build()?;
|
||||
|
||||
// TODO: we should probably be propagating the error rather than panicking,
|
||||
// but that'd break bunch of things due to type changes
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let reqwest_client = {
|
||||
let mut builder = self
|
||||
.reqwest_client_builder
|
||||
.timeout(self.timeout.unwrap_or(DEFAULT_TIMEOUT));
|
||||
if !self.custom_user_agent {
|
||||
builder =
|
||||
builder.user_agent(format!("nym-http-api-client/{}", env!("CARGO_PKG_VERSION")))
|
||||
}
|
||||
builder.build()?
|
||||
};
|
||||
|
||||
Ok(Client {
|
||||
base_url: self.url,
|
||||
reqwest_client,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
request_timeout: self.timeout.unwrap_or(DEFAULT_TIMEOUT),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple extendable client wrapper for http request with extra url sanitization.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Client {
|
||||
@@ -65,25 +150,9 @@ pub struct Client {
|
||||
impl Client {
|
||||
// no timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
|
||||
pub fn new(base_url: Url, timeout: Option<Duration>) -> Self {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let reqwest_client = reqwest::Client::new();
|
||||
|
||||
// TODO: we should probably be propagating the error rather than panicking,
|
||||
// but that'd break bunch of things due to type changes
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let reqwest_client = reqwest::ClientBuilder::new()
|
||||
.timeout(timeout.unwrap_or(DEFAULT_TIMEOUT))
|
||||
.user_agent(format!("nym-http-api-client/{}", env!("CARGO_PKG_VERSION")))
|
||||
.build()
|
||||
.expect("Client::new()");
|
||||
|
||||
Client {
|
||||
base_url,
|
||||
reqwest_client,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
request_timeout: timeout.unwrap_or(DEFAULT_TIMEOUT),
|
||||
}
|
||||
Self::new_url::<_, String>(base_url, timeout).expect(
|
||||
"we provided valid url and we were unwrapping previous construction errors anyway",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_url<U, E>(url: U, timeout: Option<Duration>) -> Result<Self, HttpClientError<E>>
|
||||
@@ -91,19 +160,21 @@ impl Client {
|
||||
U: IntoUrl,
|
||||
E: Display,
|
||||
{
|
||||
// a naive check: if the provided URL does not start with http(s), add that scheme
|
||||
let str_url = url.as_str();
|
||||
|
||||
if !str_url.starts_with("http") {
|
||||
let alt = format!("http://{str_url}");
|
||||
warn!("the provided url ('{str_url}') does not contain scheme information. Changing it to '{alt}' ...");
|
||||
// TODO: or should we maybe default to https?
|
||||
Self::new_url(alt, timeout)
|
||||
} else {
|
||||
Ok(Self::new(url.into_url()?, timeout))
|
||||
let builder = Self::builder(url)?;
|
||||
match timeout {
|
||||
Some(timeout) => builder.with_timeout(timeout).build(),
|
||||
None => builder.build(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builder<U, E>(url: U) -> Result<ClientBuilder, HttpClientError<E>>
|
||||
where
|
||||
U: IntoUrl,
|
||||
E: Display,
|
||||
{
|
||||
ClientBuilder::new(url)
|
||||
}
|
||||
|
||||
pub fn change_base_url(&mut self, new_url: Url) {
|
||||
self.base_url = new_url
|
||||
}
|
||||
@@ -112,6 +183,19 @@ impl Client {
|
||||
&self.base_url
|
||||
}
|
||||
|
||||
pub fn create_get_request<K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> RequestBuilder
|
||||
where
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
self.reqwest_client.get(url)
|
||||
}
|
||||
|
||||
async fn send_get_request<K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
@@ -142,6 +226,21 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_post_request<B, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> RequestBuilder
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
self.reqwest_client.post(url).json(json_body)
|
||||
}
|
||||
|
||||
async fn send_post_request<B, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
@@ -407,7 +506,7 @@ pub fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
|
||||
url
|
||||
}
|
||||
|
||||
async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
|
||||
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
E: DeserializeOwned + Display,
|
||||
|
||||
@@ -32,17 +32,4 @@ pub use nym_validator_client::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRp
|
||||
pub use nym_validator_client::client::IdentityKey;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
pub use wasm_utils::set_panic_hook;
|
||||
|
||||
@@ -14,7 +14,7 @@ js-sys = { workspace = true }
|
||||
wasm-bindgen = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde-wasm-bindgen = { workspace = true }
|
||||
indexed_db_futures = { version = " 0.3.0"}
|
||||
indexed_db_futures = { version = "0.4.1" }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
nym-store-cipher = { path = "../../store-cipher", features = ["json"] }
|
||||
|
||||
@@ -21,7 +21,7 @@ macro_rules! wasm_error {
|
||||
|
||||
impl From<$struct> for js_sys::Promise {
|
||||
fn from(value: $struct) -> Self {
|
||||
Promise::reject(&value.into())
|
||||
js_sys::Promise::reject(&value.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -41,6 +41,18 @@ macro_rules! console_error {
|
||||
($($t:tt)*) => ($crate::error(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
|
||||
+2
-1
@@ -17,7 +17,8 @@ fn pretty_build_info_static() -> &'static str {
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, about, long_version = pretty_build_info_static())]
|
||||
#[clap(author = "Nymtech", version, about, long_version = pretty_build_in#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, about, long_version = pretty_build_info_static())]fo_static())]
|
||||
struct Cli {
|
||||
/// Path pointing to an env file that configures the mixnode.
|
||||
#[clap(short, long)]
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::router::api;
|
||||
use crate::router::types::{ErrorResponse, RequestError};
|
||||
use axum::Router;
|
||||
use nym_node_requests::api as api_requests;
|
||||
use nym_node_requests::routes::api::v1;
|
||||
use nym_node_requests::routes::api::{v1, v1_absolute};
|
||||
use utoipa::openapi::security::{Http, HttpAuthScheme};
|
||||
use utoipa::{openapi::security::SecurityScheme, Modify, OpenApi};
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
@@ -97,7 +97,8 @@ impl Modify for SecurityAddon {
|
||||
|
||||
pub(crate) fn route<S: Send + Sync + 'static + Clone>() -> Router<S> {
|
||||
// provide absolute path to the openapi.json
|
||||
let config = utoipa_swagger_ui::Config::from("/api/v1/api-docs/openapi.json");
|
||||
let config =
|
||||
utoipa_swagger_ui::Config::from(format!("{}/api-docs/openapi.json", v1_absolute()));
|
||||
SwaggerUi::new(v1::SWAGGER)
|
||||
.url("/api-docs/openapi.json", ApiDoc::openapi())
|
||||
.config(config)
|
||||
|
||||
@@ -25,7 +25,7 @@ use wasm_bindgen::prelude::*;
|
||||
pub fn main() {
|
||||
wasm_utils::console_log!("[rust main]: rust module loaded");
|
||||
wasm_utils::console_log!(
|
||||
"wasm client version used: {:#?}",
|
||||
nym_bin_common::bin_info!()
|
||||
"wasm client version used: {}",
|
||||
nym_bin_common::bin_info_owned!()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# Default target
|
||||
all: build build-node
|
||||
|
||||
# --- non-nodejs ---
|
||||
|
||||
build: build-go-opt build-rust build-package-json
|
||||
|
||||
check-fmt: check-fmt-go check-fmt-rust
|
||||
|
||||
build-debug-dev:
|
||||
wasm-pack build --scope nymproject --target no-modules
|
||||
$(MAKE) -C go-mix-conn build-debug-dev
|
||||
|
||||
build-go:
|
||||
$(MAKE) -C go-mix-conn build-go
|
||||
|
||||
build-go-opt:
|
||||
$(MAKE) -C go-mix-conn build-go-opt
|
||||
|
||||
build-rust:
|
||||
wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/mix-fetch
|
||||
wasm-opt -Oz -o ../../dist/wasm/mix-fetch/mix_fetch_wasm_bg.wasm ../../dist/wasm/mix-fetch/mix_fetch_wasm_bg.wasm
|
||||
|
||||
build-package-json:
|
||||
node build.mjs
|
||||
|
||||
check-fmt-go:
|
||||
$(MAKE) -C go-mix-conn check-fmt
|
||||
|
||||
check-fmt-rust:
|
||||
cargo fmt --check
|
||||
cargo clippy --target wasm32-unknown-unknown -- -Dwarnings
|
||||
|
||||
# --- nodejs ---
|
||||
|
||||
build-rust-node:
|
||||
wasm-pack build --scope nymproject --target nodejs --out-dir ../../dist/node/wasm/mix-fetch
|
||||
wasm-opt -Oz -o ../../dist/node/wasm/mix-fetch/mix_fetch_wasm_bg.wasm ../../dist/node/wasm/mix-fetch/mix_fetch_wasm_bg.wasm
|
||||
|
||||
build-package-json-node:
|
||||
node build-node.mjs
|
||||
|
||||
copy-go-conn:
|
||||
mkdir -p ../../dist/node/wasm/mix-fetch
|
||||
cp ../../dist/wasm/mix-fetch/go_conn.wasm ../../dist/node/wasm/mix-fetch/go_conn.wasm
|
||||
cp ../../dist/wasm/mix-fetch/wasm_exec.js ../../dist/node/wasm/mix-fetch/wasm_exec.js
|
||||
|
||||
build-node: build-go-opt copy-go-conn build-rust-node build-package-json-node
|
||||
@@ -31,5 +31,8 @@ use wasm_bindgen::prelude::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn main() {
|
||||
wasm_utils::console_log!("[rust main]: rust module loaded");
|
||||
wasm_utils::console_log!("mix fetch version used: {:#?}", nym_bin_common::bin_info!());
|
||||
wasm_utils::console_log!(
|
||||
"mix fetch version used: {}",
|
||||
nym_bin_common::bin_info_owned!()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
target_arch = "wasm32"
|
||||
@@ -0,0 +1,46 @@
|
||||
[package]
|
||||
name = "zknym-lib"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
async-trait.workspace = true
|
||||
bs58.workspace = true
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
js-sys.workspace = true
|
||||
wasm-bindgen.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror.workspace = true
|
||||
tsify = { workspace = true, features = ["js"] }
|
||||
uuid = { version = "*", features = ["serde"] }
|
||||
reqwest = { workspace = true }
|
||||
wasmtimer = { workspace = true }
|
||||
zeroize.workspace = true
|
||||
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
|
||||
|
||||
nym-bin-common = { path = "../../common/bin-common" }
|
||||
nym-coconut = { path = "../../common/nymcoconut", features = ["key-zeroize"] }
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
|
||||
nym-http-api-client = { path = "../../common/http-api-client" }
|
||||
wasm-utils = { path = "../../common/wasm/utils" }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspac = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
@@ -0,0 +1,18 @@
|
||||
all: build build-node
|
||||
|
||||
|
||||
build:
|
||||
wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/zknym-lib
|
||||
wasm-opt -Oz -o ../../dist/wasm/zknym-lib/zknym_lib_bg.wasm ../../dist/wasm/zknym-lib/zknym_lib_bg.wasm
|
||||
|
||||
build-debug-dev:
|
||||
wasm-pack build --scope nymproject --target no-modules
|
||||
|
||||
build-rust-node:
|
||||
wasm-pack build --scope nymproject --target nodejs --out-dir ../../dist/node/wasm/zknym-lib
|
||||
wasm-opt -Oz -o ../../dist/node/wasm/zknym-lib/zknym_lib_bg.wasm ../../dist/node/wasm/zknym-lib/zknym_lib_bg.wasm
|
||||
|
||||
#build-package-json-node:
|
||||
# node build-node.mjs
|
||||
|
||||
build-node: # build-rust-node build-package-json-node
|
||||
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// A dependency graph that contains any wasm must all be imported
|
||||
// asynchronously. This `bootstrap.js` file does the single async import, so
|
||||
// that no one else needs to worry about it again.
|
||||
import('./index.js')
|
||||
.catch(e => console.error('Error importing `index.js`:', e));
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>zknym-lib internal dev</title>
|
||||
<script src="bootstrap.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1> Internal dev of zknym-lib</h1>
|
||||
|
||||
|
||||
<hr>
|
||||
<p>
|
||||
<span id="output"></span>
|
||||
</p>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2024 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
class WebWorkerClient {
|
||||
worker = null;
|
||||
|
||||
constructor() {
|
||||
this.worker = new Worker('./worker.js');
|
||||
|
||||
this.worker.onmessage = (ev) => {
|
||||
console.log("message:", ev)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let client = null;
|
||||
|
||||
async function main() {
|
||||
client = new WebWorkerClient();
|
||||
|
||||
}
|
||||
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "create-wasm-app",
|
||||
"version": "0.1.0",
|
||||
"description": "create an app to consume rust-generated wasm packages",
|
||||
"main": "index.js",
|
||||
"bin": {
|
||||
"create-wasm-app": ".bin/create-wasm-app.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --config webpack.config.js",
|
||||
"build:wasm": "cd ../ && make wasm-build",
|
||||
"start": "webpack-dev-server --port 8001"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rustwasm/create-wasm-app.git"
|
||||
},
|
||||
"keywords": [
|
||||
"webassembly",
|
||||
"wasm",
|
||||
"rust",
|
||||
"webpack"
|
||||
],
|
||||
"author": "Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nymtech/nym/issues"
|
||||
},
|
||||
"homepage": "https://nymtech.net/docs",
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"hello-wasm-pack": "^0.1.0",
|
||||
"webpack": "^5.70.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nymproject/nym-zknym-lib": "file:../pkg"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
performance: {
|
||||
hints: false,
|
||||
maxEntrypointSize: 512000,
|
||||
maxAssetSize: 512000
|
||||
},
|
||||
entry: {
|
||||
bootstrap: './bootstrap.js',
|
||||
worker: './worker.js',
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: '[name].js',
|
||||
},
|
||||
mode: 'development',
|
||||
// mode: 'production',
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
'index.html',
|
||||
{
|
||||
from: '../pkg/*.(js|wasm)',
|
||||
to: '[name][ext]',
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
],
|
||||
experiments: { syncWebAssembly: true },
|
||||
};
|
||||
@@ -0,0 +1,193 @@
|
||||
// Copyright 2020-2023 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
const RUST_WASM_URL = "zknym_lib_bg.wasm"
|
||||
|
||||
importScripts('zknym_lib.js');
|
||||
|
||||
console.log('Initializing worker');
|
||||
|
||||
// wasm_bindgen creates a global variable (with the exports attached) that is in scope after `importScripts`
|
||||
const {
|
||||
set_panic_hook,
|
||||
prepareBlindSign,
|
||||
blindSign,
|
||||
setup,
|
||||
signSimple,
|
||||
verifySimple,
|
||||
keygen,
|
||||
simpleRandomiseCredential,
|
||||
ttpKeygen,
|
||||
aggregateVerificationKeyShares,
|
||||
aggregateSignatureShares,
|
||||
proveBandwidthCredential,
|
||||
verifyCredential,
|
||||
NymIssuanceBandwidthVoucher
|
||||
} = wasm_bindgen;
|
||||
|
||||
|
||||
// I've made it before starting on VPN API so didn't know exact requirements
|
||||
function simpleGenericUsage() {
|
||||
// SIMPLE SETUP: no threshold, no private attributes
|
||||
console.log(">>> simple example...")
|
||||
setup({numAttributes: 4, setGlobal: true})
|
||||
let keypair = keygen()
|
||||
|
||||
let vk = keypair.verificationKey();
|
||||
|
||||
let goodAttributes = ["foomp", "foo", "bar"]
|
||||
let badAttributes = ["I", "didn't", "sign", "that"]
|
||||
|
||||
let sig_simple = signSimple(goodAttributes, keypair)
|
||||
console.log("signature:", sig_simple.stringify());
|
||||
|
||||
let verified = verifySimple(vk, goodAttributes, sig_simple)
|
||||
let verified2 = verifySimple(vk, badAttributes, sig_simple)
|
||||
|
||||
let randomised = simpleRandomiseCredential(sig_simple);
|
||||
let verified3 = verifySimple(vk, goodAttributes, randomised)
|
||||
|
||||
console.log(`verified1: ${verified} (good attributes), verified2: ${verified2} (bad attributes), verified3: ${verified3} (good attributes + randomised)`)
|
||||
|
||||
|
||||
// 'NORMAL' SETUP: threshold with private attributes:
|
||||
console.log(">>> full example...")
|
||||
let t3_5_keys = ttpKeygen({threshold: 3, authorities: 5})
|
||||
|
||||
// currently they HAVE TO correspond to our bandwidth credential attributes,
|
||||
// i.e. serial number and binding number, because it seems we removed generic verification methods
|
||||
// ¯\_(ツ)_/¯
|
||||
let serialNumber = "credential-serial-number"
|
||||
let bindingNumber = "credential-binding-number"
|
||||
let privateAttributes = [serialNumber, bindingNumber]
|
||||
let publicAttributes = ["foo", "bar"]
|
||||
|
||||
// sign the attributes
|
||||
console.log("creating partial credentials...");
|
||||
let blindSignRequestData = prepareBlindSign(privateAttributes, publicAttributes)
|
||||
let blindSignRequest = blindSignRequestData.blindSignRequest()
|
||||
let blindPartial1 = blindSign(t3_5_keys[0], blindSignRequest, publicAttributes)
|
||||
let blindPartial2 = blindSign(t3_5_keys[1], blindSignRequest, publicAttributes)
|
||||
let blindPartial3 = blindSign(t3_5_keys[2], blindSignRequest, publicAttributes)
|
||||
let blindPartial4 = blindSign(t3_5_keys[3], blindSignRequest, publicAttributes)
|
||||
let blindPartial5 = blindSign(t3_5_keys[4], blindSignRequest, publicAttributes)
|
||||
|
||||
console.log("unblinding signatures...");
|
||||
let pedersenOpenings = blindSignRequestData.pedersenCommitmentsOpenings()
|
||||
let partialSig1 = blindPartial1.unblind(t3_5_keys[0].verificationKey(), pedersenOpenings)
|
||||
let partialSig2 = blindPartial2.unblind(t3_5_keys[1].verificationKey(), pedersenOpenings)
|
||||
let partialSig3 = blindPartial3.unblind(t3_5_keys[2].verificationKey(), pedersenOpenings)
|
||||
let partialSig4 = blindPartial4.unblind(t3_5_keys[3].verificationKey(), pedersenOpenings)
|
||||
let partialSig5 = blindPartial5.unblind(t3_5_keys[4].verificationKey(), pedersenOpenings)
|
||||
|
||||
// aggregate signature:
|
||||
console.log("aggregating signatures...")
|
||||
let sigShare1 = partialSig1.intoShare(t3_5_keys[0].index())
|
||||
let sigShare2 = partialSig2.intoShare(t3_5_keys[1].index())
|
||||
let sigShare3 = partialSig3.intoShare(t3_5_keys[2].index())
|
||||
let sigShare4 = partialSig4.intoShare(t3_5_keys[3].index())
|
||||
let sigShare5 = partialSig5.intoShare(t3_5_keys[4].index())
|
||||
let masterCred1 = aggregateSignatureShares([sigShare1.cloneDataPointer(), sigShare3, sigShare4])
|
||||
let masterCred2 = aggregateSignatureShares([sigShare1, sigShare2, sigShare5])
|
||||
|
||||
// key shares:
|
||||
console.log("aggregating verification keys...");
|
||||
let vk1 = t3_5_keys[0].verificationKeyShare()
|
||||
let vk2 = t3_5_keys[1].verificationKeyShare()
|
||||
let vk3 = t3_5_keys[2].verificationKeyShare()
|
||||
let vk4 = t3_5_keys[3].verificationKeyShare()
|
||||
let vk5 = t3_5_keys[4].verificationKeyShare()
|
||||
|
||||
// master verification key:
|
||||
let masterVk1 = aggregateVerificationKeyShares([vk1, vk2.cloneDataPointer(), vk3])
|
||||
let masterVk2 = aggregateVerificationKeyShares([vk2, vk4, vk5])
|
||||
|
||||
// attempt to 'spend'/'prove' the credential (note that the master keys and credentials should be cryptographically identical):
|
||||
console.log("attempting to spend the credential...");
|
||||
let verifyReq1 = proveBandwidthCredential(masterVk1, masterCred2, serialNumber, bindingNumber)
|
||||
let verifyReq2 = proveBandwidthCredential(masterVk2, masterCred1, serialNumber, bindingNumber)
|
||||
|
||||
console.log("verifying the credential...");
|
||||
let verifiedMaster1 = verifyCredential(masterVk1, verifyReq1, publicAttributes)
|
||||
let verifiedMaster2 = verifyCredential(masterVk1, verifyReq2, publicAttributes)
|
||||
|
||||
console.log(`verified1: ${verifiedMaster1}, verified2: ${verifiedMaster2}`)
|
||||
}
|
||||
|
||||
async function frontendSimulation() {
|
||||
console.log("getting opts");
|
||||
const res = await fetch("http://localhost:8080/api/v1/bandwidth-voucher/prehashed-public-attributes", {
|
||||
headers: new Headers({'Authorization': 'Bearer foomp'})
|
||||
});
|
||||
const opts = await res.json()
|
||||
|
||||
const issuanceVoucher = new NymIssuanceBandwidthVoucher(opts)
|
||||
const blindSignRequest = issuanceVoucher.getBlindSignRequest()
|
||||
|
||||
console.log("getting partial vks");
|
||||
const partialVksRes = await fetch("http://localhost:8080/api/v1/bandwidth-voucher/partial-verification-keys", {
|
||||
headers: new Headers({'Authorization': 'Bearer foomp'})
|
||||
});
|
||||
const partialVks = await partialVksRes.json()
|
||||
|
||||
console.log("getting master vk")
|
||||
const masterVkRes = await fetch("http://localhost:8080/api/v1/bandwidth-voucher/master-verification-key", {
|
||||
headers: new Headers({'Authorization': 'Bearer foomp'})
|
||||
});
|
||||
const masterVk = await masterVkRes.json()
|
||||
|
||||
console.log("getting blinded shares")
|
||||
const sharesRes = await fetch("http://localhost:8080/api/v1/bandwidth-voucher/obtain", {
|
||||
method: "POST",
|
||||
headers: new Headers(
|
||||
{
|
||||
'Authorization': 'Bearer foomp',
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
),
|
||||
body: JSON.stringify({
|
||||
blindSignRequest
|
||||
})
|
||||
});
|
||||
|
||||
const credentialShares = await sharesRes.json()
|
||||
|
||||
console.log("unblinding shares");
|
||||
const bandwidthVoucher = issuanceVoucher.unblindShares(credentialShares, partialVks)
|
||||
console.log("is valid: ", bandwidthVoucher.ensureIsValid(masterVk.bs58EncodedKey))
|
||||
|
||||
const serialised = bandwidthVoucher.serialise();
|
||||
console.log("serialised:\n", serialised)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(">>>>>>>>>>>>>>>>>>>>> JS WORKER MAIN START");
|
||||
|
||||
// load rust WASM package
|
||||
await wasm_bindgen(RUST_WASM_URL);
|
||||
console.log('Loaded RUST WASM');
|
||||
|
||||
// sets up better stack traces in case of in-rust panics
|
||||
set_panic_hook();
|
||||
|
||||
|
||||
await frontendSimulation()
|
||||
|
||||
|
||||
console.log(">>>>>>>>>>>>>>>>>>>>> JS WORKER MAIN END")
|
||||
}
|
||||
|
||||
|
||||
// Let's get started!
|
||||
main();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,228 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ZkNymError;
|
||||
use crate::generic_scheme::get_params;
|
||||
use crate::types::{CredentialWrapper, ParametersWrapper, UnblindableShare};
|
||||
use crate::vpn_api_client::types::{
|
||||
AttributesResponse, BandwidthVoucherResponse, PartialVerificationKeysResponse,
|
||||
};
|
||||
use nym_coconut::{
|
||||
Base58, BlindSignRequest, BlindedSignature, PrivateAttribute, PublicAttribute, Scalar,
|
||||
SignatureShare, VerificationKey,
|
||||
};
|
||||
use nym_credentials::coconut::bandwidth::issuance::Coin;
|
||||
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
|
||||
use nym_credentials::coconut::bandwidth::voucher::BandwidthVoucherIssuedData;
|
||||
use nym_credentials::IssuedBandwidthCredential;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use tsify::Tsify;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_utils::console_error;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
|
||||
// tiny 'hacks' to just allow passing responses from vpn-api queries
|
||||
pub type NymIssuanceBandwidthVoucherOpts = AttributesResponse;
|
||||
pub type VoucherShares = BandwidthVoucherResponse;
|
||||
pub type VoucherIssuers = PartialVerificationKeysResponse;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct NymIssuanceBandwidthVoucher {
|
||||
serial_number: PrivateAttribute,
|
||||
|
||||
binding_number: PrivateAttribute,
|
||||
|
||||
credential_amount: Coin,
|
||||
|
||||
prehashed_amount: PublicAttribute,
|
||||
prehashed_type: PublicAttribute,
|
||||
|
||||
blind_sign_request: BlindSignRequest,
|
||||
pedersen_commitments_openings: Zeroizing<Vec<Scalar>>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl NymIssuanceBandwidthVoucher {
|
||||
#[wasm_bindgen(js_name = "prepareNew", constructor)]
|
||||
pub fn prepare_new(
|
||||
opts: NymIssuanceBandwidthVoucherOpts,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<NymIssuanceBandwidthVoucher, ZkNymError> {
|
||||
let deposit_amount: u128 = opts
|
||||
.credential_amount_string
|
||||
.parse()
|
||||
.map_err(|_| ZkNymError::InvalidDepositAmount)?;
|
||||
let credential_amount = Coin::new(deposit_amount, opts.credential_amount_denom);
|
||||
|
||||
let params = get_params(¶meters);
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
|
||||
let prehashed_amount = Scalar::try_from_bs58(opts.bs58_prehashed_amount)?;
|
||||
let prehashed_type = Scalar::try_from_bs58(opts.bs58_prehashed_type)?;
|
||||
|
||||
let public_attributes = vec![&prehashed_amount, &prehashed_type];
|
||||
let private_attributes = vec![&serial_number, &binding_number];
|
||||
|
||||
let (pedersen_commitments_openings, blind_sign_request) =
|
||||
nym_coconut::prepare_blind_sign(params, &private_attributes, &public_attributes)?;
|
||||
|
||||
Ok(NymIssuanceBandwidthVoucher {
|
||||
serial_number,
|
||||
binding_number,
|
||||
credential_amount,
|
||||
prehashed_amount,
|
||||
prehashed_type,
|
||||
blind_sign_request,
|
||||
pedersen_commitments_openings: Zeroizing::new(pedersen_commitments_openings),
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "getBlindSignRequest")]
|
||||
pub fn get_blind_sign_request(&self) -> String {
|
||||
self.blind_sign_request.to_bs58()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "unblindShare")]
|
||||
pub fn unblind_share(&self, share: UnblindableShare) -> Result<CredentialWrapper, ZkNymError> {
|
||||
let blinded_sig = BlindedSignature::try_from_bs58(share.blinded_share_bs58)?;
|
||||
let vk = VerificationKey::try_from_bs58(share.issuer_key_bs58)?;
|
||||
|
||||
Ok(blinded_sig
|
||||
.unblind(&vk, &self.pedersen_commitments_openings)
|
||||
.into())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "unblindShares")]
|
||||
pub fn unblind_shares(
|
||||
self,
|
||||
shares: VoucherShares,
|
||||
issuers: VoucherIssuers,
|
||||
) -> Result<NymIssuedBandwidthVoucher, ZkNymError> {
|
||||
if shares.epoch_id != issuers.epoch_id {
|
||||
console_error!(
|
||||
"the provided shares and issuers are not from the same epoch! {} and {}",
|
||||
shares.epoch_id,
|
||||
issuers.epoch_id
|
||||
);
|
||||
return Err(ZkNymError::InconsistentEpochId {
|
||||
shares: shares.epoch_id,
|
||||
issuers: issuers.epoch_id,
|
||||
});
|
||||
}
|
||||
|
||||
let mut decoded_keys = HashMap::new();
|
||||
for key in issuers.keys {
|
||||
let vk = VerificationKey::try_from_bs58(key.bs58_encoded_key)?;
|
||||
decoded_keys.insert(key.node_index, vk);
|
||||
}
|
||||
|
||||
let mut credential_shares = Vec::new();
|
||||
for share in shares.shares {
|
||||
let blinded_sig = BlindedSignature::try_from_bs58(share.bs58_encoded_share)?;
|
||||
let Some(vk) = decoded_keys.get(&share.node_index) else {
|
||||
console_error!("received a share from issuer {} but did not receive a corresponding verification key!", share.node_index);
|
||||
continue;
|
||||
};
|
||||
let unblinded_sig = blinded_sig.unblind(vk, &self.pedersen_commitments_openings);
|
||||
credential_shares.push(SignatureShare::new(unblinded_sig, share.node_index));
|
||||
}
|
||||
|
||||
let signature = nym_coconut::aggregate_signature_shares(&credential_shares)?;
|
||||
|
||||
let voucher_data = BandwidthCredentialIssuedDataVariant::Voucher(
|
||||
BandwidthVoucherIssuedData::new(self.credential_amount),
|
||||
);
|
||||
|
||||
Ok(NymIssuedBandwidthVoucher {
|
||||
inner: IssuedBandwidthCredential::new(
|
||||
self.serial_number,
|
||||
self.binding_number,
|
||||
signature,
|
||||
voucher_data,
|
||||
self.prehashed_type,
|
||||
shares.epoch_id,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct NymIssuedBandwidthVoucher {
|
||||
inner: IssuedBandwidthCredential,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl NymIssuedBandwidthVoucher {
|
||||
#[wasm_bindgen(js_name = "ensureIsValid")]
|
||||
pub fn ensure_is_valid(
|
||||
&self,
|
||||
master_vk: String,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> bool {
|
||||
let params = get_params(¶meters);
|
||||
let Ok(master_vk) = VerificationKey::try_from_bs58(master_vk) else {
|
||||
console_error!("malformed master verification key");
|
||||
return false;
|
||||
};
|
||||
|
||||
let spending_req = match self.inner.prepare_for_spending(&master_vk) {
|
||||
Ok(req) => req,
|
||||
Err(err) => {
|
||||
console_error!("failed to prepare spending request: {err}");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
spending_req.verify(params, &master_vk)
|
||||
}
|
||||
|
||||
pub fn serialise(self) -> SerialisedNymIssuedBandwidthVoucher {
|
||||
SerialisedNymIssuedBandwidthVoucher {
|
||||
serialisation_revision:
|
||||
nym_credentials::coconut::bandwidth::issued::CURRENT_SERIALIZATION_REVISION,
|
||||
bs58_encoded_data: bs58::encode(&self.inner.pack_v1()).into_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SerialisedNymIssuedBandwidthVoucher {
|
||||
pub serialisation_revision: u8,
|
||||
pub bs58_encoded_data: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::vpn_api_client::{new_client, NymVpnApiClient};
|
||||
|
||||
#[tokio::test]
|
||||
async fn end_to_end() -> anyhow::Result<()> {
|
||||
let client = new_client("http://0.0.0.0:8080", "foomp")?;
|
||||
let opts = client.get_prehashed_public_attributes().await?;
|
||||
let issuance = NymIssuanceBandwidthVoucher::prepare_new(opts, None)?;
|
||||
|
||||
let shares = client
|
||||
.get_bandwidth_voucher_blinded_shares(issuance.blind_sign_request.clone())
|
||||
.await?;
|
||||
let keys = client.get_partial_verification_keys().await?;
|
||||
let master_key = client.get_master_verification_key().await?;
|
||||
|
||||
let voucher = issuance.unblind_shares(shares, keys)?;
|
||||
|
||||
println!(
|
||||
"valid: {}",
|
||||
voucher.ensure_is_valid(master_key.bs58_encoded_key, None)
|
||||
);
|
||||
let serialised = voucher.serialise();
|
||||
println!("final: {serialised:#?}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
use wasm_utils::wasm_error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ZkNymError {
|
||||
#[error("cryptographic failure: {source}")]
|
||||
CoconutFailure {
|
||||
#[from]
|
||||
source: nym_coconut::CoconutError,
|
||||
},
|
||||
//
|
||||
// #[error("failed to contact the vpn api")]
|
||||
// HttpClientFailure {
|
||||
// #[from]
|
||||
// source: NymVpnApiClientError,
|
||||
// },
|
||||
#[error("the provided shares and issuers are not from the same epoch! {shares} and {issuers}")]
|
||||
InconsistentEpochId { shares: u64, issuers: u64 },
|
||||
|
||||
#[error("the provided deposit amount is malformed")]
|
||||
InvalidDepositAmount,
|
||||
|
||||
#[error("global parameters have already been set before")]
|
||||
GlobalParamsAlreadySet,
|
||||
|
||||
#[error("no parameters were provided - they need to be provided either explicitly or a global ones need to be set")]
|
||||
NoParametersProvided,
|
||||
|
||||
#[error("failed to recover ed25519 private key from its base58 representation: {0}")]
|
||||
MalformedEd25519Key(String),
|
||||
|
||||
#[error("failed to recover x25519 private key from its base58 representation: {0}")]
|
||||
MalformedX25519Key(String),
|
||||
|
||||
#[error("failed to recover the deposit transaction hash from its [hex] representation: {0}")]
|
||||
MalformedTransactionHash(String),
|
||||
}
|
||||
|
||||
wasm_error!(ZkNymError);
|
||||
@@ -0,0 +1,340 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ZkNymError;
|
||||
use crate::types::{
|
||||
BlindSignRequestData, BlindSignRequestWrapper, BlindedCredentialWrapper,
|
||||
CredentialShareWrapper, CredentialWrapper, KeyPairWrapper, ParametersWrapper, ScalarsWrapper,
|
||||
VerificationKeyShareWrapper, VerificationKeyWrapper, VerifyCredentialRequestWrapper,
|
||||
};
|
||||
use crate::GLOBAL_PARAMS;
|
||||
use nym_coconut::{hash_to_scalar, Parameters, SignerIndex};
|
||||
use nym_credentials::IssuanceBandwidthCredential;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::OnceLock;
|
||||
use tsify::Tsify;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
// works under the assumption of having 4 attributes in the underlying credential(s)
|
||||
pub fn default_bandwidth_credential_params() -> &'static Parameters {
|
||||
static BANDWIDTH_CREDENTIAL_PARAMS: OnceLock<Parameters> = OnceLock::new();
|
||||
BANDWIDTH_CREDENTIAL_PARAMS.get_or_init(IssuanceBandwidthCredential::default_parameters)
|
||||
}
|
||||
|
||||
// attempt to extract appropriate system parameters in the following order:
|
||||
// 1. attempt to get explicit provided value
|
||||
// 2. then try a globally set value
|
||||
// 3. finally fallback to sane default: the bandwidth credential params
|
||||
pub(crate) fn get_params(explicit_params: &Option<ParametersWrapper>) -> &Parameters {
|
||||
if let Some(explicit) = explicit_params.as_ref() {
|
||||
return explicit;
|
||||
}
|
||||
|
||||
if let Some(global) = GLOBAL_PARAMS.get() {
|
||||
return global;
|
||||
}
|
||||
|
||||
default_bandwidth_credential_params()
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize, Default)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetupOpts {
|
||||
#[tsify(optional)]
|
||||
pub num_attributes: Option<u32>,
|
||||
|
||||
#[tsify(optional)]
|
||||
pub set_global: Option<bool>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn setup(opts: SetupOpts) -> Result<ParametersWrapper, ZkNymError> {
|
||||
let num_attributes = opts
|
||||
.num_attributes
|
||||
.unwrap_or(IssuanceBandwidthCredential::ENCODED_ATTRIBUTES);
|
||||
|
||||
let params = nym_coconut::setup(num_attributes)?;
|
||||
|
||||
if let Some(true) = opts.set_global {
|
||||
GLOBAL_PARAMS
|
||||
.set(params.clone())
|
||||
.map_err(|_| ZkNymError::GlobalParamsAlreadySet)?;
|
||||
}
|
||||
|
||||
Ok(params.into())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn keygen(parameters: Option<ParametersWrapper>) -> KeyPairWrapper {
|
||||
let params = get_params(¶meters);
|
||||
nym_coconut::keygen(params).into()
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TtpKeygenOpts {
|
||||
pub threshold: u64,
|
||||
|
||||
pub authorities: u64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "ttpKeygen")]
|
||||
pub fn ttp_keygen(
|
||||
opts: TtpKeygenOpts,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<Vec<KeyPairWrapper>, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let keys = nym_coconut::ttp_keygen(params, opts.threshold, opts.authorities)?;
|
||||
Ok(keys.into_iter().map(Into::into).collect())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "signSimple")]
|
||||
pub fn sign_simple(
|
||||
attributes: Vec<String>,
|
||||
keys: &KeyPairWrapper,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::sign(params, keys.secret_key(), &attributes_ref)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "prepareBlindSign")]
|
||||
pub fn prepare_blind_sign(
|
||||
private_attributes: Vec<String>,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<BlindSignRequestData, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let public_attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
let private_attributes = private_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let private_attributes_ref = private_attributes.iter().collect::<Vec<_>>();
|
||||
let (pedersen_commitments_openings, blind_sign_request) =
|
||||
nym_coconut::prepare_blind_sign(params, &private_attributes_ref, &public_attributes_ref)?;
|
||||
|
||||
Ok(BlindSignRequestData {
|
||||
blind_sign_request,
|
||||
pedersen_commitments_openings,
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "blindSign")]
|
||||
pub fn blind_sign(
|
||||
keys: &KeyPairWrapper,
|
||||
blind_sign_request: &BlindSignRequestWrapper,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<BlindedCredentialWrapper, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let public_attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::blind_sign(
|
||||
params,
|
||||
keys.secret_key(),
|
||||
blind_sign_request,
|
||||
&public_attributes_ref,
|
||||
)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "unblindSignatureShare")]
|
||||
pub fn unblind_signature_share(
|
||||
blinded_signature: &BlindedCredentialWrapper,
|
||||
partial_verification_key: &VerificationKeyWrapper,
|
||||
pedersen_commitments_openings: &ScalarsWrapper,
|
||||
) -> CredentialWrapper {
|
||||
BlindedCredentialWrapper::unblind(
|
||||
blinded_signature,
|
||||
partial_verification_key,
|
||||
pedersen_commitments_openings,
|
||||
)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "unblindAndVerifySignatureShare")]
|
||||
pub fn unblind_and_verify_signature_share(
|
||||
blinded_signature: &BlindedCredentialWrapper,
|
||||
partial_verification_key: &VerificationKeyWrapper,
|
||||
request: &BlindSignRequestData,
|
||||
private_attributes: Vec<String>,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
BlindedCredentialWrapper::unblind_and_verify(
|
||||
blinded_signature,
|
||||
partial_verification_key,
|
||||
request,
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
parameters,
|
||||
)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "aggregateSignatureShares")]
|
||||
pub fn aggregate_signature_shares(
|
||||
shares: Vec<CredentialShareWrapper>,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
let shares = shares.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::aggregate_signature_shares(&shares)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "aggregateSignatureSharesAndVerify")]
|
||||
pub fn aggregate_signature_shares_and_verify(
|
||||
verification_key: &VerificationKeyWrapper,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
private_attributes: Vec<String>,
|
||||
public_attributes: Vec<String>,
|
||||
shares: Vec<CredentialShareWrapper>,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let private_attributes = private_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let attributes = private_attributes
|
||||
.iter()
|
||||
.chain(public_attributes.iter())
|
||||
.collect::<Vec<_>>();
|
||||
let shares = shares.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::aggregate_signature_shares_and_verify(
|
||||
params,
|
||||
verification_key,
|
||||
&attributes,
|
||||
&shares,
|
||||
)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "aggregateVerificationKeyShares")]
|
||||
pub fn aggregate_verification_key_shares(
|
||||
shares: Vec<VerificationKeyShareWrapper>,
|
||||
) -> Result<VerificationKeyWrapper, ZkNymError> {
|
||||
let shares = shares.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::aggregate_key_shares(&shares)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "aggregateVerificationKeys")]
|
||||
pub fn aggregate_verification_keys(
|
||||
keys: Vec<VerificationKeyWrapper>,
|
||||
indices: Vec<SignerIndex>,
|
||||
) -> Result<VerificationKeyWrapper, ZkNymError> {
|
||||
let keys = keys.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::aggregate_verification_keys(&keys, Some(&indices))
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "proveBandwidthCredential")]
|
||||
pub fn prove_bandwidth_credential(
|
||||
verification_key: &VerificationKeyWrapper,
|
||||
credential: &CredentialWrapper,
|
||||
serial_number: String,
|
||||
binding_number: String,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<VerifyCredentialRequestWrapper, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
nym_coconut::prove_bandwidth_credential(
|
||||
params,
|
||||
verification_key,
|
||||
credential,
|
||||
&hash_to_scalar(serial_number),
|
||||
&hash_to_scalar(binding_number),
|
||||
)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "verifyCredential")]
|
||||
pub fn verify_credential(
|
||||
verification_key: &VerificationKeyWrapper,
|
||||
verification_request: &VerifyCredentialRequestWrapper,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> bool {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::verify_credential(
|
||||
params,
|
||||
verification_key,
|
||||
verification_request,
|
||||
&attributes_ref,
|
||||
)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "verifySimple")]
|
||||
pub fn verify_simple(
|
||||
verification_key: &VerificationKeyWrapper,
|
||||
attributes: Vec<String>,
|
||||
credential: &CredentialWrapper,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> bool {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::verify(params, verification_key, &attributes_ref, credential)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "simpleRandomiseCredential")]
|
||||
pub fn simple_randomise_credential(
|
||||
credential: &CredentialWrapper,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> CredentialWrapper {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
CredentialWrapper {
|
||||
inner: credential.inner.randomise_simple(params),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// due to the code generated by Tsify
|
||||
#![allow(clippy::empty_docs)]
|
||||
|
||||
use nym_coconut::Parameters;
|
||||
use std::sync::OnceLock;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub mod bandwidth_voucher;
|
||||
pub mod error;
|
||||
pub mod generic_scheme;
|
||||
pub mod types;
|
||||
|
||||
// keep in internal to the crate since I'm not sure how temporary this thing is going to be
|
||||
// I mostly got it, so I could test the whole thing end to end
|
||||
pub(crate) mod vpn_api_client;
|
||||
|
||||
pub(crate) static GLOBAL_PARAMS: OnceLock<Parameters> = OnceLock::new();
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
// #[cfg(target_arch = "wasm32")]
|
||||
pub fn main() {
|
||||
wasm_utils::console_log!("[rust main]: rust module loaded");
|
||||
wasm_utils::console_log!(
|
||||
"wasm zk-nym version used: {}",
|
||||
nym_bin_common::bin_info_owned!()
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ZkNymError;
|
||||
use crate::generic_scheme::get_params;
|
||||
use nym_coconut::{
|
||||
hash_to_scalar, Base58, BlindSignRequest, BlindedSignature, KeyPair, Parameters, Scalar,
|
||||
SecretKey, Signature, SignatureShare, SignerIndex, VerificationKey, VerificationKeyShare,
|
||||
VerifyCredentialRequest,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use tsify::Tsify;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
macro_rules! wasm_wrapper {
|
||||
($base:ident, $wrapper:ident) => {
|
||||
#[wasm_bindgen]
|
||||
pub struct $wrapper {
|
||||
pub(crate) inner: $base,
|
||||
}
|
||||
|
||||
impl Deref for $wrapper {
|
||||
type Target = $base;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$base> for $wrapper {
|
||||
fn from(inner: $base) -> Self {
|
||||
$wrapper { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$wrapper> for $base {
|
||||
fn from(value: $wrapper) -> Self {
|
||||
value.inner
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! data_pointer_clone {
|
||||
($wrapper:ident) => {
|
||||
#[wasm_bindgen]
|
||||
impl $wrapper {
|
||||
#[wasm_bindgen(js_name = "cloneDataPointer")]
|
||||
pub fn clone_data_pointer(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! wasm_wrapper_bs58 {
|
||||
($base:ident, $wrapper:ident) => {
|
||||
wasm_wrapper!($base, $wrapper);
|
||||
|
||||
impl std::fmt::Display for $wrapper {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.inner.to_bs58().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for $wrapper {
|
||||
type Err = ZkNymError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok($base::try_from_bs58(s)?.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl $wrapper {
|
||||
pub fn stringify(&self) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "fromString")]
|
||||
pub fn from_string(raw: String) -> Result<$wrapper, ZkNymError> {
|
||||
raw.parse()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
wasm_wrapper!(Parameters, ParametersWrapper);
|
||||
wasm_wrapper_bs58!(Signature, CredentialWrapper);
|
||||
wasm_wrapper_bs58!(BlindedSignature, BlindedCredentialWrapper);
|
||||
wasm_wrapper!(SignatureShare, CredentialShareWrapper);
|
||||
wasm_wrapper_bs58!(Scalar, ScalarWrapper);
|
||||
|
||||
wasm_wrapper!(KeyPair, KeyPairWrapper);
|
||||
wasm_wrapper!(SecretKey, SecretKeyWrapper);
|
||||
wasm_wrapper!(BlindSignRequest, BlindSignRequestWrapper);
|
||||
wasm_wrapper_bs58!(VerificationKey, VerificationKeyWrapper);
|
||||
wasm_wrapper_bs58!(VerifyCredentialRequest, VerifyCredentialRequestWrapper);
|
||||
wasm_wrapper!(VerificationKeyShare, VerificationKeyShareWrapper);
|
||||
|
||||
data_pointer_clone!(VerificationKeyShareWrapper);
|
||||
data_pointer_clone!(CredentialShareWrapper);
|
||||
data_pointer_clone!(BlindSignRequestWrapper);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl BlindedCredentialWrapper {
|
||||
pub fn unblind(
|
||||
&self,
|
||||
partial_verification_key: &VerificationKeyWrapper,
|
||||
pedersen_commitments_openings: &ScalarsWrapper,
|
||||
) -> CredentialWrapper {
|
||||
self.inner
|
||||
.unblind(partial_verification_key, pedersen_commitments_openings)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "unblindAndVerify")]
|
||||
pub fn unblind_and_verify(
|
||||
&self,
|
||||
partial_verification_key: &VerificationKeyWrapper,
|
||||
request: &BlindSignRequestData,
|
||||
private_attributes: Vec<String>,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let public_attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
let private_attributes = private_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let private_attributes_ref = private_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
let unblinded_signature = self.inner.unblind_and_verify(
|
||||
params,
|
||||
partial_verification_key,
|
||||
&private_attributes_ref,
|
||||
&public_attributes_ref,
|
||||
&request.blind_sign_request.get_commitment_hash(),
|
||||
&request.pedersen_commitments_openings,
|
||||
)?;
|
||||
|
||||
Ok(unblinded_signature.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl CredentialWrapper {
|
||||
#[wasm_bindgen(js_name = "intoShare")]
|
||||
pub fn into_share(self, index: SignerIndex) -> CredentialShareWrapper {
|
||||
CredentialShareWrapper {
|
||||
inner: SignatureShare::new(self.inner, index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl KeyPairWrapper {
|
||||
#[wasm_bindgen(js_name = "verificationKey")]
|
||||
pub fn verification_key(&self) -> VerificationKeyWrapper {
|
||||
self.inner.verification_key().clone().into()
|
||||
}
|
||||
|
||||
pub fn index(&self) -> Option<SignerIndex> {
|
||||
self.inner.index
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "verificationKeyShare")]
|
||||
pub fn verification_key_share(&self) -> Option<VerificationKeyShareWrapper> {
|
||||
self.inner.to_verification_key_share().map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct BlindSignRequestData {
|
||||
pub(crate) blind_sign_request: BlindSignRequest,
|
||||
pub(crate) pedersen_commitments_openings: Vec<Scalar>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl BlindSignRequestData {
|
||||
#[wasm_bindgen(js_name = "blindSignRequest")]
|
||||
pub fn blind_sign_request(&self) -> BlindSignRequestWrapper {
|
||||
self.blind_sign_request.clone().into()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "pedersenCommitmentsOpenings")]
|
||||
pub fn pedersen_commitments_openings(&self) -> ScalarsWrapper {
|
||||
ScalarsWrapper(self.pedersen_commitments_openings.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct ScalarsWrapper(pub(crate) Vec<Scalar>);
|
||||
|
||||
impl Deref for ScalarsWrapper {
|
||||
type Target = Vec<Scalar>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop,
|
||||
)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KeypairWrapper {
|
||||
pub private_key: String,
|
||||
pub public_key: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UnblindableShare {
|
||||
pub issuer_index: u64,
|
||||
pub issuer_key_bs58: String,
|
||||
pub blinded_share_bs58: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl UnblindableShare {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(issuer_index: u64, issuer_key_bs58: String, blinded_share_bs58: String) -> Self {
|
||||
UnblindableShare {
|
||||
issuer_index,
|
||||
issuer_key_bs58,
|
||||
blinded_share_bs58,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ZkNymError;
|
||||
use crate::vpn_api_client::types::{
|
||||
AttributesResponse, BandwidthVoucherRequest, BandwidthVoucherResponse, ErrorResponse,
|
||||
MasterVerificationKeyResponse, PartialVerificationKeysResponse,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use nym_coconut::BlindSignRequest;
|
||||
pub use nym_http_api_client::Client;
|
||||
use nym_http_api_client::{parse_response, HttpClientError, PathSegments, NO_PARAMS};
|
||||
use reqwest::IntoUrl;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
pub type NymVpnApiClientError = HttpClientError<ErrorResponse>;
|
||||
|
||||
pub struct VpnApiClient {
|
||||
inner: Client,
|
||||
bearer_token: String,
|
||||
}
|
||||
|
||||
pub fn new_client(
|
||||
base_url: impl IntoUrl,
|
||||
bearer_token: impl Into<String>,
|
||||
) -> Result<VpnApiClient, ZkNymError> {
|
||||
Ok(VpnApiClient {
|
||||
inner: Client::builder(base_url)?
|
||||
.with_user_agent(format!("nym-wasm-znym-lib/{}", env!("CARGO_PKG_VERSION")))
|
||||
.build()?,
|
||||
bearer_token: bearer_token.into(),
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: do it properly by implementing auth headers on `ApiClient` trait
|
||||
#[async_trait(?Send)]
|
||||
pub trait NymVpnApiClient {
|
||||
async fn simple_get<T>(&self, path: PathSegments<'_>) -> Result<T, NymVpnApiClientError>
|
||||
where
|
||||
T: DeserializeOwned;
|
||||
|
||||
async fn get_prehashed_public_attributes(
|
||||
&self,
|
||||
) -> Result<AttributesResponse, NymVpnApiClientError> {
|
||||
self.simple_get(&[
|
||||
"/api",
|
||||
"/v1",
|
||||
"/bandwidth-voucher",
|
||||
"/prehashed-public-attributes",
|
||||
])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_partial_verification_keys(
|
||||
&self,
|
||||
) -> Result<PartialVerificationKeysResponse, NymVpnApiClientError> {
|
||||
self.simple_get(&[
|
||||
"/api",
|
||||
"/v1",
|
||||
"/bandwidth-voucher",
|
||||
"/partial-verification-keys",
|
||||
])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_master_verification_key(
|
||||
&self,
|
||||
) -> Result<MasterVerificationKeyResponse, NymVpnApiClientError> {
|
||||
self.simple_get(&[
|
||||
"/api",
|
||||
"/v1",
|
||||
"/bandwidth-voucher",
|
||||
"/master-verification-key",
|
||||
])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_bandwidth_voucher_blinded_shares(
|
||||
&self,
|
||||
blind_sign_request: BlindSignRequest,
|
||||
) -> Result<BandwidthVoucherResponse, NymVpnApiClientError>;
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl crate::vpn_api_client::NymVpnApiClient for VpnApiClient {
|
||||
async fn simple_get<T>(&self, path: PathSegments<'_>) -> Result<T, NymVpnApiClientError>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let req = self
|
||||
.inner
|
||||
.create_get_request(path, NO_PARAMS)
|
||||
.bearer_auth(&self.bearer_token)
|
||||
.send();
|
||||
|
||||
// the only reason for that target lock is so that I could call this method from an ephemeral test
|
||||
// running in non-wasm mode (since I wanted to use tokio)
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let res = wasmtimer::tokio::timeout(std::time::Duration::from_secs(5), req)
|
||||
.await
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = req.await?;
|
||||
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
async fn get_bandwidth_voucher_blinded_shares(
|
||||
&self,
|
||||
blind_sign_request: BlindSignRequest,
|
||||
) -> Result<BandwidthVoucherResponse, NymVpnApiClientError> {
|
||||
let req = self.inner.create_post_request(
|
||||
&["/api", "/v1", "/bandwidth-voucher", "/obtain"],
|
||||
NO_PARAMS,
|
||||
&BandwidthVoucherRequest { blind_sign_request },
|
||||
);
|
||||
|
||||
let fut = req.bearer_auth(&self.bearer_token).send();
|
||||
|
||||
// the only reason for that target lock is so that I could call this method from an ephemeral test
|
||||
// running in non-wasm mode (since I wanted to use tokio)
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let res = wasmtimer::tokio::timeout(std::time::Duration::from_secs(5), fut)
|
||||
.await
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = fut.await?;
|
||||
|
||||
parse_response(res, false).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(test)]
|
||||
mod client;
|
||||
|
||||
pub mod types;
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// just copied over from dot com repo
|
||||
|
||||
use nym_coconut::BlindSignRequest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use tsify::Tsify;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BandwidthVoucherRequest {
|
||||
/// base58 encoded blind sign request
|
||||
pub blind_sign_request: BlindSignRequest,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BandwidthVoucherResponse {
|
||||
pub epoch_id: u64,
|
||||
pub shares: Vec<CredentialShare>,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PartialVerificationKeysResponse {
|
||||
pub epoch_id: u64,
|
||||
pub keys: Vec<PartialVerificationKey>,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CurrentEpochResponse {
|
||||
pub epoch_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CredentialShare {
|
||||
pub node_index: u64,
|
||||
pub bs58_encoded_share: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PartialVerificationKey {
|
||||
pub node_index: u64,
|
||||
pub bs58_encoded_key: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MasterVerificationKeyResponse {
|
||||
pub epoch_id: u64,
|
||||
pub bs58_encoded_key: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DepositResponse {
|
||||
pub current_deposit_amount: u128,
|
||||
pub current_deposit_denom: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AttributesResponse {
|
||||
pub credential_type_string: String,
|
||||
pub credential_amount_string: String,
|
||||
pub credential_amount_denom: String,
|
||||
|
||||
pub bs58_prehashed_type: String,
|
||||
pub bs58_prehashed_amount: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FreepassCredentialResponse {
|
||||
pub bs58_encoded_value: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ErrorResponse {
|
||||
pub uuid: Option<Uuid>,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl Display for ErrorResponse {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.message)?;
|
||||
if let Some(uuid) = self.uuid {
|
||||
write!(f, ". request uuid: {uuid}")?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user