Skip to content

feat: Updating SDK: NodeJS MASP param storage #1590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions packages/shared/lib/src/sdk/masp/masp.node.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,71 @@ function existsSync(path) {
return fs.existsSync(path);
}

const MASP_MPC_RELEASE_URL =
"https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/";

const MaspParam = {
Output: "masp-output.params",
Convert: "masp-convert.params",
Spend: "masp-spend.params",
};

async function hasMaspParams() {
return (
(await has(MaspParam.Spend)) &&
(await has(MaspParam.Output)) &&
(await has(MaspParam.Convert))
);
}

async function fetchAndStoreMaspParams(url) {
return Promise.all([
fetchAndStore(MaspParam.Spend, url),
fetchAndStore(MaspParam.Output, url),
fetchAndStore(MaspParam.Convert, url),
]);
}

async function getMaspParams() {
return Promise.all([
get(MaspParam.Spend),
get(MaspParam.Output),
get(MaspParam.Convert),
]);
}

async function fetchAndStore(param, url) {
return await fetchParams(param, url)
.then((data) => set(param, data))
.catch((e) => {
return Promise.reject(`Encountered errors fetching ${param}: ${e}`);
});
}

async function fetchParams(param, url) {
if (!url) {
url = MASP_MPC_RELEASE_URL;
}
return fetch(`${url}${param}`)
.then((response) => response.arrayBuffer())
.then((ab) => {
const bytes = new Uint8Array(ab);
return validateMaspParamBytes({ param, bytes });
});
}

async function set(path, data) {
console.log({ path, data });
console.warn("TODO: IMPLEMENT FOR NODEJS!");
}

module.exports = {
writeFileSync,
readFileSync,
renameSync,
unlinkSync,
existsSync,
fetchAndStoreMaspParams,
hasMaspParams,
getMaspParams,
};
226 changes: 226 additions & 0 deletions packages/shared/lib/src/sdk/masp/masp.web.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
const PREFIX = "Namada::SDK";
const MASP_MPC_RELEASE_URL =
"https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/";

const sha256Hash = async (msg: Uint8Array): Promise<string> => {
const hashBuffer = await crypto.subtle.digest("SHA-256", msg);
const hashArray = Array.from(new Uint8Array(hashBuffer));
// Return hash as hex
return hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
};

enum MaspParam {
Output = "masp-output.params",
Convert = "masp-convert.params",
Spend = "masp-spend.params",
}

type MaspParamBytes = {
param: MaspParam;
bytes: Uint8Array;
};

/**
* The following sha256 digests where produced by downloading the following:
* https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/masp-convert.params
* https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/masp-spend.params
* https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/masp-output.params
*
* And running "sha256sum" against each file:
*
* > sha256sum masp-convert.params
* 8e049c905e0e46f27662c7577a4e3480c0047ee1171f7f6d9c5b0de757bf71f1 masp-convert.params
*
* > sha256sum masp-spend.params
* 62b3c60ca54bd99eb390198e949660624612f7db7942db84595fa9f1b4a29fd8 masp-spend.params
*
* > sha256sum masp-output.params
* ed8b5d354017d808cfaf7b31eca5c511936e65ef6d276770251f5234ec5328b8 masp-output.params
*
* Length is specified in bytes, and can be retrieved with:
*
* > wc -c < masp-convert.params
* 22570940
* > wc -c < masp-spend.params
* 49848572
* > wc -c < masp-output.params
* 16398620
*/
const MASP_PARAM_ATTR: Record<
MaspParam,
{ length: number; sha256sum: string }
> = {
[MaspParam.Output]: {
length: 16398620,
sha256sum:
"ed8b5d354017d808cfaf7b31eca5c511936e65ef6d276770251f5234ec5328b8",
},
[MaspParam.Spend]: {
length: 49848572,
sha256sum:
"62b3c60ca54bd99eb390198e949660624612f7db7942db84595fa9f1b4a29fd8",
},
[MaspParam.Convert]: {
length: 22570940,
sha256sum:
"8e049c905e0e46f27662c7577a4e3480c0047ee1171f7f6d9c5b0de757bf71f1",
},
};

const validateMaspParamBytes = async ({
param,
bytes,
}: MaspParamBytes): Promise<Uint8Array> => {
const { length, sha256sum } = MASP_PARAM_ATTR[param];

// Reject if invalid length (incomplete download or invalid)
console.info(`Validating data length for ${param}, expecting ${length}...`);

if (length !== bytes.length) {
return Promise.reject(
`[${param}]: Invalid data length! Expected ${length}, received ${bytes.length}!`
);
}

// Reject if invalid hash (otherwise invalid data)
console.info(`Validating sha256sum for ${param}, expecting ${sha256sum}...`);
const hash = await sha256Hash(bytes);

if (hash !== sha256sum) {
return Promise.reject(
`[${param}]: Invalid sha256sum! Expected ${sha256sum}, received ${hash}!`
);
}

return bytes;
};

export async function hasMaspParams(): Promise<boolean> {
return (
(await has(MaspParam.Spend)) &&
(await has(MaspParam.Output)) &&
(await has(MaspParam.Convert))
);
}

export async function fetchAndStoreMaspParams(
url?: string
): Promise<[void, void, void]> {
return Promise.all([
fetchAndStore(MaspParam.Spend, url),
fetchAndStore(MaspParam.Output, url),
fetchAndStore(MaspParam.Convert, url),
]);
}

export async function getMaspParams(): Promise<[unknown, unknown, unknown]> {
return Promise.all([
get(MaspParam.Spend),
get(MaspParam.Output),
get(MaspParam.Convert),
]);
}

export async function fetchAndStore(
param: MaspParam,
url?: string
): Promise<void> {
return await fetchParams(param, url)
.then((data) => set(param, data))
.catch((e) => {
return Promise.reject(`Encountered errors fetching ${param}: ${e}`);
});
}

export async function fetchParams(
param: MaspParam,
url: string = MASP_MPC_RELEASE_URL
): Promise<Uint8Array> {
return fetch(`${url}${param}`)
.then((response) => response.arrayBuffer())
.then((ab) => {
const bytes = new Uint8Array(ab);
return validateMaspParamBytes({ param, bytes });
});
}

function getDB(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(PREFIX);
request.onerror = (event) => {
event.stopPropagation();
reject(event.target);
};

request.onupgradeneeded = (event) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const db = (event.target as any).result;

db.createObjectStore(PREFIX, { keyPath: "key" });
};

request.onsuccess = () => {
resolve(request.result);
};
});
}

export async function get(key: string): Promise<unknown> {
const tx = (await getDB()).transaction(PREFIX, "readonly");
const store = tx.objectStore(PREFIX);

return new Promise((resolve, reject) => {
const request = store.get(key);
request.onerror = (event) => {
event.stopPropagation();

reject(event.target);
};
request.onsuccess = () => {
if (!request.result) {
resolve(undefined);
} else {
resolve(request.result.data);
}
};
});
}

export async function has(key: string): Promise<boolean> {
const tx = (await getDB()).transaction(PREFIX, "readonly");
const store = tx.objectStore(PREFIX);

return new Promise((resolve, reject) => {
const request = store.openCursor(key);
request.onerror = (event) => {
event.stopPropagation();

reject(event.target);
};
request.onsuccess = (e) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const cursor = (e.target as any).result;
resolve(!!cursor);
};
});
}

export async function set(key: string, data: unknown): Promise<void> {
const tx = (await getDB()).transaction(PREFIX, "readwrite");
const store = tx.objectStore(PREFIX);

return new Promise((resolve, reject) => {
const request = store.put({
key,
data,
});
request.onerror = (event) => {
event.stopPropagation();

reject(event.target);
};
request.onsuccess = () => {
resolve();
};
});
}
24 changes: 24 additions & 0 deletions packages/shared/lib/src/sdk/masp/masp_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,22 @@ fn file_exists(path: PathBuf) -> bool {
.unwrap()
}

pub async fn has_masp_params() -> Result<JsValue, JsValue> {
let has = js_has_masp_params().await?;

Ok(js_sys::Boolean::from(has.as_bool().unwrap()).into())
}

pub async fn fetch_and_store_masp_params(url: Option<String>) -> Result<(), JsValue> {
js_fetch_and_store_masp_params(url).await?;
Ok(())
}

pub async fn get_masp_params() -> Result<JsValue, JsValue> {
let params = js_get_masp_params().await?;
Ok(params)
}

#[wasm_bindgen(module = "/src/sdk/masp/masp.node.js")]
extern "C" {
#[wasm_bindgen(catch, js_name = "writeFileSync")]
Expand All @@ -202,4 +218,12 @@ extern "C" {

#[wasm_bindgen(catch, js_name = "existsSync")]
fn exists_sync(path: JsValue) -> Result<JsValue, JsValue>;

// MASP Param methods
#[wasm_bindgen(catch, js_name = "getMaspParams")]
async fn js_get_masp_params() -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch, js_name = "hasMaspParams")]
async fn js_has_masp_params() -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch, js_name = "fetchAndStoreMaspParams")]
async fn js_fetch_and_store_masp_params(url: Option<String>) -> Result<JsValue, JsValue>;
}
28 changes: 27 additions & 1 deletion packages/shared/lib/src/sdk/masp/masp_web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use namada_sdk::masp::{ContextSyncStatus, DispatcherCache, ShieldedUtils};
use namada_sdk::masp_proofs::prover::LocalTxProver;
use namada_sdk::ShieldedWallet;
use rexie::{Error, ObjectStore, Rexie, TransactionMode};
use wasm_bindgen::{JsError, JsValue};
use wasm_bindgen::{prelude::*, JsError, JsValue};

use crate::utils::to_bytes;

Expand Down Expand Up @@ -265,3 +265,29 @@ impl ShieldedUtils for WebShieldedUtils {
DispatcherCache::deserialize(&mut &stored_cache_bytes[..])
}
}

pub async fn has_masp_params() -> Result<JsValue, JsValue> {
let has = js_has_masp_params().await?;

Ok(js_sys::Boolean::from(has.as_bool().unwrap()).into())
}

pub async fn fetch_and_store_masp_params(url: Option<String>) -> Result<(), JsValue> {
js_fetch_and_store_masp_params(url).await?;
Ok(())
}

pub async fn get_masp_params() -> Result<JsValue, JsValue> {
let params = js_get_masp_params().await?;
Ok(params)
}

#[wasm_bindgen(module = "/src/sdk/masp/masp.web.js")]
extern "C" {
#[wasm_bindgen(catch, js_name = "getMaspParams")]
async fn js_get_masp_params() -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch, js_name = "hasMaspParams")]
async fn js_has_masp_params() -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch, js_name = "fetchAndStoreMaspParams")]
async fn js_fetch_and_store_masp_params(url: Option<String>) -> Result<JsValue, JsValue>;
}
10 changes: 8 additions & 2 deletions packages/shared/lib/src/sdk/masp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@
mod masp_web;

#[cfg(feature = "web")]
pub use masp_web::WebShieldedUtils as JSShieldedUtils;
pub use masp_web::{
fetch_and_store_masp_params, get_masp_params, has_masp_params,
WebShieldedUtils as JSShieldedUtils,
};

#[cfg(feature = "nodejs")]
mod masp_node;

#[cfg(feature = "nodejs")]
pub use masp_node::NodeShieldedUtils as JSShieldedUtils;
pub use masp_node::{
fetch_and_store_masp_params, get_masp_params, has_masp_params,
NodeShieldedUtils as JSShieldedUtils,
};

pub mod sync;
Loading