From 3616dbb3a0b8b94c6b12ad740665706c428e2543 Mon Sep 17 00:00:00 2001 From: Nicolas Di Prima Date: Thu, 21 Jun 2018 04:29:12 +0200 Subject: [PATCH] add example of `wasm-bindgen` --- wallet-wasm2/Cargo.toml | 17 ++ wallet-wasm2/LICENSE | 18 ++ wallet-wasm2/README.md | 59 ++++++ wallet-wasm2/js-test/index.html | 11 + wallet-wasm2/js-test/index.js | 20 ++ wallet-wasm2/js-test/package.json | 13 ++ wallet-wasm2/js-test/webpack.config.js | 9 + wallet-wasm2/src/lib.rs | 268 +++++++++++++++++++++++++ 8 files changed, 415 insertions(+) create mode 100644 wallet-wasm2/Cargo.toml create mode 100644 wallet-wasm2/LICENSE create mode 100644 wallet-wasm2/README.md create mode 100644 wallet-wasm2/js-test/index.html create mode 100644 wallet-wasm2/js-test/index.js create mode 100644 wallet-wasm2/js-test/package.json create mode 100644 wallet-wasm2/js-test/webpack.config.js create mode 100644 wallet-wasm2/src/lib.rs diff --git a/wallet-wasm2/Cargo.toml b/wallet-wasm2/Cargo.toml new file mode 100644 index 0000000..0eee3fe --- /dev/null +++ b/wallet-wasm2/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "wallet-wasm2" +version = "0.1.0" +authors = [ "Nicolas Di Prima " + , "Vincent Hanquez " + ] +description = "JS/Wasm binding for Cardano's Wallet rust library." +repository = "https://github.com/input-output-hk/js-cardano-wasm" +license = "MIT" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +raw_cbor = { path = "../rust/cbor" } +wallet-crypto = { path = "../rust/wallet-crypto" } +wasm-bindgen = "0.2" diff --git a/wallet-wasm2/LICENSE b/wallet-wasm2/LICENSE new file mode 100644 index 0000000..efa744e --- /dev/null +++ b/wallet-wasm2/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2018 IOHK + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/wallet-wasm2/README.md b/wallet-wasm2/README.md new file mode 100644 index 0000000..595dbd1 --- /dev/null +++ b/wallet-wasm2/README.md @@ -0,0 +1,59 @@ +# Wallet Wasm (using wasm-bindgen) + +This is an experimental js/wasm/rust binding of +[cardano-rust](https://github.com/input-output-hk/rust-cardano)'s `wallet-crypto` +crate. It uses the binding generator tool +[`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) and +[`wasm-pack`](https://github.com/rustwasm/wasm-pack). + +## Generate npm package + +We use `wasm-pack` to generate a NPM package: + +1. [install `rustup`](https://www.rust-lang.org/en-US/install.html): + ``` + $ curl https://sh.rustup.rs -sSf | sh + ``` +2. install `wasm-pack`: + ``` + $ cargo install wasm-pack + ``` +3. generate it all: + ``` + $ wasm-pack init . + ``` + +## Example + +see [js-test](./js-test) for an example of use: + +``` +$ cd js-test +$ npm install +$ npm run serve +``` + +open your browser to: http://localhost:8080/index.html and look at the JSON logs. + +## features + +Here we strive to remove some of the hand-written functions that came with limitations +regarding: allocating more memory than needed for output, using JSON +RPC style... + +Instead, we expose a collection of objects: + +- private key; +- public key; +- payload; + +```js +const loadModule = import('wallet-wasm2/wallet_wasm2.js'); + +loadModule.then(Cardano => { + const MNEMONICS = "town refuse ribbon antenna average enough crumble ice fashion giant question glance"; + + // retrieve a root private key + let root_xprv = Cardano.XPrv.from_daedalus_mnemonics(MNEMONICS); +}); +``` diff --git a/wallet-wasm2/js-test/index.html b/wallet-wasm2/js-test/index.html new file mode 100644 index 0000000..5632e61 --- /dev/null +++ b/wallet-wasm2/js-test/index.html @@ -0,0 +1,11 @@ + + + + + cardano wallet example + + + + + + diff --git a/wallet-wasm2/js-test/index.js b/wallet-wasm2/js-test/index.js new file mode 100644 index 0000000..6037f01 --- /dev/null +++ b/wallet-wasm2/js-test/index.js @@ -0,0 +1,20 @@ +const loadModule = import("wallet-wasm2/wallet_wasm2.js"); + +loadModule.then(Cardano => { + const MNEMONICS = "town refuse ribbon antenna average enough crumble ice fashion giant question glance"; + const PATH = [0x8000000, 0x8000000]; + + console.log("retrieving root_xprv from a daedalus mnemonic", MNEMONICS); + let root_xprv = Cardano.XPrv.from_daedalus_mnemonics(MNEMONICS); + let root_xpub = root_xprv.public(); + + console.log("daedalus derivation from derivation path", PATH); + let xprv = root_xprv; + PATH.forEach(index => xprv = xprv.derive_v1(index)); + let xpub = xprv.public(); + console.log("daedalus payload"); + let payload = Cardano.Payload.new(root_xpub, PATH); + + let addr = xpub.to_adddress_with_payload(payload); + console.log("Daedalus Address", addr.to_base58()); +}); diff --git a/wallet-wasm2/js-test/package.json b/wallet-wasm2/js-test/package.json new file mode 100644 index 0000000..a6d14e9 --- /dev/null +++ b/wallet-wasm2/js-test/package.json @@ -0,0 +1,13 @@ +{ + "scripts": { + "serve": "webpack-dev-server" + }, + "dependencies": { + "wallet-wasm2": "file:../pkg" + }, + "devDependencies": { + "webpack": "^4.0.1", + "webpack-cli": "^2.0.10", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/wallet-wasm2/js-test/webpack.config.js b/wallet-wasm2/js-test/webpack.config.js new file mode 100644 index 0000000..3e3afe6 --- /dev/null +++ b/wallet-wasm2/js-test/webpack.config.js @@ -0,0 +1,9 @@ +const path = require('path'); +module.exports = { + entry: "./index.js", + output: { + path: path.resolve(__dirname, "dist"), + filename: "index.js", + }, + mode: "development" +}; diff --git a/wallet-wasm2/src/lib.rs b/wallet-wasm2/src/lib.rs new file mode 100644 index 0000000..0590839 --- /dev/null +++ b/wallet-wasm2/src/lib.rs @@ -0,0 +1,268 @@ +#![feature(proc_macro, wasm_custom_section, wasm_import_module)] + +extern crate wasm_bindgen; +extern crate wallet_crypto; +#[macro_use] +extern crate raw_cbor; + +use wasm_bindgen::prelude::*; + +use wallet_crypto::{*}; + +#[wasm_bindgen] +extern { + // import JS's `console.error` function so we can use it in the `handle_result' + // macro to report error happened before panic. + #[wasm_bindgen(js_namespace = console)] + fn error(s: &str); +} + +// macro to handle `Result`. This is because it is not possible to return +// an enum to JS yet. This macro may change if we find out how to handle +// error in a better way. +macro_rules! handle_result { + ($r:expr) => ({ + match $r { + Err(err) => { + error(&format!("{}:{}: `{}'\n /!\\ {:?}", file!(), line!(), stringify!($r), err)); + panic!("function failed...") + }, + Ok(k) => k + } + }) +} + +// expose the main features of the HDWallet's XPrv. +#[wasm_bindgen] +pub struct XPrv(hdwallet::XPrv); +#[wasm_bindgen] +impl XPrv { + // this is a bit of a kitchen sink function and may be changed in the future + pub fn from_daedalus_mnemonics(mnemonics: &str) -> Self { + let mnemonics = handle_result!(bip39::Mnemonics::from_string(&bip39::dictionary::ENGLISH, mnemonics)); + let entropy = handle_result!(bip39::Entropy::from_mnemonics(&mnemonics)); + + let entropy_cbor = handle_result!(cbor!(entropy.as_ref())); + let seed = hash::Blake2b256::new(entropy_cbor.as_ref()); + let seed = handle_result!(cbor!(seed.as_ref())); + let xprv = hdwallet::XPrv::generate_from_daedalus_seed(&seed); + + XPrv(xprv) + } + + pub fn from_seed(seed: &[u8]) -> Self { + let seed = handle_result!(hdwallet::Seed::from_slice(seed)); + XPrv(hdwallet::XPrv::generate_from_seed(&seed)) + } + + pub fn from_slice(bytes: &[u8]) -> Self { + let xprv = handle_result!(hdwallet::XPrv::from_slice(bytes)); + XPrv(xprv) + } + + pub fn to_hex(&self) -> String { + util::hex::encode(self.0.as_ref()) + } + + pub fn from_hex(hex: &str) -> Self { + let bytes = handle_result!(util::hex::decode(hex)); + let xprv = handle_result!(hdwallet::XPrv::from_slice(&bytes)); + XPrv(xprv) + } + + pub fn public(&self) -> XPub { + XPub(self.0.public()) + } + + pub fn derive_v1(&self, index: u32) -> Self { + XPrv(self.0.derive(hdwallet::DerivationScheme::V1, index)) + } + + pub fn derive_v2(&self, index: u32) -> Self { + XPrv(self.0.derive(hdwallet::DerivationScheme::V2, index)) + } + + pub fn sign_str(&self, msg: &str) -> Signature { + Signature(self.0.sign(msg.as_bytes())) + } + + pub fn sign(&self, msg: &[u8]) -> Signature { + Signature(self.0.sign(msg)) + } +} + +#[wasm_bindgen] +pub struct XPub(hdwallet::XPub); +#[wasm_bindgen] +impl XPub { + pub fn from_slice(bytes: &[u8]) -> Self { + let xpub = handle_result!(hdwallet::XPub::from_slice(bytes)); + XPub(xpub) + } + + pub fn to_hex(&self) -> String { + util::hex::encode(self.0.as_ref()) + } + + pub fn from_hex(hex: &str) -> Self { + let bytes = handle_result!(util::hex::decode(hex)); + let xpub = handle_result!(hdwallet::XPub::from_slice(&bytes)); + XPub(xpub) + } + + pub fn derive_v1(&self, index: u32) -> Self { + XPub(handle_result!(self.0.derive(hdwallet::DerivationScheme::V1 ,index))) + } + + pub fn derive_v2(&self, index: u32) -> Self { + XPub(handle_result!(self.0.derive(hdwallet::DerivationScheme::V2 ,index))) + } + + pub fn verify_str(&self, signature: &Signature, msg: &str) -> bool { + self.0.verify(msg.as_bytes(), &signature.0) + } + pub fn verify(&self, signature: &Signature, msg: &[u8]) -> bool { + self.0.verify(&msg, &signature.0) + } + + pub fn to_adddress(&self) -> Address { + let addr_type = address::AddrType::ATPubKey; + let sd = address::SpendingData::PubKeyASD(self.0.clone()); + let attrs = address::Attributes::new_bootstrap_era(None); + + Address(address::ExtendedAddr::new(addr_type, sd, attrs)) + } + + pub fn to_adddress_with_payload(&self, payload: &Payload) -> Address { + let addr_type = address::AddrType::ATPubKey; + let sd = address::SpendingData::PubKeyASD(self.0.clone()); + let attrs = address::Attributes::new_bootstrap_era(Some(payload.0.clone())); + + Address(address::ExtendedAddr::new(addr_type, sd, attrs)) + } +} + +#[wasm_bindgen] +pub struct Signature(hdwallet::Signature>); +#[wasm_bindgen] +impl Signature { + pub fn to_hex(&self) -> String { + util::hex::encode(self.0.as_ref()) + } + + pub fn from_hex(hex: &str) -> Self { + let bytes = handle_result!(util::hex::decode(hex)); + let signature = handle_result!(hdwallet::Signature::from_slice(&bytes)); + Signature(signature) + } +} + +#[wasm_bindgen] +pub struct Payload(hdpayload::HDAddressPayload); +#[wasm_bindgen] +impl Payload { + pub fn from_slice(bytes: &[u8]) -> Self { + Payload(hdpayload::HDAddressPayload::from_bytes(bytes)) + } + + pub fn to_hex(&self) -> String { + let r = util::hex::encode(self.0.as_ref()); + r + } + + pub fn from_hex(hex: &str) -> Self { + let bytes = handle_result!(util::hex::decode(hex)); + Payload(hdpayload::HDAddressPayload::from_bytes(&bytes)) + } + + pub fn new(xpub: &XPub, path: &[u32]) -> Self { + let key = hdpayload::HDKey::new(&xpub.0); + let path = hdpayload::Path::new(Vec::from(path)); + Payload(key.encrypt_path(&path)) + } +} + +#[wasm_bindgen] +pub struct Address(address::ExtendedAddr); +#[wasm_bindgen] +impl Address { + pub fn to_base58(&self) -> String { + util::base58::encode(&self.0.to_bytes()) + } + + pub fn from_base58(addr: &str) -> Self { + let bytes = handle_result!(util::base58::decode(addr)); + let addr = handle_result!(raw_cbor::de::RawCbor::from(&bytes).deserialize()); + Address(addr) + } + + pub fn from_slice(bytes: &[u8]) -> Self { + let addr = handle_result!(raw_cbor::de::RawCbor::from(bytes).deserialize()); + Address(addr) + } + + pub fn has_payload(&self) -> bool { + self.0.attributes.derivation_path.is_some() + } + + pub fn get_payload(&self) -> Payload { + match &self.0.attributes.derivation_path { + Some(ref dp) => Payload(dp.clone()), + None => { + error("This Address has no derivation path, use `has_payload` to check presence of dervation path first"); + panic!("no derivation path") + } + } + } +} + +#[wasm_bindgen] +pub struct Addresses(Vec); +#[wasm_bindgen] +impl Addresses { + pub fn new() -> Self { Addresses(Vec::new()) } + pub fn push(&mut self, s: &str) { self.0.push(s.to_owned())} + pub fn len(&self) -> u32 { self.0.len() as u32 } + pub fn is_empty(&self) -> bool { self.0.is_empty() } + pub fn pop(&mut self) -> String { + match self.0.pop() { + Some(addr) => addr, + None => { + error("No more addresses, use `is_empty` before calling `pop` to prevent this error."); + panic!("No more addresses...") + } + } + } +} + +#[wasm_bindgen] +pub struct RandomAddressChecker { + key: hdpayload::HDKey, +} +#[wasm_bindgen] +impl RandomAddressChecker { + pub fn new(prv: XPrv) -> Self { + let xprv = prv.0.clone(); + let xpub = xprv.public(); + let key = hdpayload::HDKey::new(&xpub); + RandomAddressChecker { key: key } + } + + pub fn check_addresses(&self, addr: &Addresses) -> Addresses { + Addresses(addr.0.iter().filter(|addr| self.check_address_base58(&addr)).cloned().collect()) + } + + pub fn check_address_base58(&self, base58_addr: &str) -> bool { + self.check_address(&Address::from_base58(base58_addr)) + } + + pub fn check_address(&self, ref_addr: &Address) -> bool { + let address = &ref_addr.0; + + if let Some(ref dp) = address.attributes.derivation_path { + self.key.decrypt_path(dp).is_some() + } else { + false + } + } +}