diff --git a/Cargo.lock b/Cargo.lock index 8627b58219..d03968f908 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,7 +11,7 @@ dependencies = [ "futures-core", "futures-sink", "log 0.4.11", - "pin-project", + "pin-project 0.4.27", "tokio 0.2.22", "tokio-util", ] @@ -83,7 +83,7 @@ dependencies = [ "log 0.4.11", "mime 0.3.16", "percent-encoding 2.1.0", - "pin-project", + "pin-project 0.4.27", "rand 0.7.3", "regex", "serde", @@ -159,7 +159,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb" dependencies = [ "futures-util", - "pin-project", + "pin-project 0.4.27", ] [[package]] @@ -219,7 +219,7 @@ dependencies = [ "futures-sink", "futures-util", "log 0.4.11", - "pin-project", + "pin-project 0.4.27", "slab 0.4.2", ] @@ -251,7 +251,7 @@ dependencies = [ "fxhash", "log 0.4.11", "mime 0.3.16", - "pin-project", + "pin-project 0.4.27", "regex", "serde", "serde_json", @@ -1712,9 +1712,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a4d35f7401e948629c9c3d6638fb9bf94e0b2121e96c3b428cc4e631f3eb74" +checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" dependencies = [ "futures-core", "futures-sink", @@ -1722,9 +1722,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d674eaa0056896d5ada519900dbf97ead2e46a7b6621e8160d79e2f2e1e2784b" +checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" [[package]] name = "futures-cpupool" @@ -1750,9 +1750,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc94b64bb39543b4e432f1790b6bf18e3ee3b74653c5449f63310e9a74b123c" +checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" [[package]] name = "futures-lite" @@ -1771,9 +1771,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f57ed14da4603b2554682e9f2ff3c65d7567b53188db96cb71538217fc64581b" +checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -1783,15 +1783,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8764258ed64ebc5d9ed185cf86a95db5cac810269c5d20ececb32e0088abbd" +checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" [[package]] name = "futures-task" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd26820a9f3637f1302da8bceba3ff33adbe53464b54ca24d4e2d4f1db30f94" +checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" dependencies = [ "once_cell", ] @@ -1804,9 +1804,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a894a0acddba51a2d49a6f4263b1e64b8c579ece8af50fa86503d52cd1eea34" +checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" dependencies = [ "futures 0.1.30", "futures-channel", @@ -1816,7 +1816,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project", + "pin-project 1.0.2", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -2201,7 +2201,7 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project", + "pin-project 0.4.27", "socket2", "tokio 0.2.22", "tower-service", @@ -3312,7 +3312,16 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" dependencies = [ - "pin-project-internal", + "pin-project-internal 0.4.27", +] + +[[package]] +name = "pin-project" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7" +dependencies = [ + "pin-project-internal 1.0.2", ] [[package]] @@ -3326,6 +3335,17 @@ dependencies = [ "syn", ] +[[package]] +name = "pin-project-internal" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.1.11" @@ -3436,9 +3456,9 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.18" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro-nested" @@ -5732,7 +5752,10 @@ dependencies = [ name = "zksync_eth_signer" version = "1.0.0" dependencies = [ + "actix-rt", + "actix-web", "async-trait", + "futures 0.3.6", "hex", "jsonrpc-core", "parity-crypto", @@ -5742,6 +5765,7 @@ dependencies = [ "serde_derive", "serde_json", "thiserror", + "tokio 0.2.22", "zksync_types", ] diff --git a/core/lib/eth_signer/Cargo.toml b/core/lib/eth_signer/Cargo.toml index 3d261d79ce..b616c6f1dd 100644 --- a/core/lib/eth_signer/Cargo.toml +++ b/core/lib/eth_signer/Cargo.toml @@ -25,3 +25,10 @@ thiserror = "1.0" jsonrpc-core = "14.0.3" async-trait = "0.1" + +[dev-dependencies] +actix-rt = "1.1.1" +tokio = { version = "0.2", features = ["full"] } +actix-web = "3.0.0" +futures = "0.3" + diff --git a/core/lib/eth_signer/src/json_rpc_signer.rs b/core/lib/eth_signer/src/json_rpc_signer.rs index 407f91a08d..c687d97266 100644 --- a/core/lib/eth_signer/src/json_rpc_signer.rs +++ b/core/lib/eth_signer/src/json_rpc_signer.rs @@ -7,15 +7,18 @@ use jsonrpc_core::types::response::Output; use zksync_types::tx::{PackedEthSignature, TxEthSignature}; use zksync_types::Address; -use parity_crypto::publickey::{public_to_address, recover, Signature}; -use parity_crypto::Keccak256; use serde_json::Value; -pub fn recover_eth_signer(signature: &Signature, msg: &[u8]) -> Result { - let signed_bytes = msg.keccak256().into(); - let public_key = recover(&signature, &signed_bytes) - .map_err(|err| SignerError::RecoverAddress(err.to_string()))?; - Ok(public_to_address(&public_key)) +pub fn is_signature_from_address( + signature: &PackedEthSignature, + msg: &[u8], + address: Address, +) -> Result { + let signature_is_correct = signature + .signature_recover_signer(msg) + .map_err(|err| SignerError::RecoverAddress(err.to_string()))? + == address; + Ok(signature_is_correct) } #[derive(Debug, Clone)] @@ -72,11 +75,7 @@ impl EthereumSigner for JsonRpcSigner { }; // Checks the correctness of the message signature without a prefix - if signature - .signature_recover_signer(msg) - .map_err(|_| SignerError::DefineAddress)? - == self.address()? - { + if is_signature_from_address(&signature, msg, self.address()?)? { Ok(TxEthSignature::EthereumSignature(signature)) } else { Err(SignerError::SigningFailed( @@ -204,18 +203,12 @@ impl JsonRpcSigner { .map_err(|err| SignerError::SigningFailed(err.to_string()))? }; - if recover_eth_signer(&signature.serialize_packed().into(), &msg.as_bytes())? - == self.address()? - { + if is_signature_from_address(&signature, &msg.as_bytes(), self.address()?)? { self.signer_type = Some(SignerType::NotNeedPrefix); } - if recover_eth_signer( - &signature.serialize_packed().into(), - &msg_with_prefix.as_bytes(), - )? == self.address()? - { - self.signer_type = Some(SignerType::NeedPrefix); + if is_signature_from_address(&signature, &msg_with_prefix.as_bytes(), self.address()?)? { + self.signer_type = Some(SignerType::NotNeedPrefix); } match self.signer_type.is_some() { @@ -299,7 +292,7 @@ mod messages { use hex::encode; use zksync_types::Address; - #[derive(Debug, Serialize)] + #[derive(Debug, Serialize, Deserialize)] pub struct JsonRpcRequest { pub id: String, pub method: String, @@ -374,3 +367,131 @@ mod messages { } } } + +#[cfg(test)] +mod tests { + use actix_web::{post, web, App, HttpResponse, HttpServer, Responder}; + use futures::future::{AbortHandle, Abortable}; + use jsonrpc_core::{Failure, Id, Output, Success, Version}; + use parity_crypto::publickey::{Generator, KeyPair, Random}; + use serde_json::json; + + use zksync_types::{ + tx::{PackedEthSignature, TxEthSignature}, + Address, + }; + + use super::{is_signature_from_address, messages::JsonRpcRequest}; + use crate::{EthereumSigner, JsonRpcSigner, RawTransaction}; + + #[post("/")] + async fn index(req: web::Json, state: web::Data) -> impl Responder { + let resp = match req.method.as_str() { + "eth_accounts" => { + let mut addresses = vec![]; + for pair in &state.key_pairs { + addresses.push(pair.address()) + } + + create_success(json!(addresses)) + } + "personal_unlockAccount" => create_success(json!(true)), + "eth_sign" => { + let _address: Address = serde_json::from_value(req.params[0].clone()).unwrap(); + let data: String = serde_json::from_value(req.params[1].clone()).unwrap(); + let data_bytes = hex::decode(&data[2..]).unwrap(); + let signature = + PackedEthSignature::sign(state.key_pairs[0].secret(), &data_bytes).unwrap(); + create_success(json!(signature)) + } + "eth_signTransaction" => { + let tx_value = json!(req.params[0].clone()).to_string(); + let tx = tx_value.as_bytes(); + let hex_data = hex::encode(tx); + create_success(json!({ "raw": hex_data })) + } + _ => create_fail(req.method.clone()), + }; + HttpResponse::Ok().json(json!(resp)) + } + + fn create_fail(method: String) -> Output { + Output::Failure(Failure { + jsonrpc: Some(Version::V2), + error: jsonrpc_core::Error { + code: jsonrpc_core::ErrorCode::ParseError, + message: method, + data: None, + }, + id: Id::Num(1), + }) + } + + fn create_success(result: serde_json::Value) -> Output { + Output::Success(Success { + jsonrpc: Some(Version::V2), + result, + id: Id::Num(1), + }) + } + #[derive(Clone)] + struct State { + key_pairs: Vec, + } + + fn run_server(state: State) -> (String, AbortHandle) { + let mut url = None; + let mut server = None; + for i in 9000..9999 { + let new_url = format!("127.0.0.1:{}", i); + // Try to bind to some port, hope that 999 variants will be enough + let tmp_state = state.clone(); + if let Ok(ser) = + HttpServer::new(move || App::new().data(tmp_state.clone()).service(index)) + .bind(new_url.clone()) + { + server = Some(ser); + url = Some(new_url); + break; + } + } + + let server = server.expect("Could not bind to port from 9000 to 9999"); + let (abort_handle, abort_registration) = AbortHandle::new_pair(); + let future = Abortable::new(server.run(), abort_registration); + tokio::spawn(future); + let address = format!("http://{}/", &url.unwrap()); + (address, abort_handle) + } + + #[actix_rt::test] + async fn run_client() { + let (address, abort_handle) = run_server(State { + key_pairs: vec![Random.generate()], + }); + // Get address is ok, unlock address is ok, recover address from signature is also ok + let client = JsonRpcSigner::new(address, None, None, None).await.unwrap(); + let msg = b"some_text_message"; + if let TxEthSignature::EthereumSignature(signature) = + client.sign_message(msg).await.unwrap() + { + assert!(is_signature_from_address(&signature, msg, client.address().unwrap()).unwrap()) + } else { + panic!("Wrong signature type") + } + let transaction_signature = client + .sign_transaction(RawTransaction { + chain_id: 0, + nonce: Default::default(), + to: None, + value: Default::default(), + gas_price: Default::default(), + gas: Default::default(), + data: vec![], + }) + .await + .unwrap(); + assert_ne!(transaction_signature.len(), 0); + abort_handle.abort(); + } +} diff --git a/core/lib/eth_signer/src/pk_signer.rs b/core/lib/eth_signer/src/pk_signer.rs index c4aae52664..253d9c41fa 100644 --- a/core/lib/eth_signer/src/pk_signer.rs +++ b/core/lib/eth_signer/src/pk_signer.rs @@ -46,3 +46,40 @@ impl EthereumSigner for PrivateKeySigner { Ok(raw_tx.rlp_encode_tx(sig)) } } + +#[cfg(test)] +mod test { + use super::PrivateKeySigner; + use super::RawTransaction; + use crate::EthereumSigner; + use zksync_types::{H160, H256, U256}; + + #[actix_rt::test] + async fn test_generating_signature() { + let private_key = H256::from([5; 32]); + let signer = PrivateKeySigner::new(private_key); + let raw_transaction = RawTransaction { + chain_id: 1, + nonce: U256::from(1), + to: Some(H160::zero()), + value: U256::from(10), + gas_price: U256::from(1), + gas: U256::from(2), + data: vec![1, 2, 3], + }; + let signature = signer + .sign_transaction(raw_transaction.clone()) + .await + .unwrap(); + assert_ne!(signature.len(), 1); + // precalculated signature with right algorithm implementation + let precalculated_signature: Vec = vec![ + 248, 96, 1, 1, 2, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, + 131, 1, 2, 3, 37, 160, 152, 202, 15, 174, 50, 167, 190, 239, 206, 183, 109, 215, 135, + 60, 43, 71, 11, 74, 252, 97, 83, 86, 66, 249, 237, 111, 118, 121, 105, 214, 130, 249, + 160, 106, 110, 143, 138, 113, 12, 177, 239, 121, 188, 247, 21, 236, 236, 163, 254, 28, + 48, 250, 5, 20, 234, 54, 58, 162, 103, 252, 20, 243, 121, 7, 19, + ]; + assert_eq!(signature, precalculated_signature); + } +}