Skip to content

Commit

Permalink
Refactor controller deployment flow
Browse files Browse the repository at this point in the history
  • Loading branch information
tarrencev committed Aug 30, 2024
1 parent d5f75a4 commit 824483c
Show file tree
Hide file tree
Showing 22 changed files with 784 additions and 523 deletions.
192 changes: 192 additions & 0 deletions packages/account-wasm/src/backend.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
use async_trait::async_trait;

use account_sdk::{
controller::Backend,
signers::{webauthn::WebauthnBackend, DeviceError},
storage::{StorageBackend, StorageError, StorageValue},
OriginProvider,
};
use futures::channel::oneshot;
use serde_json::to_value;
use wasm_bindgen::UnwrapThrowExt;
use wasm_bindgen_futures::{spawn_local, JsFuture};
use web_sys::{console, Window};
use webauthn_rs_proto::{
auth::PublicKeyCredentialRequestOptions, CreationChallengeResponse, PublicKeyCredential,
PublicKeyCredentialCreationOptions, RegisterPublicKeyCredential, RequestChallengeResponse,
};

pub fn window() -> Window {
web_sys::window().expect("Unable to retrieve window")
}

#[derive(Debug, Clone)]
pub struct BrowserBackend;

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl Backend for BrowserBackend {}

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send), )]
impl WebauthnBackend for BrowserBackend {
async fn get_assertion(
&self,
options: PublicKeyCredentialRequestOptions,
) -> Result<PublicKeyCredential, DeviceError> {
let (tx, rx) = oneshot::channel();
spawn_local(async move {
let credentials = window().navigator().credentials();
let promise = credentials
.get_with_options(
&RequestChallengeResponse {
public_key: options,
mediation: None,
}
.into(),
)
.unwrap_throw();

match JsFuture::from(promise).await {
Ok(jsval) => {
let result =
PublicKeyCredential::from(web_sys::PublicKeyCredential::from(jsval));

let value = to_value(&result.response.client_data_json).unwrap_throw();
console::debug_1(&format!("client_data_json: {:#?}", value).into());
let _ = tx.send(Ok(result));
}
Err(e) => {
let _ = tx.send(Err(DeviceError::GetAssertion(format!("{:?}", e))));
}
}
});

rx.await.unwrap_or(Err(DeviceError::Channel(
"get_assertion receiver dropped".to_string(),
)))
}

async fn create_credential(
options: PublicKeyCredentialCreationOptions,
) -> Result<RegisterPublicKeyCredential, DeviceError> {
let (tx, rx) = oneshot::channel();

spawn_local(async move {
let promise = window()
.navigator()
.credentials()
.create_with_options(
&CreationChallengeResponse {
public_key: options,
}
.into(),
)
.unwrap_throw();

match JsFuture::from(promise).await {
Ok(jsval) => {
let result = RegisterPublicKeyCredential::from(
web_sys::PublicKeyCredential::from(jsval),
);

let value = to_value(result.response.client_data_json.clone()).unwrap_throw();
console::debug_1(&format!("client_data_json:{:#?}", value).into());

let _ = tx.send(Ok(result));
}
Err(_e) => {
let _ = tx.send(Err(DeviceError::CreateCredential("".to_string())));
}
}
});

rx.await.unwrap_or(Err(DeviceError::Channel(
"credential receiver dropped".to_string(),
)))
}
}

#[cfg(target_arch = "wasm32")]
impl OriginProvider for BrowserBackend {
fn origin() -> Result<String, DeviceError> {
let origin = window()
.location()
.origin()
.map_err(|_| DeviceError::Origin("Unable to get origin".to_string()))?;
Ok(origin)
}
}

#[cfg(not(target_arch = "wasm32"))]
impl OriginProvider for BrowserBackend {
fn origin() -> Result<String, DeviceError> {
Ok("http://localhost:3001".to_string())
}
}

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait)]
impl StorageBackend for BrowserBackend {
fn set(&mut self, key: &str, value: &StorageValue) -> Result<(), StorageError> {
let local_storage = Self::local_storage()?;

let serialized = serde_json::to_string(value)?;
local_storage
.set_item(key, &serialized)
.map_err(|_| StorageError::OperationFailed("setting item in localStorage".into()))?;
Ok(())
}

fn get(&self, key: &str) -> Result<Option<StorageValue>, StorageError> {
let local_storage = Self::local_storage()?;

if let Ok(Some(value)) = local_storage.get_item(key) {
let deserialized = serde_json::from_str(&value)?;
Ok(Some(deserialized))
} else {
Ok(None)
}
}

fn remove(&mut self, key: &str) -> Result<(), StorageError> {
let local_storage = Self::local_storage()?;

local_storage
.remove_item(key)
.map_err(|_| StorageError::OperationFailed("removing item from localStorage".into()))?;
Ok(())
}

fn clear(&mut self) -> Result<(), StorageError> {
let local_storage = Self::local_storage()?;

local_storage
.clear()
.map_err(|_| StorageError::OperationFailed("clearing localStorage".into()))?;
Ok(())
}

fn keys(&self) -> Result<Vec<String>, StorageError> {
let local_storage = Self::local_storage()?;
let length = local_storage
.length()
.map_err(|_| StorageError::OperationFailed("getting localStorage length".into()))?;
let mut keys = Vec::new();
for i in 0..length {
if let Ok(Some(key)) = local_storage.key(i) {
keys.push(key);
}
}
Ok(keys)
}
}

impl BrowserBackend {
fn local_storage() -> Result<web_sys::Storage, StorageError> {
window()
.local_storage()
.map_err(|_| StorageError::OperationFailed("Failed to get localStorage".into()))?
.ok_or_else(|| StorageError::OperationFailed("localStorage not available".into()))
}
}
3 changes: 3 additions & 0 deletions packages/account-wasm/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ pub enum OperationError {
#[error("Failed to sign message: {0}")]
SignMessage(SignError),

#[error("Unsupported signer type")]
UnsupportedSignerType,

#[error(transparent)]
AccountError(#[from] AccountError<SignError>),
}
Expand Down
46 changes: 46 additions & 0 deletions packages/account-wasm/src/factory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use account_sdk::{
constants::ACCOUNT_CLASS_HASH, factory::ControllerFactory, provider::CartridgeJsonRpcProvider,
signers::webauthn::WebauthnSigner,
};
use starknet::{core::types::Felt, signers::SigningKey};
use std::{str::FromStr, sync::Arc};
use wasm_bindgen::prelude::*;

use crate::{signer::BrowserBackend, CartridgeAccount};

#[wasm_bindgen]
pub struct JsControllerFactory {
inner: ControllerFactory<S, Arc<CartridgeJsonRpcProvider>>,
}

#[wasm_bindgen]
impl JsControllerFactory {
pub fn new(inner: ControllerFactory<S, Arc<CartridgeJsonRpcProvider>>) -> Self {
Self { inner }
}

#[wasm_bindgen(js_name = deploy)]
pub async fn deploy(
&self,
account: &CartridgeAccount,
max_fee: String,
) -> Result<JsValue, JsValue> {
self.inner.deploy()
}
}

#[wasm_bindgen]
impl CartridgeAccount {
#[wasm_bindgen(js_name = getFactory)]
pub fn factory(&self) -> JsControllerFactory {
let factory = ControllerFactory::new(
ACCOUNT_CLASS_HASH,
self.controller.chain_id,
self.controller.owner.clone(),
None,
self.controller.provider.clone(),
);

JsControllerFactory::new(factory)
}
}
62 changes: 32 additions & 30 deletions packages/account-wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod backend;
mod errors;
// mod factory;
mod signer;
mod storage;
mod types;
mod utils;

Expand All @@ -16,11 +17,12 @@ use account_sdk::controller::Controller;
use account_sdk::provider::CartridgeJsonRpcProvider;
use account_sdk::signers::webauthn::{CredentialID, WebauthnSigner};
use account_sdk::signers::HashSigner;
use backend::BrowserBackend;
use base64::engine::general_purpose;
use base64::Engine;
use coset::{CborSerializable, CoseKey};
use serde_wasm_bindgen::{from_value, to_value};
use signer::BrowserBackend;
use signer::{Signer, SignerType};
use starknet::accounts::Account;
use starknet::core::types::Call;
use starknet::macros::short_string;
Expand All @@ -42,12 +44,8 @@ type Result<T> = std::result::Result<T, JsError>;

#[wasm_bindgen]
pub struct CartridgeAccount {
controller: Controller<
Arc<CartridgeJsonRpcProvider>,
WebauthnSigner<BrowserBackend>,
SigningKey,
BrowserBackend,
>,
controller:
Controller<Arc<CartridgeJsonRpcProvider>, Signer, BrowserBackend>,
}

#[wasm_bindgen]
Expand All @@ -73,21 +71,27 @@ impl CartridgeAccount {
address: JsFelt,
rp_id: String,
username: String,
credential_id: String,
public_key: String,
owner: Signer,
) -> Result<CartridgeAccount> {
set_panic_hook();

let rpc_url = Url::parse(&rpc_url)?;
let provider = CartridgeJsonRpcProvider::new(rpc_url.clone());

let credential_id_bytes = general_purpose::URL_SAFE_NO_PAD.decode(credential_id)?;
let credential_id = CredentialID::from(credential_id_bytes);
let device_signer = match owner.signer_type {
SignerType::Webauthn => {
let credential_id_bytes =
general_purpose::URL_SAFE_NO_PAD.decode(owner.credential_id.unwrap())?;
let credential_id = CredentialID::from(credential_id_bytes);

let cose_bytes = general_purpose::URL_SAFE_NO_PAD.decode(public_key)?;
let cose = CoseKey::from_slice(&cose_bytes)?;
let cose_bytes =
general_purpose::URL_SAFE_NO_PAD.decode(owner.public_key.unwrap())?;
let cose = CoseKey::from_slice(&cose_bytes)?;

let device_signer = WebauthnSigner::new(rp_id, credential_id, cose, BrowserBackend);
WebauthnSigner::new(rp_id, credential_id, cose, BrowserBackend)
}
_ => return Err(OperationError::UnsupportedSignerType.into()),
};

let dummy_guardian = SigningKey::from_secret_scalar(short_string!("CARTRIDGE_GUARDIAN"));
let username = username.to_lowercase();
Expand Down Expand Up @@ -267,20 +271,20 @@ impl CartridgeAccount {
Ok(Felts(signature.into_iter().map(JsFelt).collect()))
}

#[wasm_bindgen(js_name = deploySelf)]
pub async fn deploy_self(&self, max_fee: JsFelt) -> Result<JsValue> {
set_panic_hook();
// #[wasm_bindgen(js_name = deploySelf)]
// pub async fn deploy_self(&self, max_fee: JsValue) -> Result<JsValue> {
// set_panic_hook();

let res = self
.controller
.deploy()
.max_fee(max_fee.0)
.send()
.await
.map_err(|e| OperationError::Deployment(format!("{:#?}", e)))?;
// let res = self
// .controller
// .deploy()
// .max_fee(from_value(max_fee)?)
// .send()
// .await
// .map_err(|e| OperationError::Deployment(format!("{:#?}", e)))?;

Ok(to_value(&res)?)
}
// Ok(to_value(&res)?)
// }

#[wasm_bindgen(js_name = delegateAccount)]
pub async fn delegate_account(&self) -> Result<JsFelt> {
Expand All @@ -297,9 +301,7 @@ impl CartridgeAccount {
}

#[wasm_bindgen]
pub struct CartridgeSessionAccount(
SessionAccount<Arc<CartridgeJsonRpcProvider>, SigningKey, SigningKey>,
);
pub struct CartridgeSessionAccount(SessionAccount<Arc<CartridgeJsonRpcProvider>, SigningKey>);

#[wasm_bindgen]
impl CartridgeSessionAccount {
Expand Down
Loading

0 comments on commit 824483c

Please sign in to comment.