ability to obtain bandwidth voucher from wasm via vpn-api

This commit is contained in:
Jędrzej Stuczyński
2024-05-03 15:42:14 +01:00
parent b9524a0f58
commit 0323ba2bb9
36 changed files with 7136 additions and 1232 deletions
Generated
+3234 -1120
View File
File diff suppressed because it is too large Load Diff
+8 -6
View File
@@ -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 {
+1
View File
@@ -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 }
+130 -31
View File
@@ -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,
+1 -14
View File
@@ -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;
+1 -1
View File
@@ -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"] }
+1 -1
View File
@@ -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())
}
}
};
+12
View File
@@ -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
View File
@@ -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)
+2 -2
View File
@@ -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!()
);
}
-48
View File
@@ -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
+4 -1
View File
@@ -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!()
);
}
+3
View File
@@ -0,0 +1,3 @@
[build]
target = "wasm32-unknown-unknown"
target_arch = "wasm32"
+46
View File
@@ -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
+18
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
node_modules
dist
+5
View File
@@ -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));
+22
View File
@@ -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>
+36
View File
@@ -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();
+40
View File
@@ -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 },
};
+193
View File
@@ -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
+228
View File
@@ -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(&parameters);
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(&parameters);
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(())
}
}
+42
View File
@@ -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);
+340
View File
@@ -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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
CredentialWrapper {
inner: credential.inner.randomise_simple(params),
}
}
+30
View File
@@ -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!()
);
}
+245
View File
@@ -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(&parameters);
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,
}
}
}
+135
View File
@@ -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
}
}
+7
View File
@@ -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;
+109
View File
@@ -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(())
}
}