From e62ffbf7c6baf5a3ac459af894d858e412f7d7ee Mon Sep 17 00:00:00 2001 From: Justin Evans Date: Wed, 29 Jan 2025 15:50:18 +0000 Subject: [PATCH] feat: updating nodejs masp param storage --- packages/shared/lib/src/sdk/masp/masp.node.js | 61 +++++ packages/shared/lib/src/sdk/masp/masp.web.ts | 226 ++++++++++++++++++ packages/shared/lib/src/sdk/masp/masp_node.rs | 24 ++ packages/shared/lib/src/sdk/masp/masp_web.rs | 28 ++- packages/shared/lib/src/sdk/masp/mod.rs | 10 +- packages/shared/lib/src/sdk/mod.rs | 26 +- 6 files changed, 359 insertions(+), 16 deletions(-) create mode 100644 packages/shared/lib/src/sdk/masp/masp.web.ts diff --git a/packages/shared/lib/src/sdk/masp/masp.node.js b/packages/shared/lib/src/sdk/masp/masp.node.js index f46e969f5f..f1118a060e 100644 --- a/packages/shared/lib/src/sdk/masp/masp.node.js +++ b/packages/shared/lib/src/sdk/masp/masp.node.js @@ -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, }; diff --git a/packages/shared/lib/src/sdk/masp/masp.web.ts b/packages/shared/lib/src/sdk/masp/masp.web.ts new file mode 100644 index 0000000000..9388d215dc --- /dev/null +++ b/packages/shared/lib/src/sdk/masp/masp.web.ts @@ -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 => { + 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 => { + 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 { + 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 { + 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 { + return fetch(`${url}${param}`) + .then((response) => response.arrayBuffer()) + .then((ab) => { + const bytes = new Uint8Array(ab); + return validateMaspParamBytes({ param, bytes }); + }); +} + +function getDB(): Promise { + 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 { + 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 { + 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 { + 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(); + }; + }); +} diff --git a/packages/shared/lib/src/sdk/masp/masp_node.rs b/packages/shared/lib/src/sdk/masp/masp_node.rs index 62aa9302cf..367ca1beb5 100644 --- a/packages/shared/lib/src/sdk/masp/masp_node.rs +++ b/packages/shared/lib/src/sdk/masp/masp_node.rs @@ -186,6 +186,22 @@ fn file_exists(path: PathBuf) -> bool { .unwrap() } +pub async fn has_masp_params() -> Result { + 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) -> Result<(), JsValue> { + js_fetch_and_store_masp_params(url).await?; + Ok(()) +} + +pub async fn get_masp_params() -> Result { + 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")] @@ -202,4 +218,12 @@ extern "C" { #[wasm_bindgen(catch, js_name = "existsSync")] fn exists_sync(path: JsValue) -> Result; + + // MASP Param methods + #[wasm_bindgen(catch, js_name = "getMaspParams")] + async fn js_get_masp_params() -> Result; + #[wasm_bindgen(catch, js_name = "hasMaspParams")] + async fn js_has_masp_params() -> Result; + #[wasm_bindgen(catch, js_name = "fetchAndStoreMaspParams")] + async fn js_fetch_and_store_masp_params(url: Option) -> Result; } diff --git a/packages/shared/lib/src/sdk/masp/masp_web.rs b/packages/shared/lib/src/sdk/masp/masp_web.rs index 3aab94c222..0275c0660e 100644 --- a/packages/shared/lib/src/sdk/masp/masp_web.rs +++ b/packages/shared/lib/src/sdk/masp/masp_web.rs @@ -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; @@ -265,3 +265,29 @@ impl ShieldedUtils for WebShieldedUtils { DispatcherCache::deserialize(&mut &stored_cache_bytes[..]) } } + +pub async fn has_masp_params() -> Result { + 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) -> Result<(), JsValue> { + js_fetch_and_store_masp_params(url).await?; + Ok(()) +} + +pub async fn get_masp_params() -> Result { + 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; + #[wasm_bindgen(catch, js_name = "hasMaspParams")] + async fn js_has_masp_params() -> Result; + #[wasm_bindgen(catch, js_name = "fetchAndStoreMaspParams")] + async fn js_fetch_and_store_masp_params(url: Option) -> Result; +} diff --git a/packages/shared/lib/src/sdk/masp/mod.rs b/packages/shared/lib/src/sdk/masp/mod.rs index 4db35e53f6..996bd513b9 100644 --- a/packages/shared/lib/src/sdk/masp/mod.rs +++ b/packages/shared/lib/src/sdk/masp/mod.rs @@ -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; diff --git a/packages/shared/lib/src/sdk/mod.rs b/packages/shared/lib/src/sdk/mod.rs index 995161c959..91643f7908 100644 --- a/packages/shared/lib/src/sdk/mod.rs +++ b/packages/shared/lib/src/sdk/mod.rs @@ -107,13 +107,13 @@ impl Sdk { } pub async fn has_masp_params() -> Result { - let has = has_masp_params().await?; + let has = masp::has_masp_params().await?; Ok(js_sys::Boolean::from(has.as_bool().unwrap()).into()) } pub async fn fetch_and_store_masp_params(url: Option) -> Result<(), JsValue> { - fetch_and_store_masp_params(url).await?; + masp::fetch_and_store_masp_params(url).await?; Ok(()) } @@ -124,7 +124,7 @@ impl Sdk { chain_id: String, ) -> Result<(), JsValue> { // _dn_name is not used in the web version for a time being - let params = get_masp_params().await?; + let params = masp::get_masp_params().await?; let params_iter = js_sys::try_iter(¶ms)?.ok_or("Can't iterate over JsValue")?; let mut params_bytes = params_iter.map(|p| to_bytes(p.unwrap())); @@ -772,13 +772,13 @@ impl Sdk { to_js_result(borsh::to_vec(&tx)?) } } - -#[wasm_bindgen(module = "/src/sdk/mod.js")] -extern "C" { - #[wasm_bindgen(catch, js_name = "getMaspParams")] - async fn get_masp_params() -> Result; - #[wasm_bindgen(catch, js_name = "hasMaspParams")] - async fn has_masp_params() -> Result; - #[wasm_bindgen(catch, js_name = "fetchAndStoreMaspParams")] - async fn fetch_and_store_masp_params(url: Option) -> Result; -} +// +// #[wasm_bindgen(module = "/src/sdk/mod.js")] +// extern "C" { +// #[wasm_bindgen(catch, js_name = "getMaspParams")] +// async fn get_masp_params() -> Result; +// #[wasm_bindgen(catch, js_name = "hasMaspParams")] +// async fn has_masp_params() -> Result; +// #[wasm_bindgen(catch, js_name = "fetchAndStoreMaspParams")] +// async fn fetch_and_store_masp_params(url: Option) -> Result; +// }