diff --git a/Cargo.toml b/Cargo.toml index 50014898..9fef2caa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,5 @@ members = [ "wallet/coin-bch", "wallet/coin-filecoin", "wallet/coin-btc-fork", + "wallet/coin-ethereum2", ] \ No newline at end of file diff --git a/api/Cargo.toml b/api/Cargo.toml index cbd82954..09cd8d86 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -22,6 +22,7 @@ coin-tron = {path = "../wallet/coin-tron"} coin-ckb = {path = "../wallet/coin-ckb"} coin-bch = {path = "../wallet/coin-bch"} coin-btc-fork = {path = "../wallet/coin-btc-fork"} +coin-ethereum2 = {path = "../wallet/coin-ethereum2"} common = {path = "../common"} coin-tezos = {path = "../wallet/coin-tezos"} diff --git a/api/src/ethereum2_address.rs b/api/src/ethereum2_address.rs new file mode 100644 index 00000000..f2119d21 --- /dev/null +++ b/api/src/ethereum2_address.rs @@ -0,0 +1,17 @@ +use crate::api::PubKeyResult; +use crate::error_handling::Result; +use crate::message_handler::encode_message; +use crate::PubKeyParam; +use coin_ethereum2::address::Eth2Address; +use prost::Message; + +pub fn get_pub_key(param: &PubKeyParam) -> Result> { + let pub_key = Eth2Address::get_pub_key(¶m.path)?; + let return_message = PubKeyResult { + path: param.path.to_owned(), + chain_type: param.chain_type.to_string(), + pub_key: pub_key, + derived_mode: "".to_string(), + }; + encode_message(return_message) +} diff --git a/api/src/ethereum2_signer.rs b/api/src/ethereum2_signer.rs new file mode 100644 index 00000000..71edddb9 --- /dev/null +++ b/api/src/ethereum2_signer.rs @@ -0,0 +1,13 @@ +use crate::error_handling::Result; +use crate::message_handler::encode_message; +use coin_ethereum2::eth2api::Eth2MsgSignInput; +use coin_ethereum2::transaction::Eth2Sign; +use coin_substrate::substrateapi::SubstrateRawTxIn; +use common::SignParam; +use prost::Message; + +pub fn sign_eth2_message(data: &[u8], sign_param: &SignParam) -> Result> { + let sign_data: Eth2MsgSignInput = Eth2MsgSignInput::decode(data).expect("imkey_illegal_param"); + let signed = Eth2Sign::msg_sign(sign_data, sign_param)?; + encode_message(signed) +} diff --git a/api/src/lib.rs b/api/src/lib.rs index 3898a2e0..723be281 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -16,6 +16,8 @@ pub mod device_manager; pub mod eos_pubkey; pub mod eos_signer; pub mod error_handling; +pub mod ethereum2_address; +pub mod ethereum2_signer; pub mod ethereum_address; pub mod ethereum_signer; pub mod filecoin_address; @@ -150,6 +152,7 @@ pub unsafe extern "C" fn call_imkey_api(hex_str: *const c_char) -> *const c_char "EOS" => eos_pubkey::get_eos_pubkey(¶m), "TEZOS" => tezos_address::get_pub_key(¶m), "COSMOS" => cosmos_address::get_cosmos_pub_key(¶m), + "ETHEREUM2" => ethereum2_address::get_pub_key(¶m), _ => Err(format_err!("get_pub_key unsupported_chain")), } }), diff --git a/common/src/apdu.rs b/common/src/apdu.rs index 259f433d..7f33938f 100644 --- a/common/src/apdu.rs +++ b/common/src/apdu.rs @@ -541,6 +541,42 @@ impl ImkApdu { } } +pub struct BlsApdu(); + +impl Default for BlsApdu { + fn default() -> Self { + BlsApdu() + } +} + +impl BlsApdu { + pub fn sign(data: &[u8]) -> Vec { + Apdu::prepare_sign(0x81, data.to_vec()) + } + pub fn msg_sign(data: &[u8]) -> Vec { + Apdu::prepare_sign(0x55, data.to_vec()) + } + + pub fn get_xpub(data: &[u8]) -> String { + if data.len() as u32 > LC_MAX { + panic!("data to long"); + } + let mut apdu = ApduHeader::new(0x80, 0x82, 0x00, 0x00, data.len() as u8).to_array(); + apdu.extend(data); + apdu.push(0x00); //Le + hex::encode(apdu) + } + + pub fn register_address(name: &[u8], address: &[u8]) -> String { + let mut data: Vec = vec![]; + data.push(address.len() as u8); + data.extend(address); + data.push(name.len() as u8); + data.extend(name); + Apdu::register_address(0x83, &data) + } +} + pub struct ApduCheck {} impl ApduCheck { diff --git a/common/src/constants.rs b/common/src/constants.rs index f1349eaa..81c4fb27 100644 --- a/common/src/constants.rs +++ b/common/src/constants.rs @@ -1,6 +1,6 @@ pub const VERSION: &str = "2.9.8"; -pub const URL: &str = "https://imkey.online:1000/imkey"; -// pub const URL: &str = "https://imkeyserver.com:10444/imkey"; +// pub const URL: &str = "https://imkey.online:1000/imkey"; +pub const URL: &str = "https://imkeyserver.com:10443/imkey"; pub const TSM_ACTION_SE_SECURE_CHECK: &str = "/seSecureCheck"; pub const TSM_ACTION_APP_DOWNLOAD: &str = "/appDownload"; @@ -29,6 +29,7 @@ pub const NERVOS_AID: &str = "695F6B315F636B62"; pub const TEZOS_AID: &str = "695F65645F78747A"; pub const BCH_AID: &str = "695F626368"; pub const LTC_AID: &str = "695F6C7463"; +pub const ETH2_AID: &str = "695f65746832"; pub const BL_AID: &str = "D0426F6F746C6F61646572"; @@ -41,6 +42,7 @@ pub const NERVOS_PATH: &str = "m/44'/309'/0'/0/0"; pub const POLKADOT_PATH: &str = "m/44'/354'/0'/0'/0'"; pub const KUSAMA_PATH: &str = "m/44'/434'/0'/0'/0'"; pub const TRON_PATH: &str = "m/44'/195'/0'/0/0"; +pub const ETH2_PATH: &str = "m/12381/3600/0/0"; pub const MAX_UTXO_NUMBER: usize = 252; pub const EACH_ROUND_NUMBER: usize = 5; diff --git a/common/src/error.rs b/common/src/error.rs index b686ef0e..71e59514 100644 --- a/common/src/error.rs +++ b/common/src/error.rs @@ -82,4 +82,6 @@ pub enum CoinError { InvalidVersion, #[fail(display = "invalid addr length")] InvalidAddrLength, + #[fail(display = "signature data too long")] + SignDataTooLong, } diff --git a/device/src/bind_code.txt b/device/src/bind_code.txt new file mode 100644 index 00000000..e69de29b diff --git a/proto/build.rs b/proto/build.rs index 0f2644ed..8c644815 100644 --- a/proto/build.rs +++ b/proto/build.rs @@ -51,6 +51,10 @@ fn main() { // tcx-btc-fork env::set_var("OUT_DIR", "../wallet/coin-btc-fork/src"); prost_build::compile_protos(&["src/btcfork.proto"], &["src/"]).unwrap(); + + // eth2 + env::set_var("OUT_DIR", "../wallet/coin-ethereum2/src"); + prost_build::compile_protos(&["src/eth2.proto"], &["src/"]).unwrap(); } #[cfg(test)] diff --git a/proto/src/eth2.proto b/proto/src/eth2.proto new file mode 100644 index 00000000..78c39fa5 --- /dev/null +++ b/proto/src/eth2.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; +package eth2api; + +message Eth2MsgSignInput { + string message = 1; +} + +message Eth2MsgSignOutput { + string signature = 1; +} \ No newline at end of file diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 21998d3c..00000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -1.47.0 diff --git a/wallet/coin-ethereum2/Cargo.toml b/wallet/coin-ethereum2/Cargo.toml new file mode 100644 index 00000000..6f346af2 --- /dev/null +++ b/wallet/coin-ethereum2/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "coin-ethereum2" +version = "0.1.0" +authors = ["xiaoguang "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +common = {path = "../../common"} +device = {path = "../../device"} +transport = {path = "../../transport"} +failure = "0.1.6" +hex = "0.3.2" +prost = "0.6.1" +prost-types = "0.6.1" \ No newline at end of file diff --git a/wallet/coin-ethereum2/src/address.rs b/wallet/coin-ethereum2/src/address.rs new file mode 100644 index 00000000..09dde32b --- /dev/null +++ b/wallet/coin-ethereum2/src/address.rs @@ -0,0 +1,74 @@ +use crate::Result; + +use common::apdu::{Apdu, ApduCheck, BlsApdu}; +use common::constants::ETH2_AID; +use common::error::CoinError; +use common::path::check_path_validity; +use common::utility; +use common::utility::secp256k1_sign_verify; +use device::device_binding::KEY_MANAGER; +use hex; +use transport::message; +use transport::message::send_apdu; + +#[derive(Debug)] +pub struct Eth2Address {} + +impl Eth2Address { + pub fn get_pub_key(path: &str) -> Result { + check_path_validity(path)?; + + let select_apdu = Apdu::select_applet(ETH2_AID); + let select_response = message::send_apdu(select_apdu)?; + ApduCheck::check_response(&select_response)?; + + let key_manager_obj = KEY_MANAGER.lock(); + let bind_signature = utility::secp256k1_sign(&key_manager_obj.pri_key, &path.as_bytes())?; + + let mut apdu_pack: Vec = vec![]; + apdu_pack.push(0x00); + apdu_pack.push(bind_signature.len() as u8); + apdu_pack.extend(bind_signature.as_slice()); + apdu_pack.push(0x01); + apdu_pack.push(path.as_bytes().len() as u8); + apdu_pack.extend(path.as_bytes()); + + //get public + let msg_pubkey = BlsApdu::get_xpub(&apdu_pack); + let res_msg_pubkey = send_apdu(msg_pubkey)?; + ApduCheck::check_response(&res_msg_pubkey)?; + + let pubkey = &res_msg_pubkey[..96]; + let sign_result = &res_msg_pubkey[96..res_msg_pubkey.len() - 4]; + + //se signature verify + let sign_verify_result = secp256k1_sign_verify( + &key_manager_obj.se_pub_key, + hex::decode(sign_result).unwrap().as_slice(), + hex::decode(pubkey).unwrap().as_slice(), + )?; + if !sign_verify_result { + return Err(CoinError::ImkeySignatureVerifyFail.into()); + } + + Ok(pubkey.to_string()) + } +} + +#[cfg(test)] +mod test { + use crate::address::Eth2Address; + use common::constants; + use device::device_binding::bind_test; + + #[test] + fn test_get_pub_key() { + bind_test(); + + let uncomprs_pubkey = Eth2Address::get_pub_key(constants::ETH2_PATH).unwrap(); + assert_eq!( + &uncomprs_pubkey, + "80CCA779CE5C8C183232518B3F7BF9D1A4AB8767589D320FE39A9379A674765A766FFC99FACC7D60F157F7E5F01E9D01" + ); + } +} diff --git a/wallet/coin-ethereum2/src/eth2api.rs b/wallet/coin-ethereum2/src/eth2api.rs new file mode 100644 index 00000000..70332616 --- /dev/null +++ b/wallet/coin-ethereum2/src/eth2api.rs @@ -0,0 +1,10 @@ +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Eth2MsgSignInput { + #[prost(string, tag = "1")] + pub message: std::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Eth2MsgSignOutput { + #[prost(string, tag = "1")] + pub signature: std::string::String, +} diff --git a/wallet/coin-ethereum2/src/lib.rs b/wallet/coin-ethereum2/src/lib.rs new file mode 100644 index 00000000..27f0c85e --- /dev/null +++ b/wallet/coin-ethereum2/src/lib.rs @@ -0,0 +1,16 @@ +pub mod address; +pub mod eth2api; +pub mod transaction; + +#[macro_use] +extern crate failure; +use core::result; +pub type Result = result::Result; + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/wallet/coin-ethereum2/src/transaction.rs b/wallet/coin-ethereum2/src/transaction.rs new file mode 100644 index 00000000..f9add47f --- /dev/null +++ b/wallet/coin-ethereum2/src/transaction.rs @@ -0,0 +1,131 @@ +use crate::eth2api::{Eth2MsgSignInput, Eth2MsgSignOutput}; +use crate::Result; +use common::apdu::{Apdu, ApduCheck, BlsApdu}; +use common::error::CoinError; +use common::path::check_path_validity; +use common::utility::{is_valid_hex, secp256k1_sign}; +use common::{constants, utility, SignParam}; +use device::device_binding::KEY_MANAGER; +use transport::message::{send_apdu, send_apdu_timeout}; + +#[derive(Default, Debug, Clone, PartialEq)] +pub struct Eth2Sign {} + +impl Eth2Sign { + pub fn msg_sign( + msg_sign_data: Eth2MsgSignInput, + sign_param: &SignParam, + ) -> Result { + check_path_validity(sign_param.path.as_str()).expect("check path error"); + let select_apdu = Apdu::select_applet(constants::ETH2_AID); + let select_result = send_apdu(select_apdu)?; + ApduCheck::check_response(&select_result)?; + + let message_to_sign; + if is_valid_hex(&msg_sign_data.message) { + let value = if msg_sign_data.message.to_lowercase().starts_with("0x") { + &msg_sign_data.message[2..] + } else { + &msg_sign_data.message + }; + message_to_sign = hex::decode(value).unwrap(); + } else { + message_to_sign = msg_sign_data.message.into_bytes(); + } + + if message_to_sign.len() > 255 { + return Err(CoinError::SignDataTooLong.into()); + } + + //organize data + let mut data_pack: Vec = Vec::new(); + + data_pack.extend([1, message_to_sign.len() as u8].iter()); + data_pack.extend(message_to_sign.iter()); + + //path + data_pack.extend([2, sign_param.path.as_bytes().len() as u8].iter()); + data_pack.extend(sign_param.path.as_bytes().iter()); + + let key_manager_obj = KEY_MANAGER.lock(); + let bind_signature = secp256k1_sign(&key_manager_obj.pri_key, &data_pack).unwrap(); + + let mut apdu_pack: Vec = Vec::new(); + apdu_pack.push(0x00); + apdu_pack.push(bind_signature.len() as u8); + apdu_pack.extend(bind_signature.as_slice()); + apdu_pack.extend(data_pack.as_slice()); + + //sign + let mut sign_response = "".to_string(); + let sign_apdus = BlsApdu::msg_sign(&apdu_pack); + for apdu in sign_apdus { + sign_response = send_apdu_timeout(apdu, constants::TIMEOUT_LONG)?; + ApduCheck::check_response(&sign_response)?; + } + + // verify + let sign_len = usize::from_str_radix(&sign_response[..2], 16).unwrap() * 2 + 2; + let sign_source_val = &sign_response[..sign_len]; + let sign_result = &sign_response[sign_len..sign_response.len() - 4]; + let sign_verify_result = utility::secp256k1_sign_verify( + &key_manager_obj.se_pub_key, + hex::decode(sign_result).unwrap().as_slice(), + hex::decode(sign_source_val).unwrap().as_slice(), + )?; + + if !sign_verify_result { + return Err(CoinError::ImkeySignatureVerifyFail.into()); + } + + let sig = hex::decode(&sign_response[2..sign_len])?; + + Ok(Eth2MsgSignOutput { + signature: hex::encode(sig), + }) + } +} + +#[cfg(test)] +mod test { + use crate::eth2api::Eth2MsgSignInput; + use crate::transaction::Eth2Sign; + use common::{constants, SignParam}; + use device::device_binding::bind_test; + + #[test] + fn msg_sign_test() { + //mnemonicx = "gauge hole clog property soccer idea cycle stadium utility slice hold chief" + bind_test(); + let sign_param = SignParam { + chain_type: "ETHEREUM2".to_string(), + path: constants::ETH2_PATH.to_string(), + network: "".to_string(), + input: None, + payment: "".to_string(), + receiver: "".to_string(), + sender: "".to_string(), + fee: "".to_string(), + }; + let eth2MsgSignInput = Eth2MsgSignInput { + message: "0x64726E3DA8".to_string(), + }; + let signature = Eth2Sign::msg_sign(eth2MsgSignInput, &sign_param); + assert_eq!(signature.unwrap().signature, "a1367b7e99d5bf139a9cd6cd857bbf12c379397b1c9347afd794da0459efda3850298c33c9f292e5ebb05143e77afe4d092c51170866cb852abfd9bb7139e6e72e34b44318d6ab30390d48d6095c2df7489877de6091cc68329a0537a8da4bf2"); + let sign_param = SignParam { + chain_type: "ETHEREUM2".to_string(), + path: constants::ETH2_PATH.to_string(), + network: "".to_string(), + input: None, + payment: "".to_string(), + receiver: "".to_string(), + sender: "".to_string(), + fee: "".to_string(), + }; + let eth2MsgSignInput = Eth2MsgSignInput { + message: "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111".to_string(), + }; + let signature = Eth2Sign::msg_sign(eth2MsgSignInput, &sign_param); + assert_eq!(signature.unwrap().signature, "b24f8da68fdfcc492fd57c68e8818513a2613c18f293042af9ac4d9a371f5f33d57848552a07292c313411db1306d1e409eba509a33c3162703b146516ef498d1bf32e6e4157922b345d688b5a75b4aab26d3c0f74e10040e481b9066b40abfc"); + } +}