Skip to content
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

Improvements and fixes to wasm transaction planner and wasm view server #2973

Merged
merged 1 commit into from
Sep 21, 2023
Merged
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions crates/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,19 @@ penumbra-tct = { path = "../crypto/tct" }
penumbra-transaction = { path = "../core/transaction", default-features = false }

anyhow = "1.0.75"
ark-ff = { version = "0.4.2", features = ["std"] }
base64 = "0.21.2"
console_error_panic_hook = { version = "0.1.7", optional = true }
decaf377 = { version = "0.5", features = ["r1cs"] }
hex = "0.4.3"
indexed_db_futures = "0.3.0"
rand_core = { version = "0.6.4", features = ["getrandom"] }
serde = { version = "1.0.186", features = ["derive"] }
serde-wasm-bindgen = "0.5.0"
thiserror = "1.0"
wasm-bindgen = "0.2.87"
wasm-bindgen-futures = "0.4.37"
wasm-bindgen-test = "0.3.0"
web-sys = { version = "0.3.64", features = ["console"] }

[dev-dependencies]
Expand Down
69 changes: 69 additions & 0 deletions crates/wasm/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use base64::DecodeError;
use hex::FromHexError;
use penumbra_tct::error::{InsertBlockError, InsertEpochError, InsertError};
use serde_wasm_bindgen::Error;
use std::convert::Infallible;
use thiserror::Error;
use wasm_bindgen::{JsError, JsValue};
use web_sys::DomException;

pub type WasmResult<T> = Result<T, WasmError>;

#[derive(Error, Debug)]
pub enum WasmError {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job on these! Can you the errors below in alphabetical order?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#[error("{0}")]
Anyhow(#[from] anyhow::Error),

#[error("{0}")]
DecodeError(#[from] DecodeError),

#[error("{0}")]
Dom(#[from] DomError),

#[error("{0}")]
FromHexError(#[from] FromHexError),

#[error("{0}")]
Infallible(#[from] Infallible),

#[error("{0}")]
InsertBlockError(#[from] InsertBlockError),

#[error("{0}")]
InsertEpochError(#[from] InsertEpochError),

#[error("{0}")]
InsertError(#[from] InsertError),

#[error("{0}")]
Wasm(#[from] serde_wasm_bindgen::Error),
}

impl From<WasmError> for serde_wasm_bindgen::Error {
fn from(wasm_err: WasmError) -> Self {
Error::new(wasm_err.to_string())
}
}

impl From<WasmError> for JsValue {
fn from(error: WasmError) -> Self {
JsError::from(error).into()
}
}

#[derive(Debug)]
pub struct DomError(DomException);

impl std::fmt::Display for DomError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "DOM Exception: {:?}", self.0)
}
}

impl std::error::Error for DomError {}

impl From<DomException> for WasmError {
fn from(dom_exception: DomException) -> Self {
WasmError::Dom(DomError(dom_exception))
}
}
106 changes: 106 additions & 0 deletions crates/wasm/src/keys.rs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think this file has some helper functions that are small enough to be moved into the main function. It's similar feedback as the other. Will have to move WasmResult to the the top level function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use crate::error::WasmResult;
use penumbra_keys::keys::{SeedPhrase, SpendKey};
use penumbra_keys::{Address, FullViewingKey};
use penumbra_proto::{core::crypto::v1alpha1 as pb, serializers::bech32str, DomainType};
use rand_core::OsRng;
use std::str::FromStr;
use wasm_bindgen::prelude::*;

/// generate a spend key from a seed phrase
/// Arguments:
/// seed_phrase: `string`
/// Returns: `bech32 string`
#[wasm_bindgen]
pub fn generate_spend_key(seed_phrase: &str) -> WasmResult<JsValue> {
let seed = SeedPhrase::from_str(seed_phrase)?;
let spend_key = SpendKey::from_seed_phrase_bip39(seed, 0);

let proto = spend_key.to_proto();

let spend_key_str = bech32str::encode(
&proto.inner,
bech32str::spend_key::BECH32_PREFIX,
bech32str::Bech32m,
);

Ok(JsValue::from_str(&spend_key_str))
}

/// get full viewing key from spend key
/// Arguments:
/// spend_key_str: `bech32 string`
/// Returns: `bech32 string`
#[wasm_bindgen]
pub fn get_full_viewing_key(spend_key: &str) -> WasmResult<JsValue> {
let spend_key = SpendKey::from_str(spend_key)?;

let fvk: &FullViewingKey = spend_key.full_viewing_key();

let proto = fvk.to_proto();

let fvk_bech32 = bech32str::encode(
&proto.inner,
bech32str::full_viewing_key::BECH32_PREFIX,
bech32str::Bech32m,
);
Ok(JsValue::from_str(&fvk_bech32))
}

/// get address by index using FVK
/// Arguments:
/// full_viewing_key: `bech32 string`
/// index: `u32`
/// Returns: `pb::Address`
#[wasm_bindgen]
pub fn get_address_by_index(full_viewing_key: &str, index: u32) -> WasmResult<JsValue> {
let fvk = FullViewingKey::from_str(full_viewing_key)?;
let (address, _dtk) = fvk.incoming().payment_address(index.into());
let proto = address.to_proto();
let result = serde_wasm_bindgen::to_value(&proto)?;
Ok(result)
}

/// get ephemeral (randomizer) address using FVK
/// The derivation tree is like "spend key / address index / ephemeral address" so we must also pass index as an argument
/// Arguments:
/// full_viewing_key: `bech32 string`
/// index: `u32`
/// Returns: `pb::Address`
#[wasm_bindgen]
pub fn get_ephemeral_address(full_viewing_key: &str, index: u32) -> WasmResult<JsValue> {
let fvk = FullViewingKey::from_str(full_viewing_key)?;
let (address, _dtk) = fvk.ephemeral_address(OsRng, index.into());
let proto = address.to_proto();
let result = serde_wasm_bindgen::to_value(&proto)?;
Ok(result)
}

/// Check if the address is FVK controlled
/// Arguments:
/// full_viewing_key: `bech32 String`
/// address: `bech32 String`
/// Returns: `Option<pb::AddressIndex>`
#[wasm_bindgen]
pub fn is_controlled_address(full_viewing_key: &str, address: &str) -> WasmResult<JsValue> {
let fvk = FullViewingKey::from_str(full_viewing_key)?;
let index: Option<pb::AddressIndex> = fvk
.address_index(&Address::from_str(address)?)
.map(Into::into);
let result = serde_wasm_bindgen::to_value(&index)?;
Ok(result)
}

/// Get canonical short form address by index
/// This feature is probably redundant and will be removed from wasm in the future
/// Arguments:
/// full_viewing_key: `bech32 string`
/// index: `u32`
/// Returns: `String`
#[wasm_bindgen]
pub fn get_short_address_by_index(full_viewing_key: &str, index: u32) -> WasmResult<JsValue> {
let fvk = FullViewingKey::from_str(full_viewing_key)?;

let (address, _dtk) = fvk.incoming().payment_address(index.into());
let short_address = address.display_short_form();
Ok(JsValue::from_str(&short_address))
}
124 changes: 4 additions & 120 deletions crates/wasm/src/lib.rs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thank you for making /lib clean 👍

Original file line number Diff line number Diff line change
@@ -1,131 +1,15 @@
#![deny(clippy::unwrap_used)]
#![allow(dead_code)]
extern crate core;

mod error;
mod keys;
mod note_record;
mod planner;
mod storage;
mod swap_record;
mod tx;
mod utils;
mod view_server;
use penumbra_proto::{core::crypto::v1alpha1 as pb, serializers::bech32str, DomainType};
mod wasm_planner;

use penumbra_keys::{Address, FullViewingKey};
use std::convert::TryFrom;
use std::str::FromStr;

use penumbra_keys::keys::{SeedPhrase, SpendKey};
use wasm_bindgen::prelude::*;

use penumbra_transaction::Transaction;

pub use tx::send_plan;
pub use view_server::ViewServer;

#[wasm_bindgen]
pub fn generate_spend_key(seed_phrase: &str) -> JsValue {
utils::set_panic_hook();
let seed =
SeedPhrase::from_str(seed_phrase).expect("the provided string is a valid seed phrase");
let spend_key = SpendKey::from_seed_phrase_bip39(seed, 0);

let proto = spend_key.to_proto();
let spend_key_str = &bech32str::encode(
&proto.inner,
bech32str::spend_key::BECH32_PREFIX,
bech32str::Bech32m,
);

serde_wasm_bindgen::to_value(&spend_key_str).expect("able to serialize spend key")
}

#[wasm_bindgen]
pub fn get_full_viewing_key(spend_key_str: &str) -> JsValue {
utils::set_panic_hook();
let spend_key =
SpendKey::from_str(spend_key_str).expect("the provided string is a valid spend key");

let fvk: &FullViewingKey = spend_key.full_viewing_key();

let proto = pb::FullViewingKey::from(fvk.to_proto());

let fvk_str = &bech32str::encode(
&proto.inner,
bech32str::full_viewing_key::BECH32_PREFIX,
bech32str::Bech32m,
);
serde_wasm_bindgen::to_value(&fvk_str).expect("able to serialize full viewing key")
}

#[wasm_bindgen]
pub fn get_address_by_index(full_viewing_key: &str, index: u32) -> JsValue {
utils::set_panic_hook();
let fvk = FullViewingKey::from_str(full_viewing_key.as_ref())
.expect("the provided string is a valid FullViewingKey");

let (address, _dtk) = fvk.incoming().payment_address(index.into());

let proto = address.to_proto();
let address_str = &bech32str::encode(
&proto.inner,
bech32str::address::BECH32_PREFIX,
bech32str::Bech32m,
);

serde_wasm_bindgen::to_value(&address_str).expect("able to serialize address")
}

#[wasm_bindgen]
pub fn base64_to_bech32(prefix: &str, base64_str: &str) -> JsValue {
utils::set_panic_hook();

let bech32 = &bech32str::encode(
&base64::Engine::decode(&base64::engine::general_purpose::STANDARD, base64_str)
.expect("the provided string is a valid base64 string"),
prefix,
bech32str::Bech32m,
);
serde_wasm_bindgen::to_value(bech32).expect("able to serialize bech32 string")
}
#[wasm_bindgen]
pub fn is_controlled_address(full_viewing_key: &str, address: &str) -> JsValue {
utils::set_panic_hook();
let fvk = FullViewingKey::from_str(full_viewing_key.as_ref())
.expect("the provided string is a valid FullViewingKey");

let index = fvk.address_index(&Address::from_str(address.as_ref()).expect("valid address"));

serde_wasm_bindgen::to_value(&index).expect("able to serialize address index")
}

#[wasm_bindgen]
pub fn get_short_address_by_index(full_viewing_key: &str, index: u32) -> JsValue {
utils::set_panic_hook();
let fvk = FullViewingKey::from_str(full_viewing_key.as_ref())
.expect("The provided string is not a valid FullViewingKey");

let (address, _dtk) = fvk.incoming().payment_address(index.into());
let short_address = address.display_short_form();
serde_wasm_bindgen::to_value(&short_address).expect("able to serialize address")
}

#[wasm_bindgen]
pub fn decode_transaction(tx_bytes: &str) -> JsValue {
utils::set_panic_hook();
let tx_vec: Vec<u8> =
base64::Engine::decode(&base64::engine::general_purpose::STANDARD, tx_bytes)
.expect("the provided tx string is a valid base64 string");
let transaction: Transaction =
Transaction::try_from(tx_vec).expect("the provided tx string is a valid transaction");
serde_wasm_bindgen::to_value(&transaction).expect("able to serialize transaction")
}

#[wasm_bindgen]
pub fn decode_nct_root(tx_bytes: &str) -> JsValue {
utils::set_panic_hook();
let tx_vec: Vec<u8> =
hex::decode(tx_bytes).expect("the provided tx string is a valid hex string");
let root = penumbra_tct::Root::decode(tx_vec.as_slice())
.expect("the provided tx string is a valid nct root");
serde_wasm_bindgen::to_value(&root).expect("able to serialize nct root")
}
3 changes: 1 addition & 2 deletions crates/wasm/src/note_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ use penumbra_proto::{view::v1alpha1 as pb, DomainType, TypeUrl};
use penumbra_sct::Nullifier;
use penumbra_shielded_pool::{note, Note};
use penumbra_tct as tct;
use std::convert::{TryFrom, TryInto};

use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};

/// Corresponds to the SpendableNoteRecord proto
#[derive(Serialize, Deserialize, Debug, Clone)]
Expand Down
Loading