diff --git a/libwallet/Cargo.toml b/libwallet/Cargo.toml index da3cc32..e4bac8f 100644 --- a/libwallet/Cargo.toml +++ b/libwallet/Cargo.toml @@ -15,14 +15,15 @@ serde = {version = "1.0", default-features = false, features = ["derive"], optio hmac = {version = "0.12.1", default-features = false, optional = true} pbkdf2 = {version = "0.11.0", default-features = false, optional = true} sha2 = {version = "0.10.2", default-features = false, optional = true} - -mnemonic = {package = "bip0039", version = "0.10.1", default-features = false, optional = true} - +pjs = { path = "../pjs-rs", optional = true, default-features = false } +mnemonic = { package = "bip0039", version = "0.10.1", default-features = false, optional = true} +sp-core = { version = "32.0.0", optional = true } rand_core = {version = "0.6.3", optional = true} # substrate related schnorrkel = {version = "0.11.4", default-features = false, optional = true}# soft derivation in no_std rand_chacha = {version = "0.3.1", default-features = false, optional = true} - +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +log = "0.4.17" # vault os keyring = {version = "1.1.2", optional = true} # vault pass @@ -35,7 +36,7 @@ serde_json = {version = "1.0", default-features = false, features = ["alloc"]} dirs = "4.0" [features] -# default = ["std", "substrate", "vault_simple", "mnemonic", "rand", "vault_pass", "vault_os", "util_pin"] +default = ["std", "substrate", "vault_simple", "mnemonic", "rand", "vault_pass", "vault_os", "util_pin", "vault_pjs"] rand = ["rand_core", "schnorrkel?/getrandom"] sr25519 = ["dep:schnorrkel"] std = [ @@ -46,6 +47,7 @@ util_pin = ["pbkdf2", "hmac", "sha2"] vault_os = ["keyring"] vault_pass = ["prs-lib"] vault_simple = [] +vault_pjs = ["pjs", "sp-core"] [workspace] members = [ diff --git a/libwallet/js/src/wallet.rs b/libwallet/js/src/wallet.rs index 9e346c0..cf9934f 100644 --- a/libwallet/js/src/wallet.rs +++ b/libwallet/js/src/wallet.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_wasm_bindgen::from_value; use wasm_bindgen::prelude::*; -use libwallet::{vault::Simple, Signer, Wallet}; +use libwallet::{vault::Simple, Account, Signer, Wallet}; #[derive(Serialize, Deserialize)] pub enum WalletConstructor { @@ -13,7 +13,7 @@ pub enum WalletConstructor { #[wasm_bindgen(inspectable)] pub struct JsWallet { phrase: String, - wallet: Wallet, + wallet: Wallet>, } #[wasm_bindgen] @@ -48,7 +48,7 @@ impl JsWallet { #[wasm_bindgen] pub async fn unlock(&mut self, credentials: JsValue) -> Result<(), JsValue> { - let credentials: ::Credentials = + let credentials: as libwallet::Vault>::Credentials = if credentials.is_null() || credentials.is_undefined() { None } else { @@ -56,7 +56,7 @@ impl JsWallet { }; self.wallet - .unlock(credentials) + .unlock(None, credentials) .await .map_err(|e| JsError::new(&e.to_string()))?; @@ -64,58 +64,50 @@ impl JsWallet { } #[wasm_bindgen(js_name = getAddress)] - pub fn get_address(&self) -> Result { + pub fn get_address(&self) -> Result { if self.wallet.is_locked() { return Err(JsError::new( "The wallet is locked. You should unlock it first by using the .unlock() method", )); } - Ok(JsPublicAddress::new( - self.wallet.default_account().public().as_ref().to_vec(), + Ok(JsBytes::new( + self.wallet.default_account().unwrap().public().as_ref().to_vec(), )) } #[wasm_bindgen] - pub fn sign(&self, message: &[u8]) -> Result, JsError> { + pub async fn sign(&self, message: &[u8]) -> Result { if self.wallet.is_locked() { return Err(JsError::new( "The wallet is locked. You should unlock it first by using the .unlock() method", )); } - let sig = self.wallet.sign(message); - - if !self - .wallet - .default_account() - .verify(&message, &sig.as_ref()) - { - return Err(JsError::new("Message could not be verified")); - } - - Ok(sig.as_ref().to_vec().into_boxed_slice()) + let sig = self.wallet.sign(message).await.map_err(|_| JsError::new("Error while signing"))?; + let f = sig.as_ref().to_vec(); + Ok(JsBytes::new(f)) } #[wasm_bindgen] - pub fn verify(&self, msg: &[u8], sig: &[u8]) -> Result { + pub async fn verify(&self, msg: &[u8], sig: &[u8]) -> Result { if self.wallet.is_locked() { return Err(JsError::new( "The wallet is locked. You should unlock it first by using the .unlock() method", )); } - Ok(self.wallet.default_account().verify(msg, sig)) + Ok(self.wallet.default_account().unwrap().verify(msg, sig).await) } } #[wasm_bindgen(inspectable)] -pub struct JsPublicAddress { +pub struct JsBytes { repr: Vec, } #[wasm_bindgen] -impl JsPublicAddress { +impl JsBytes { #[wasm_bindgen(constructor)] pub fn new(repr: Vec) -> Self { Self { repr } diff --git a/libwallet/src/key_pair.rs b/libwallet/src/key_pair.rs index 9004e82..3d13d5d 100644 --- a/libwallet/src/key_pair.rs +++ b/libwallet/src/key_pair.rs @@ -1,4 +1,4 @@ -use core::{convert::TryFrom, fmt::Debug}; +use core::{convert::{TryFrom, TryInto}, fmt::Debug}; pub use derive::Derive; @@ -20,7 +20,11 @@ pub trait Pair: Signer + Derive { pub trait Public: AsRef<[u8]> + Debug {} impl Public for Bytes {} -pub trait Signature: AsRef<[u8]> + Debug + PartialEq {} +pub trait Signature: AsRef<[u8]> + Debug + PartialEq { + fn as_bytes(&self) -> Bytes { + self.as_ref().try_into().expect("error getting the bytes") + } +} impl Signature for Bytes {} /// Something that can sign messages diff --git a/libwallet/src/vault.rs b/libwallet/src/vault.rs index e320904..d0bca22 100644 --- a/libwallet/src/vault.rs +++ b/libwallet/src/vault.rs @@ -1,16 +1,21 @@ //! Collection of supported Vault backends #[cfg(feature = "vault_os")] mod os; + +#[cfg(feature = "vault_pjs")] +pub mod pjs; + +#[cfg(feature = "vault_pjs")] +pub use pjs::*; + #[cfg(feature = "vault_pass")] mod pass; -mod simple; -#[cfg(feature = "vault_os")] -pub use os::*; #[cfg(feature = "vault_pass")] pub use pass::*; -pub use simple::*; +mod simple; +pub use simple::*; use crate::account::Account; /// Abstraction for storage of private keys that are protected by some credentials. diff --git a/libwallet/src/vault/pjs.rs b/libwallet/src/vault/pjs.rs new file mode 100644 index 0000000..43d03e6 --- /dev/null +++ b/libwallet/src/vault/pjs.rs @@ -0,0 +1,87 @@ +extern crate alloc; +use crate::{any::AnySignature, Account, Signer, Vault}; +use alloc::vec::Vec; +use pjs::{Account as PjsAccount, Error, PjsExtension}; +use log; +use hex; +use sp_core::crypto::{AccountId32, Ss58Codec}; + +#[derive(Clone)] +pub struct Pjs { + inner: PjsExtension, +} + +impl Pjs { + pub async fn connect(name: &str) -> Result { + Ok(Pjs { + inner: PjsExtension::connect(name).await?, + }) + } + + pub async fn list_accounts(&mut self) -> Result, Error> { + self.inner.fetch_accounts().await?; + Ok(self.inner.accounts()) + } + + pub fn select_account(&mut self, idx: u8) { + self.inner.select_account(idx) + } +} + +impl Signer for Pjs { + type Signature = AnySignature; + + async fn sign_msg(&self, msg: impl AsRef<[u8]>) -> Result { + log::info!("signing: {}", hex::encode(&msg.as_ref())); + let sig = self.inner.sign(msg.as_ref()).await.map_err(|_| ())?; + log::info!("signature {:?}", hex::encode(&sig)); + Ok(AnySignature::from(sig)) + } + + async fn verify(&self, _: impl AsRef<[u8]>, _: impl AsRef<[u8]>) -> bool { + unimplemented!() + } +} + +impl Account for Pjs { + fn public(&self) -> impl crate::Public { + let mut key = [0u8; 32]; + + let address = self + .inner + .get_selected() + .expect("an account must be defined") + .address(); + + let address = ::from_string(&address) + .expect("it must be a valid ss58 address"); + key.copy_from_slice(address.as_ref()); + key + } +} + +impl core::fmt::Display for Pjs { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + for byte in self.public().as_ref() { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } +} + +impl Vault for Pjs { + type Id = u8; + type Credentials = (); + type Account = Pjs; + type Error = Error; + + async fn unlock( + &mut self, + account: Self::Id, + _: impl Into, + ) -> Result { + let mut pjs_signer = self.clone(); + pjs_signer.select_account(account); + Ok(pjs_signer) + } +} diff --git a/pjs-rs/Cargo.toml b/pjs-rs/Cargo.toml index 6bbb4ae..fc8e709 100644 --- a/pjs-rs/Cargo.toml +++ b/pjs-rs/Cargo.toml @@ -4,8 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] -wasm-bindgen = { version = "0.2.92", default-features = false } +wasm-bindgen = { version = "0.2.91", default-features = false } wasm-bindgen-futures = "0.4.42" +log = "0.4.19" +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } [dependencies.web-sys] version = "0.3.69" @@ -18,6 +20,4 @@ default = ["js"] js = [] [lib] -crate-type = ["cdylib"] - - +crate-type = ["cdylib", "lib"] diff --git a/pjs-rs/src/lib.rs b/pjs-rs/src/lib.rs index 19a4006..7b0f7c0 100644 --- a/pjs-rs/src/lib.rs +++ b/pjs-rs/src/lib.rs @@ -1,7 +1,7 @@ +use log; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; use web_sys::js_sys::{Array, Function, Object, Promise, Reflect}; - macro_rules! get { (^ $obj:expr, $($prop:expr),+ $(,)?) => {{ let val = get!($obj, $($prop),+); @@ -19,6 +19,7 @@ macro_rules! get { const NULL: JsValue = JsValue::null(); +#[derive(Clone)] #[cfg_attr(feature = "js", wasm_bindgen)] pub struct PjsExtension { pjs: JsValue, @@ -59,12 +60,13 @@ impl PjsExtension { } #[cfg_attr(feature = "js", wasm_bindgen(js_name = sign))] - pub async fn js_sign(&self, payload: &str, cb: &Function) -> Result { + pub async fn js_sign(&self, payload: &str) -> Result { let sign: Function = get!(^ &self.pjs, "signer", "signRaw"); let account = self .accounts .get(self.selected.ok_or(Error::NoAccountSelected)? as usize) .ok_or(Error::NoAccounts)?; + let data = { let o = Object::new(); Reflect::set(&o, &"address".into(), &account.address.as_str().into()).unwrap(); @@ -72,14 +74,17 @@ impl PjsExtension { Reflect::set(&o, &"type".into(), &"bytes".into()).unwrap(); o }; + log::info!("{:?}", data); let p = sign .call1(&NULL, &data.into()) .expect("promise") .unchecked_into::(); let signature = JsFuture::from(p).await.map_err(|_| Error::Sign)?; - let res = cb.call1(&NULL, &signature).map_err(|_| Error::Sign)?; - Ok(get!(&res, "signature")) + log::info!("Signature: {:?}", &signature); + // let res = cb.call1(&NULL, &signature).map_err(|_| Error::Sign)?; + // log::info!("{:?}", &res); + Ok(get!(&signature, "signature")) } #[cfg_attr(feature = "js", wasm_bindgen(js_name = fetchAccounts))] @@ -119,36 +124,41 @@ impl PjsExtension { impl PjsExtension { pub async fn sign(&self, payload: &[u8]) -> Result<[u8; 64], Error> { - let payload = Self::to_hex(payload); + let payload = format!("{}", hex::encode(payload)); let mut signature = [0u8; 64]; - let cb = Closure::wrap(Box::new(move |s: JsValue| { - Self::from_hex(s.as_string().unwrap_or_default().as_str(), &mut signature) - }) as Box); - self.js_sign(payload.as_str(), cb.as_ref().unchecked_ref()) - .await?; + // let cb: Closure = Closure::wrap(Box::new(move |s: JsValue| { + // log::info!("Signature received {:?}", &s); + // let s = get!(&s, "signature"); + // let s = s.as_string(); + // let f = s.unwrap_or_default(); + // log::info!("final {:?}", &f); + // }) as Box); + + let sig = self.js_sign(payload.as_str()).await?; + log::info!("returned from pjs {:?}", &sig); + let s = sig.as_string(); + let f = s.unwrap_or_default(); + Self::from_hex(f.as_str(), &mut signature); + log::info!("after sign in extension {:?}", hex::encode(&signature)); Ok(signature) } - fn to_hex(bytes: &[u8]) -> String { - use std::fmt::Write; - let mut s = String::with_capacity(2 + bytes.len()); - let _ = write!(s, "0x"); - for b in bytes { - let _ = write!(s, "{b:x}"); - } - s - } + + fn from_hex(input: &str, buf: &mut [u8]) { for (i, b) in buf.iter_mut().enumerate() { - let Some(s) = input.get(i * 2..i * 2 + 2) else { + let Some(s) = input.get((i * 2 + 2)..(i * 2 + 4)) else { return; }; + log::info!("s({:?})", s); *b = u8::from_str_radix(s, 16).unwrap_or_default(); + log::info!("b({:?})", &b); } + log::info!("buf({:?})", &buf); } } -#[wasm_bindgen] +#[cfg_attr(feature = "js", wasm_bindgen)] #[derive(Debug)] pub enum Error { ExtensionUnavailable, @@ -159,7 +169,7 @@ pub enum Error { Sign, } -#[wasm_bindgen] +#[cfg_attr(feature = "js", wasm_bindgen)] #[derive(Debug, Clone)] pub struct Account { name: String, @@ -194,7 +204,7 @@ impl Account { } } -#[wasm_bindgen] +#[cfg_attr(feature = "js", wasm_bindgen)] #[derive(Debug, Clone, Copy)] pub enum Network { Generic,