From b049bef1e5560cbcbf25210c10fb2585cbfb7131 Mon Sep 17 00:00:00 2001 From: nickwest Date: Fri, 15 Dec 2023 06:07:30 -0800 Subject: [PATCH] add eip712 data struct for starknet --- bindings/wasm/src/json_rpc_signer.rs | 6 +- interface/src/json_rpc_signer.rs | 4 +- interface/src/sign_transfer.rs | 4 +- signers/Cargo.toml | 1 + .../src/starknet_signer/ecdsa_signature.rs | 2 + signers/src/starknet_signer/pk_signer.rs | 42 +++--- .../starknet_json_rpc_signer.rs | 14 +- .../src/starknet_signer/typed_data/message.rs | 4 +- signers/src/starknet_signer/typed_data/mod.rs | 123 +++++++++++++++++- signers/src/zklink_signer/pk_signer.rs | 6 +- types/src/tx_type/mod.rs | 2 +- types/src/tx_type/transfer.rs | 2 +- types/src/tx_type/withdraw.rs | 2 +- 13 files changed, 168 insertions(+), 44 deletions(-) diff --git a/bindings/wasm/src/json_rpc_signer.rs b/bindings/wasm/src/json_rpc_signer.rs index ef191b3d..99e79369 100755 --- a/bindings/wasm/src/json_rpc_signer.rs +++ b/bindings/wasm/src/json_rpc_signer.rs @@ -28,14 +28,14 @@ pub struct JsonRpcSigner { //#[wasm_bindgen(constructor)] #[wasm_bindgen(js_name=newRpcSignerWtihProvider)] pub fn new_with_provider(provider: Provider) -> Result { - let inner = InterfaceJsonRpcSigner::new(JsonRpcProvider::Provider(provider),None)?; + let inner = InterfaceJsonRpcSigner::new(JsonRpcProvider::Provider(provider),None,None)?; Ok(JsonRpcSigner { inner }) } //#[wasm_bindgen(constructor)] #[wasm_bindgen(js_name=newRpcSignerWithSigner)] -pub fn new_with_signer(signer: Signer, pub_key: String) -> Result { - let inner = InterfaceJsonRpcSigner::new(JsonRpcProvider::Signer(signer),Some(pub_key))?; +pub fn new_with_signer(signer: Signer, pub_key: String,chain_id: String) -> Result { + let inner = InterfaceJsonRpcSigner::new(JsonRpcProvider::Signer(signer),Some(pub_key),Some(chain_id))?; Ok(JsonRpcSigner { inner }) } diff --git a/interface/src/json_rpc_signer.rs b/interface/src/json_rpc_signer.rs index 8579b337..e7ed42f4 100755 --- a/interface/src/json_rpc_signer.rs +++ b/interface/src/json_rpc_signer.rs @@ -40,12 +40,12 @@ pub struct JsonRpcSigner { } impl JsonRpcSigner { - pub fn new(provider: JsonRpcProvider,pub_key: Option) -> Result { + pub fn new(provider: JsonRpcProvider,pub_key: Option,chain_id: Option) -> Result { let eth_json_rpc_signer = match provider { JsonRpcProvider::Provider(provider) => Layer1JsonRpcSigner::EthSigner(EthJsonRpcSigner::new(provider)), JsonRpcProvider::Signer(signer) => - Layer1JsonRpcSigner::StarknetSigner(StarknetJsonRpcSigner::new(signer,pub_key.unwrap())) + Layer1JsonRpcSigner::StarknetSigner(StarknetJsonRpcSigner::new(signer,pub_key.unwrap(),chain_id.unwrap())) }; let default_zklink_signer = ZkLinkSigner::new()?; Ok(Self { diff --git a/interface/src/sign_transfer.rs b/interface/src/sign_transfer.rs index ea4f91ff..cc6936f2 100644 --- a/interface/src/sign_transfer.rs +++ b/interface/src/sign_transfer.rs @@ -56,8 +56,8 @@ pub async fn sign_starknet_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?; + let message = tx.get_starknet_sign_msg(token_symbol); + let starknet_signature = starknet_signer.sign_message(TypedDataMessage::Transaction(message)).await?; Ok(TxSignature { tx: tx.into(), diff --git a/signers/Cargo.toml b/signers/Cargo.toml index ba8fbc42..6d2864d5 100644 --- a/signers/Cargo.toml +++ b/signers/Cargo.toml @@ -19,6 +19,7 @@ serde_json = "1.0" sha2 = "0.10" starknet = { git = "https://github.com/xJonathanLEI/starknet-rs" } starknet-signers = { git = "https://github.com/xJonathanLEI/starknet-rs" } +starknet-crypto = "0.6.1" thiserror = "1.0" wasm-bindgen = { version = "0.2.87", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4" diff --git a/signers/src/starknet_signer/ecdsa_signature.rs b/signers/src/starknet_signer/ecdsa_signature.rs index a226a61d..fae3c0de 100644 --- a/signers/src/starknet_signer/ecdsa_signature.rs +++ b/signers/src/starknet_signer/ecdsa_signature.rs @@ -8,6 +8,7 @@ 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,Serialize, Deserialize,Eq,Debug)] @@ -46,6 +47,7 @@ impl StarkSignature { Ok(Self { s,r }) } + 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/pk_signer.rs b/signers/src/starknet_signer/pk_signer.rs index d238b160..6d974850 100644 --- a/signers/src/starknet_signer/pk_signer.rs +++ b/signers/src/starknet_signer/pk_signer.rs @@ -64,10 +64,11 @@ mod tests { use super::*; use serde::{Deserialize, Serialize}; use crate::starknet_signer::typed_data::TypedData; - use crate::starknet_signer::typed_data::message::{TypedDataMessage, Message}; + use crate::starknet_signer::typed_data::message::{TypedDataMessage, Message, TxMessage}; use starknet_signers::VerifyingKey; use num::BigUint; use std::str::FromStr; + use starknet::core::crypto::Signature; #[derive(Serialize, Deserialize, Debug)] struct TestSignature { @@ -99,32 +100,31 @@ mod tests { let s_str = "3203688086163592132535350422117785905751559823323905824858605377390311728388"; let pubkey = "1082125475812817975721104073212648033952831721853656627074253194227094744819"; let msg_hash = "0x51d5faacb1bdeb6293d52fd4be0a7c62417cb73962cdd6aff385b67239cf081"; - // let msg = TypedDataMessage::CreateL2Key(Message { - // data: "Create zkLink L2".to_string() - // }); - // let typed_data = TypedData::new(msg); - // - let mut s = [0;32]; - let mut r = [0;32]; - let r_num = BigUint::from_str(r_str).unwrap(); - let s_num = BigUint::from_str(s_str).unwrap(); - s.clone_from_slice(&s_num.to_bytes_be()); - r.clone_from_slice(&r_num.to_bytes_be()); + let sig_str = "0x02647618b4fe405d0dccbdfd25c20bfdeb87631a332491c633943e6f59f16ef306f72dfce21313b636bef4afff3fdc929e5c3d01e3a1f586690ef7db7ebc280a042b54b6bfc5970163d3b9166fd8f24671dfbf850554eeaf12a5e8f4db06c7f3"; + let addr = "0x04A69b67bcaBfA7D3CCb96e1d25C2e6fC93589fE24A6fD04566B8700ff97a71a"; let pub_key = FieldElement::from_str(&pubkey).unwrap(); - // let signature = StarkECDSASignature { - // pub_key: FieldElement::from_hex_be(&pubkey).unwrap(), - // signature: StarkSignature { - // s: FieldElement::from_bytes_be(BigUint::from_str(s).unwrap().to_bytes_be()).unwrap(), - // r: FieldElement::from_bytes_be(BigUint::from_str(r).unwrap().to_bytes_be()).unwrap() - // } }; + let transfer = TxMessage { + amount: "0.0012345678998".to_string(), + fee: "0.00000001".to_string(), + nonce: "1".to_string(), + to: "0x5505a8cd4594dbf79d8c59c0df1414ab871ca896".to_string(), + token: "USDC".to_string(), + transaction: "Transfer".to_string(), + }; + + let message = transfer.clone(); + let typed_data = TypedData::new(TypedDataMessage::Transaction(transfer),"SN_GOERLI".to_string()); + let msg_hash = typed_data.get_message_hash(addr.to_string()).unwrap(); + println!("{:?}",msg_hash); + let signature = StarkECDSASignature::from_hex(sig_str).unwrap(); let verifying_key = VerifyingKey::from_scalar(pub_key); let is_ok = verifying_key .verify( - &FieldElement::from_hex_be(msg_hash).unwrap(), + &FieldElement::from_hex_be(&hex::encode(msg_hash.to_bytes_be())).unwrap(), &Signature { - s: FieldElement::from_str(&s_str).unwrap(), - r: FieldElement::from_str(&r_str).unwrap() + s: signature.signature.s, + r: signature.signature.r, }, ) .unwrap(); diff --git a/signers/src/starknet_signer/starknet_json_rpc_signer.rs b/signers/src/starknet_signer/starknet_json_rpc_signer.rs index 9dd9809c..54c52796 100644 --- a/signers/src/starknet_signer/starknet_json_rpc_signer.rs +++ b/signers/src/starknet_signer/starknet_json_rpc_signer.rs @@ -1,9 +1,11 @@ use wasm_bindgen::prelude::*; use crate::starknet_signer::{StarkSignature, StarkECDSASignature}; use crate::starknet_signer::error::StarkSignerError; -use crate::starknet_signer::typed_data::{message::TypedDataMessage, TypedData}; +use crate::starknet_signer::typed_data::message::TypedDataMessage; use starknet::core::types::FieldElement; use std::str::FromStr; +use serde::Serialize; +use crate::starknet_signer::typed_data::TypedData; #[wasm_bindgen] // Rustfmt removes the 'async' keyword from async functions in extern blocks. It's fixed @@ -15,23 +17,27 @@ extern "C" { #[wasm_bindgen(structural,catch, method)] async fn signMessage(_: &Signer,msg: &JsValue) -> Result; + + #[wasm_bindgen(method, getter)] + fn address(this: &Signer) -> String; } pub struct StarknetJsonRpcSigner { signer: Signer, pub_key: String, + chain_id: String, } impl StarknetJsonRpcSigner { - pub fn new(signer: Signer,pub_key: String) -> StarknetJsonRpcSigner{ - StarknetJsonRpcSigner { signer,pub_key } + pub fn new(signer: Signer,pub_key: String,chain_id: String) -> StarknetJsonRpcSigner{ + StarknetJsonRpcSigner { signer,pub_key,chain_id } } pub async fn sign_message( &self, message: TypedDataMessage, ) -> Result { - let typed_data = TypedData::new(message); + let typed_data = TypedData::new(message,self.chain_id.clone()); 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| { diff --git a/signers/src/starknet_signer/typed_data/message.rs b/signers/src/starknet_signer/typed_data/message.rs index 81024bdb..e354ba80 100644 --- a/signers/src/starknet_signer/typed_data/message.rs +++ b/signers/src/starknet_signer/typed_data/message.rs @@ -1,13 +1,13 @@ use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize,Debug)] +#[derive(Serialize, Deserialize,Debug,Clone)] pub struct TxMessage { pub transaction: String, pub amount: String, pub fee: String, pub token: String, pub to: String, - pub nonce: u64, + pub nonce: String, } #[derive(Serialize, Deserialize,Debug)] diff --git a/signers/src/starknet_signer/typed_data/mod.rs b/signers/src/starknet_signer/typed_data/mod.rs index 4543c13a..a179fd46 100644 --- a/signers/src/starknet_signer/typed_data/mod.rs +++ b/signers/src/starknet_signer/typed_data/mod.rs @@ -2,6 +2,10 @@ pub mod message; use serde::{Deserialize, Serialize}; use crate::starknet_signer::typed_data::message::TypedDataMessage; +use crate::starknet_signer::error::StarkSignerError; +use num::{BigUint, Num}; +use starknet_crypto::FieldElement; +use starknet::core::utils::starknet_keccak; #[derive(Serialize, Deserialize,Debug)] #[serde(rename_all = "camelCase")] @@ -11,7 +15,7 @@ pub struct StarknetDomain { pub chain_id: String } -#[derive(Serialize, Deserialize,Debug)] +#[derive(Serialize, Deserialize,Debug,Clone)] pub struct TypeDefine { pub name: String, pub r#type: String, @@ -34,7 +38,7 @@ pub struct TypedData { } impl TypedData { - pub fn new(message: TypedDataMessage) -> Self { + pub fn new(message: TypedDataMessage,chain_id: String) -> 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() }, @@ -48,7 +52,7 @@ impl TypedData { let domain = StarknetDomain { name: "zklink".to_string(), version: "1".to_string(), - chain_id: "SN_GOERLI".to_string() + chain_id, }; Self { types, @@ -72,9 +76,120 @@ impl TypedData { 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() }, + TypeDefine { name: "nonce".to_string(), r#type: "string".to_string() }, ] }, } } + + fn string_to_hex(s: &str) -> String { + if let Ok(num) = BigUint::from_str_radix(s.trim_start_matches("0x"),16) { + format!("0x{}",num.to_str_radix(16)) + } else { + format!("0x{}",hex::encode(s)) + } + } + + pub fn get_type_define(&self,struct_type: &str) -> Vec { + if struct_type == "StarkNetDomain" { + self.types.stark_net_domain.clone() + } else if struct_type == "Message" { + self.types.message.clone() + } else { + vec![] + } + } + + pub fn encode_type(&self,struct_type: &str) -> String { + let td = self.get_type_define(struct_type); + let mut ret = struct_type.to_string() + "("; + let mut fields = vec![]; + for t in td { + let field = format!("{}:{}",t.name,t.r#type); + fields.push(field); + } + ret += &fields.join(","); + ret += ")"; + ret + } + + pub fn compute_hash_on_elements(data: Vec) -> Result { + let mut result = FieldElement::from(0u32); + for e in &data { + let fe = FieldElement::from_hex_be(e) + .map_err(|e| StarkSignerError::SignError(e.to_string()))?; + result = starknet_crypto::pedersen_hash(&result, &fe); + } + + let data_len = FieldElement::from(data.len()); + Ok(starknet_crypto::pedersen_hash(&result, &data_len)) + } + + pub fn get_struct_hash(&self,struct_type: &str,data: &T) -> Result<[u8;32],StarkSignerError> { + let mut types_arry = vec![]; + let mut data_arry = vec![]; + types_arry.push("felt".to_string()); + let encoded_type = self.encode_type(struct_type); + println!("{}",encoded_type); + let type_hash = starknet_keccak(self.encode_type(struct_type).as_bytes()); + data_arry.push(format!("0x{}",hex::encode(type_hash.to_bytes_be()))); + let data_value = serde_json::to_value(data) + .map_err(|e| StarkSignerError::SignError(e.to_string()))?; + let data_map = data_value.as_object().unwrap(); + //type must be exist + let td = self.get_type_define(struct_type); + if td.is_empty() { + return Err(StarkSignerError::SignError("Invalid type name".to_string())); + } + + for t in td { + types_arry.push(t.r#type.clone()); + let v_str = data_map.get(&t.name).unwrap().as_str().unwrap(); + let v = Self::string_to_hex(&v_str); + data_arry.push(v); + } + + println!("{:?}",data_arry); + let result = Self::compute_hash_on_elements(data_arry)?; + Ok(result.to_bytes_be()) + } + + pub fn encode(&self,addr: String) -> Result, StarkSignerError> { + let domain = self.get_struct_hash("StarkNetDomain", &self.domain) + .map_err(|e| StarkSignerError::SignError(e.to_string()))?; + let message = self.get_struct_hash("Message",&self.message) + .map_err(|e| StarkSignerError::SignError(e.to_string()))?; + //StarkNet Message + let stark_net_message = Self::string_to_hex("StarkNet Message"); + Ok(vec![stark_net_message,format!("0x{}",hex::encode(&domain)),addr,format!("0x{}",hex::encode(&message))]) + } + + pub fn get_message_hash(&self,addr: String) -> Result { + let data = self.encode(addr)?; + println!("{:?}",data); + Ok(Self::compute_hash_on_elements(data)?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::starknet_signer::typed_data::message::Message; + use num::Num; + + #[test] + fn test_typed_data() { + let addr = "04a69b67bcabfa7d3ccb96e1d25c2e6fc93589fe24a6fd04566b8700ff97a71a"; + let message = Message { + data: "Create zkLink L2".to_string() + }; + + let s = "0x5505a8cd4594dbf79d8c59c0df1414ab871ca896"; + BigUint::from_str_radix(s.trim_start_matches("0x"),16).unwrap(); + let typed_data = TypedData::new(TypedDataMessage::CreateL2Key(message),"SN_GOERLI".to_string()); + let data = typed_data.encode( + "0x04a69b67bcabfa7d3ccb96e1d25c2e6fc93589fe24a6fd04566b8700ff97a71a".to_string()).unwrap(); + println!("{:?}",data); + + } } \ 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 68af5cc7..e6921eec 100755 --- a/signers/src/zklink_signer/pk_signer.rs +++ b/signers/src/zklink_signer/pk_signer.rs @@ -129,11 +129,11 @@ impl ZkLinkSigner { #[cfg(feature = "web")] pub async fn new_from_starknet_rpc_signer(starknet_signer: &StarknetJsonRpcSigner) -> Result { - let message = TypedDataMessage::CreateL2Key(Message { + let message = Message { data: Self::STARKNET_SIGN_MESSAGE.to_string() - }); + }; let signature = starknet_signer - .sign_message(message) + .sign_message(TypedDataMessage::CreateL2Key(message)) .await?; let seed = signature.signature.to_bytes_be(); Self::new_from_seed(&seed) diff --git a/types/src/tx_type/mod.rs b/types/src/tx_type/mod.rs index 0a55da07..6c5429e9 100644 --- a/types/src/tx_type/mod.rs +++ b/types/src/tx_type/mod.rs @@ -104,7 +104,7 @@ pub fn starknet_sign_message_part( amount: &BigUint, fee: &BigUint, to: &ZkLinkAddress, - nonce: u64, + nonce: String, ) -> TxMessage { let message = TxMessage { transaction: transaction.to_string(), diff --git a/types/src/tx_type/transfer.rs b/types/src/tx_type/transfer.rs index 7a3e3837..67563682 100644 --- a/types/src/tx_type/transfer.rs +++ b/types/src/tx_type/transfer.rs @@ -98,7 +98,7 @@ impl Transfer { &self.amount, &self.fee, &self.to, - self.nonce.into(), + self.nonce.to_string(), ) } diff --git a/types/src/tx_type/withdraw.rs b/types/src/tx_type/withdraw.rs index 636f541b..e12a5aa7 100644 --- a/types/src/tx_type/withdraw.rs +++ b/types/src/tx_type/withdraw.rs @@ -105,7 +105,7 @@ impl Withdraw { &self.amount, &self.fee, &self.to, - self.nonce.into(), + self.nonce.to_string(), ) }