From 82898da1db65cd22daedecde2f668c85328b3194 Mon Sep 17 00:00:00 2001 From: nickwest Date: Wed, 13 Dec 2023 22:55:31 -0800 Subject: [PATCH] support starknet wallets for sdk --- bindings/wasm/Cargo.toml | 2 +- bindings/wasm/src/json_rpc_signer.rs | 21 +++-- bindings/wasm/src/rpc_type_converter.rs | 4 +- .../Javascript/js-example/1_change_pubkey.js | 2 +- .../node-example/3_update_global_var.js | 2 +- interface/src/json_rpc_signer.rs | 90 ++++++++++++++----- interface/src/sign_transfer.rs | 30 ++++++- interface/src/sign_withdraw.rs | 27 +++++- interface/src/signer.rs | 4 +- signers/Cargo.toml | 2 + signers/src/eth_signer/json_rpc_signer.rs | 4 +- .../src/starknet_signer/ecdsa_signature.rs | 23 ++++- signers/src/starknet_signer/mod.rs | 3 + signers/src/starknet_signer/pk_signer.rs | 43 +++++++-- .../starknet_json_rpc_signer.rs | 55 ++++++++++++ .../src/starknet_signer/typed_data/message.rs | 23 +++++ signers/src/starknet_signer/typed_data/mod.rs | 80 +++++++++++++++++ signers/src/zklink_signer/pk_signer.rs | 20 ++++- types/src/signatures.rs | 7 +- types/src/tx_type/mod.rs | 21 +++++ types/src/tx_type/transfer.rs | 27 ++++-- types/src/tx_type/withdraw.rs | 15 +++- 22 files changed, 438 insertions(+), 67 deletions(-) create mode 100644 signers/src/starknet_signer/starknet_json_rpc_signer.rs create mode 100644 signers/src/starknet_signer/typed_data/message.rs create mode 100644 signers/src/starknet_signer/typed_data/mod.rs diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index b18d1ec4..3666c25e 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -33,7 +33,7 @@ ffi = [] web = ["zklink_sdk_interface/web","zklink_sdk_signers/web"] [dev-dependencies] -wasm-bindgen-test = "0.3" +wasm-bindgen-test = "0.3.39" [package.metadata.wasm-pack.profile.release] wasm-opt = false diff --git a/bindings/wasm/src/json_rpc_signer.rs b/bindings/wasm/src/json_rpc_signer.rs index ed3d9741..ed9eb7ea 100755 --- a/bindings/wasm/src/json_rpc_signer.rs +++ b/bindings/wasm/src/json_rpc_signer.rs @@ -17,19 +17,30 @@ use zklink_sdk_types::tx_type::order_matching::{ use zklink_sdk_types::tx_type::transfer::Transfer as TxTransfer; use zklink_sdk_types::tx_type::withdraw::Withdraw as TxWithdraw; use zklink_sdk_types::tx_type::zklink_tx::ZkLinkTx; +use zklink_sdk_signers::starknet_signer::starknet_json_rpc_signer::Signer; +use zklink_sdk_interface::json_rpc_signer::JsonRpcProvider; #[wasm_bindgen] pub struct JsonRpcSigner { inner: InterfaceJsonRpcSigner, } +//#[wasm_bindgen(constructor)] +#[wasm_bindgen(js_name=newRpcSignerWtihProvider)] +pub fn new_with_provider(provider: Provider) -> Result { + let inner = InterfaceJsonRpcSigner::new(JsonRpcProvider::Provider(provider))?; + Ok(JsonRpcSigner { inner }) +} + +//#[wasm_bindgen(constructor)] +#[wasm_bindgen(js_name=newRpcSignerWithSigner)] +pub fn new_with_signer(signer: Signer) -> Result { + let inner = InterfaceJsonRpcSigner::new(JsonRpcProvider::Signer(signer))?; + Ok(JsonRpcSigner { inner }) +} + #[wasm_bindgen] impl JsonRpcSigner { - #[wasm_bindgen(constructor)] - pub fn new(provider: Provider) -> Result { - let inner = InterfaceJsonRpcSigner::new(provider)?; - Ok(JsonRpcSigner { inner }) - } #[wasm_bindgen(js_name = initZklinkSigner)] pub async fn init_zklink_signer(&mut self, signature: Option) -> Result<(), JsValue> { diff --git a/bindings/wasm/src/rpc_type_converter.rs b/bindings/wasm/src/rpc_type_converter.rs index 1cbc7bc0..04b1abbe 100644 --- a/bindings/wasm/src/rpc_type_converter.rs +++ b/bindings/wasm/src/rpc_type_converter.rs @@ -3,7 +3,7 @@ use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; use zklink_sdk_provider::response::AccountQuery as RpcAccountQuery; use zklink_sdk_signers::eth_signer::{EIP1271Signature, PackedEthSignature}; -use zklink_sdk_signers::starknet_signer::StarkECDSASignature; +use zklink_sdk_signers::starknet_signer::{StarkECDSASignature, StarkSignature}; use zklink_sdk_signers::zklink_signer::PackedSignature; use zklink_sdk_types::basic_types::AccountId; use zklink_sdk_types::prelude::ZkLinkSignature; @@ -125,7 +125,7 @@ impl TryFrom for TypesTxLayer1Signature { ))) } L1SignatureType::Stark => { - let signature = StarkECDSASignature::from_hex(&signature.signature) + let signature = StarkSignature::from_hex(&signature.signature) .map_err(|e| JsValue::from_str(&format!("error: {e}")))?; Ok(TypesTxLayer1Signature::StarkSignature(signature)) diff --git a/examples/Javascript/js-example/1_change_pubkey.js b/examples/Javascript/js-example/1_change_pubkey.js index 29372ac7..ecb8a937 100755 --- a/examples/Javascript/js-example/1_change_pubkey.js +++ b/examples/Javascript/js-example/1_change_pubkey.js @@ -19,7 +19,7 @@ async function testEcdsaAuth() { const provider = window.bitkeep && window.bitkeep.ethereum; await provider.request({ method: 'eth_requestAccounts' }); - const signer = new wasm.JsonRpcSigner(provider); + const signer = new wasm.newW(provider); // use cached ethereum signature to init zklink signer //const signature = "0x1111111111"; diff --git a/examples/Javascript/node-example/3_update_global_var.js b/examples/Javascript/node-example/3_update_global_var.js index e8090d75..c4e24d1d 100644 --- a/examples/Javascript/node-example/3_update_global_var.js +++ b/examples/Javascript/node-example/3_update_global_var.js @@ -31,7 +31,7 @@ async function testUpdGlobalVar() { const parameter = new Parameter(ParameterType.MarginInfo,margin_info) console.log(parameter); - let tx_builder = new UpdateGlobalVarBuilder(1,8,parameter,1000); + let tx_builder = new UpdateGlobalVarBuilder(1,8,parameter_funding,1000); console.log(tx_builder); let tx = newUpdateGlobalVar(tx_builder); console.log(tx.jsonValue()); diff --git a/interface/src/json_rpc_signer.rs b/interface/src/json_rpc_signer.rs index e8a33ec9..e193fc89 100755 --- a/interface/src/json_rpc_signer.rs +++ b/interface/src/json_rpc_signer.rs @@ -3,12 +3,16 @@ use crate::error::SignError; use crate::sign_change_pubkey::do_sign_change_pubkey_with_create2data_auth; use crate::sign_forced_exit::sign_forced_exit; use crate::sign_order_matching::sign_order_matching; -use crate::sign_transfer::sign_eth_transfer; -use crate::sign_withdraw::sign_eth_withdraw; +use crate::sign_transfer::{sign_eth_transfer, sign_starknet_transfer}; +use crate::sign_withdraw::{sign_eth_withdraw, sign_starknet_withdraw}; use zklink_sdk_signers::eth_signer::json_rpc_signer::{ JsonRpcSigner as EthJsonRpcSigner, Provider, }; -use zklink_sdk_signers::zklink_signer::{ZkLinkSignature, ZkLinkSigner}; +use zklink_sdk_signers::starknet_signer::starknet_json_rpc_signer::{ + StarknetJsonRpcSigner,Signer as StarknetAccountSigner +}; + +use zklink_sdk_signers::zklink_signer::{ZkLinkSignature, ZkLinkSigner, ZkSignerError}; use zklink_sdk_types::prelude::PackedEthSignature; use zklink_sdk_types::signatures::TxSignature; use zklink_sdk_types::tx_type::change_pubkey::{ChangePubKey, ChangePubKeyAuthData, Create2Data}; @@ -18,15 +22,33 @@ use zklink_sdk_types::tx_type::transfer::Transfer; use zklink_sdk_types::tx_type::withdraw::Withdraw; use zklink_sdk_types::tx_type::zklink_tx::ZkLinkTx; use zklink_sdk_types::tx_type::ZkSignatureTrait; +use zklink_sdk_signers::eth_signer::EthSignerError; +use zklink_sdk_signers::starknet_signer::StarkSignature; +use zklink_sdk_signers::starknet_signer::typed_data::message::{TypedDataMessage, Message}; +use zklink_sdk_signers::starknet_signer::error::StarkSignerError; + +pub enum JsonRpcProvider { + Provider(Provider), + Signer(StarknetAccountSigner) +} +pub enum Layer1JsonRpcSigner { + EthSigner(EthJsonRpcSigner), + StarknetSigner(StarknetJsonRpcSigner), +} pub struct JsonRpcSigner { zklink_signer: ZkLinkSigner, - eth_signer: EthJsonRpcSigner, + eth_signer: Layer1JsonRpcSigner, } impl JsonRpcSigner { - pub fn new(provider: Provider) -> Result { - let eth_json_rpc_signer = EthJsonRpcSigner::new(provider)?; + pub fn new(provider: JsonRpcProvider) -> Result { + let eth_json_rpc_signer = match provider { + JsonRpcProvider::Provider(provider) => + Layer1JsonRpcSigner::EthSigner(EthJsonRpcSigner::new(provider)), + JsonRpcProvider::Signer(signer) => + Layer1JsonRpcSigner::StarknetSigner(StarknetJsonRpcSigner::new(signer)) + }; let default_zklink_signer = ZkLinkSigner::new()?; Ok(Self { zklink_signer: default_zklink_signer, @@ -36,11 +58,25 @@ impl JsonRpcSigner { pub async fn init_zklink_signer(&mut self, signature: Option) -> Result<(), SignError> { let zklink_signer = if let Some(s) = signature { - let signature = PackedEthSignature::from_hex(&s)?; - let seed = signature.serialize_packed(); - ZkLinkSigner::new_from_seed(&seed)? - } else { - ZkLinkSigner::new_from_eth_rpc_signer(&self.eth_signer).await? + match &self.eth_signer { + Layer1JsonRpcSigner::EthSigner(_) => { + let signature = PackedEthSignature::from_hex(&s)?; + let seed = signature.serialize_packed(); + ZkLinkSigner::new_from_seed(&seed)? + }, + Layer1JsonRpcSigner::StarknetSigner(_) => { + let signature = StarkSignature::from_hex(&s)?; + let seed = signature.to_bytes_be(); + ZkLinkSigner::new_from_seed(&seed)? + } + } + } else { + match &self.eth_signer { + Layer1JsonRpcSigner::EthSigner(signer) => + ZkLinkSigner::new_from_eth_rpc_signer(signer).await?, + Layer1JsonRpcSigner::StarknetSigner(signer) => + ZkLinkSigner::new_from_starknet_rpc_signer(signer).await?, + } }; self.zklink_signer = zklink_signer; Ok(()) @@ -51,7 +87,12 @@ impl JsonRpcSigner { tx: Transfer, token_symbol: &str, ) -> Result { - sign_eth_transfer(&self.eth_signer, &self.zklink_signer, tx, token_symbol).await + match &self.eth_signer { + Layer1JsonRpcSigner::EthSigner(signer) => + sign_eth_transfer(signer, &self.zklink_signer, tx, token_symbol).await, + Layer1JsonRpcSigner::StarknetSigner(signer) => + sign_starknet_transfer(signer, &self.zklink_signer, tx, token_symbol).await, + } } #[inline] @@ -74,10 +115,14 @@ impl JsonRpcSigner { // create auth data let eth_sign_msg = ChangePubKey::get_eth_sign_msg(&tx.new_pk_hash, tx.nonce, tx.account_id); - let eth_signature = self - .eth_signer - .sign_message(eth_sign_msg.as_bytes()) - .await?; + let eth_signature = match &self.eth_signer { + Layer1JsonRpcSigner::EthSigner(signer) => + signer.sign_message(eth_sign_msg.as_bytes()).await?, + Layer1JsonRpcSigner::StarknetSigner(_) => { + //starknet only support change_pubkey_onchain + return Err(StarkSignerError::SignError("Not support for starknet".to_string()).into()); + } + }; tx.eth_auth_data = ChangePubKeyAuthData::EthECDSA { eth_signature }; @@ -92,13 +137,12 @@ impl JsonRpcSigner { tx: Withdraw, l2_source_token_symbol: &str, ) -> Result { - sign_eth_withdraw( - &self.eth_signer, - &self.zklink_signer, - tx, - l2_source_token_symbol, - ) - .await + match &self.eth_signer { + Layer1JsonRpcSigner::EthSigner(signer) => + sign_eth_withdraw(signer, &self.zklink_signer, tx, l2_source_token_symbol).await, + Layer1JsonRpcSigner::StarknetSigner(signer) => + sign_starknet_withdraw(signer, &self.zklink_signer, tx, l2_source_token_symbol).await, + } } #[inline] diff --git a/interface/src/sign_transfer.rs b/interface/src/sign_transfer.rs index a132fefa..64e87d11 100644 --- a/interface/src/sign_transfer.rs +++ b/interface/src/sign_transfer.rs @@ -1,6 +1,8 @@ use crate::error::SignError; #[cfg(feature = "web")] use zklink_sdk_signers::eth_signer::json_rpc_signer::JsonRpcSigner; +#[cfg(feature = "web")] +use zklink_sdk_signers::starknet_signer::starknet_json_rpc_signer::StarknetJsonRpcSigner; #[cfg(not(feature = "web"))] use zklink_sdk_signers::eth_signer::pk_signer::EthSigner; #[cfg(not(feature = "web"))] @@ -9,6 +11,7 @@ use zklink_sdk_signers::zklink_signer::pk_signer::ZkLinkSigner; use zklink_sdk_types::basic_types::GetBytes; use zklink_sdk_types::prelude::TxSignature; use zklink_sdk_types::tx_type::transfer::Transfer; +use zklink_sdk_signers::starknet_signer::typed_data::message::TypedDataMessage; #[cfg(not(feature = "web"))] pub fn sign_eth_transfer( @@ -30,11 +33,11 @@ pub fn sign_eth_transfer( #[cfg(feature = "web")] pub async fn sign_eth_transfer( eth_signer: &JsonRpcSigner, - zklink_syner: &ZkLinkSigner, + zklink_signer: &ZkLinkSigner, mut tx: Transfer, token_symbol: &str, ) -> Result { - tx.signature = zklink_syner.sign_musig(&tx.get_bytes())?; + tx.signature = zklink_signer.sign_musig(&tx.get_bytes())?; let message = tx.get_eth_sign_msg(token_symbol).as_bytes().to_vec(); let eth_signature = eth_signer.sign_message(&message).await?; @@ -44,15 +47,34 @@ pub async fn sign_eth_transfer( }) } +#[cfg(feature = "web")] +pub async fn sign_starknet_transfer( + starknet_signer: &StarknetJsonRpcSigner, + zklink_signer: &ZkLinkSigner, + mut tx: Transfer, + token_symbol: &str, +) -> Result { + tx.signature = zklink_signer.sign_musig(&tx.get_bytes())?; + let message = TypedDataMessage::Transaction(tx.get_starknet_sign_msg(token_symbol)); + let starknet_signature = starknet_signer.sign_message(message).await?; + + Ok(TxSignature { + tx: tx.into(), + layer1_signature: Some(starknet_signature.into()), + }) +} + #[cfg(not(feature = "web"))] pub fn sign_starknet_transfer( signer: &StarkSigner, zklink_syner: &ZkLinkSigner, mut tx: Transfer, + token_symbol: &str, ) -> Result { tx.signature = zklink_syner.sign_musig(&tx.get_bytes())?; - let message = tx.get_starknet_sign_msg(); - let starknet_signature = signer.sign_message(&message)?; + //let message = tx.get_starknet_sign_msg(token_symbol); + let message = tx.get_eth_sign_msg(token_symbol); + let starknet_signature = signer.sign_message(message.as_bytes())?; Ok(TxSignature { tx: tx.into(), diff --git a/interface/src/sign_withdraw.rs b/interface/src/sign_withdraw.rs index e51ae6bb..32a4d1a1 100644 --- a/interface/src/sign_withdraw.rs +++ b/interface/src/sign_withdraw.rs @@ -9,16 +9,23 @@ use zklink_sdk_signers::zklink_signer::pk_signer::ZkLinkSigner; use zklink_sdk_types::prelude::TxSignature; use zklink_sdk_types::tx_type::withdraw::Withdraw; use zklink_sdk_types::tx_type::ZkSignatureTrait; +#[cfg(feature = "web")] +use crate::json_rpc_signer::Layer1JsonRpcSigner; +#[cfg(feature = "web")] +use zklink_sdk_signers::starknet_signer::starknet_json_rpc_signer::StarknetJsonRpcSigner; +use zklink_sdk_signers::starknet_signer::typed_data::message::TypedDataMessage; #[cfg(not(feature = "web"))] pub fn sign_starknet_withdraw( signer: &StarkSigner, zklink_singer: &ZkLinkSigner, mut tx: Withdraw, + l2_source_token_symbol: &str, ) -> Result { tx.sign(zklink_singer)?; - let message = tx.get_starknet_sign_msg(); - let signature = signer.sign_message(&message)?; + //todo: use eip712 + let message = tx.get_eth_sign_msg(l2_source_token_symbol); + let signature = signer.sign_message(message.as_bytes())?; Ok(TxSignature { tx: tx.into(), @@ -60,6 +67,22 @@ pub async fn sign_eth_withdraw( }) } +#[cfg(feature = "web")] +pub async fn sign_starknet_withdraw( + stark_signer: &StarknetJsonRpcSigner, + zklink_singer: &ZkLinkSigner, + mut tx: Withdraw, + l2_source_token_symbol: &str, +) -> Result { + tx.sign(zklink_singer)?; + let message = tx.get_starknet_sign_msg(l2_source_token_symbol); + let stark_signature = stark_signer.sign_message(TypedDataMessage::Transaction(message)).await?; + + Ok(TxSignature { + tx: tx.into(), + layer1_signature: Some(stark_signature.into()), + }) +} #[cfg(test)] mod tests { use super::*; diff --git a/interface/src/signer.rs b/interface/src/signer.rs index 2b888367..a19a18a6 100644 --- a/interface/src/signer.rs +++ b/interface/src/signer.rs @@ -134,7 +134,7 @@ impl Signer { sign_eth_transfer(signer, &self.zklink_signer, tx, token_symbol) } Layer1Sginer::StarknetSigner(signer) => { - sign_starknet_transfer(signer, &self.zklink_signer, tx) + sign_starknet_transfer(signer, &self.zklink_signer, tx,token_symbol) } } } @@ -152,7 +152,7 @@ impl Signer { sign_eth_withdraw(signer, &self.zklink_signer, tx, l2_source_token_symbol) } Layer1Sginer::StarknetSigner(signer) => { - sign_starknet_withdraw(signer, &self.zklink_signer, tx) + sign_starknet_withdraw(signer, &self.zklink_signer, tx,l2_source_token_symbol) } } } diff --git a/signers/Cargo.toml b/signers/Cargo.toml index 84c0c969..ba8fbc42 100644 --- a/signers/Cargo.toml +++ b/signers/Cargo.toml @@ -23,6 +23,8 @@ thiserror = "1.0" wasm-bindgen = { version = "0.2.87", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4" zklink_sdk_utils = { path = "../utils" } +web-sys = "0.3" +num = { version = "0.4", features = ["serde"] } [features] default = [] diff --git a/signers/src/eth_signer/json_rpc_signer.rs b/signers/src/eth_signer/json_rpc_signer.rs index 5f70906a..72ad71a9 100755 --- a/signers/src/eth_signer/json_rpc_signer.rs +++ b/signers/src/eth_signer/json_rpc_signer.rs @@ -30,8 +30,8 @@ pub struct JsonRpcSigner { } impl JsonRpcSigner { - pub fn new(provider: Provider) -> Result { - Ok(JsonRpcSigner { provider }) + pub fn new(provider: Provider) -> JsonRpcSigner { + JsonRpcSigner { provider } } pub async fn sign_message(&self, message: &[u8]) -> Result { diff --git a/signers/src/starknet_signer/ecdsa_signature.rs b/signers/src/starknet_signer/ecdsa_signature.rs index 807623b9..1eaad852 100644 --- a/signers/src/starknet_signer/ecdsa_signature.rs +++ b/signers/src/starknet_signer/ecdsa_signature.rs @@ -8,8 +8,10 @@ use starknet_signers::VerifyingKey; use std::fmt; use std::fmt::Formatter; use zklink_sdk_utils::serde::ZeroPrefixHexSerde; +use num::BigUint; +use std::str::FromStr; -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq,Serialize, Deserialize,Eq,Debug)] pub struct StarkSignature { pub s: FieldElement, pub r: FieldElement, @@ -30,6 +32,25 @@ impl StarkSignature { hex::encode(bytes) } + + pub fn from_hex(s: &str) -> Result { + let s = s.strip_prefix("0x").unwrap_or(s); + let bytes = hex::decode(s).map_err(StarkSignerError::invalid_signature)?; + Self::from_bytes_be(&bytes) + } + + pub fn from_str(r:&str,s: &str) -> Result { + let r = BigUint::from_str(r) + .map_err(|e| StarkSignerError::InvalidSignature(e.to_string()))?; + let s = BigUint::from_str(s) + .map_err(|e| StarkSignerError::InvalidSignature(e.to_string()))?; + let mut bytes = [0;64]; + bytes[0..32].clone_from_slice(&s.to_bytes_be()); + bytes[32..].clone_from_slice(&r.to_bytes_be()); + Self::from_bytes_be(&bytes) + } + + pub fn from_bytes_be(bytes: &[u8]) -> Result { let mut s = [0_u8; 32]; let mut r = [0_u8; 32]; diff --git a/signers/src/starknet_signer/mod.rs b/signers/src/starknet_signer/mod.rs index d751d5cd..13860901 100644 --- a/signers/src/starknet_signer/mod.rs +++ b/signers/src/starknet_signer/mod.rs @@ -1,6 +1,9 @@ pub mod ecdsa_signature; pub mod error; pub mod pk_signer; +#[cfg(feature = "web")] +pub mod starknet_json_rpc_signer; +pub mod typed_data; pub use ecdsa_signature::{StarkECDSASignature, StarkSignature}; pub use pk_signer::StarkSigner; diff --git a/signers/src/starknet_signer/pk_signer.rs b/signers/src/starknet_signer/pk_signer.rs index be78bcec..9f08dd0c 100644 --- a/signers/src/starknet_signer/pk_signer.rs +++ b/signers/src/starknet_signer/pk_signer.rs @@ -32,20 +32,23 @@ impl StarkSigner { /// 1. get the hash of the message /// 2. sign hash - pub fn sign_message(&self, msg: &[u8]) -> Result { + pub fn sign_message(&self, msg: &[u8]) -> Result { let hash = Self::get_msg_hash(msg); let signature = self .0 .sign(&hash) .map_err(|e| Error::sign_error(e.to_string()))?; - let s = StarkECDSASignature { - pub_key: self.public_key(), - signature: StarkSignature { - s: signature.s, - r: signature.r, - }, - }; - Ok(s) + // let s = StarkECDSASignature { + // pub_key: self.public_key(), + // signature: StarkSignature { + // s: signature.s, + // r: signature.r, + // }, + // }; + Ok(StarkSignature { + s: signature.s, + r: signature.r, + }) } /// 1. change msg to FieldElement list @@ -63,6 +66,8 @@ impl StarkSigner { mod tests { use super::*; use serde::{Deserialize, Serialize}; + use crate::starknet_signer::typed_data::TypedData; + use crate::starknet_signer::typed_data::message::{TypedDataMessage, Message}; #[derive(Serialize, Deserialize, Debug)] struct TestSignature { @@ -83,4 +88,24 @@ mod tests { let data2: TestSignature = serde_json::from_str(&s).unwrap(); println!("{data2:?}"); } + + // #[test] + // fn test_signature_verify() { + // let r = "1242659239673499744454485192657408749892787349417938392478090301874476933289"; + // let s = "3203688086163592132535350422117785905751559823323905824858605377390311728388"; + // let pubkey = "0x4893e2057aabcfc20bfa81a09efdcf39807698f9748123c51145fc81aeadab1"; + // let msg = TypedDataMessage::CreateL2Key(Message { + // data: "Create zkLink L2".to_string() + // }); + // let typed_data = TypedData::new(msg); + // + // let signature = StarkECDSASignature { + // pub_key: FieldElement::from_hex_be(&pubkey).unwrap(), + // signature: StarkSignature { + // s: FieldElement::from_hex_be(&s).unwrap(), + // r: FieldElement::from_hex_be(&r).unwrap() + // } }; + // let is_ok = signature.verify(msg).unwrap(); + // assert!(is_ok); + // } } diff --git a/signers/src/starknet_signer/starknet_json_rpc_signer.rs b/signers/src/starknet_signer/starknet_json_rpc_signer.rs new file mode 100644 index 00000000..62bb4f97 --- /dev/null +++ b/signers/src/starknet_signer/starknet_json_rpc_signer.rs @@ -0,0 +1,55 @@ +use wasm_bindgen::prelude::*; +use crate::starknet_signer::StarkSignature; +use crate::starknet_signer::error::StarkSignerError; +use crate::starknet_signer::typed_data::{message::TypedDataMessage, TypedData}; + +#[wasm_bindgen] +// Rustfmt removes the 'async' keyword from async functions in extern blocks. It's fixed +// in rustfmt 2. +#[rustfmt::skip] +extern "C" { + #[derive(Clone, Debug)] + pub type Signer; + + #[wasm_bindgen(structural,catch, method)] + async fn signMessage(_: &Signer,msg: &JsValue) -> Result; +} + +pub struct StarknetJsonRpcSigner { + signer: Signer, +} + +impl StarknetJsonRpcSigner { + pub fn new(signer: Signer) -> StarknetJsonRpcSigner{ + StarknetJsonRpcSigner { signer } + } + + pub async fn sign_message( + &self, + message: TypedDataMessage, + ) -> Result { + let typed_data = TypedData::new(message); + let typed_data = serde_wasm_bindgen::to_value(&typed_data) + .map_err(|e| StarkSignerError::SignError(e.to_string()))?; + let signature = self.signer.signMessage(&typed_data).await.map_err(|e| { + StarkSignerError::SignError( + serde_wasm_bindgen::from_value::(e).unwrap_or_default(), + ) + })?; + let signature: Vec = serde_wasm_bindgen::from_value::>(signature) + .map_err(|e| StarkSignerError::InvalidSignature(e.to_string()))?; + StarkSignature::from_str(&signature[0],&signature[1]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_typed_data() { + let typed_data = TypedData::new("123".to_string()); + let typed_data = serde_json::to_string(&typed_data) + .unwrap(); + println!("{:?}",typed_data); + } +} \ No newline at end of file diff --git a/signers/src/starknet_signer/typed_data/message.rs b/signers/src/starknet_signer/typed_data/message.rs new file mode 100644 index 00000000..81024bdb --- /dev/null +++ b/signers/src/starknet_signer/typed_data/message.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize,Debug)] +pub struct TxMessage { + pub transaction: String, + pub amount: String, + pub fee: String, + pub token: String, + pub to: String, + pub nonce: u64, +} + +#[derive(Serialize, Deserialize,Debug)] +pub struct Message { + pub data: String +} + +#[derive(Serialize, Deserialize,Debug)] +#[serde(untagged)] +pub enum TypedDataMessage { + CreateL2Key(Message), + Transaction(TxMessage), +} \ No newline at end of file diff --git a/signers/src/starknet_signer/typed_data/mod.rs b/signers/src/starknet_signer/typed_data/mod.rs new file mode 100644 index 00000000..3f4ffdbe --- /dev/null +++ b/signers/src/starknet_signer/typed_data/mod.rs @@ -0,0 +1,80 @@ +pub mod message; + +use serde::{Deserialize, Serialize}; +use crate::starknet_signer::typed_data::message::TypedDataMessage; + +#[derive(Serialize, Deserialize,Debug)] +#[serde(rename_all = "camelCase")] +pub struct StarknetDomain { + pub name: String, + pub version: String, + pub chain_id: String +} + +#[derive(Serialize, Deserialize,Debug)] +pub struct TypeDefine { + pub name: String, + pub r#type: String, +} + +#[derive(Serialize, Deserialize,Debug)] +#[serde(rename_all = "PascalCase")] +pub struct DataType { + pub stark_net_domain: Vec, + pub message: Vec +} + +#[derive(Serialize, Deserialize,Debug)] +#[serde(rename_all = "camelCase")] +pub struct TypedData { + pub types: DataType, + pub primary_type: String, + pub domain: StarknetDomain, + pub message: TypedDataMessage, +} + +impl TypedData { + pub fn new(message: TypedDataMessage) -> Self { + let starknet_domain_type = vec![ + TypeDefine { name: "name".to_string(), r#type: "string".to_string() }, + TypeDefine { name: "version".to_string(), r#type: "string".to_string() }, + TypeDefine { name: "chainId".to_string(), r#type: "string".to_string() }, + ]; + let message_type = Self::get_message_type(&message); + let types = DataType { + stark_net_domain: starknet_domain_type, + message: message_type + }; + let domain = StarknetDomain { + name: "zklink".to_string(), + version: "1".to_string(), + chain_id: "SN_MAIN".to_string() + }; + Self { + types, + primary_type: "Message".to_string(), + domain, + message + } + } + + pub fn get_message_type(data_type: &TypedDataMessage) -> Vec { + match data_type { + TypedDataMessage::CreateL2Key(_) => { + vec![ + TypeDefine { name: "data".to_string(), r#type: "string".to_string() }, + ] + }, + TypedDataMessage::Transaction(_) => { + vec![ + TypeDefine { name: "transaction".to_string(), r#type: "string".to_string() }, + TypeDefine { name: "amount".to_string(), r#type: "string".to_string() }, + TypeDefine { name: "fee".to_string(), r#type: "string".to_string() }, + TypeDefine { name: "token".to_string(), r#type: "string".to_string() }, + TypeDefine { name: "to".to_string(), r#type: "string".to_string() }, + TypeDefine { name: "nonce".to_string(), r#type: "felt".to_string() }, + ] + }, + } + } +} \ No newline at end of file diff --git a/signers/src/zklink_signer/pk_signer.rs b/signers/src/zklink_signer/pk_signer.rs index fdbd549d..81668143 100755 --- a/signers/src/zklink_signer/pk_signer.rs +++ b/signers/src/zklink_signer/pk_signer.rs @@ -15,8 +15,12 @@ use std::fmt; #[cfg(feature = "web")] use crate::eth_signer::json_rpc_signer::JsonRpcSigner; +#[cfg(feature = "web")] +use crate::starknet_signer::starknet_json_rpc_signer::StarknetJsonRpcSigner; use crate::eth_signer::pk_signer::EthSigner; use crate::starknet_signer::StarkSigner; +#[cfg(feature = "web")] +use crate::starknet_signer::typed_data::message::{TypedDataMessage, Message}; pub struct ZkLinkSigner(EddsaPrivKey); @@ -54,6 +58,8 @@ pub fn sha256_bytes(input: &[u8]) -> Vec { impl ZkLinkSigner { const SIGN_MESSAGE: &'static str = "Sign this message to create a key to interact with zkLink's layer2 services.\nNOTE: This application is powered by zkLink protocol.\n\nOnly sign this message for a trusted client!"; + const STARKNET_SIGN_MESSAGE: &'static str = + "Create zkLink's layer2 key.\n"; pub fn new() -> Result { let eth_pk = H256::random(); let eth_signer = EthSigner::from(eth_pk); @@ -107,7 +113,7 @@ impl ZkLinkSigner { /// create zkLink signer from starknet signer pub fn new_from_starknet_signer(starknet_signer: &StarkSigner) -> Result { let signature = starknet_signer.sign_message(Self::SIGN_MESSAGE.as_bytes())?; - let seed = signature.signature.to_bytes_be(); + let seed = signature.to_bytes_be(); Self::new_from_seed(&seed) } @@ -120,6 +126,18 @@ impl ZkLinkSigner { Self::new_from_seed(&seed) } + #[cfg(feature = "web")] + pub async fn new_from_starknet_rpc_signer(starknet_signer: &StarknetJsonRpcSigner) -> Result { + let message = TypedDataMessage::CreateL2Key(Message { + data: Self::STARKNET_SIGN_MESSAGE.to_string() + }); + let signature = starknet_signer + .sign_message(message) + .await?; + let seed = signature.to_bytes_be(); + Self::new_from_seed(&seed) + } + pub fn new_from_bytes(bytes: &[u8]) -> Result { let mut fs_repr = FsRepr::default(); fs_repr diff --git a/types/src/signatures.rs b/types/src/signatures.rs index 1c2f2c96..02e12652 100644 --- a/types/src/signatures.rs +++ b/types/src/signatures.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use zklink_sdk_signers::eth_signer::eip1271_signature::EIP1271Signature; use zklink_sdk_signers::eth_signer::packed_eth_signature::PackedEthSignature; use zklink_sdk_signers::starknet_signer::ecdsa_signature::StarkECDSASignature; +use zklink_sdk_signers::starknet_signer::StarkSignature; /// Representation of the signature secured by L1. /// May be either a signature generated via Ethereum private key @@ -13,7 +14,7 @@ use zklink_sdk_signers::starknet_signer::ecdsa_signature::StarkECDSASignature; pub enum TxLayer1Signature { EthereumSignature(PackedEthSignature), EIP1271Signature(EIP1271Signature), - StarkSignature(StarkECDSASignature), + StarkSignature(StarkSignature), } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -34,8 +35,8 @@ impl From for TxLayer1Signature { } } -impl From for TxLayer1Signature { - fn from(value: StarkECDSASignature) -> Self { +impl From for TxLayer1Signature { + fn from(value: StarkSignature) -> Self { Self::StarkSignature(value) } } diff --git a/types/src/tx_type/mod.rs b/types/src/tx_type/mod.rs index eec227bb..0a55da07 100644 --- a/types/src/tx_type/mod.rs +++ b/types/src/tx_type/mod.rs @@ -18,6 +18,7 @@ use zklink_sdk_signers::zklink_signer::error::ZkSignerError; use zklink_sdk_signers::zklink_signer::pk_signer::{sha256_bytes, ZkLinkSigner}; use zklink_sdk_signers::zklink_signer::signature::ZkLinkSignature; use zklink_sdk_signers::zklink_signer::PubKeyHash; +use zklink_sdk_signers::starknet_signer::typed_data::message::TxMessage; pub mod validator; @@ -96,6 +97,26 @@ pub fn ethereum_sign_message_part( message } +pub fn starknet_sign_message_part( + transaction: &str, + token_symbol: &str, + decimals: u8, + amount: &BigUint, + fee: &BigUint, + to: &ZkLinkAddress, + nonce: u64, +) -> TxMessage { + let message = TxMessage { + transaction: transaction.to_string(), + amount: format_units(amount, decimals), + token: token_symbol.to_string(), + fee: format_units(fee, decimals), + to: to.to_string(), + nonce, + }; + message +} + /// Formats amount in wei to tokens with precision. /// Behaves just like ethers.utils.formatUnits pub fn format_units(wei: impl ToString, units: u8) -> String { diff --git a/types/src/tx_type/transfer.rs b/types/src/tx_type/transfer.rs index ff9b5174..7a3e3837 100644 --- a/types/src/tx_type/transfer.rs +++ b/types/src/tx_type/transfer.rs @@ -3,7 +3,7 @@ use crate::basic_types::{ AccountId, GetBytes, Nonce, SubAccountId, TimeStamp, TokenId, ZkLinkAddress, }; use crate::tx_type::validator::*; -use crate::tx_type::{ethereum_sign_message_part, TxTrait, ZkSignatureTrait}; +use crate::tx_type::{ethereum_sign_message_part, TxTrait, ZkSignatureTrait, starknet_sign_message_part}; use crate::params::{SIGNED_TRANSFER_BIT_WIDTH, TOKEN_MAX_PRECISION, TX_TYPE_BIT_WIDTH}; #[cfg(feature = "ffi")] @@ -20,6 +20,7 @@ use zklink_sdk_signers::starknet_signer::StarkSigner; use zklink_sdk_signers::zklink_signer::error::ZkSignerError; use zklink_sdk_signers::zklink_signer::signature::ZkLinkSignature; use zklink_sdk_utils::serde::BigUintSerdeAsRadix10Str; +use zklink_sdk_signers::starknet_signer::typed_data::message::TxMessage; /// `Transfer` transaction performs a move of funds from one zklink account to another. #[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)] @@ -89,8 +90,16 @@ impl Transfer { message } - pub fn get_starknet_sign_msg(&self) -> Vec { - self.get_bytes() + pub fn get_starknet_sign_msg(&self,token_symbol: &str) -> TxMessage { + starknet_sign_message_part( + "Transfer", + token_symbol, + TOKEN_MAX_PRECISION, + &self.amount, + &self.fee, + &self.to, + self.nonce.into(), + ) } #[cfg(not(feature = "ffi"))] @@ -121,9 +130,12 @@ impl Transfer { pub fn starknet_signature( &self, starknet_signer: &StarkSigner, + token_symbol: &str, ) -> Result { - let message = self.get_starknet_sign_msg(); - let signature = starknet_signer.sign_message(&message)?; + //todo: use eip712 + //let message = self.get_starknet_sign_msg(token_symbol); + let message = self.get_eth_sign_msg(token_symbol); + let signature = starknet_signer.sign_message(message.as_bytes())?; let tx_eth_signature = TxLayer1Signature::StarkSignature(signature); Ok(tx_eth_signature) } @@ -132,9 +144,10 @@ impl Transfer { pub fn starknet_signature( &self, starknet_signer: Arc, + token_symbol: &str, ) -> Result { - let message = self.get_starknet_sign_msg(); - let signature = starknet_signer.sign_message(&message)?; + let message = self.get_eth_sign_msg(token_symbol); + let signature = starknet_signer.sign_message(message.as_bytes())?; let tx_eth_signature = TxLayer1Signature::StarkSignature(signature); Ok(tx_eth_signature) } diff --git a/types/src/tx_type/withdraw.rs b/types/src/tx_type/withdraw.rs index 5d8a2b82..636f541b 100644 --- a/types/src/tx_type/withdraw.rs +++ b/types/src/tx_type/withdraw.rs @@ -17,7 +17,8 @@ use crate::params::TOKEN_MAX_PRECISION; #[cfg(feature = "ffi")] use crate::prelude::WithdrawBuilder; use crate::tx_type::validator::*; -use crate::tx_type::{ethereum_sign_message_part, TxTrait, ZkSignatureTrait}; +use crate::tx_type::{ethereum_sign_message_part, TxTrait, ZkSignatureTrait, starknet_sign_message_part}; +use zklink_sdk_signers::starknet_signer::typed_data::message::TxMessage; /// `Withdraw` transaction performs a withdrawal of funds from zklink account to L1 account. #[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)] @@ -96,8 +97,16 @@ impl Withdraw { message } - pub fn get_starknet_sign_msg(&self) -> Vec { - self.get_bytes() + pub fn get_starknet_sign_msg(&self,token_symbol:&str) -> TxMessage { + starknet_sign_message_part( + "Withdraw", + token_symbol, + TOKEN_MAX_PRECISION, + &self.amount, + &self.fee, + &self.to, + self.nonce.into(), + ) } #[cfg(feature = "ffi")]