diff --git a/token-core/tcx-btc-kin/src/signer.rs b/token-core/tcx-btc-kin/src/signer.rs index 2f653da2..99f1e2bd 100644 --- a/token-core/tcx-btc-kin/src/signer.rs +++ b/token-core/tcx-btc-kin/src/signer.rs @@ -1153,7 +1153,6 @@ mod tests { } mod ltc { - //todo: test error use super::*; #[test] diff --git a/token-core/tcx-crypto/src/crypto.rs b/token-core/tcx-crypto/src/crypto.rs index c141210e..ad775735 100644 --- a/token-core/tcx-crypto/src/crypto.rs +++ b/token-core/tcx-crypto/src/crypto.rs @@ -304,6 +304,7 @@ impl Crypto { fn derive_key(&self, password: &str) -> Result> { let mut derived_key: Credential = [0u8; CREDENTIAL_LEN]; self.kdf.derive_key(password.as_bytes(), &mut derived_key); + Ok(derived_key.to_vec()) } diff --git a/token-core/tcx-eth/src/api.rs b/token-core/tcx-eth/src/api.rs deleted file mode 100644 index dd8c2a3c..00000000 --- a/token-core/tcx-eth/src/api.rs +++ /dev/null @@ -1,88 +0,0 @@ -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct EthTxInput { - #[prost(string, tag = "1")] - pub nonce: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub gas_price: ::prost::alloc::string::String, - #[prost(string, tag = "3")] - pub gas_limit: ::prost::alloc::string::String, - #[prost(string, tag = "4")] - pub to: ::prost::alloc::string::String, - #[prost(string, tag = "5")] - pub value: ::prost::alloc::string::String, - #[prost(string, tag = "6")] - pub data: ::prost::alloc::string::String, - #[prost(string, tag = "7")] - pub chain_id: ::prost::alloc::string::String, - #[prost(string, tag = "8")] - pub tx_type: ::prost::alloc::string::String, - #[prost(string, tag = "9")] - pub max_fee_per_gas: ::prost::alloc::string::String, - #[prost(string, tag = "10")] - pub max_priority_fee_per_gas: ::prost::alloc::string::String, - #[prost(message, repeated, tag = "11")] - pub access_list: ::prost::alloc::vec::Vec, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct AccessList { - #[prost(string, tag = "1")] - pub address: ::prost::alloc::string::String, - #[prost(string, repeated, tag = "2")] - pub storage_keys: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct EthTxOutput { - #[prost(string, tag = "1")] - pub signature: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub tx_hash: ::prost::alloc::string::String, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct EthMessageInput { - #[prost(string, tag = "1")] - pub message: ::prost::alloc::string::String, - #[prost(enumeration = "SignatureType", tag = "2")] - pub signature_type: i32, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct EthMessageOutput { - #[prost(string, tag = "1")] - pub signature: ::prost::alloc::string::String, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct EthRecoverAddressInput { - #[prost(string, tag = "1")] - pub message: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub signature: ::prost::alloc::string::String, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct EthRecoverAddressOutput { - #[prost(string, tag = "1")] - pub address: ::prost::alloc::string::String, -} -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum SignatureType { - PersonalSign = 0, - EcSign = 1, -} -impl SignatureType { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - SignatureType::PersonalSign => "PersonalSign", - SignatureType::EcSign => "EcSign", - } - } -} diff --git a/token-core/tcx-eth/src/lib.rs b/token-core/tcx-eth/src/lib.rs index dc389425..5b6cb94d 100644 --- a/token-core/tcx-eth/src/lib.rs +++ b/token-core/tcx-eth/src/lib.rs @@ -1,8 +1,8 @@ pub mod address; pub mod signer; -pub mod transaction; +pub mod transaction_types; -pub mod api; +pub mod transaction; use core::result; @@ -14,8 +14,8 @@ pub mod ethereum { pub const CHAINS: [&str; 1] = ["ETHEREUM"]; pub type Address = EthAddress; - pub type TransactionInput = crate::api::EthTxInput; - pub type TransactionOutput = crate::api::EthTxOutput; - pub type MessageInput = crate::api::EthMessageInput; - pub type MessageOutput = crate::api::EthMessageOutput; + pub type TransactionInput = crate::transaction::EthTxInput; + pub type TransactionOutput = crate::transaction::EthTxOutput; + pub type MessageInput = crate::transaction::EthMessageInput; + pub type MessageOutput = crate::transaction::EthMessageOutput; } diff --git a/token-core/tcx-eth/src/signer.rs b/token-core/tcx-eth/src/signer.rs index fe520c34..2a801acf 100644 --- a/token-core/tcx-eth/src/signer.rs +++ b/token-core/tcx-eth/src/signer.rs @@ -1,9 +1,9 @@ use crate::address::pubkey_to_address; -use crate::api::{ +use crate::transaction::{ EthMessageInput, EthMessageOutput, EthRecoverAddressInput, EthRecoverAddressOutput, EthTxInput, EthTxOutput, SignatureType, }; -use crate::transaction::{Signature, Transaction, TransactionType}; +use crate::transaction_types::{Signature, Transaction, TransactionType}; use crate::Result; use ethereum_types::{Address, H256}; use std::str::FromStr; @@ -82,7 +82,7 @@ impl TryFrom<&EthTxInput> for Transaction { } impl EthTxInput { - pub fn access_list(&self) -> Result { + pub fn access_list(&self) -> Result { let mut ret_access_list = vec![]; for item in &self.access_list { @@ -91,7 +91,7 @@ impl EthTxInput { storage_keys.push(H256::from_str(key)?); } - ret_access_list.push(crate::transaction::AccessListItem { + ret_access_list.push(crate::transaction_types::AccessListItem { address: Address::from_slice(&Vec::from_0x_hex(&item.address)?), storage_keys, }); @@ -128,7 +128,7 @@ impl EthRecoverAddressInput { #[cfg(test)] mod test { - use crate::api::{ + use crate::transaction::{ AccessList, EthMessageInput, EthMessageOutput, EthRecoverAddressInput, EthTxInput, EthTxOutput, SignatureType, }; diff --git a/token-core/tcx-eth/src/transaction.rs b/token-core/tcx-eth/src/transaction.rs index 6a5a6eb8..dd8c2a3c 100644 --- a/token-core/tcx-eth/src/transaction.rs +++ b/token-core/tcx-eth/src/transaction.rs @@ -1,403 +1,88 @@ -use super::Result; -use anyhow::anyhow; -use ethereum_types::{Address, H256, U256, U64}; -use rlp::RlpStream; -use secp256k1::{ecdsa::RecoverableSignature, ecdsa::RecoveryId, Message, PublicKey}; -use std::str::FromStr; -use tcx_common::{keccak256, Hash256, ToHex}; -use tcx_primitive::SECP256K1_ENGINE; - -pub fn to_eip155_v>(recovery_id: T, chain_id: U64) -> U64 { - U64::from(recovery_id.into() + 35 + chain_id.as_u64() * 2) +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EthTxInput { + #[prost(string, tag = "1")] + pub nonce: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub gas_price: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub gas_limit: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub to: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub value: ::prost::alloc::string::String, + #[prost(string, tag = "6")] + pub data: ::prost::alloc::string::String, + #[prost(string, tag = "7")] + pub chain_id: ::prost::alloc::string::String, + #[prost(string, tag = "8")] + pub tx_type: ::prost::alloc::string::String, + #[prost(string, tag = "9")] + pub max_fee_per_gas: ::prost::alloc::string::String, + #[prost(string, tag = "10")] + pub max_priority_fee_per_gas: ::prost::alloc::string::String, + #[prost(message, repeated, tag = "11")] + pub access_list: ::prost::alloc::vec::Vec, } - -#[derive(Debug, Clone, PartialEq)] -pub enum TransactionType { - Legacy, - Eip2930, - Eip1559, +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AccessList { + #[prost(string, tag = "1")] + pub address: ::prost::alloc::string::String, + #[prost(string, repeated, tag = "2")] + pub storage_keys: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } - -impl Default for TransactionType { - fn default() -> Self { - TransactionType::Legacy - } -} - -impl FromStr for TransactionType { - type Err = anyhow::Error; - - fn from_str(tx_type: &str) -> Result { - match tx_type { - "0x01" | "1" | "01" => Ok(TransactionType::Eip2930), - "0x02" | "2" | "02" => Ok(TransactionType::Eip1559), - _ => Ok(TransactionType::Legacy), - } - } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EthTxOutput { + #[prost(string, tag = "1")] + pub signature: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub tx_hash: ::prost::alloc::string::String, } - -pub type AccessList = Vec; - -#[derive(Debug, Default, Clone, PartialEq)] -pub struct AccessListItem { - pub address: Address, - pub storage_keys: Vec, +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EthMessageInput { + #[prost(string, tag = "1")] + pub message: ::prost::alloc::string::String, + #[prost(enumeration = "SignatureType", tag = "2")] + pub signature_type: i32, } - -#[derive(Debug, Default, Clone, PartialEq)] -pub struct Signature { - pub v: U64, - pub r: U256, - pub s: U256, +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EthMessageOutput { + #[prost(string, tag = "1")] + pub signature: ::prost::alloc::string::String, } - -impl ToHex for Signature { - fn to_hex(&self) -> String { - <[u8; 65]>::from(self).to_hex() - } -} - -impl From<&Signature> for [u8; 65] { - fn from(src: &Signature) -> [u8; 65] { - let mut sig = [0u8; 65]; - let mut r_bytes = [0u8; 32]; - let mut s_bytes = [0u8; 32]; - src.r.to_big_endian(&mut r_bytes); - src.s.to_big_endian(&mut s_bytes); - sig[..32].copy_from_slice(&r_bytes); - sig[32..64].copy_from_slice(&s_bytes); - // TODO: What if we try to serialize a signature where - // the `v` is not normalized? - - // The u64 to u8 cast is safe because `sig.v` can only ever be 27 or 28 - // here. Regarding EIP-155, the modification to `v` happens during tx - // creation only _after_ the transaction is signed using - // `ethers_signers::to_eip155_v`. - sig[64] = src.v.as_u64() as u8; - sig - } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EthRecoverAddressInput { + #[prost(string, tag = "1")] + pub message: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub signature: ::prost::alloc::string::String, } - -impl Signature { - pub fn from_slice(data: &[u8]) -> Result { - if data.len() != 65 { - return Err(anyhow!("Invalid signature length")); - } - - let v = if data[64] >= 27 { - U64::from(data[64]) - } else { - U64::from(data[64] + 27) - }; - - Ok(Signature { - v, - r: U256::from_big_endian(&data[0..32]), - s: U256::from_big_endian(&data[32..64]), - }) - } - - pub fn recover_public_key(&self, hash: &[u8]) -> Result { - let message = Message::from_slice(hash)?; - let recovery_id = RecoveryId::from_i32(self.v.as_u32() as i32 - 27)?; - let sig = <[u8; 65]>::from(self); - let signature = RecoverableSignature::from_compact(&sig[0..64], recovery_id)?; - - Ok(SECP256K1_ENGINE.recover_ecdsa(&message, &signature)?) - } -} - -#[derive(Debug, Default, Clone, PartialEq)] -pub struct Transaction { - pub to: Address, - pub nonce: U64, - pub gas: U256, - pub gas_price: U256, - pub value: U256, - pub data: Vec, - pub access_list: AccessList, - pub max_priority_fee_per_gas: U256, - pub max_fee_per_gas: U256, - pub chain_id: U64, - pub transaction_type: TransactionType, -} - -#[derive(Debug, Default, Clone, PartialEq)] -pub struct SignedTransaction { - pub transaction: Transaction, - pub signature: Signature, -} - -impl SignedTransaction { - pub fn hash(&self) -> H256 { - H256::from(keccak256(self.raw().as_slice())) - } - - pub fn raw(&self) -> Vec { - self.transaction.encode(Some(&self.signature)) - } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EthRecoverAddressOutput { + #[prost(string, tag = "1")] + pub address: ::prost::alloc::string::String, } - -impl Transaction { - fn rlp_append_legacy(&self, stream: &mut RlpStream) { - stream.append(&self.nonce); - stream.append(&self.gas_price); - stream.append(&self.gas); - stream.append(&self.to); - stream.append(&self.value); - stream.append(&self.data); - } - - fn encode_legacy(&self, signature: Option<&Signature>) -> RlpStream { - let mut stream = RlpStream::new(); - - stream.begin_list(9); - - self.rlp_append_legacy(&mut stream); - - if let Some(signature) = signature { - self.rlp_append_signature(&mut stream, signature); - } else { - stream.append(&self.chain_id); - stream.append(&0u8); - stream.append(&0u8); - } - - stream - } - - fn encode_eip2930_payload(&self, signature: Option<&Signature>) -> RlpStream { - let mut stream = RlpStream::new(); - - let list_size = if signature.is_some() { 11 } else { 8 }; - stream.begin_list(list_size); - - // append chain_id. from EIP-2930: chainId is defined to be an integer of arbitrary size. - stream.append(&self.chain_id); - - self.rlp_append_legacy(&mut stream); - self.rlp_append_access_list(&mut stream); - - if let Some(signature) = signature { - self.rlp_append_signature(&mut stream, signature); - } - - stream - } - - fn encode_eip1559_payload(&self, signature: Option<&Signature>) -> RlpStream { - let mut stream = RlpStream::new(); - - let list_size = if signature.is_some() { 12 } else { 9 }; - stream.begin_list(list_size); - - stream.append(&self.chain_id); - - stream.append(&self.nonce); - stream.append(&self.max_priority_fee_per_gas); - stream.append(&self.max_fee_per_gas); - stream.append(&self.gas); - stream.append(&self.to); - stream.append(&self.value); - stream.append(&self.data); - - self.rlp_append_access_list(&mut stream); - - if let Some(signature) = signature { - self.rlp_append_signature(&mut stream, signature); - } - - stream - } - - fn rlp_append_signature(&self, stream: &mut RlpStream, signature: &Signature) { - match self.transaction_type { - TransactionType::Eip2930 | TransactionType::Eip1559 => { - stream.append(&U64::from(signature.v.as_u64() - 27)) - } - _ => stream.append(&to_eip155_v(signature.v.as_u64() - 27, self.chain_id)), - }; - - stream.append(&signature.r); - stream.append(&signature.s); - } - - fn rlp_append_access_list(&self, stream: &mut RlpStream) { - stream.begin_list(self.access_list.len()); - for access in self.access_list.iter() { - stream.begin_list(2); - stream.append(&access.address); - stream.begin_list(access.storage_keys.len()); - for storage_key in access.storage_keys.iter() { - stream.append(storage_key); - } - } - } - - pub fn encode(&self, signature: Option<&Signature>) -> Vec { - match self.transaction_type { - TransactionType::Legacy => { - let stream = self.encode_legacy(signature); - stream.out().to_vec() - } - - TransactionType::Eip2930 => { - let stream = self.encode_eip2930_payload(signature); - [&[1], stream.as_raw()].concat() - } - - TransactionType::Eip1559 => { - let stream = self.encode_eip1559_payload(signature); - [&[2], stream.as_raw()].concat() - } - } - } - - pub fn sighash(&self) -> Hash256 { - keccak256(&self.encode(None)) - } - - pub fn to_signed_tx(&self, signature: Signature) -> SignedTransaction { - SignedTransaction { - transaction: self.clone(), - signature, - } - } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SignatureType { + PersonalSign = 0, + EcSign = 1, } - -#[cfg(test)] -mod test { - use super::{Signature, SignedTransaction, Transaction, TransactionType}; - use crate::transaction::AccessListItem; - use ethereum_types::{Address, H160, H256, U256, U64}; - use std::str::FromStr; - use tcx_common::ToHex; - - fn test_encode_transaction() { - let inputs = vec![ - "f8ac826b4685152b17381683015f90946149c26cd2f7b5ccdb32029af817123f6e37df5b80b844a9059cbb0000000000000000000000004bc7610cc68c1647a3898273fd5c6fada35231ec000000000000000000000000000000000000000000000016a8ea51575148000025a0cf11204d960208f37e5cbb1f77c65868e94fcd7d1b6e1a4024c3de2ff038fbd6a04355c8fc835261d4a31b0195af79dfc6a24e3c1e8efb540144618c36f2c9b61a", - "02f875018321ad6f847735940085266ac4320082520894526ea8b99ba85ec6d883b06545f4b76b60ffe14d879790f8dbf7100080c080a06126c506f0f6b35eca2087e8eb05287d98ccde7fb29499338c70e836538ec212a067eaf101982fb70eb15074c7e58807dac1c725bb33871801d769019efbb0721c", - "01f902b801820486854671ef231a8307a120947da08ac2740268f36634c2579739be26a123595880b8e4a4629a100000000000000000000000006d7b6dad6abed1dfa5eba37a6667ba9dcfd49077000000000000000000000000132eeb05d5cb6829bd34f552cde0b6b708ef501400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000003da7cd5f6614bd440000000000000000000000000000000000000000000000456a8e21f079c20b8c0000000000000000000000000000000000000000000000003e39a04969da8e6e0000000000000000000000000000000000000000000000000000000000c8913df90168d6941b40183efb4dd766f11bda7a7c3ad8982e998421c0d694c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2c0f89b946d7b6dad6abed1dfa5eba37a6667ba9dcfd49077f884a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000008a0000000000000000000000000000000000000000000000000000000000000000cf89b94132eeb05d5cb6829bd34f552cde0b6b708ef5014f884a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000008a0000000000000000000000000000000000000000000000000000000000000000c80a0e8f03759dc73deaf10ceabc76e206b3a3353a19cbc908ba87c34e5f1d4dc7ef9a0681de4e59147a73ec88422fe9266a7f263c86c4c809e9179a5431fdd5cbe68b4", - "f8a8808503dea3b69c82d03294dac17f958d2ee523a2206206994597c13d831ec780b844095ea7b30000000000000000000000008a42d311d282bfcaa5133b2de0a8bcdbecea3073ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff269f5327baacf5a3ae6523c6e6ebaa0cd8f98a8ac513c1baed06ec4811b1cfd979a04e4a4714104d70a6f6b91e80846ff8864aa5343e3afcd9ec59683fb011059cfb"]; - let hashs = vec![ - "83ab13113196831454d55ace3a3975da33ec0dc7126767c420258df11407df89", - "a44c3f4b6fd573f1b5585e6fb9624eb589537f03c25461828767c7b3d56fbb3f", - "e19e246e305b2c092e77028777a86144c5fc3f56d41c4bdfcc32ce2438993358", - "885525dbd0a74e58570a0e96a851225cb8f5c690c49b554d9a69c79389ac0374", - ]; - let signed_transactions = vec![ - SignedTransaction { - transaction: Transaction { - to: Address::from_str("6149c26cd2f7b5ccdb32029af817123f6e37df5b").unwrap(), - nonce: U64::from(0x6b46), - gas: U256::from_str("15f90").unwrap(), - gas_price: U256::from_str("152b173816").unwrap(), - value: U256::from_str("00").unwrap(), - data: hex::decode("a9059cbb0000000000000000000000004bc7610cc68c1647a3898273fd5c6fada35231ec000000000000000000000000000000000000000000000016a8ea515751480000").unwrap(), - transaction_type: TransactionType::Legacy, - access_list: vec![], - max_priority_fee_per_gas: U256::from_str("00").unwrap(), - max_fee_per_gas: U256::from_str("00").unwrap(), - chain_id: U64::from(1), - }, - signature: Signature { - v: U64::from(0x25+27), - r: U256::from_str("cf11204d960208f37e5cbb1f77c65868e94fcd7d1b6e1a4024c3de2ff038fbd6").unwrap(), - s: U256::from_str("4355c8fc835261d4a31b0195af79dfc6a24e3c1e8efb540144618c36f2c9b61a").unwrap(), - }, - }, - SignedTransaction { - transaction: Transaction { - gas: U256::from_str("5208").unwrap(), - gas_price: U256::from_str("266ac43200").unwrap(), - max_fee_per_gas: U256::from_str("266ac43200").unwrap(), - max_priority_fee_per_gas: U256::from_str("77359400").unwrap(), - data: vec![], - nonce: U64::from(0x21ad6f), - value: U256::from_str("9790f8dbf71000").unwrap(), - to: Address::from_str("526ea8b99ba85ec6d883b06545f4b76b60ffe14d").unwrap(), - transaction_type: TransactionType::Eip1559, - access_list: vec![], - chain_id: U64::from(1), - }, - signature: Signature { - v: U64::from(0x0+27), - r: U256::from_str("6126c506f0f6b35eca2087e8eb05287d98ccde7fb29499338c70e836538ec212").unwrap(), - s: U256::from_str("67eaf101982fb70eb15074c7e58807dac1c725bb33871801d769019efbb0721c").unwrap(), - }, - }, - SignedTransaction { - transaction: Transaction { - to: Address::from_str("7da08ac2740268f36634c2579739be26a1235958").unwrap(), - nonce: U64::from(0x486), - gas: U256::from_str("7a120").unwrap(), - gas_price: U256::from_str("4671ef231a").unwrap(), - max_fee_per_gas: U256::from_str("4671ef231a").unwrap(), - value: U256::from_str("0").unwrap(), - data: hex::decode("a4629a100000000000000000000000006d7b6dad6abed1dfa5eba37a6667ba9dcfd49077000000000000000000000000132eeb05d5cb6829bd34f552cde0b6b708ef501400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000003da7cd5f6614bd440000000000000000000000000000000000000000000000456a8e21f079c20b8c0000000000000000000000000000000000000000000000003e39a04969da8e6e0000000000000000000000000000000000000000000000000000000000c8913d").unwrap(), - transaction_type: TransactionType::Eip2930, - access_list: vec![ - AccessListItem { - address: H160::from_str("1b40183efb4dd766f11bda7a7c3ad8982e998421").unwrap(), - storage_keys: vec![], - }, - AccessListItem { - address: H160::from_str("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(), - storage_keys: vec![] - }, - AccessListItem { - address: H160::from_str("6d7b6dad6abed1dfa5eba37a6667ba9dcfd49077").unwrap(), - storage_keys: vec![ - H256::from_str("0000000000000000000000000000000000000000000000000000000000000006").unwrap(), - H256::from_str("0000000000000000000000000000000000000000000000000000000000000007").unwrap(), - H256::from_str("0000000000000000000000000000000000000000000000000000000000000008").unwrap(), - H256::from_str("000000000000000000000000000000000000000000000000000000000000000c").unwrap(), - ], - }, - AccessListItem { - address: H160::from_str("132eeb05d5cb6829bd34f552cde0b6b708ef5014").unwrap(), - storage_keys: vec![ - H256::from_str("0000000000000000000000000000000000000000000000000000000000000006").unwrap(), - H256::from_str("0000000000000000000000000000000000000000000000000000000000000007").unwrap(), - H256::from_str("0000000000000000000000000000000000000000000000000000000000000008").unwrap(), - H256::from_str("000000000000000000000000000000000000000000000000000000000000000c").unwrap(), - ] - } - ], - max_priority_fee_per_gas: U256::from_str("0").unwrap(), - chain_id: U64::from(1), - }, - signature: Signature { - v: U64::from(0x0 + 27), - r: U256::from_str("e8f03759dc73deaf10ceabc76e206b3a3353a19cbc908ba87c34e5f1d4dc7ef9").unwrap(), - s: U256::from_str("681de4e59147a73ec88422fe9266a7f263c86c4c809e9179a5431fdd5cbe68b4").unwrap(), - }, - }, - SignedTransaction { - transaction: Transaction { - gas: U256::from_str("d032").unwrap(), - gas_price: U256::from_str("03dea3b69c").unwrap(), - max_fee_per_gas: U256::from_str("03dea3b69c").unwrap(), - max_priority_fee_per_gas: U256::from_str("0").unwrap(), - data: hex::decode("095ea7b30000000000000000000000008a42d311d282bfcaa5133b2de0a8bcdbecea3073ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(), - nonce: U64::from(0x0), - value: U256::from_str("0").unwrap(), - to: Address::from_str("dac17f958d2ee523a2206206994597c13d831ec7").unwrap(), - transaction_type: TransactionType::Legacy, - access_list: vec![], - chain_id: U64::from(1), - }, - signature: Signature { - v: U64::from(38), - r: U256::from_str("5327baacf5a3ae6523c6e6ebaa0cd8f98a8ac513c1baed06ec4811b1cfd979").unwrap(), - s: U256::from_str("4e4a4714104d70a6f6b91e80846ff8864aa5343e3afcd9ec59683fb011059cfb").unwrap(), - }, - }, - ]; - - for i in 0..inputs.len() { - assert_eq!(signed_transactions[i].raw().to_hex(), inputs[i],); - assert_eq!(signed_transactions[i].hash().to_hex(), hashs[i],); +impl SignatureType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + SignatureType::PersonalSign => "PersonalSign", + SignatureType::EcSign => "EcSign", } } } diff --git a/token-core/tcx-eth/src/transaction_types.rs b/token-core/tcx-eth/src/transaction_types.rs new file mode 100644 index 00000000..d7de003e --- /dev/null +++ b/token-core/tcx-eth/src/transaction_types.rs @@ -0,0 +1,403 @@ +use super::Result; +use anyhow::anyhow; +use ethereum_types::{Address, H256, U256, U64}; +use rlp::RlpStream; +use secp256k1::{ecdsa::RecoverableSignature, ecdsa::RecoveryId, Message, PublicKey}; +use std::str::FromStr; +use tcx_common::{keccak256, Hash256, ToHex}; +use tcx_primitive::SECP256K1_ENGINE; + +pub fn to_eip155_v>(recovery_id: T, chain_id: U64) -> U64 { + U64::from(recovery_id.into() + 35 + chain_id.as_u64() * 2) +} + +#[derive(Debug, Clone, PartialEq)] +pub enum TransactionType { + Legacy, + Eip2930, + Eip1559, +} + +impl Default for TransactionType { + fn default() -> Self { + TransactionType::Legacy + } +} + +impl FromStr for TransactionType { + type Err = anyhow::Error; + + fn from_str(tx_type: &str) -> Result { + match tx_type { + "0x01" | "1" | "01" => Ok(TransactionType::Eip2930), + "0x02" | "2" | "02" => Ok(TransactionType::Eip1559), + _ => Ok(TransactionType::Legacy), + } + } +} + +pub type AccessList = Vec; + +#[derive(Debug, Default, Clone, PartialEq)] +pub struct AccessListItem { + pub address: Address, + pub storage_keys: Vec, +} + +#[derive(Debug, Default, Clone, PartialEq)] +pub struct Signature { + pub v: U64, + pub r: U256, + pub s: U256, +} + +impl ToHex for Signature { + fn to_hex(&self) -> String { + <[u8; 65]>::from(self).to_hex() + } +} + +impl From<&Signature> for [u8; 65] { + fn from(src: &Signature) -> [u8; 65] { + let mut sig = [0u8; 65]; + let mut r_bytes = [0u8; 32]; + let mut s_bytes = [0u8; 32]; + src.r.to_big_endian(&mut r_bytes); + src.s.to_big_endian(&mut s_bytes); + sig[..32].copy_from_slice(&r_bytes); + sig[32..64].copy_from_slice(&s_bytes); + // TODO: What if we try to serialize a signature where + // the `v` is not normalized? + + // The u64 to u8 cast is safe because `sig.v` can only ever be 27 or 28 + // here. Regarding EIP-155, the modification to `v` happens during tx + // creation only _after_ the transaction is signed using + // `ethers_signers::to_eip155_v`. + sig[64] = src.v.as_u64() as u8; + sig + } +} + +impl Signature { + pub fn from_slice(data: &[u8]) -> Result { + if data.len() != 65 { + return Err(anyhow!("Invalid signature length")); + } + + let v = if data[64] >= 27 { + U64::from(data[64]) + } else { + U64::from(data[64] + 27) + }; + + Ok(Signature { + v, + r: U256::from_big_endian(&data[0..32]), + s: U256::from_big_endian(&data[32..64]), + }) + } + + pub fn recover_public_key(&self, hash: &[u8]) -> Result { + let message = Message::from_slice(hash)?; + let recovery_id = RecoveryId::from_i32(self.v.as_u32() as i32 - 27)?; + let sig = <[u8; 65]>::from(self); + let signature = RecoverableSignature::from_compact(&sig[0..64], recovery_id)?; + + Ok(SECP256K1_ENGINE.recover_ecdsa(&message, &signature)?) + } +} + +#[derive(Debug, Default, Clone, PartialEq)] +pub struct Transaction { + pub to: Address, + pub nonce: U64, + pub gas: U256, + pub gas_price: U256, + pub value: U256, + pub data: Vec, + pub access_list: AccessList, + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub chain_id: U64, + pub transaction_type: TransactionType, +} + +#[derive(Debug, Default, Clone, PartialEq)] +pub struct SignedTransaction { + pub transaction: Transaction, + pub signature: Signature, +} + +impl SignedTransaction { + pub fn hash(&self) -> H256 { + H256::from(keccak256(self.raw().as_slice())) + } + + pub fn raw(&self) -> Vec { + self.transaction.encode(Some(&self.signature)) + } +} + +impl Transaction { + fn rlp_append_legacy(&self, stream: &mut RlpStream) { + stream.append(&self.nonce); + stream.append(&self.gas_price); + stream.append(&self.gas); + stream.append(&self.to); + stream.append(&self.value); + stream.append(&self.data); + } + + fn encode_legacy(&self, signature: Option<&Signature>) -> RlpStream { + let mut stream = RlpStream::new(); + + stream.begin_list(9); + + self.rlp_append_legacy(&mut stream); + + if let Some(signature) = signature { + self.rlp_append_signature(&mut stream, signature); + } else { + stream.append(&self.chain_id); + stream.append(&0u8); + stream.append(&0u8); + } + + stream + } + + fn encode_eip2930_payload(&self, signature: Option<&Signature>) -> RlpStream { + let mut stream = RlpStream::new(); + + let list_size = if signature.is_some() { 11 } else { 8 }; + stream.begin_list(list_size); + + // append chain_id. from EIP-2930: chainId is defined to be an integer of arbitrary size. + stream.append(&self.chain_id); + + self.rlp_append_legacy(&mut stream); + self.rlp_append_access_list(&mut stream); + + if let Some(signature) = signature { + self.rlp_append_signature(&mut stream, signature); + } + + stream + } + + fn encode_eip1559_payload(&self, signature: Option<&Signature>) -> RlpStream { + let mut stream = RlpStream::new(); + + let list_size = if signature.is_some() { 12 } else { 9 }; + stream.begin_list(list_size); + + stream.append(&self.chain_id); + + stream.append(&self.nonce); + stream.append(&self.max_priority_fee_per_gas); + stream.append(&self.max_fee_per_gas); + stream.append(&self.gas); + stream.append(&self.to); + stream.append(&self.value); + stream.append(&self.data); + + self.rlp_append_access_list(&mut stream); + + if let Some(signature) = signature { + self.rlp_append_signature(&mut stream, signature); + } + + stream + } + + fn rlp_append_signature(&self, stream: &mut RlpStream, signature: &Signature) { + match self.transaction_type { + TransactionType::Eip2930 | TransactionType::Eip1559 => { + stream.append(&U64::from(signature.v.as_u64() - 27)) + } + _ => stream.append(&to_eip155_v(signature.v.as_u64() - 27, self.chain_id)), + }; + + stream.append(&signature.r); + stream.append(&signature.s); + } + + fn rlp_append_access_list(&self, stream: &mut RlpStream) { + stream.begin_list(self.access_list.len()); + for access in self.access_list.iter() { + stream.begin_list(2); + stream.append(&access.address); + stream.begin_list(access.storage_keys.len()); + for storage_key in access.storage_keys.iter() { + stream.append(storage_key); + } + } + } + + pub fn encode(&self, signature: Option<&Signature>) -> Vec { + match self.transaction_type { + TransactionType::Legacy => { + let stream = self.encode_legacy(signature); + stream.out().to_vec() + } + + TransactionType::Eip2930 => { + let stream = self.encode_eip2930_payload(signature); + [&[1], stream.as_raw()].concat() + } + + TransactionType::Eip1559 => { + let stream = self.encode_eip1559_payload(signature); + [&[2], stream.as_raw()].concat() + } + } + } + + pub fn sighash(&self) -> Hash256 { + keccak256(&self.encode(None)) + } + + pub fn to_signed_tx(&self, signature: Signature) -> SignedTransaction { + SignedTransaction { + transaction: self.clone(), + signature, + } + } +} + +#[cfg(test)] +mod test { + use super::{Signature, SignedTransaction, Transaction, TransactionType}; + use crate::transaction_types::AccessListItem; + use ethereum_types::{Address, H160, H256, U256, U64}; + use std::str::FromStr; + use tcx_common::ToHex; + + fn test_encode_transaction() { + let inputs = vec![ + "f8ac826b4685152b17381683015f90946149c26cd2f7b5ccdb32029af817123f6e37df5b80b844a9059cbb0000000000000000000000004bc7610cc68c1647a3898273fd5c6fada35231ec000000000000000000000000000000000000000000000016a8ea51575148000025a0cf11204d960208f37e5cbb1f77c65868e94fcd7d1b6e1a4024c3de2ff038fbd6a04355c8fc835261d4a31b0195af79dfc6a24e3c1e8efb540144618c36f2c9b61a", + "02f875018321ad6f847735940085266ac4320082520894526ea8b99ba85ec6d883b06545f4b76b60ffe14d879790f8dbf7100080c080a06126c506f0f6b35eca2087e8eb05287d98ccde7fb29499338c70e836538ec212a067eaf101982fb70eb15074c7e58807dac1c725bb33871801d769019efbb0721c", + "01f902b801820486854671ef231a8307a120947da08ac2740268f36634c2579739be26a123595880b8e4a4629a100000000000000000000000006d7b6dad6abed1dfa5eba37a6667ba9dcfd49077000000000000000000000000132eeb05d5cb6829bd34f552cde0b6b708ef501400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000003da7cd5f6614bd440000000000000000000000000000000000000000000000456a8e21f079c20b8c0000000000000000000000000000000000000000000000003e39a04969da8e6e0000000000000000000000000000000000000000000000000000000000c8913df90168d6941b40183efb4dd766f11bda7a7c3ad8982e998421c0d694c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2c0f89b946d7b6dad6abed1dfa5eba37a6667ba9dcfd49077f884a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000008a0000000000000000000000000000000000000000000000000000000000000000cf89b94132eeb05d5cb6829bd34f552cde0b6b708ef5014f884a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000008a0000000000000000000000000000000000000000000000000000000000000000c80a0e8f03759dc73deaf10ceabc76e206b3a3353a19cbc908ba87c34e5f1d4dc7ef9a0681de4e59147a73ec88422fe9266a7f263c86c4c809e9179a5431fdd5cbe68b4", + "f8a8808503dea3b69c82d03294dac17f958d2ee523a2206206994597c13d831ec780b844095ea7b30000000000000000000000008a42d311d282bfcaa5133b2de0a8bcdbecea3073ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff269f5327baacf5a3ae6523c6e6ebaa0cd8f98a8ac513c1baed06ec4811b1cfd979a04e4a4714104d70a6f6b91e80846ff8864aa5343e3afcd9ec59683fb011059cfb"]; + let hashs = vec![ + "83ab13113196831454d55ace3a3975da33ec0dc7126767c420258df11407df89", + "a44c3f4b6fd573f1b5585e6fb9624eb589537f03c25461828767c7b3d56fbb3f", + "e19e246e305b2c092e77028777a86144c5fc3f56d41c4bdfcc32ce2438993358", + "885525dbd0a74e58570a0e96a851225cb8f5c690c49b554d9a69c79389ac0374", + ]; + let signed_transactions = vec![ + SignedTransaction { + transaction: Transaction { + to: Address::from_str("6149c26cd2f7b5ccdb32029af817123f6e37df5b").unwrap(), + nonce: U64::from(0x6b46), + gas: U256::from_str("15f90").unwrap(), + gas_price: U256::from_str("152b173816").unwrap(), + value: U256::from_str("00").unwrap(), + data: hex::decode("a9059cbb0000000000000000000000004bc7610cc68c1647a3898273fd5c6fada35231ec000000000000000000000000000000000000000000000016a8ea515751480000").unwrap(), + transaction_type: TransactionType::Legacy, + access_list: vec![], + max_priority_fee_per_gas: U256::from_str("00").unwrap(), + max_fee_per_gas: U256::from_str("00").unwrap(), + chain_id: U64::from(1), + }, + signature: Signature { + v: U64::from(0x25+27), + r: U256::from_str("cf11204d960208f37e5cbb1f77c65868e94fcd7d1b6e1a4024c3de2ff038fbd6").unwrap(), + s: U256::from_str("4355c8fc835261d4a31b0195af79dfc6a24e3c1e8efb540144618c36f2c9b61a").unwrap(), + }, + }, + SignedTransaction { + transaction: Transaction { + gas: U256::from_str("5208").unwrap(), + gas_price: U256::from_str("266ac43200").unwrap(), + max_fee_per_gas: U256::from_str("266ac43200").unwrap(), + max_priority_fee_per_gas: U256::from_str("77359400").unwrap(), + data: vec![], + nonce: U64::from(0x21ad6f), + value: U256::from_str("9790f8dbf71000").unwrap(), + to: Address::from_str("526ea8b99ba85ec6d883b06545f4b76b60ffe14d").unwrap(), + transaction_type: TransactionType::Eip1559, + access_list: vec![], + chain_id: U64::from(1), + }, + signature: Signature { + v: U64::from(0x0+27), + r: U256::from_str("6126c506f0f6b35eca2087e8eb05287d98ccde7fb29499338c70e836538ec212").unwrap(), + s: U256::from_str("67eaf101982fb70eb15074c7e58807dac1c725bb33871801d769019efbb0721c").unwrap(), + }, + }, + SignedTransaction { + transaction: Transaction { + to: Address::from_str("7da08ac2740268f36634c2579739be26a1235958").unwrap(), + nonce: U64::from(0x486), + gas: U256::from_str("7a120").unwrap(), + gas_price: U256::from_str("4671ef231a").unwrap(), + max_fee_per_gas: U256::from_str("4671ef231a").unwrap(), + value: U256::from_str("0").unwrap(), + data: hex::decode("a4629a100000000000000000000000006d7b6dad6abed1dfa5eba37a6667ba9dcfd49077000000000000000000000000132eeb05d5cb6829bd34f552cde0b6b708ef501400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000003da7cd5f6614bd440000000000000000000000000000000000000000000000456a8e21f079c20b8c0000000000000000000000000000000000000000000000003e39a04969da8e6e0000000000000000000000000000000000000000000000000000000000c8913d").unwrap(), + transaction_type: TransactionType::Eip2930, + access_list: vec![ + AccessListItem { + address: H160::from_str("1b40183efb4dd766f11bda7a7c3ad8982e998421").unwrap(), + storage_keys: vec![], + }, + AccessListItem { + address: H160::from_str("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(), + storage_keys: vec![] + }, + AccessListItem { + address: H160::from_str("6d7b6dad6abed1dfa5eba37a6667ba9dcfd49077").unwrap(), + storage_keys: vec![ + H256::from_str("0000000000000000000000000000000000000000000000000000000000000006").unwrap(), + H256::from_str("0000000000000000000000000000000000000000000000000000000000000007").unwrap(), + H256::from_str("0000000000000000000000000000000000000000000000000000000000000008").unwrap(), + H256::from_str("000000000000000000000000000000000000000000000000000000000000000c").unwrap(), + ], + }, + AccessListItem { + address: H160::from_str("132eeb05d5cb6829bd34f552cde0b6b708ef5014").unwrap(), + storage_keys: vec![ + H256::from_str("0000000000000000000000000000000000000000000000000000000000000006").unwrap(), + H256::from_str("0000000000000000000000000000000000000000000000000000000000000007").unwrap(), + H256::from_str("0000000000000000000000000000000000000000000000000000000000000008").unwrap(), + H256::from_str("000000000000000000000000000000000000000000000000000000000000000c").unwrap(), + ] + } + ], + max_priority_fee_per_gas: U256::from_str("0").unwrap(), + chain_id: U64::from(1), + }, + signature: Signature { + v: U64::from(0x0 + 27), + r: U256::from_str("e8f03759dc73deaf10ceabc76e206b3a3353a19cbc908ba87c34e5f1d4dc7ef9").unwrap(), + s: U256::from_str("681de4e59147a73ec88422fe9266a7f263c86c4c809e9179a5431fdd5cbe68b4").unwrap(), + }, + }, + SignedTransaction { + transaction: Transaction { + gas: U256::from_str("d032").unwrap(), + gas_price: U256::from_str("03dea3b69c").unwrap(), + max_fee_per_gas: U256::from_str("03dea3b69c").unwrap(), + max_priority_fee_per_gas: U256::from_str("0").unwrap(), + data: hex::decode("095ea7b30000000000000000000000008a42d311d282bfcaa5133b2de0a8bcdbecea3073ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(), + nonce: U64::from(0x0), + value: U256::from_str("0").unwrap(), + to: Address::from_str("dac17f958d2ee523a2206206994597c13d831ec7").unwrap(), + transaction_type: TransactionType::Legacy, + access_list: vec![], + chain_id: U64::from(1), + }, + signature: Signature { + v: U64::from(38), + r: U256::from_str("5327baacf5a3ae6523c6e6ebaa0cd8f98a8ac513c1baed06ec4811b1cfd979").unwrap(), + s: U256::from_str("4e4a4714104d70a6f6b91e80846ff8864aa5343e3afcd9ec59683fb011059cfb").unwrap(), + }, + }, + ]; + + for i in 0..inputs.len() { + assert_eq!(signed_transactions[i].raw().to_hex(), inputs[i],); + assert_eq!(signed_transactions[i].hash().to_hex(), hashs[i],); + } + } +} diff --git a/token-core/tcx-keystore/src/keystore/hd.rs b/token-core/tcx-keystore/src/keystore/hd.rs index f2e2ac70..ee9f1c7e 100644 --- a/token-core/tcx-keystore/src/keystore/hd.rs +++ b/token-core/tcx-keystore/src/keystore/hd.rs @@ -7,7 +7,7 @@ use super::{transform_mnemonic_error, Account, Address, Error, Metadata, Result, use std::collections::{hash_map::Entry, HashMap}; -use tcx_common::ToHex; +use tcx_common::{FromHex, ToHex}; use tcx_constants::{coin_info::get_xpub_prefix, CoinInfo, CurveType}; use tcx_crypto::{Crypto, Key}; use tcx_primitive::{ @@ -224,8 +224,18 @@ impl HdKeystore { &self.store().identity } - pub(crate) fn verify_password(&self, password: &str) -> bool { - self.store.crypto.verify_password(password) + pub(crate) fn verify_password(&self, key: &Key) -> bool { + match key { + Key::Password(password) => { + return self.store.crypto.verify_password(password); + } + Key::DerivedKey(derived_key_hex) => { + let Ok(derived_key) = Vec::from_hex_auto(derived_key_hex) else { + return false; + }; + return self.store.crypto.verify_derived_key(&derived_key); + } + } } } @@ -235,7 +245,7 @@ mod tests { use crate::keystore::{metadata_default_time, IdentityNetwork}; use crate::keystore::tests::MockAddress; - use crate::Source; + use crate::{Keystore, Source}; use bitcoin_hashes::hex::ToHex; use std::string::ToString; use tcx_common::FromHex; @@ -258,8 +268,13 @@ mod tests { let keystore = HdKeystore::from_mnemonic(TEST_MNEMONIC, TEST_PASSWORD, Metadata::default()).unwrap(); - assert!(keystore.verify_password(TEST_PASSWORD)); - assert!(!keystore.verify_password("WrongPassword")); + let derived_key = Keystore::Hd(keystore.clone()) + .get_derived_key(TEST_PASSWORD) + .unwrap(); + assert!(keystore.verify_password(&Key::Password(TEST_PASSWORD.to_string()))); + assert!(keystore.verify_password(&Key::DerivedKey(derived_key.to_string()))); + assert!(!keystore.verify_password(&Key::Password("WRONG PASSWORD".to_string()))); + assert!(!keystore.verify_password(&Key::DerivedKey("731dd44109f9897eb39980907161b7531be44714352ddaa40542da22fb4fab7533678f2e132226389174faad4e653c542811a7b0c9391ae3cce4e75039a15adc".to_string()))); } #[test] diff --git a/token-core/tcx-keystore/src/keystore/mod.rs b/token-core/tcx-keystore/src/keystore/mod.rs index a84d029e..f3170d77 100644 --- a/token-core/tcx-keystore/src/keystore/mod.rs +++ b/token-core/tcx-keystore/src/keystore/mod.rs @@ -403,11 +403,8 @@ impl Keystore { } } - pub fn backup(&self, password: &str) -> Result { - let unlocker = self - .store() - .crypto - .use_key(&Key::Password(password.to_string()))?; + pub fn backup(&self, key: &Key) -> Result { + let unlocker = self.store().crypto.use_key(key)?; let decrypted = unlocker.decrypt_enc_pair(&self.store().enc_original)?; let original = String::from_utf8_lossy(&decrypted); Ok(original.to_string()) @@ -417,10 +414,10 @@ impl Keystore { &self.store().identity } - pub fn verify_password(&self, password: &str) -> bool { + pub fn verify_password(&self, key: &Key) -> bool { match self { - Keystore::PrivateKey(ks) => ks.verify_password(password), - Keystore::Hd(ks) => ks.verify_password(password), + Keystore::PrivateKey(ks) => ks.verify_password(key), + Keystore::Hd(ks) => ks.verify_password(key), } } @@ -773,8 +770,11 @@ pub(crate) mod tests { "password_incorrect" ); - assert!(keystore.verify_password(TEST_PASSWORD)); - assert!(!keystore.verify_password("WRONG PASSWORD")); + let derived_key = keystore.get_derived_key(TEST_PASSWORD).unwrap(); + assert!(keystore.verify_password(&Key::Password(TEST_PASSWORD.to_string()))); + assert!(keystore.verify_password(&Key::DerivedKey(derived_key.to_string()))); + assert!(!keystore.verify_password(&Key::Password("WRONG PASSWORD".to_string()))); + assert!(!keystore.verify_password(&Key::DerivedKey("731dd44109f9897eb39980907161b7531be44714352ddaa40542da22fb4fab7533678f2e132226389174faad4e653c542811a7b0c9391ae3cce4e75039a15adc".to_string()))); keystore.unlock_by_password(TEST_PASSWORD).unwrap(); assert_eq!( "inject kidney empty canal shadow pact comfort wife crush horse wife sketch", @@ -889,9 +889,11 @@ pub(crate) mod tests { assert!(keystore.identity().identifier.starts_with("im")); assert_eq!(keystore.meta().name, "Unknown"); assert_ne!(keystore.id(), ""); - - assert!(keystore.verify_password(&TEST_PASSWORD)); - assert!(!keystore.verify_password(&WRONG_PASSWORD)); + let derived_key = keystore.get_derived_key(TEST_PASSWORD).unwrap(); + assert!(keystore.verify_password(&Key::Password(TEST_PASSWORD.to_string()))); + assert!(keystore.verify_password(&Key::DerivedKey(derived_key.to_string()))); + assert!(!keystore.verify_password(&Key::Password("WRONG PASSWORD".to_string()))); + assert!(!keystore.verify_password(&Key::DerivedKey("731dd44109f9897eb39980907161b7531be44714352ddaa40542da22fb4fab7533678f2e132226389174faad4e653c542811a7b0c9391ae3cce4e75039a15adc".to_string()))); let coin_info = CoinInfo { coin: "BITCOIN".to_string(), @@ -959,9 +961,11 @@ pub(crate) mod tests { format!("0x{}", keystore.export().unwrap()), TEST_PRIVATE_KEY.to_string() ); - - assert!(keystore.verify_password(&TEST_PASSWORD)); - assert!(!keystore.verify_password(&WRONG_PASSWORD)); + let derived_key = keystore.get_derived_key(TEST_PASSWORD).unwrap(); + assert!(keystore.verify_password(&Key::Password(TEST_PASSWORD.to_string()))); + assert!(keystore.verify_password(&Key::DerivedKey(derived_key.to_string()))); + assert!(!keystore.verify_password(&Key::Password("WRONG PASSWORD".to_string()))); + assert!(!keystore.verify_password(&Key::DerivedKey("731dd44109f9897eb39980907161b7531be44714352ddaa40542da22fb4fab7533678f2e132226389174faad4e653c542811a7b0c9391ae3cce4e75039a15adc".to_string()))); let coin_info = CoinInfo { coin: "BITCOIN".to_string(), diff --git a/token-core/tcx-keystore/src/keystore/private.rs b/token-core/tcx-keystore/src/keystore/private.rs index 6630410e..329ee4d0 100644 --- a/token-core/tcx-keystore/src/keystore/private.rs +++ b/token-core/tcx-keystore/src/keystore/private.rs @@ -1,5 +1,6 @@ use super::Account; use super::{Address, Metadata}; +use bitcoin::util; use tcx_constants::{CoinInfo, CurveType}; use tcx_crypto::{Crypto, Key}; @@ -82,8 +83,18 @@ impl PrivateKeystore { Ok(account) } - pub(crate) fn verify_password(&self, password: &str) -> bool { - self.store.crypto.verify_password(password) + pub(crate) fn verify_password(&self, key: &Key) -> bool { + match key { + Key::Password(password) => { + return self.store.crypto.verify_password(password); + } + Key::DerivedKey(derived_key_hex) => { + let Ok(derived_key) = Vec::from_hex_auto(derived_key_hex) else { + return false; + }; + return self.store.crypto.verify_derived_key(&derived_key); + } + } } pub fn from_private_key( @@ -160,7 +171,7 @@ impl PrivateKeystore { mod tests { use crate::keystore::private::fingerprint_from_private_key; use crate::keystore::tests::MockAddress; - use crate::{Metadata, PrivateKeystore, Source}; + use crate::{Keystore, Metadata, PrivateKeystore, Source}; use tcx_common::FromHex; use tcx_constants::{CoinInfo, CurveType, TEST_PASSWORD, TEST_PRIVATE_KEY}; use tcx_crypto::Key; @@ -205,8 +216,13 @@ mod tests { ) .unwrap(); - assert!(keystore.verify_password(TEST_PASSWORD)); - assert!(!keystore.verify_password("WrongPassword")); + let derived_key = Keystore::PrivateKey(keystore.clone()) + .get_derived_key(TEST_PASSWORD) + .unwrap(); + assert!(keystore.verify_password(&Key::Password(TEST_PASSWORD.to_string()))); + assert!(keystore.verify_password(&Key::DerivedKey(derived_key.to_string()))); + assert!(!keystore.verify_password(&Key::Password("WRONG PASSWORD".to_string()))); + assert!(!keystore.verify_password(&Key::DerivedKey("731dd44109f9897eb39980907161b7531be44714352ddaa40542da22fb4fab7533678f2e132226389174faad4e653c542811a7b0c9391ae3cce4e75039a15adc".to_string()))); } #[test] diff --git a/token-core/tcx-migration/src/keystore_upgrade.rs b/token-core/tcx-migration/src/keystore_upgrade.rs index 1079d566..a0c6bac3 100644 --- a/token-core/tcx-migration/src/keystore_upgrade.rs +++ b/token-core/tcx-migration/src/keystore_upgrade.rs @@ -576,13 +576,15 @@ mod tests { ); let json = serde_json::from_str(json_str).unwrap(); let old_ks = KeystoreUpgrade::new(json); - let ks = old_ks - .upgrade( - &Key::Password(TEST_PASSWORD.to_string()), - &IdentityNetwork::Mainnet, - ) - .unwrap(); - let ori = ks.backup(TEST_PASSWORD).unwrap(); + let key = Key::Password(TEST_PASSWORD.to_string()); + let ks = old_ks.upgrade(&key, &IdentityNetwork::Mainnet).unwrap(); + let ori = ks.backup(&key).unwrap(); + assert_eq!( + ori, + "685634d212eabe016a1cb09d9f1ea1ea757ebe590b9a097d7b1c9379ad280171" + ); + + let ori = ks.backup(&Key::DerivedKey("1a60471067b6c6a3202e0014de2ce9b2d45fd73e2289b3cc3d8e5b58fe99ff242fd61e9fe63e75abbdc0ed87a50756cc10c57daf1d6297b99ec9a3b174eee017".to_string())).unwrap(); assert_eq!( ori, "685634d212eabe016a1cb09d9f1ea1ea757ebe590b9a097d7b1c9379ad280171" @@ -596,13 +598,15 @@ mod tests { ); let json = serde_json::from_str(json_str).unwrap(); let old_ks = KeystoreUpgrade::new(json); - let ks = old_ks - .upgrade( - &Key::Password(TEST_PASSWORD.to_string()), - &IdentityNetwork::Mainnet, - ) - .unwrap(); - let ori = ks.backup(TEST_PASSWORD).unwrap(); + let key = Key::Password(TEST_PASSWORD.to_string()); + let ks = old_ks.upgrade(&key, &IdentityNetwork::Mainnet).unwrap(); + let ori = ks.backup(&key).unwrap(); + assert_eq!( + ori, + "edskS3E5CLrkwHRYAbDvw5xC913C9GGseMcyNGeGbeaD57Yvvi2jqizpAAZyzUtRK626UvkKYdJwCYE9oKMcqFCtJeBpDYcrVH" + ); + + let ori = ks.backup(&Key::DerivedKey("b009d3c4e961411836028a9fffbea994e03c71f75589a571cd52125884537f2ac165b92e7bc49c7828d4be0c5c05263a306744f0b9dc785142c8562d45ce4345".to_string())).unwrap(); assert_eq!( ori, "edskS3E5CLrkwHRYAbDvw5xC913C9GGseMcyNGeGbeaD57Yvvi2jqizpAAZyzUtRK626UvkKYdJwCYE9oKMcqFCtJeBpDYcrVH" @@ -616,13 +620,13 @@ mod tests { ); let json = serde_json::from_str(json_str).unwrap(); let old_ks = KeystoreUpgrade::new(json); - let ks = old_ks - .upgrade( - &Key::Password(TEST_PASSWORD.to_string()), - &IdentityNetwork::Mainnet, - ) - .unwrap(); - let ori = ks.backup(TEST_PASSWORD).unwrap(); + let key = Key::Password(TEST_PASSWORD.to_string()); + let ks = old_ks.upgrade(&key, &IdentityNetwork::Mainnet).unwrap(); + let ori = ks.backup(&key).unwrap(); + + assert_eq!(ori, "TBRMznXcDf2HK2jBKJsqjBpsEdaiaZUBGKN8aKdwTMrPnMNB5UQM"); + let ori = ks.backup(&Key::DerivedKey("2e70651f06a28d2f6053a90ee55ab8cb14518ab82182cd922926b4239713286ac6746ad4448608fc1599e6f4e0af33c65f70bc5de13a376933e5e145681d0f80".to_string())).unwrap(); + assert_eq!(ori, "TBRMznXcDf2HK2jBKJsqjBpsEdaiaZUBGKN8aKdwTMrPnMNB5UQM"); } @@ -633,13 +637,14 @@ mod tests { ); let json = serde_json::from_str(json_str).unwrap(); let old_ks = KeystoreUpgrade::new(json); - let ks = old_ks - .upgrade( - &Key::Password(TEST_PASSWORD.to_string()), - &IdentityNetwork::Mainnet, - ) - .unwrap(); - let ori = ks.backup(TEST_PASSWORD).unwrap(); + let key = Key::Password(TEST_PASSWORD.to_string()); + let ks = old_ks.upgrade(&key, &IdentityNetwork::Mainnet).unwrap(); + let ori = ks.backup(&key).unwrap(); + + assert_eq!(ori, "L1xDTJYPqhofU8DQCiwjStEBr1X6dhiNfweUhxhoRSgYyMJPcZ6B"); + + let ori = ks.backup(&Key::DerivedKey("d175dad756f59a59b6a311f2b369802537d92011c8e3bf6dc2dfaf8df00d942648bf83133bd0204ebc43efcbd0ab79a8d5551c8dcf7ae1f67fc2d1d4aff33c06".to_string())).unwrap(); + assert_eq!(ori, "L1xDTJYPqhofU8DQCiwjStEBr1X6dhiNfweUhxhoRSgYyMJPcZ6B"); } @@ -650,13 +655,11 @@ mod tests { ); let json = serde_json::from_str(json_str).unwrap(); let old_ks = KeystoreUpgrade::new(json); - let ks = old_ks - .upgrade( - &Key::Password(TEST_PASSWORD.to_string()), - &IdentityNetwork::Mainnet, - ) - .unwrap(); - let ori = ks.backup(TEST_PASSWORD).unwrap(); + let key = Key::Password(TEST_PASSWORD.to_string()); + let ks = old_ks.upgrade(&key, &IdentityNetwork::Mainnet).unwrap(); + let ori = ks.backup(&key).unwrap(); + assert!(ori.contains("FDS7ZJpJg4R7Kd2hzfsEc6mtW5iknjZ3UazX76EsnbH74v8")); + let ori = ks.backup(&Key::DerivedKey("daa734e276c7420bd7eb6f9d59d39f3072d9ab0c4c0bda09eb410ab930c745302fb39714a2c4ca1935b18d1d216db11455a4c962daf8bf360ba41c65d872a5a8".to_string())).unwrap(); assert!(ori.contains("FDS7ZJpJg4R7Kd2hzfsEc6mtW5iknjZ3UazX76EsnbH74v8")); } @@ -667,13 +670,15 @@ mod tests { ); let json = serde_json::from_str(json_str).unwrap(); let old_ks = KeystoreUpgrade::new(json); - let ks = old_ks - .upgrade( - &Key::Password(TEST_PASSWORD.to_string()), - &IdentityNetwork::Mainnet, - ) - .unwrap(); - let ori = ks.backup(TEST_PASSWORD).unwrap(); + let key = Key::Password(TEST_PASSWORD.to_string()); + let ks = old_ks.upgrade(&key, &IdentityNetwork::Mainnet).unwrap(); + let ori = ks.backup(&key).unwrap(); + + let expected_ori = + r#"{"Type":"secp256k1","PrivateKey":"o5JgTvwvrZwLPaQ7X2mKLj8nDxcNhZkSvg1UdCJ1xfY="}"#; + assert_eq!(ori, expected_ori.as_bytes().to_hex()); + + let ori = ks.backup(&Key::DerivedKey("bee56a3cc9e6536dd22a695742ff1b4a00a797fa6c0ddd6f2c3bfd2ec25d05d61918ab3ceaf5c135f771d957889ee2b361c5d70f1911cc8857299d349a0d9ee6".to_string())).unwrap(); let expected_ori = r#"{"Type":"secp256k1","PrivateKey":"o5JgTvwvrZwLPaQ7X2mKLj8nDxcNhZkSvg1UdCJ1xfY="}"#; assert_eq!(ori, expected_ori.as_bytes().to_hex()); @@ -686,13 +691,15 @@ mod tests { ); let json = serde_json::from_str(json_str).unwrap(); let old_ks = KeystoreUpgrade::new(json); - let ks = old_ks - .upgrade( - &Key::Password(TEST_PASSWORD.to_string()), - &IdentityNetwork::Mainnet, - ) - .unwrap(); - let ori = ks.backup(TEST_PASSWORD).unwrap(); + let key = Key::Password(TEST_PASSWORD.to_string()); + let ks = old_ks.upgrade(&key, &IdentityNetwork::Mainnet).unwrap(); + let ori = ks.backup(&key).unwrap(); + + let expected_ori = + r#"{"Type":"bls","PrivateKey":"i7kO+zxc6QS+v7YygcmUYh7MUYSRes6anigiLhKF808="}"#; + assert_eq!(ori, expected_ori.as_bytes().to_hex()); + + let ori = ks.backup(&Key::DerivedKey("f3207a9f25adbdae5eff58bb13a9d9be4d763c6d47269fb14e4bd7a59ed667ba7282f1ba40746c60ae842c3f9dfeba91060f29f547ef2a94a66cfee93573ffaa".to_string())).unwrap(); let expected_ori = r#"{"Type":"bls","PrivateKey":"i7kO+zxc6QS+v7YygcmUYh7MUYSRes6anigiLhKF808="}"#; assert_eq!(ori, expected_ori.as_bytes().to_hex()); diff --git a/token-core/tcx-migration/src/migration.rs b/token-core/tcx-migration/src/migration.rs index 447b6de7..7d47e9b6 100644 --- a/token-core/tcx-migration/src/migration.rs +++ b/token-core/tcx-migration/src/migration.rs @@ -515,13 +515,9 @@ mod tests { let json_str = include_str!("../../test-data/wallets-ios-2_14_1/9f4acb4a-7431-4c7d-bd25-a19656a86ea0"); let old_ks = LegacyKeystore::from_json_str(json_str).unwrap(); - let ks = old_ks - .migrate( - &Key::Password(TEST_PASSWORD.to_string()), - &IdentityNetwork::Mainnet, - ) - .unwrap(); - let ori = ks.backup(TEST_PASSWORD).unwrap(); + let key = Key::Password(TEST_PASSWORD.to_string()); + let ks = old_ks.migrate(&key, &IdentityNetwork::Mainnet).unwrap(); + let ori = ks.backup(&key).unwrap(); assert_eq!(ori, "L1xDTJYPqhofU8DQCiwjStEBr1X6dhiNfweUhxhoRSgYyMJPcZ6B"); } @@ -531,13 +527,9 @@ mod tests { let json_str = include_str!("../../test-data/wallets-ios-2_14_1/60573d8d-8e83-45c3-85a5-34fbb2aad5e1"); let old_ks = LegacyKeystore::from_json_str(json_str).unwrap(); - let ks = old_ks - .migrate( - &Key::Password(TEST_PASSWORD.to_string()), - &IdentityNetwork::Mainnet, - ) - .unwrap(); - let ori = ks.backup(TEST_PASSWORD).unwrap(); + let key = Key::Password(TEST_PASSWORD.to_string()); + let ks = old_ks.migrate(&key, &IdentityNetwork::Mainnet).unwrap(); + let ori = ks.backup(&key).unwrap(); // ciphertext is 9b62... assert!(ori.contains("9b62a4c07c96ca9b0b82b5b5eae4e7c9b2b7db531a6d2991198eb6809a8c35ac")); } @@ -547,13 +539,9 @@ mod tests { let json_str = include_str!("../../test-data/wallets-ios-2_14_1/0597526e-105f-425b-bb44-086fc9dc9568"); let old_ks = LegacyKeystore::from_json_str(json_str).unwrap(); - let ks = old_ks - .migrate( - &Key::Password(TEST_PASSWORD.to_string()), - &IdentityNetwork::Mainnet, - ) - .unwrap(); - let ori = ks.backup(TEST_PASSWORD).unwrap(); + let key = Key::Password(TEST_PASSWORD.to_string()); + let ks = old_ks.migrate(&key, &IdentityNetwork::Mainnet).unwrap(); + let ori = ks.backup(&key).unwrap(); assert_eq!( ori, @@ -566,13 +554,9 @@ mod tests { let json_str = include_str!("../../test-data/wallets-ios-2_14_1/f3615a56-cb03-4aa4-a893-89944e49920d"); let old_ks = LegacyKeystore::from_json_str(json_str).unwrap(); - let ks = old_ks - .migrate( - &Key::DerivedKey("0x79c74b67fc73a255bc66afc1e7c25867a19e6d2afa5b8e3107a472de13201f1924fed05e811e7f5a4c3e72a8a6e047a80393c215412bde239ec7ded520896630".to_string()), - &IdentityNetwork::Mainnet, - ) - .unwrap(); - let ori = ks.backup(TEST_PASSWORD).unwrap(); + let key = Key::DerivedKey("0x79c74b67fc73a255bc66afc1e7c25867a19e6d2afa5b8e3107a472de13201f1924fed05e811e7f5a4c3e72a8a6e047a80393c215412bde239ec7ded520896630".to_string()); + let ks = old_ks.migrate(&key, &IdentityNetwork::Mainnet).unwrap(); + let ori = ks.backup(&key).unwrap(); assert_eq!( ori, @@ -585,13 +569,9 @@ mod tests { let json_str = include_str!("../../test-data/wallets-ios-2_14_1/ac59ccc1-285b-47a7-92f5-a6c432cee21a"); let old_ks = LegacyKeystore::from_json_str(json_str).unwrap(); - let ks = old_ks - .migrate( - &Key::Password(TEST_PASSWORD.to_string()), - &IdentityNetwork::Mainnet, - ) - .unwrap(); - let ori = ks.backup(TEST_PASSWORD).unwrap(); + let key = Key::Password(TEST_PASSWORD.to_string()); + let ks = old_ks.migrate(&key, &IdentityNetwork::Mainnet).unwrap(); + let ori = ks.backup(&key).unwrap(); assert_eq!( ori, diff --git a/token-core/tcx-primitive/src/ecc.rs b/token-core/tcx-primitive/src/ecc.rs index cd72609f..7cc2128a 100644 --- a/token-core/tcx-primitive/src/ecc.rs +++ b/token-core/tcx-primitive/src/ecc.rs @@ -29,8 +29,7 @@ pub enum KeyError { InvalidChildNumber, #[error("cannot_derive_from_hardened_key")] CannotDeriveFromHardenedKey, - // todo: why use this key? - #[error("cannot_derive_key")] + #[error("invalid_base58")] InvalidBase58, #[error("invalid_private_key")] InvalidPrivateKey, diff --git a/token-core/tcx-proto/src/api.proto b/token-core/tcx-proto/src/api.proto index d3c21443..8e796675 100644 --- a/token-core/tcx-proto/src/api.proto +++ b/token-core/tcx-proto/src/api.proto @@ -117,7 +117,10 @@ message ExportJsonParam { //// verify the password of the keystore message WalletKeyParam { string id = 1; - string password = 2; + oneof key { + string password = 2; + string derivedKey = 3; + } } message ExportMnemonicParam { diff --git a/token-core/tcx-proto/src/eth.proto b/token-core/tcx-proto/src/eth.proto index a2ca5d47..ace1f8e7 100644 --- a/token-core/tcx-proto/src/eth.proto +++ b/token-core/tcx-proto/src/eth.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package api; +package transaction; message EthTxInput { string nonce = 1; diff --git a/token-core/tcx-proto/src/params.proto b/token-core/tcx-proto/src/params.proto index ac36982b..680557cf 100644 --- a/token-core/tcx-proto/src/params.proto +++ b/token-core/tcx-proto/src/params.proto @@ -192,15 +192,6 @@ message DeriveSubAccountsResult { repeated AccountResponse accounts = 1; } -message RemoveWalletParam { - string id = 1; - string password = 2; -} - -message RemoveWalletResult { - bool isSuccess = 1; -} - message EncryptDataToIpfsParam { string identifier = 1; string content = 2; @@ -225,7 +216,10 @@ message SignAuthenticationMessageParam { uint64 accessTime = 1; string identifier = 2 ; string deviceToken = 3; - string password = 4; + oneof key { + string password = 4; + string derivedKey = 5; + } } message SignAuthenticationMessageResult { diff --git a/token-core/tcx-substrate/src/address.rs b/token-core/tcx-substrate/src/address.rs index 83a0eabd..717ee436 100644 --- a/token-core/tcx-substrate/src/address.rs +++ b/token-core/tcx-substrate/src/address.rs @@ -12,7 +12,6 @@ pub struct SubstrateAddress(String); impl Address for SubstrateAddress { fn from_public_key(public_key: &TypedPublicKey, coin: &CoinInfo) -> Result { - // todo: TypedPublicKey to public key let sr_pk = Sr25519PublicKey::from_slice(&public_key.to_bytes())?; let address = match coin.coin.as_str() { "KUSAMA" => sr_pk diff --git a/token-core/tcx/src/api.rs b/token-core/tcx/src/api.rs index 0f80df8e..5a55d538 100644 --- a/token-core/tcx/src/api.rs +++ b/token-core/tcx/src/api.rs @@ -196,8 +196,19 @@ pub struct ExportJsonParam { pub struct WalletKeyParam { #[prost(string, tag = "1")] pub id: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub password: ::prost::alloc::string::String, + #[prost(oneof = "wallet_key_param::Key", tags = "2, 3")] + pub key: ::core::option::Option, +} +/// Nested message and enum types in `WalletKeyParam`. +pub mod wallet_key_param { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Key { + #[prost(string, tag = "2")] + Password(::prost::alloc::string::String), + #[prost(string, tag = "3")] + DerivedKey(::prost::alloc::string::String), + } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -510,20 +521,6 @@ pub struct DeriveSubAccountsResult { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct RemoveWalletParam { - #[prost(string, tag = "1")] - pub id: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub password: ::prost::alloc::string::String, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct RemoveWalletResult { - #[prost(bool, tag = "1")] - pub is_success: bool, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] pub struct EncryptDataToIpfsParam { #[prost(string, tag = "1")] pub identifier: ::prost::alloc::string::String, @@ -563,8 +560,19 @@ pub struct SignAuthenticationMessageParam { pub identifier: ::prost::alloc::string::String, #[prost(string, tag = "3")] pub device_token: ::prost::alloc::string::String, - #[prost(string, tag = "4")] - pub password: ::prost::alloc::string::String, + #[prost(oneof = "sign_authentication_message_param::Key", tags = "4, 5")] + pub key: ::core::option::Option, +} +/// Nested message and enum types in `SignAuthenticationMessageParam`. +pub mod sign_authentication_message_param { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Key { + #[prost(string, tag = "4")] + Password(::prost::alloc::string::String), + #[prost(string, tag = "5")] + DerivedKey(::prost::alloc::string::String), + } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/token-core/tcx/src/handler.rs b/token-core/tcx/src/handler.rs index f857c7af..135ac5d4 100644 --- a/token-core/tcx/src/handler.rs +++ b/token-core/tcx/src/handler.rs @@ -28,13 +28,13 @@ use tcx_filecoin::KeyInfo; use crate::api::derive_accounts_param::Derivation; use crate::api::{ - export_private_key_param, AccountResponse, BackupResult, CreateKeystoreParam, - DecryptDataFromIpfsParam, DecryptDataFromIpfsResult, DeriveAccountsParam, DeriveAccountsResult, - DeriveSubAccountsParam, DeriveSubAccountsResult, DerivedKeyResult, EncryptDataToIpfsParam, - EncryptDataToIpfsResult, ExistsJsonParam, ExistsKeystoreResult, ExistsMnemonicParam, - ExistsPrivateKeyParam, ExportJsonParam, ExportJsonResult, ExportMnemonicParam, - ExportMnemonicResult, ExportPrivateKeyParam, ExportPrivateKeyResult, GeneralResult, - GetExtendedPublicKeysParam, GetExtendedPublicKeysResult, GetPublicKeysParam, + self, export_private_key_param, wallet_key_param, AccountResponse, BackupResult, + CreateKeystoreParam, DecryptDataFromIpfsParam, DecryptDataFromIpfsResult, DeriveAccountsParam, + DeriveAccountsResult, DeriveSubAccountsParam, DeriveSubAccountsResult, DerivedKeyResult, + EncryptDataToIpfsParam, EncryptDataToIpfsResult, ExistsJsonParam, ExistsKeystoreResult, + ExistsMnemonicParam, ExistsPrivateKeyParam, ExportJsonParam, ExportJsonResult, + ExportMnemonicParam, ExportMnemonicResult, ExportPrivateKeyParam, ExportPrivateKeyResult, + GeneralResult, GetExtendedPublicKeysParam, GetExtendedPublicKeysResult, GetPublicKeysParam, GetPublicKeysResult, ImportJsonParam, ImportMnemonicParam, ImportPrivateKeyParam, ImportPrivateKeyResult, KeystoreResult, MnemonicToPublicKeyParam, MnemonicToPublicKeyResult, ScanKeystoresResult, SignAuthenticationMessageParam, SignAuthenticationMessageResult, @@ -132,10 +132,6 @@ fn fingerprint_from_any_format_pk(pk: &str) -> Result { fingerprint_from_private_key(&key_data) } -// fn fingerprint_from_tezos_format_pk(pk: &str) -> Result { -// fingerprint_from_private_key(&key_data) -// } - fn import_private_key_internal( param: &ImportPrivateKeyParam, source: Option, @@ -333,8 +329,8 @@ fn key_info_from_v3(keystore: &str, password: &str) -> Result<(Vec, String)> ks.validate_v3(password)?; let key = tcx_crypto::Key::Password(password.to_string()); let unlocker = ks.crypto.use_key(&key)?; - let pk = unlocker.plaintext()?; - Ok((pk, "Imported ETH".to_string())) + let private_key = unlocker.plaintext()?; + Ok((private_key, "Imported ETH".to_string())) } fn key_info_from_substrate_keystore(keystore: &str, password: &str) -> Result<(Vec, String)> { @@ -344,6 +340,24 @@ fn key_info_from_substrate_keystore(keystore: &str, password: &str) -> Result<(V Ok((pk, ks.meta.name)) } +fn curve_to_chain_type(curve: &CurveType) -> Vec { + match curve { + CurveType::SECP256k1 => vec![ + "BITCOIN".to_string(), + "BITCOINCASH".to_string(), + "LITECOIN".to_string(), + "FILECOIN".to_string(), + "EOS".to_string(), + "TRON".to_string(), + "COSMOS".to_string(), + ], + CurveType::ED25519 => vec!["TEZOS".to_string()], + CurveType::SR25519 => vec!["KUSAMA".to_string(), "POLKADOT".to_string()], + CurveType::BLS => vec!["FILECOIN".to_string()], + _ => vec![], + } +} + pub fn init_token_core_x(data: &[u8]) -> Result<()> { let InitTokenCoreXParam { file_dir, @@ -371,6 +385,7 @@ pub fn init_token_core_x(data: &[u8]) -> Result<()> { Ok(()) } + pub(crate) fn scan_keystores() -> Result { clean_keystore(); let file_dir = WALLET_FILE_DIR.read(); @@ -443,24 +458,6 @@ pub(crate) fn scan_keystores() -> Result { }) } -fn curve_to_chain_type(curve: &CurveType) -> Vec { - match curve { - CurveType::SECP256k1 => vec![ - "BITCOIN".to_string(), - "BITCOINCASH".to_string(), - "LITECOIN".to_string(), - "FILECOIN".to_string(), - "EOS".to_string(), - "TRON".to_string(), - "COSMOS".to_string(), - ], - CurveType::ED25519 => vec!["TEZOS".to_string()], - CurveType::SR25519 => vec!["KUSAMA".to_string(), "POLKADOT".to_string()], - CurveType::BLS => vec!["FILECOIN".to_string()], - _ => vec![], - } -} - pub(crate) fn create_keystore(data: &[u8]) -> Result> { let param: CreateKeystoreParam = CreateKeystoreParam::decode(data).expect("create_keystore param"); @@ -552,7 +549,6 @@ pub(crate) fn import_mnemonic(data: &[u8]) -> Result> { } impl_to_key!(crate::api::derive_accounts_param::Key); - pub(crate) fn derive_accounts(data: &[u8]) -> Result> { let param: DeriveAccountsParam = DeriveAccountsParam::decode(data).expect("derive_accounts param"); @@ -691,7 +687,7 @@ pub(crate) fn verify_password(data: &[u8]) -> Result> { _ => Err(anyhow!("{}", "wallet_not_found")), }?; - if keystore.verify_password(¶m.password) { + if keystore.verify_password(¶m.key.clone().unwrap().into()) { let rsp = GeneralResult { is_success: true, error: "".to_owned(), @@ -702,6 +698,7 @@ pub(crate) fn verify_password(data: &[u8]) -> Result> { } } +impl_to_key!(crate::api::wallet_key_param::Key); pub(crate) fn delete_keystore(data: &[u8]) -> Result> { let param: WalletKeyParam = WalletKeyParam::decode(data).expect("delete_keystore param"); let mut map = KEYSTORE_MAP.write(); @@ -710,7 +707,7 @@ pub(crate) fn delete_keystore(data: &[u8]) -> Result> { _ => Err(anyhow!("{}", "wallet_not_found")), }?; - if keystore.verify_password(¶m.password) { + if keystore.verify_password(¶m.key.clone().unwrap().into()) { delete_keystore_file(¶m.id)?; map.remove(¶m.id); @@ -891,7 +888,10 @@ pub(crate) fn get_derived_key(data: &[u8]) -> Result> { _ => Err(anyhow!("{}", "wallet_not_found")), }?; - let dk = keystore.get_derived_key(¶m.password)?; + let Some(api::wallet_key_param::Key::Password(password)) = param.key else { + return Err(anyhow!("{}", "get_derived_key need password")); + }; + let dk = keystore.get_derived_key(&password)?; let ret = DerivedKeyResult { id: param.id.to_owned(), @@ -1047,23 +1047,18 @@ pub(crate) fn backup(data: &[u8]) -> Result> { Some(keystore) => Ok(keystore), _ => Err(anyhow!("{}", "wallet_not_found")), }?; - let original = keystore.backup(¶m.password)?; + + let original = keystore.backup(¶m.key.clone().unwrap().into())?; let fingerprint = match keystore.meta().source { - Source::Mnemonic | Source::NewMnemonic => fingerprint_from_mnemonic(&original)?, - Source::KeystoreV3 => { - let (private_key_bytes, _) = key_info_from_v3(&original, ¶m.password)?; - let private_key = private_key_bytes.to_hex(); - fingerprint_from_any_format_pk(&private_key)? - } - Source::SubstrateKeystore => { - let (private_key_bytes, _) = - key_info_from_substrate_keystore(&original, ¶m.password)?; - let private_key = private_key_bytes.to_hex(); - fingerprint_from_any_format_pk(&private_key)? - } - Source::Private | Source::Wif => fingerprint_from_any_format_pk(&original)?, + Source::Mnemonic | Source::NewMnemonic => Some(fingerprint_from_mnemonic(&original)?), + Source::Private | Source::Wif => Some(fingerprint_from_any_format_pk(&original)?), + Source::KeystoreV3 | Source::SubstrateKeystore => None, }; - if fingerprint.eq_ignore_ascii_case(keystore.fingerprint()) { + if fingerprint.is_none() + || fingerprint + .unwrap() + .eq_ignore_ascii_case(keystore.fingerprint()) + { encode_message(BackupResult { original }) } else { Err(anyhow!("fingerprint_not_match")) @@ -1078,7 +1073,7 @@ pub(crate) fn unlock_then_crash(data: &[u8]) -> Result> { _ => Err(anyhow!("{}", "wallet_not_found")), }?; - let _guard = KeystoreGuard::unlock_by_password(keystore, ¶m.password)?; + let _guard = keystore.unlock(¶m.key.unwrap().into()); panic!("test_unlock_then_crash"); } @@ -1118,6 +1113,7 @@ pub(crate) fn decrypt_data_from_ipfs(data: &[u8]) -> Result> { encode_message(output) } +impl_to_key!(crate::api::sign_authentication_message_param::Key); pub(crate) fn sign_authentication_message(data: &[u8]) -> Result> { let param = SignAuthenticationMessageParam::decode(data).expect("SignAuthenticationMessageParam"); @@ -1127,8 +1123,10 @@ pub(crate) fn sign_authentication_message(data: &[u8]) -> Result> { return Err(anyhow::anyhow!("identity_not_found")); }; - let key = tcx_crypto::Key::Password(param.password); - let unlocker = identity_ks.store().crypto.use_key(&key)?; + let unlocker = identity_ks + .store() + .crypto + .use_key(¶m.key.clone().unwrap().into())?; let signature = identity_ks.identity().sign_authentication_message( param.access_time, diff --git a/token-core/tcx/src/lib.rs b/token-core/tcx/src/lib.rs index 32a3474f..ae841d25 100644 --- a/token-core/tcx/src/lib.rs +++ b/token-core/tcx/src/lib.rs @@ -167,6 +167,7 @@ mod tests { use crate::api::derive_accounts_param::Derivation; use crate::api::sign_hashes_param::DataToSign; use crate::filemanager::KEYSTORE_MAP; + use crate::handler::scan_keystores; use api::sign_param::Key; use error_handling::Result; use serial_test::serial; @@ -206,10 +207,12 @@ mod tests { use sp_core::ByteArray; use sp_runtime::traits::Verify; - use tcx_btc_kin::Utxo; + use tcx_btc_kin::{OmniTxInput, Utxo}; use tcx_ckb::{CachedCell, CellInput, CkbTxInput, CkbTxOutput, OutPoint, Script, Witness}; - use tcx_eth::api::{AccessList, EthMessageInput, EthMessageOutput, EthTxInput, EthTxOutput}; + use tcx_eth::transaction::{ + AccessList, EthMessageInput, EthMessageOutput, EthTxInput, EthTxOutput, + }; use tcx_filecoin::{SignedMessage, UnsignedMessage}; use tcx_substrate::{SubstrateKeystore, SubstrateRawTxIn, SubstrateTxOut}; use tcx_tezos::transaction::{TezosRawTxIn, TezosTxOut}; @@ -366,14 +369,17 @@ mod tests { #[ignore = "for debug"] fn test_call_tcx_api() { run_test(|| { - let bytes = &Vec::::from_hex_auto("0a0f6465726976655f6163636f756e747312770a176170692e4465726976654163636f756e7473506172616d125c0a2430313831653533662d346566642d343262352d623430302d39333134656239376339373412083132333435363738222a0a08455448455245554d12106d2f3434272f3630272f30272f302f302a01313209736563703235366b31").unwrap(); + let bytes = &Vec::::from_hex_auto("0a077369676e5f747812c6020a0d6170692e5369676e506172616d12b4020a2431613663643861642d376265392d343762622d613533642d306463363962366134643966120b71713330373939303538382207424954434f494e2a0f6d2f3439272f30272f30272f302f303209736563703235366b313a074d41494e4e45544206503257504b484ac8010a197472616e73616374696f6e2e4274634b696e5478496e70757412aa010a7d0a4066646461616535663763346565323135343135333361636163653934376162363464626434663061383932353864613763333636643339343064373663383661100018e38a032222334d465a673136634b79547047527054547947746a4e594c63337a734a78764a61352a0f6d2f3439272f30272f30272f302f3012223351657271594e5143644854357a504545314a3532673354376d5971714e7362503618c0843d208b1f").unwrap(); let action = TcxAction::decode(bytes.as_slice()).unwrap(); dbg!(&action); - let param = - DeriveAccountsParam::decode(action.param.unwrap().value.as_slice()).unwrap(); + let param = SignParam::decode(action.param.unwrap().value.as_slice()).unwrap(); + let input = OmniTxInput::decode(param.input.unwrap().value.as_slice()).unwrap(); + let _wallet = import_default_wallet(); - dbg!(¶m); - // call_tcx_api(bytes.to_hex()) + dbg!(&input); + unsafe { + call_tcx_api(CString::new(bytes.to_hex()).unwrap().as_ptr()); + } assert!(true); }); } @@ -529,7 +535,9 @@ mod tests { let param = WalletKeyParam { id: wallet.id.to_string(), - password: TEST_PASSWORD.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), }; let ret = call_api("export_mnemonic", param).unwrap(); let result: ExportMnemonicResult = @@ -541,7 +549,9 @@ mod tests { let param = WalletKeyParam { id: wallet.id.to_string(), - password: TEST_PASSWORD.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), }; unsafe { clear_err() }; let ret = call_api("export_mnemonic", param); @@ -1934,7 +1944,9 @@ mod tests { for id in wallet_id { let param: WalletKeyParam = WalletKeyParam { id: id.to_string(), - password: TEST_PASSWORD.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), }; let ret_bytes = call_api("verify_password", param).unwrap(); @@ -1943,7 +1955,9 @@ mod tests { let param: WalletKeyParam = WalletKeyParam { id: id.to_string(), - password: "WRONG PASSWORD".to_string(), + key: Some(api::wallet_key_param::Key::Password( + "WRONG PASSWORD".to_string(), + )), }; let ret = call_api("verify_password", param); @@ -1955,7 +1969,7 @@ mod tests { #[test] #[serial] - pub fn test_delete_keystore() { + pub fn test_delete_keystore_by_password() { run_test(|| { let param: ImportPrivateKeyParam = ImportPrivateKeyParam { private_key: "5JZc7wGRUr4J1RHDcM9ySWKLfQ2xjRUEo612qC4RLJ3G7jzJ4qx".to_string(), @@ -1969,10 +1983,11 @@ mod tests { let ret_bytes = import_private_key(&encode_message(param).unwrap()).unwrap(); let import_result: KeystoreResult = KeystoreResult::decode(ret_bytes.as_slice()).unwrap(); - let param: WalletKeyParam = WalletKeyParam { id: import_result.id.to_string(), - password: "WRONG PASSWORD".to_string(), + key: Some(api::wallet_key_param::Key::Password( + "WRONG PASSWORD".to_string(), + )), }; let ret = call_api("delete_keystore", param); @@ -1981,7 +1996,69 @@ mod tests { let param: WalletKeyParam = WalletKeyParam { id: import_result.id.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), + }; + + let ret_bytes = call_api("delete_keystore", param).unwrap(); + let ret: GeneralResult = GeneralResult::decode(ret_bytes.as_slice()).unwrap(); + assert!(ret.is_success); + + let param: ExistsPrivateKeyParam = ExistsPrivateKeyParam { + private_key: "5JZc7wGRUr4J1RHDcM9ySWKLfQ2xjRUEo612qC4RLJ3G7jzJ4qx".to_string(), + }; + + let ret_bytes = call_api("exists_private_key", param).unwrap(); + let ret: ExistsKeystoreResult = + ExistsKeystoreResult::decode(ret_bytes.as_slice()).unwrap(); + + assert_eq!(false, ret.is_exists); + }) + } + + #[test] + #[serial] + pub fn test_delete_keystore_by_derived_key() { + run_test(|| { + let param: ImportPrivateKeyParam = ImportPrivateKeyParam { + private_key: "5JZc7wGRUr4J1RHDcM9ySWKLfQ2xjRUEo612qC4RLJ3G7jzJ4qx".to_string(), password: TEST_PASSWORD.to_string(), + name: "test_delete_keystore".to_string(), + password_hint: "".to_string(), + network: "".to_string(), + overwrite: true, + }; + + let ret_bytes = import_private_key(&encode_message(param).unwrap()).unwrap(); + let import_result: KeystoreResult = + KeystoreResult::decode(ret_bytes.as_slice()).unwrap(); + let param = WalletKeyParam { + id: import_result.id.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), + }; + let ret_bytes = get_derived_key(&encode_message(param).unwrap()).unwrap(); + let derived_key_result: DerivedKeyResult = + DerivedKeyResult::decode(ret_bytes.as_slice()).unwrap(); + + let param: WalletKeyParam = WalletKeyParam { + id: import_result.id.to_string(), + key: Some(api::wallet_key_param::Key::DerivedKey( + "2de5cb10b712be587f31e428e22984bd9ee420d198ddd742f70d746fff27d19904629dd64246a0ce2dbb1484c193d51bb2fd47d5611def5b4db4531d7abed824".to_string(), + )), + }; + + let ret = call_api("delete_keystore", param); + assert!(ret.is_err()); + assert_eq!(format!("{}", ret.err().unwrap()), "password_incorrect"); + + let param: WalletKeyParam = WalletKeyParam { + id: import_result.id.to_string(), + key: Some(api::wallet_key_param::Key::DerivedKey( + derived_key_result.derived_key.to_owned(), + )), }; let ret_bytes = call_api("delete_keystore", param).unwrap(); @@ -2819,7 +2896,9 @@ mod tests { let param = WalletKeyParam { id: import_result.id.to_string(), - password: TEST_PASSWORD.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), }; let ret_bytes = get_derived_key(&encode_message(param).unwrap()).unwrap(); let ret: DerivedKeyResult = DerivedKeyResult::decode(ret_bytes.as_slice()).unwrap(); @@ -2925,7 +3004,9 @@ mod tests { let dk_param = WalletKeyParam { id: wallet.id.to_string(), - password: TEST_PASSWORD.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), }; let ret_bytes = get_derived_key(&encode_message(dk_param).unwrap()).unwrap(); @@ -3074,37 +3155,6 @@ mod tests { }) } - // #[test] - // fn test_get_derived_key() { - // let param = InitTokenCoreXParam { - // file_dir: "../test-data".to_string(), - // xpub_common_key: "B888D25EC8C12BD5043777B1AC49F872".to_string(), - // xpub_common_iv: "9C0C30889CBCC5E01AB5B2BB88715799".to_string(), - // is_debug: true, - // }; - - // handler::init_token_core_x(&encode_message(param).unwrap()).expect("should init tcx"); - - // let param = WalletKeyParam { - // id: "cb1ba2d7-7b89-4595-9753-d16b6e317c6b".to_string(), - // password: "WRONG PASSWORD".to_string(), - // }; - - // let ret = call_api("get_derived_key", param); - // assert!(ret.is_err()); - // assert_eq!(format!("{}", ret.err().unwrap()), "password_incorrect"); - - // let param = WalletKeyParam { - // id: "cb1ba2d7-7b89-4595-9753-d16b6e317c6b".to_string(), - // password: TEST_PASSWORD.to_string(), - // }; - - // let ret = call_api("get_derived_key", param).unwrap(); - // let dk_ret: DerivedKeyResult = DerivedKeyResult::decode(ret.as_slice()).unwrap(); - // assert_eq!(dk_ret.derived_key, "119a38ab626aaf8806e223833b29da7aa1d0623e282164d1dd73b0b5e0a88fb4b88937efadd9ca9d4ee931d7b2b33594d75ac4f4d651602819998237b27860fa"); - // } - // - #[test] #[serial] #[ignore = "this case is test panic"] @@ -3113,7 +3163,9 @@ mod tests { let wallet = import_default_wallet(); let param = WalletKeyParam { id: wallet.id.to_string(), - password: TEST_PASSWORD.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), }; let _ret = call_api("unlock_then_crash", param); let err = unsafe { _to_str(get_last_err_message()) }; @@ -4364,7 +4416,7 @@ mod tests { #[test] #[serial] - pub fn test_backup() { + pub fn test_backup_v3_keystore() { run_test(|| { let json = r#"{ "version": 3, @@ -4401,7 +4453,31 @@ mod tests { let param = WalletKeyParam { id: import_result.id.to_string(), - password: TEST_PASSWORD.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), + }; + let ret = call_api("backup", param).unwrap(); + let export_result: BackupResult = BackupResult::decode(ret.as_slice()).unwrap(); + assert!(export_result + .original + .contains("0x6031564e7b2F5cc33737807b2E58DaFF870B590b")); + + let param = WalletKeyParam { + id: import_result.id.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), + }; + + let ret = call_api("get_derived_key", param).unwrap(); + let derived_key_result: DerivedKeyResult = + DerivedKeyResult::decode(ret.as_slice()).unwrap(); + let param = WalletKeyParam { + id: import_result.id.to_string(), + key: Some(api::wallet_key_param::Key::DerivedKey( + derived_key_result.derived_key, + )), }; let ret = call_api("backup", param).unwrap(); let export_result: BackupResult = BackupResult::decode(ret.as_slice()).unwrap(); @@ -4413,13 +4489,146 @@ mod tests { #[test] #[serial] - pub fn test_backup_mnemonic() { + pub fn test_backup_pjs_kystore() { run_test(|| { - let param: ImportMnemonicParam = ImportMnemonicParam { + let keystore_str: &str = r#"{ + "address": "JHBkzZJnLZ3S3HLvxjpFAjd6ywP7WAk5miL7MwVCn9a7jHS", + "encoded": "0xf7e7e89d3016c9b4d93bb1129adf69e5949ca1fb58c29da4591ddc72c52238a35835e3f2ae023f9867ff301bc4132463527ac03525eaac54664a7cb658eae68a0bbc99354222c194d6100b2bf3a492639229077a2e2818d8196e002f0b5556104be23b11633858259dbbd3f91ea1d34d6ce182b62d8381af1ef3c35e9ab1583267cfa41aa58bfd64435c2b5047baf9052f0953d9f7854d2d396dfcad13", + "encoding": { + "content": [ + "pkcs8", + "sr25519" + ], + "type": "xsalsa20-poly1305", + "version": "2" + }, + "meta": { + "genesisHash": "0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe", + "name": "i_can_save_name", + "tags": [], + "whenCreated": 1593591324334 + } + }"#; + let param: ImportJsonParam = ImportJsonParam { password: TEST_PASSWORD.to_string(), + json: keystore_str.to_string(), + overwrite: true, + }; + let ret = call_api("import_json", param).unwrap(); + let import_result: ImportPrivateKeyResult = + ImportPrivateKeyResult::decode(ret.as_slice()).unwrap(); + assert_eq!( + vec!["KUSAMA".to_string(), "POLKADOT".to_string(),], + import_result.identified_chain_types + ); + + let param = WalletKeyParam { + id: import_result.id.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), + }; + let ret = call_api("backup", param).unwrap(); + let export_result: BackupResult = BackupResult::decode(ret.as_slice()).unwrap(); + assert!(export_result + .original + .contains("JHBkzZJnLZ3S3HLvxjpFAjd6ywP7WAk5miL7MwVCn9a7jHS")); + + let param = WalletKeyParam { + id: import_result.id.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), + }; + + let ret = call_api("get_derived_key", param).unwrap(); + let derived_key_result: DerivedKeyResult = + DerivedKeyResult::decode(ret.as_slice()).unwrap(); + let param = WalletKeyParam { + id: import_result.id.to_string(), + key: Some(api::wallet_key_param::Key::DerivedKey( + derived_key_result.derived_key, + )), + }; + let ret = call_api("backup", param).unwrap(); + let export_result: BackupResult = BackupResult::decode(ret.as_slice()).unwrap(); + assert!(export_result + .original + .contains("JHBkzZJnLZ3S3HLvxjpFAjd6ywP7WAk5miL7MwVCn9a7jHS")); + }) + } + + #[test] + #[serial] + pub fn test_backup_private_key() { + run_test(|| { + let param: ImportPrivateKeyParam = ImportPrivateKeyParam { + password: TEST_PASSWORD.to_string(), + private_key: TEST_WIF.to_string(), name: "".to_string(), password_hint: "".to_string(), + network: "TESTNET".to_string(), + overwrite: true, + }; + let ret = call_api("import_private_key", param).unwrap(); + let import_result: ImportPrivateKeyResult = + ImportPrivateKeyResult::decode(ret.as_slice()).unwrap(); + assert_eq!( + vec![ + "BITCOIN".to_string(), + "BITCOINCASH".to_string(), + "LITECOIN".to_string() + ], + import_result.identified_chain_types + ); + + let param = WalletKeyParam { + id: import_result.id.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), + }; + let ret = call_api("backup", param).unwrap(); + let export_result: BackupResult = BackupResult::decode(ret.as_slice()).unwrap(); + assert_eq!(export_result.original, TEST_WIF); + + let param = WalletKeyParam { + id: import_result.id.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), + }; + + let ret = call_api("get_derived_key", param).unwrap(); + let derived_key_result: DerivedKeyResult = + DerivedKeyResult::decode(ret.as_slice()).unwrap(); + let param = WalletKeyParam { + id: import_result.id.to_string(), + key: Some(api::wallet_key_param::Key::DerivedKey( + derived_key_result.derived_key, + )), + }; + let ret = call_api("backup", param.clone()).unwrap(); + let backup_result = BackupResult::decode(ret.as_slice()).unwrap(); + assert_eq!(backup_result.original, TEST_WIF); + + change_fingerprint_in_keystore(&import_result.id, &import_result.source_fingerprint); + scan_keystores().unwrap(); + + let ret = call_api("backup", param); + assert_eq!(format!("{}", ret.err().unwrap()), "fingerprint_not_match"); + }) + } + + #[test] + #[serial] + pub fn test_backup_mnemonic() { + run_test(|| { + let param: ImportMnemonicParam = ImportMnemonicParam { + password: TEST_PASSWORD.to_string(), mnemonic: TEST_MNEMONIC.to_string(), + password_hint: "".to_string(), + name: "".to_string(), network: "MAINNET".to_string(), overwrite: true, }; @@ -4428,14 +4637,49 @@ mod tests { let param = WalletKeyParam { id: import_result.id.to_string(), - password: TEST_PASSWORD.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), }; let ret = call_api("backup", param).unwrap(); let export_result: BackupResult = BackupResult::decode(ret.as_slice()).unwrap(); assert_eq!(export_result.original, TEST_MNEMONIC.to_string()); + + let param = WalletKeyParam { + id: import_result.id.to_string(), + key: Some(api::wallet_key_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), + }; + + let ret = call_api("get_derived_key", param).unwrap(); + let derived_key_result: DerivedKeyResult = + DerivedKeyResult::decode(ret.as_slice()).unwrap(); + let param = WalletKeyParam { + id: import_result.id.to_string(), + key: Some(api::wallet_key_param::Key::DerivedKey( + derived_key_result.derived_key, + )), + }; + let ret = call_api("backup", param.clone()).unwrap(); + let export_result: BackupResult = BackupResult::decode(ret.as_slice()).unwrap(); + assert_eq!(export_result.original, TEST_MNEMONIC.to_string()); + + change_fingerprint_in_keystore(&import_result.id, &import_result.source_fingerprint); + scan_keystores().unwrap(); + + let ret = call_api("backup", param); + assert_eq!(format!("{}", ret.err().unwrap()), "fingerprint_not_match"); }) } + fn change_fingerprint_in_keystore(id: &str, fingerprint: &str) { + let file_path = format!("/tmp/imtoken/walletsV2/{}.json", id); + let contents = fs::read_to_string(&file_path).unwrap(); + let new_contents = contents.replace(fingerprint, "0x00000000000000000000"); + fs::write(file_path, new_contents).expect("change fingerprint"); + } + #[bench] fn bench_import_mnemonic(b: &mut Bencher) { b.iter(|| { @@ -4479,7 +4723,9 @@ mod tests { access_time: 1514736000, identifier: wallet.identifier, device_token: "12345ABCDE".to_string(), - password: TEST_PASSWORD.to_string(), + key: Some(api::sign_authentication_message_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), }; let ret = call_api("sign_authentication_message", param).unwrap(); let resp: SignAuthenticationMessageResult =