diff --git a/binding_tests/interface_test.go b/binding_tests/interface_test.go index d762b928..1f2a4d31 100644 --- a/binding_tests/interface_test.go +++ b/binding_tests/interface_test.go @@ -246,7 +246,7 @@ func TestSignWithdraw(t *testing.T) { fmt.Printf("zklink tx: %s\n", zklinkTx) // test signer - signer, err := sdk.NewSigner(s); + signer, err := sdk.NewSigner(s, sdk.L1TypeEth); assert.Nil(t, err) tx_signature, err := signer.SignWithdraw(tx, l2SourceTokenSymbol) assert.Nil(t, err) diff --git a/bindings/sdk/src/ffi.udl b/bindings/sdk/src/ffi.udl index f335d538..8e449c46 100644 --- a/bindings/sdk/src/ffi.udl +++ b/bindings/sdk/src/ffi.udl @@ -481,6 +481,27 @@ interface EthSigner { Address get_address(); }; +// =========================== starknet crypto ============================ +[Error] +enum StarkSignerError { + "InvalidStarknetSigner", + "InvalidSignature", + "InvalidPrivKey", + "SignError", +}; +[Custom] +typedef string StarkECDSASignature; + +interface StarkSigner { + constructor(); + [Throws=StarkSignerError,Name=new_from_hex_str] + constructor([ByRef] string hex_str); + [Throws=StarkSignerError] + StarkECDSASignature sign_message([ByRef] sequence message); +}; + + + // ============================ zklink crypto ============================ [Error] enum ZkSignerError { @@ -490,7 +511,8 @@ enum ZkSignerError { "InvalidSeed", "InvalidPubkey", "InvalidPubkeyHash", - "PackedETHSignatureError", + "EthSignerError", + "StarkSignerError", }; [Custom] @@ -513,6 +535,8 @@ interface ZkLinkSigner { constructor([ByRef] sequence seed); [Throws=ZkSignerError,Name=new_from_hex_eth_signer] constructor([ByRef] string eth_hex_private_key); + [Throws=ZkSignerError,Name=new_from_hex_stark_signer] + constructor([ByRef] string hex_private_key); [Throws=ZkSignerError,Name=new_from_bytes] constructor([ByRef] sequence slice); PackedPublicKey public_key(); @@ -527,6 +551,7 @@ interface ZkLinkSigner { enum SignError { "EthSigningError", "ZkSigningError", + "StarkSigningError", "IncorrectTx", }; @@ -538,9 +563,14 @@ dictionary TxSignature { TxLayer1Signature ?layer1_signature; }; +enum L1Type { + "Eth", + "Starknet", +}; + interface Signer { [Throws=SignError] - constructor([ByRef] string private_key); + constructor([ByRef] string private_key, L1Type l1_type); [Throws=SignError] TxSignature sign_change_pubkey_with_create2data_auth(ChangePubKey tx, Create2Data crate2data); [Throws=SignError] diff --git a/bindings/sdk/src/lib.rs b/bindings/sdk/src/lib.rs index b74826cb..790066f7 100644 --- a/bindings/sdk/src/lib.rs +++ b/bindings/sdk/src/lib.rs @@ -10,6 +10,9 @@ use zklink_sdk_signers::eth_signer::packed_eth_signature::PackedEthSignature; use zklink_sdk_signers::eth_signer::pk_signer::EthSigner; use zklink_sdk_signers::eth_signer::{Address, H256}; +use zklink_sdk_signers::starknet_signer::error::StarkSignerError; +use zklink_sdk_signers::starknet_signer::{StarkECDSASignature, StarkSigner}; + use zklink_sdk_signers::zklink_signer::error::ZkSignerError; use zklink_sdk_signers::zklink_signer::pk_signer::ZkLinkSigner; use zklink_sdk_signers::zklink_signer::pubkey_hash::PubKeyHash; @@ -31,7 +34,7 @@ use zklink_sdk_interface::error::SignError; use zklink_sdk_interface::sign_change_pubkey::{ create_signed_change_pubkey, eth_signature_of_change_pubkey, }; -use zklink_sdk_interface::signer::Signer; +use zklink_sdk_interface::signer::{L1Type, Signer}; use zklink_sdk_interface::ChangePubKeyAuthRequest; include!(concat!(env!("OUT_DIR"), "/ffi.uniffi.rs")); diff --git a/bindings/sdk/src/type_convert/hex_convert.rs b/bindings/sdk/src/type_convert/hex_convert.rs index 8fffba0f..766a0596 100644 --- a/bindings/sdk/src/type_convert/hex_convert.rs +++ b/bindings/sdk/src/type_convert/hex_convert.rs @@ -2,6 +2,7 @@ use crate::{ PackedEthSignature, PackedPublicKey, PackedSignature, PubKeyHash, TxHash, UniffiCustomTypeConverter, }; +use zklink_sdk_signers::starknet_signer::StarkECDSASignature; macro_rules! ffi_hex_convert { ($(#[$attr:meta])* $name:ident) => { @@ -23,6 +24,7 @@ ffi_hex_convert!(PackedPublicKey); ffi_hex_convert!(PackedSignature); ffi_hex_convert!(PubKeyHash); ffi_hex_convert!(PackedEthSignature); +ffi_hex_convert!(StarkECDSASignature); #[cfg(test)] mod test { diff --git a/bindings/wasm/src/rpc_type_converter.rs b/bindings/wasm/src/rpc_type_converter.rs index 476341d3..1cbc7bc0 100644 --- a/bindings/wasm/src/rpc_type_converter.rs +++ b/bindings/wasm/src/rpc_type_converter.rs @@ -125,10 +125,10 @@ impl TryFrom for TypesTxLayer1Signature { ))) } L1SignatureType::Stark => { - Ok(TypesTxLayer1Signature::StarkSignature(StarkECDSASignature( - hex::decode(signature.signature) - .map_err(|e| JsValue::from_str(&format!("error: {e}")))?, - ))) + let signature = StarkECDSASignature::from_hex(&signature.signature) + .map_err(|e| JsValue::from_str(&format!("error: {e}")))?; + + Ok(TypesTxLayer1Signature::StarkSignature(signature)) } } } diff --git a/bindings/wasm/src/signer.rs b/bindings/wasm/src/signer.rs index 29291c48..f46abb45 100644 --- a/bindings/wasm/src/signer.rs +++ b/bindings/wasm/src/signer.rs @@ -10,6 +10,7 @@ use crate::tx_types::transfer::Transfer; use crate::tx_types::withdraw::Withdraw; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; +use zklink_sdk_interface::signer::L1Type as InnerL1Type; use zklink_sdk_interface::signer::Signer as InterfaceSigner; use zklink_sdk_types::tx_type::change_pubkey::ChangePubKey as TxChangePubKey; use zklink_sdk_types::tx_type::change_pubkey::Create2Data as ChangePubKeyCreate2Data; @@ -30,11 +31,27 @@ pub struct Signer { inner: InterfaceSigner, } +#[wasm_bindgen] +#[derive(Copy, Clone)] +pub enum L1Type { + Eth, + Starknet, +} + +impl From for InnerL1Type { + fn from(value: L1Type) -> InnerL1Type { + match value { + L1Type::Eth => InnerL1Type::Eth, + L1Type::Starknet => InnerL1Type::Starknet, + } + } +} + #[wasm_bindgen] impl Signer { #[wasm_bindgen(constructor)] - pub fn new(private_key: &str) -> Result { - let inner = InterfaceSigner::new(private_key)?; + pub fn new(private_key: &str, l1_type: L1Type) -> Result { + let inner = InterfaceSigner::new(private_key, l1_type.into())?; Ok(Signer { inner }) } diff --git a/examples/Golang/10_update_global_var.go b/examples/Golang/10_update_global_var.go index 68f473ca..a8de547f 100644 --- a/examples/Golang/10_update_global_var.go +++ b/examples/Golang/10_update_global_var.go @@ -34,7 +34,7 @@ func HighLevelUpdateGlobalVar() { } tx := sdk.NewUpdateGlobalVar(builder) - signer, err := sdk.NewSigner(privateKey) + signer, err := sdk.NewSigner(privateKey, sdk.L1TypeEth) if err != nil { return } diff --git a/examples/Golang/1_change_pubkey.go b/examples/Golang/1_change_pubkey.go index 50045102..839d576c 100644 --- a/examples/Golang/1_change_pubkey.go +++ b/examples/Golang/1_change_pubkey.go @@ -154,7 +154,7 @@ func HighLevelChangePubkeyEcdsa() { timeStamp, } tx := sdk.NewChangePubKey(builder) - signer, err := sdk.NewSigner(privateKey) + signer, err := sdk.NewSigner(privateKey, sdk.L1TypeEth) if err != nil { return } diff --git a/examples/Golang/2_withdraw.go b/examples/Golang/2_withdraw.go index bf50383e..d897898b 100644 --- a/examples/Golang/2_withdraw.go +++ b/examples/Golang/2_withdraw.go @@ -49,7 +49,7 @@ func HighLevelWithdraw() { Timestamp: timestamp, } tx := sdk.NewWithdraw(builder) - signer, err := sdk.NewSigner(privateKey) + signer, err := sdk.NewSigner(privateKey, sdk.L1TypeEth) if err != nil { return } diff --git a/examples/Golang/3_transfer.go b/examples/Golang/3_transfer.go index 6e13e307..3a2092ca 100644 --- a/examples/Golang/3_transfer.go +++ b/examples/Golang/3_transfer.go @@ -39,7 +39,7 @@ func HighLevelTransfer() { } tokenSymbol := "DAI" tx := sdk.NewTransfer(builder) - signer, err := sdk.NewSigner(privateKey) + signer, err := sdk.NewSigner(privateKey, sdk.L1TypeEth) if err != nil { return } diff --git a/examples/Golang/4_forced_exit.go b/examples/Golang/4_forced_exit.go index cbb21d2b..8be74109 100644 --- a/examples/Golang/4_forced_exit.go +++ b/examples/Golang/4_forced_exit.go @@ -35,7 +35,7 @@ func HighLevelForcedExit() { Timestamp: sdk.TimeStamp(1693472232), } tx := sdk.NewForcedExit(builder) - signer, err := sdk.NewSigner(privateKey) + signer, err := sdk.NewSigner(privateKey, sdk.L1TypeEth) if err != nil { return } diff --git a/examples/Golang/5_order_matching.go b/examples/Golang/5_order_matching.go index 70c9ddcb..1ba72816 100644 --- a/examples/Golang/5_order_matching.go +++ b/examples/Golang/5_order_matching.go @@ -78,7 +78,7 @@ func HighLevelOrderMatching() { *big.NewInt(5479779), } tx := sdk.NewOrderMatching(builder) - signer, err := sdk.NewSigner(privateKey) + signer, err := sdk.NewSigner(privateKey, sdk.L1TypeEth) if err != nil { return } diff --git a/examples/Golang/6_contract_matching.go b/examples/Golang/6_contract_matching.go index c9390b6e..4980ac74 100644 --- a/examples/Golang/6_contract_matching.go +++ b/examples/Golang/6_contract_matching.go @@ -100,7 +100,7 @@ func HighLevelContractMatching() { } tx := sdk.NewContractMatching(builder) - signer, err := sdk.NewSigner(privateKey) + signer, err := sdk.NewSigner(privateKey, sdk.L1TypeEth) if err != nil { return } diff --git a/examples/Golang/7_auto_deleveraging.go b/examples/Golang/7_auto_deleveraging.go index 09f6742e..3a5452c4 100644 --- a/examples/Golang/7_auto_deleveraging.go +++ b/examples/Golang/7_auto_deleveraging.go @@ -65,7 +65,7 @@ func HighLevelAutoDeleveraging() { sdk.TokenId(18), } tx := sdk.NewAutoDeleveraging(builder) - signer, err := sdk.NewSigner(privateKey) + signer, err := sdk.NewSigner(privateKey, sdk.L1TypeEth) if err != nil { return } diff --git a/examples/Golang/8_funding.go b/examples/Golang/8_funding.go index 724117b6..28ce3120 100644 --- a/examples/Golang/8_funding.go +++ b/examples/Golang/8_funding.go @@ -34,7 +34,7 @@ func HighLevelFunding() { sdk.TokenId(17), } tx := sdk.NewFunding(builder) - signer, err := sdk.NewSigner(privateKey) + signer, err := sdk.NewSigner(privateKey, sdk.L1TypeEth) if err != nil { return } diff --git a/examples/Golang/9_liquidation.go b/examples/Golang/9_liquidation.go index 2a9c3154..8c1c51fc 100644 --- a/examples/Golang/9_liquidation.go +++ b/examples/Golang/9_liquidation.go @@ -63,7 +63,7 @@ func HighLevelLiquidation() { } tx := sdk.NewLiquidation(builder) - signer, err := sdk.NewSigner(privateKey) + signer, err := sdk.NewSigner(privateKey, sdk.L1TypeEth) if err != nil { return } diff --git a/interface/src/error.rs b/interface/src/error.rs index 3d4b12d9..29cddec8 100644 --- a/interface/src/error.rs +++ b/interface/src/error.rs @@ -2,6 +2,7 @@ use thiserror::Error; #[cfg(target_arch = "wasm32")] use wasm_bindgen::JsValue; use zklink_sdk_signers::eth_signer::error::EthSignerError; +use zklink_sdk_signers::starknet_signer::error::StarkSignerError; use zklink_sdk_signers::zklink_signer::error::ZkSignerError; #[derive(Debug, Error)] @@ -10,6 +11,8 @@ pub enum SignError { EthSigningError(#[from] EthSignerError), #[error("ZkSigning error: {0}")] ZkSigningError(#[from] ZkSignerError), + #[error("Starknet signing error: {0}")] + StarkSigningError(#[from] StarkSignerError), #[error("Incorrect tx format")] IncorrectTx, } diff --git a/interface/src/json_rpc_signer.rs b/interface/src/json_rpc_signer.rs index 71d44251..e8a33ec9 100755 --- a/interface/src/json_rpc_signer.rs +++ b/interface/src/json_rpc_signer.rs @@ -3,8 +3,8 @@ 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_transfer; -use crate::sign_withdraw::sign_withdraw; +use crate::sign_transfer::sign_eth_transfer; +use crate::sign_withdraw::sign_eth_withdraw; use zklink_sdk_signers::eth_signer::json_rpc_signer::{ JsonRpcSigner as EthJsonRpcSigner, Provider, }; @@ -51,7 +51,7 @@ impl JsonRpcSigner { tx: Transfer, token_symbol: &str, ) -> Result { - sign_transfer(&self.eth_signer, &self.zklink_signer, tx, token_symbol).await + sign_eth_transfer(&self.eth_signer, &self.zklink_signer, tx, token_symbol).await } #[inline] @@ -92,7 +92,7 @@ impl JsonRpcSigner { tx: Withdraw, l2_source_token_symbol: &str, ) -> Result { - sign_withdraw( + sign_eth_withdraw( &self.eth_signer, &self.zklink_signer, tx, diff --git a/interface/src/sign_transfer.rs b/interface/src/sign_transfer.rs index b15d74ff..a132fefa 100644 --- a/interface/src/sign_transfer.rs +++ b/interface/src/sign_transfer.rs @@ -3,13 +3,15 @@ use crate::error::SignError; use zklink_sdk_signers::eth_signer::json_rpc_signer::JsonRpcSigner; #[cfg(not(feature = "web"))] use zklink_sdk_signers::eth_signer::pk_signer::EthSigner; +#[cfg(not(feature = "web"))] +use zklink_sdk_signers::starknet_signer::StarkSigner; 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; #[cfg(not(feature = "web"))] -pub fn sign_transfer( +pub fn sign_eth_transfer( eth_signer: &EthSigner, zklink_syner: &ZkLinkSigner, mut tx: Transfer, @@ -26,7 +28,7 @@ pub fn sign_transfer( } #[cfg(feature = "web")] -pub async fn sign_transfer( +pub async fn sign_eth_transfer( eth_signer: &JsonRpcSigner, zklink_syner: &ZkLinkSigner, mut tx: Transfer, @@ -41,6 +43,22 @@ pub async fn sign_transfer( layer1_signature: Some(eth_signature.into()), }) } + +#[cfg(not(feature = "web"))] +pub fn sign_starknet_transfer( + signer: &StarkSigner, + zklink_syner: &ZkLinkSigner, + mut tx: Transfer, +) -> Result { + tx.signature = zklink_syner.sign_musig(&tx.get_bytes())?; + let message = tx.get_starknet_sign_msg(); + let starknet_signature = signer.sign_message(&message)?; + + Ok(TxSignature { + tx: tx.into(), + layer1_signature: Some(starknet_signature.into()), + }) +} #[cfg(test)] mod tests { use super::*; @@ -67,7 +85,7 @@ mod tests { }; let tx = builder.build(); - let signature = sign_transfer(ð_signer, &zk_signer, tx, "USD").unwrap(); + let signature = sign_eth_transfer(ð_signer, &zk_signer, tx, "USD").unwrap(); let eth_sign = signature .layer1_signature .expect("transfer must has eth signature"); diff --git a/interface/src/sign_withdraw.rs b/interface/src/sign_withdraw.rs index baf86f00..e51ae6bb 100644 --- a/interface/src/sign_withdraw.rs +++ b/interface/src/sign_withdraw.rs @@ -3,13 +3,31 @@ use crate::error::SignError; use zklink_sdk_signers::eth_signer::json_rpc_signer::JsonRpcSigner; #[cfg(not(feature = "web"))] use zklink_sdk_signers::eth_signer::pk_signer::EthSigner; +#[cfg(not(feature = "web"))] +use zklink_sdk_signers::starknet_signer::StarkSigner; 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(not(feature = "web"))] -pub fn sign_withdraw( +pub fn sign_starknet_withdraw( + signer: &StarkSigner, + zklink_singer: &ZkLinkSigner, + mut tx: Withdraw, +) -> Result { + tx.sign(zklink_singer)?; + let message = tx.get_starknet_sign_msg(); + let signature = signer.sign_message(&message)?; + + Ok(TxSignature { + tx: tx.into(), + layer1_signature: Some(signature.into()), + }) +} + +#[cfg(not(feature = "web"))] +pub fn sign_eth_withdraw( eth_signer: &EthSigner, zklink_singer: &ZkLinkSigner, mut tx: Withdraw, @@ -17,16 +35,16 @@ pub fn sign_withdraw( ) -> Result { tx.sign(zklink_singer)?; let message = tx.get_eth_sign_msg(l2_source_token_symbol); - let eth_signature = eth_signer.sign_message(message.as_bytes())?; + let signature = eth_signer.sign_message(message.as_bytes())?; Ok(TxSignature { tx: tx.into(), - layer1_signature: Some(eth_signature.into()), + layer1_signature: Some(signature.into()), }) } #[cfg(feature = "web")] -pub async fn sign_withdraw( +pub async fn sign_eth_withdraw( eth_signer: &JsonRpcSigner, zklink_singer: &ZkLinkSigner, mut tx: Withdraw, @@ -70,7 +88,7 @@ mod tests { let tx = builder.build(); let eth_signer = eth_pk.into(); let zk_signer = ZkLinkSigner::new_from_eth_signer(ð_signer).unwrap(); - let signature = sign_withdraw(ð_signer, &zk_signer, tx, "USD").unwrap(); + let signature = sign_eth_withdraw(ð_signer, &zk_signer, tx, "USD").unwrap(); // let eth_sign = signature // .layer1_signature diff --git a/interface/src/signer.rs b/interface/src/signer.rs index 39619b82..2b888367 100644 --- a/interface/src/signer.rs +++ b/interface/src/signer.rs @@ -2,8 +2,8 @@ use crate::error::SignError; use crate::sign_auto_deleveraging::sign_auto_deleveraging; use crate::sign_forced_exit::sign_forced_exit; use crate::sign_liquidation::sign_liquidation; -use crate::sign_transfer::sign_transfer; -use crate::sign_withdraw::sign_withdraw; +use crate::sign_transfer::{sign_eth_transfer, sign_starknet_transfer}; +use crate::sign_withdraw::{sign_eth_withdraw, sign_starknet_withdraw}; use zklink_sdk_types::prelude::{PubKeyHash, TxSignature}; use crate::do_submitter_signature; @@ -19,6 +19,8 @@ use cfg_if::cfg_if; use std::sync::Arc; use zklink_sdk_signers::eth_signer::error::EthSignerError; use zklink_sdk_signers::eth_signer::pk_signer::EthSigner; +use zklink_sdk_signers::starknet_signer::error::StarkSignerError; +use zklink_sdk_signers::starknet_signer::pk_signer::StarkSigner; use zklink_sdk_signers::zklink_signer::pk_signer::ZkLinkSigner; use zklink_sdk_signers::zklink_signer::signature::ZkLinkSignature; #[cfg(not(feature = "ffi"))] @@ -42,19 +44,39 @@ cfg_if! { } } +pub enum Layer1Sginer { + EthSigner(EthSigner), + StarknetSigner(StarkSigner), +} + +pub enum L1Type { + Eth, + Starknet, +} + pub struct Signer { zklink_signer: ZkLinkSigner, - eth_signer: EthSigner, + layer1_signer: Layer1Sginer, } impl Signer { - pub fn new(private_key: &str) -> Result { + pub fn new(private_key: &str, l1_signer_type: L1Type) -> Result { let zklink_signer = ZkLinkSigner::new_from_hex_eth_signer(private_key)?; - let eth_signer = - EthSigner::try_from(private_key).map_err(|_| EthSignerError::InvalidEthSigner)?; + let layer1_signer = match l1_signer_type { + L1Type::Eth => { + let eth_signer = EthSigner::try_from(private_key) + .map_err(|_| EthSignerError::InvalidEthSigner)?; + Layer1Sginer::EthSigner(eth_signer) + } + L1Type::Starknet => { + let stark_signer = StarkSigner::new_from_hex_str(private_key) + .map_err(|_| StarkSignerError::InvalidStarknetSigner)?; + Layer1Sginer::StarknetSigner(stark_signer) + } + }; Ok(Self { zklink_signer, - eth_signer, + layer1_signer, }) } @@ -92,7 +114,11 @@ impl Signer { ) -> Result { #[cfg(feature = "ffi")] let tx = (*tx).clone(); - do_sign_change_pubkey_with_eth_ecdsa_auth(&self.eth_signer, &self.zklink_signer, tx) + if let Layer1Sginer::EthSigner(signer) = &self.layer1_signer { + do_sign_change_pubkey_with_eth_ecdsa_auth(signer, &self.zklink_signer, tx) + } else { + Err(EthSignerError::InvalidEthSigner.into()) + } } #[cfg(not(feature = "web"))] @@ -103,7 +129,14 @@ impl Signer { ) -> Result { #[cfg(feature = "ffi")] let tx = (*tx).clone(); - sign_transfer(&self.eth_signer, &self.zklink_signer, tx, token_symbol) + match &self.layer1_signer { + Layer1Sginer::EthSigner(signer) => { + sign_eth_transfer(signer, &self.zklink_signer, tx, token_symbol) + } + Layer1Sginer::StarknetSigner(signer) => { + sign_starknet_transfer(signer, &self.zklink_signer, tx) + } + } } #[cfg(not(feature = "web"))] @@ -114,12 +147,14 @@ impl Signer { ) -> Result { #[cfg(feature = "ffi")] let tx = (*tx).clone(); - sign_withdraw( - &self.eth_signer, - &self.zklink_signer, - tx, - l2_source_token_symbol, - ) + match &self.layer1_signer { + Layer1Sginer::EthSigner(signer) => { + sign_eth_withdraw(signer, &self.zklink_signer, tx, l2_source_token_symbol) + } + Layer1Sginer::StarknetSigner(signer) => { + sign_starknet_withdraw(signer, &self.zklink_signer, tx) + } + } } pub fn sign_forced_exit(&self, tx: ForcedExit) -> Result { diff --git a/signers/Cargo.toml b/signers/Cargo.toml index a9c73fc5..84c0c969 100644 --- a/signers/Cargo.toml +++ b/signers/Cargo.toml @@ -17,6 +17,8 @@ serde-wasm-bindgen = "0.5" serde_eip712 = "0.2.2" serde_json = "1.0" sha2 = "0.10" +starknet = { git = "https://github.com/xJonathanLEI/starknet-rs" } +starknet-signers = { git = "https://github.com/xJonathanLEI/starknet-rs" } 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 d5580d3e..807623b9 100644 --- a/signers/src/starknet_signer/ecdsa_signature.rs +++ b/signers/src/starknet_signer/ecdsa_signature.rs @@ -1,18 +1,123 @@ #![allow(dead_code)] +use super::error::StarkSignerError; +use crate::starknet_signer::pk_signer::StarkSigner; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use starknet::core::crypto::Signature; +use starknet::core::types::FieldElement; +use starknet_signers::VerifyingKey; use std::fmt; +use std::fmt::Formatter; use zklink_sdk_utils::serde::ZeroPrefixHexSerde; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct StarkECDSASignature(pub Vec); +#[derive(Clone, PartialEq, Eq)] +pub struct StarkSignature { + pub s: FieldElement, + pub r: FieldElement, +} + +impl StarkSignature { + pub fn to_bytes_be(&self) -> [u8; 64] { + let mut bytes = [0; 64]; + let s = self.s.to_bytes_be(); + let r = self.r.to_bytes_be(); + bytes[0..32].clone_from_slice(&s); + bytes[32..].clone_from_slice(&r); + bytes + } + + pub fn as_hex(&self) -> String { + let bytes = self.to_bytes_be(); + hex::encode(bytes) + } + + pub fn from_bytes_be(bytes: &[u8]) -> Result { + let mut s = [0_u8; 32]; + let mut r = [0_u8; 32]; + if bytes.len() != 64 { + return Err(StarkSignerError::invalid_signature( + "bytes should be 64 length", + )); + } + s.clone_from_slice(&bytes[0..32]); + r.clone_from_slice(&bytes[32..]); + let s = FieldElement::from_bytes_be(&s) + .map_err(|e| StarkSignerError::invalid_signature(e.to_string()))?; + let r = FieldElement::from_bytes_be(&r) + .map_err(|e| StarkSignerError::invalid_signature(e.to_string()))?; + Ok(Self { s, r }) + } +} + +#[derive(Clone, PartialEq, Eq)] +pub struct StarkECDSASignature { + /// starknet public key + pub pub_key: FieldElement, + /// starknet signature + pub signature: StarkSignature, +} + +impl StarkECDSASignature { + pub fn to_bytes_be(&self) -> Vec { + let mut bytes = [0_u8; 96]; + let pub_key = self.pub_key.to_bytes_be(); + let signature = self.signature.to_bytes_be(); + bytes[0..32].copy_from_slice(&pub_key); + bytes[32..].copy_from_slice(&signature); + bytes.to_vec() + } + + pub fn from_bytes_be(bytes: &[u8]) -> Result { + if bytes.len() != 96 { + return Err(StarkSignerError::invalid_signature( + "bytes length should be equal to 96", + )); + } + let mut pub_key = [0_u8; 32]; + pub_key.clone_from_slice(&bytes[0..32]); + let pub_key = FieldElement::from_bytes_be(&pub_key) + .map_err(|_| StarkSignerError::invalid_signature("invalid public key"))?; + let signature = StarkSignature::from_bytes_be(&bytes[32..])?; + Ok(Self { pub_key, signature }) + } + + pub fn as_hex(&self) -> String { + let bytes = self.to_bytes_be(); + format!("0x{}", 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) + } +} + +impl StarkECDSASignature { + pub fn verify(&self, msg: &[u8]) -> Result { + let verifying_key = VerifyingKey::from_scalar(self.pub_key); + let hash = StarkSigner::get_msg_hash(msg); + let is_ok = verifying_key + .verify( + &hash, + &Signature { + r: self.signature.r, + s: self.signature.s, + }, + ) + .map_err(StarkSignerError::invalid_signature)?; + Ok(is_ok) + } +} impl fmt::Display for StarkECDSASignature { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "StarkECDSASignature 0x{}", - hex::encode(self.0.as_slice()) - ) + write!(f, "StarkECDSASignature {}", self.as_hex()) + } +} + +impl fmt::Debug for StarkECDSASignature { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_hex()) } } @@ -22,12 +127,14 @@ impl<'de> Deserialize<'de> for StarkECDSASignature { D: Deserializer<'de>, { let bytes = ZeroPrefixHexSerde::deserialize(deserializer)?; - Ok(Self(bytes)) + let signature: Self = Self::from_bytes_be(&bytes).map_err(serde::de::Error::custom)?; + Ok(signature) } } impl Serialize for StarkECDSASignature { fn serialize(&self, serializer: S) -> Result { - ZeroPrefixHexSerde::serialize(&self.0, serializer) + let bytes = self.to_bytes_be(); + ZeroPrefixHexSerde::serialize(bytes, serializer) } } diff --git a/signers/src/starknet_signer/error.rs b/signers/src/starknet_signer/error.rs new file mode 100644 index 00000000..3e90bcfb --- /dev/null +++ b/signers/src/starknet_signer/error.rs @@ -0,0 +1,33 @@ +use thiserror::Error; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsValue; +#[derive(Debug, Error)] +pub enum StarkSignerError { + #[error("invalid starknet signer")] + InvalidStarknetSigner, + #[error("signature failed: {0}")] + InvalidSignature(String), + #[error("invalid private key:{0}")] + InvalidPrivKey(String), + #[error("sign error: {0}")] + SignError(String), +} + +impl StarkSignerError { + pub fn invalid_signature(s: T) -> Self { + Self::InvalidSignature(s.to_string()) + } + pub fn invalid_privkey(s: T) -> Self { + Self::InvalidPrivKey(s.to_string()) + } + pub fn sign_error(s: T) -> Self { + Self::SignError(s.to_string()) + } +} + +#[cfg(target_arch = "wasm32")] +impl From for JsValue { + fn from(error: StarkSignerError) -> Self { + JsValue::from_str(&format!("error: {error}")) + } +} diff --git a/signers/src/starknet_signer/mod.rs b/signers/src/starknet_signer/mod.rs index be244666..d751d5cd 100644 --- a/signers/src/starknet_signer/mod.rs +++ b/signers/src/starknet_signer/mod.rs @@ -1,3 +1,6 @@ pub mod ecdsa_signature; +pub mod error; +pub mod pk_signer; -pub use ecdsa_signature::StarkECDSASignature; +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 new file mode 100644 index 00000000..be78bcec --- /dev/null +++ b/signers/src/starknet_signer/pk_signer.rs @@ -0,0 +1,86 @@ +use super::error::StarkSignerError as Error; +use crate::starknet_signer::ecdsa_signature::StarkSignature; +use crate::starknet_signer::StarkECDSASignature; +use starknet::core::crypto::compute_hash_on_elements; +use starknet::core::types::FieldElement; +use starknet_signers::SigningKey; + +pub struct StarkSigner(SigningKey); + +impl Default for StarkSigner { + fn default() -> Self { + Self::new() + } +} + +impl StarkSigner { + pub fn new() -> Self { + let signing_key = SigningKey::from_random(); + Self(signing_key) + } + pub fn public_key(&self) -> FieldElement { + let verifying_key = self.0.verifying_key(); + verifying_key.scalar() + } + + pub fn new_from_hex_str(hex_str: &str) -> Result { + let private_key = + FieldElement::from_hex_be(hex_str).map_err(|e| Error::InvalidPrivKey(e.to_string()))?; + let signing_key = SigningKey::from_secret_scalar(private_key); + Ok(Self(signing_key)) + } + + /// 1. get the hash of the message + /// 2. sign hash + 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) + } + + /// 1. change msg to FieldElement list + /// 2. compute hash of the FieldElement list + pub fn get_msg_hash(msg: &[u8]) -> FieldElement { + let elements: Vec<_> = msg + .chunks(32) + .map(|val| FieldElement::from_byte_slice_be(val).unwrap()) + .collect(); + compute_hash_on_elements(&elements) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, Debug)] + struct TestSignature { + signature: StarkECDSASignature, + } + + #[test] + fn test_starknet_sign() { + let private_key = "0x02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159"; + let stark_signer = StarkSigner::new_from_hex_str(private_key).unwrap(); + let msg = b"hello world"; + let signature = stark_signer.sign_message(msg).unwrap(); + let is_ok = signature.verify(msg).unwrap(); + assert!(is_ok); + let data = TestSignature { signature }; + let s = serde_json::to_string(&data).unwrap(); + println!("{s}"); + let data2: TestSignature = serde_json::from_str(&s).unwrap(); + println!("{data2:?}"); + } +} diff --git a/signers/src/zklink_signer/error.rs b/signers/src/zklink_signer/error.rs index 9361b6e0..d5a1e131 100644 --- a/signers/src/zklink_signer/error.rs +++ b/signers/src/zklink_signer/error.rs @@ -1,4 +1,5 @@ use crate::eth_signer::error::EthSignerError; +use crate::starknet_signer::error::StarkSignerError; use thiserror::Error; #[cfg(target_arch = "wasm32")] use wasm_bindgen::JsValue; @@ -17,8 +18,10 @@ pub enum ZkSignerError { InvalidPubkey(String), #[error("invalid public key hash:{0}")] InvalidPubkeyHash(String), - #[error("invalid eth signature: {0}")] - PackedETHSignatureError(#[from] EthSignerError), + #[error("{0}")] + EthSignerError(#[from] EthSignerError), + #[error("{0}")] + StarkSignerError(#[from] StarkSignerError), } impl ZkSignerError { diff --git a/signers/src/zklink_signer/pk_signer.rs b/signers/src/zklink_signer/pk_signer.rs index 02c4b4b5..fdbd549d 100755 --- a/signers/src/zklink_signer/pk_signer.rs +++ b/signers/src/zklink_signer/pk_signer.rs @@ -16,6 +16,7 @@ use std::fmt; #[cfg(feature = "web")] use crate::eth_signer::json_rpc_signer::JsonRpcSigner; use crate::eth_signer::pk_signer::EthSigner; +use crate::starknet_signer::StarkSigner; pub struct ZkLinkSigner(EddsaPrivKey); @@ -92,12 +93,24 @@ impl ZkLinkSigner { Self::new_from_seed(&seed) } + pub fn new_from_hex_stark_signer(hex_private_key: &str) -> Result { + let stark_signer = StarkSigner::new_from_hex_str(hex_private_key)?; + Self::new_from_starknet_signer(&stark_signer) + } + pub fn new_from_eth_signer(eth_signer: &EthSigner) -> Result { let signature = eth_signer.sign_message(Self::SIGN_MESSAGE.as_bytes())?; let seed = signature.serialize_packed(); Self::new_from_seed(&seed) } + /// 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(); + Self::new_from_seed(&seed) + } + #[cfg(feature = "web")] pub async fn new_from_eth_rpc_signer(eth_signer: &JsonRpcSigner) -> Result { let signature = eth_signer diff --git a/types/src/tx_type/order_matching.rs b/types/src/tx_type/order_matching.rs index 8513c43d..6e4e35f8 100644 --- a/types/src/tx_type/order_matching.rs +++ b/types/src/tx_type/order_matching.rs @@ -248,6 +248,10 @@ impl OrderMatching { ) } + pub fn get_starknet_sign_msg(&self) -> Vec { + self.get_bytes() + } + #[cfg(feature = "ffi")] pub fn eth_signature( &self, diff --git a/types/src/tx_type/transfer.rs b/types/src/tx_type/transfer.rs index 14230b52..ff9b5174 100644 --- a/types/src/tx_type/transfer.rs +++ b/types/src/tx_type/transfer.rs @@ -15,6 +15,8 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; use validator::Validate; use zklink_sdk_signers::eth_signer::pk_signer::EthSigner; +use zklink_sdk_signers::starknet_signer::error::StarkSignerError; +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; @@ -87,6 +89,10 @@ impl Transfer { message } + pub fn get_starknet_sign_msg(&self) -> Vec { + self.get_bytes() + } + #[cfg(not(feature = "ffi"))] pub fn eth_signature( &self, @@ -110,6 +116,28 @@ impl Transfer { let tx_eth_signature = TxLayer1Signature::EthereumSignature(eth_signature); Ok(tx_eth_signature) } + + #[cfg(not(feature = "ffi"))] + pub fn starknet_signature( + &self, + starknet_signer: &StarkSigner, + ) -> Result { + let message = self.get_starknet_sign_msg(); + let signature = starknet_signer.sign_message(&message)?; + let tx_eth_signature = TxLayer1Signature::StarkSignature(signature); + Ok(tx_eth_signature) + } + + #[cfg(feature = "ffi")] + pub fn starknet_signature( + &self, + starknet_signer: Arc, + ) -> Result { + let message = self.get_starknet_sign_msg(); + let signature = starknet_signer.sign_message(&message)?; + let tx_eth_signature = TxLayer1Signature::StarkSignature(signature); + Ok(tx_eth_signature) + } } impl GetBytes for Transfer { @@ -210,7 +238,7 @@ mod test { let pubkey_hash = pubkey.public_key_hash(); assert_eq!(pubkey_hash, recover_pubkey_hash); - //check l1 signature + //check eth signature let l1_signature = PackedEthSignature::from_hex(eth_signature).unwrap(); let token_symbol = "USDC"; let message = tx.get_eth_sign_msg(token_symbol).as_bytes().to_vec(); @@ -218,5 +246,15 @@ mod test { let private_key = EthSigner::try_from(private_key_str).unwrap(); let address = private_key.get_address(); assert_eq!(address, recover_address); + + // check starknet signature + let private_key_str = "0x02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159"; + let private_key = StarkSigner::new_from_hex_str(private_key_str).unwrap(); + let message = tx.get_starknet_sign_msg(); + let signature = private_key.sign_message(&message).unwrap(); + let is_ok = signature.verify(&message).unwrap(); + assert!(is_ok); + let starknet_signature = "0x0226424352505249f119fd6913430aad28321afcff18036139b6fa77c4fad6cc064a51bbd082ecb0956589f6089ee24dd9ec32b083f5d56352ca94e5c51d3aa504b98450aa2cc9f9f1f5c0ab47dc28ccff41f7661b6e20181e8204ada6213e1e"; + assert_eq!(signature.as_hex(), starknet_signature); } } diff --git a/types/src/tx_type/withdraw.rs b/types/src/tx_type/withdraw.rs index 7486bc31..5d8a2b82 100644 --- a/types/src/tx_type/withdraw.rs +++ b/types/src/tx_type/withdraw.rs @@ -96,6 +96,10 @@ impl Withdraw { message } + pub fn get_starknet_sign_msg(&self) -> Vec { + self.get_bytes() + } + #[cfg(feature = "ffi")] pub fn eth_signature( &self,