-
Notifications
You must be signed in to change notification settings - Fork 305
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(wasm)!: refactor transaction planner and view (#2973)
Collects several improvements to the WASM interface for building transactions via the planner: * added wasm planner which allows more flexible creation of TransactionPlan on TS side * added logic of storing and reading advice when scanning blocks * added function for generating ephemeral address * planner reads data from indexedDB directly from rust Co-authored-by: Conor Schaefer <[email protected]> Co-authored-by: Valentine <[email protected]>
- Loading branch information
1 parent
081fe52
commit cb57996
Showing
12 changed files
with
1,135 additions
and
853 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | ||
#[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)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.