diff --git a/token-core/tcx-eth/src/signer.rs b/token-core/tcx-eth/src/signer.rs index 2a801acf..ffa3f6e1 100644 --- a/token-core/tcx-eth/src/signer.rs +++ b/token-core/tcx-eth/src/signer.rs @@ -126,6 +126,17 @@ impl EthRecoverAddressInput { } } +pub fn batch_personal_sign(keystore: &mut Keystore, data: Vec) -> Result> { + let mut signatures = vec![]; + for val in data.iter() { + let message = hash_message(utf8_or_hex_to_bytes(&val)?); + let mut signature = keystore.secp256k1_ecdsa_sign_recoverable(&message, "")?; + signature[64] = signature[64] + 27; + signatures.push(signature.to_0x_hex()); + } + Ok(signatures) +} + #[cfg(test)] mod test { use crate::transaction::{ @@ -133,6 +144,7 @@ mod test { EthTxOutput, SignatureType, }; + use crate::signer::batch_personal_sign; use tcx_constants::{CurveType, TEST_MNEMONIC, TEST_PASSWORD}; use tcx_keystore::{Keystore, MessageSigner, Metadata, SignatureParameters, TransactionSigner}; @@ -669,4 +681,19 @@ mod test { println!("{}", output.address); assert_eq!(output.address, "0xed54a7c1d8634bb589f24bb7f05a5554b36f9618"); } + + #[test] + fn test_batch_personal_sign() { + let mut keystore = + private_key_store("a392604efc2fad9c0b3da43b5f698a2e3f270f170d859912be0d54742275c5f6"); + + let test_data = vec![ + "Hello imToken".to_string(), + "0xef678007d18427e6022059dbc264f27507cd1ffc".to_string(), + ]; + + let result = batch_personal_sign(&mut keystore, test_data).unwrap(); + assert_eq!(result[0], "0x1be38ff0ab0e6d97cba73cf61421f0641628be8ee91dcb2f73315e7fdf4d0e2770b0cb3cc7350426798d43f0fb05602664a28bb2c9fcf46a07fa1c8c4e322ec01b".to_string()); + assert_eq!(result[1], "0xb12a1c9d3a7bb722d952366b06bd48cb35bdf69065dee92351504c3716a782493c697de7b5e59579bdcc624aa277f8be5e7f42dc65fe7fcd4cc68fef29ff28c21b".to_string()); + } } diff --git a/token-core/tcx-proto/src/api.proto b/token-core/tcx-proto/src/api.proto index 8e796675..584d55ba 100644 --- a/token-core/tcx-proto/src/api.proto +++ b/token-core/tcx-proto/src/api.proto @@ -131,3 +131,16 @@ message ExportMnemonicParam { } } +message EthBatchPersonalSignParam { + string id = 1; + oneof key { + string password = 2; + string derivedKey = 3; + } + repeated string data = 4; +} + +message EthBatchPersonalSignResult { + repeated string signatures = 1; +} + diff --git a/token-core/tcx/src/api.rs b/token-core/tcx/src/api.rs index 5a55d538..ce1f1557 100644 --- a/token-core/tcx/src/api.rs +++ b/token-core/tcx/src/api.rs @@ -229,6 +229,33 @@ pub mod export_mnemonic_param { DerivedKey(::prost::alloc::string::String), } } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EthBatchPersonalSignParam { + #[prost(string, tag = "1")] + pub id: ::prost::alloc::string::String, + #[prost(string, repeated, tag = "4")] + pub data: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(oneof = "eth_batch_personal_sign_param::Key", tags = "2, 3")] + pub key: ::core::option::Option, +} +/// Nested message and enum types in `EthBatchPersonalSignParam`. +pub mod eth_batch_personal_sign_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)] +pub struct EthBatchPersonalSignResult { + #[prost(string, repeated, tag = "1")] + pub signatures: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} /// FUNCTION: create_keystore(CreateKeystoreParam): KeystoreResult /// /// create a new hd keystore diff --git a/token-core/tcx/src/handler.rs b/token-core/tcx/src/handler.rs index e8d3a5c1..bde72f31 100644 --- a/token-core/tcx/src/handler.rs +++ b/token-core/tcx/src/handler.rs @@ -31,10 +31,11 @@ use crate::api::{ 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, + EncryptDataToIpfsParam, EncryptDataToIpfsResult, EthBatchPersonalSignParam, + EthBatchPersonalSignResult, 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, @@ -57,6 +58,7 @@ use tcx_constants::coin_info::coin_info_from_param; use tcx_constants::{CoinInfo, CurveType}; use tcx_crypto::aes::cbc::encrypt_pkcs7; use tcx_crypto::KDF_ROUNDS; +use tcx_eth::signer::batch_personal_sign; use tcx_keystore::{MessageSigner, TransactionSigner}; use tcx_primitive::Ss58Codec; @@ -1213,6 +1215,23 @@ pub(crate) fn sign_bls_to_execution_change(data: &[u8]) -> Result> { encode_message(result) } +impl_to_key!(crate::api::eth_batch_personal_sign_param::Key); +pub(crate) fn eth_batch_personal_sign(data: &[u8]) -> Result> { + let param: EthBatchPersonalSignParam = EthBatchPersonalSignParam::decode(data)?; + + let mut map = KEYSTORE_MAP.write(); + let keystore: &mut Keystore = match map.get_mut(¶m.id) { + Some(keystore) => Ok(keystore), + _ => Err(anyhow!("{}", "wallet_not_found")), + }?; + + let mut keystore = KeystoreGuard::unlock(keystore, param.key.clone().unwrap().into())?; + + let signatures = batch_personal_sign(keystore.keystore_mut(), param.data)?; + + encode_message(EthBatchPersonalSignResult { signatures }) +} + #[cfg(test)] mod tests { use tcx_constants::CurveType; diff --git a/token-core/tcx/src/lib.rs b/token-core/tcx/src/lib.rs index 53bcd26b..d824ba1d 100644 --- a/token-core/tcx/src/lib.rs +++ b/token-core/tcx/src/lib.rs @@ -22,11 +22,11 @@ use std::result; use crate::error_handling::{landingpad, LAST_ERROR}; use crate::handler::{ create_keystore, decrypt_data_from_ipfs, delete_keystore, derive_accounts, derive_sub_accounts, - encode_message, encrypt_data_to_ipfs, exists_json, exists_mnemonic, exists_private_key, - export_json, export_mnemonic, export_private_key, get_derived_key, get_extended_public_keys, - get_public_keys, import_json, import_mnemonic, import_private_key, mnemonic_to_public, - sign_authentication_message, sign_hashes, sign_message, sign_tx, unlock_then_crash, - verify_password, + encode_message, encrypt_data_to_ipfs, eth_batch_personal_sign, exists_json, exists_mnemonic, + exists_private_key, export_json, export_mnemonic, export_private_key, get_derived_key, + get_extended_public_keys, get_public_keys, import_json, import_mnemonic, import_private_key, + mnemonic_to_public, sign_authentication_message, sign_hashes, sign_message, sign_tx, + unlock_then_crash, verify_password, }; use crate::migration::{migrate_keystore, scan_legacy_keystores}; @@ -119,6 +119,9 @@ pub unsafe extern "C" fn call_tcx_api(hex_str: *const c_char) -> *const c_char { "sign_bls_to_execution_change" => { landingpad(|| sign_bls_to_execution_change(&action.param.unwrap().value)) } + "eth_batch_personal_sign" => { + landingpad(|| eth_batch_personal_sign(&action.param.unwrap().value)) + } _ => landingpad(|| Err(anyhow!("unsupported_method"))), }; match reply { @@ -185,7 +188,8 @@ mod tests { export_private_key_param, migrate_keystore_param, sign_param, BackupResult, CreateKeystoreParam, DecryptDataFromIpfsParam, DecryptDataFromIpfsResult, DeriveAccountsParam, DeriveAccountsResult, DeriveSubAccountsParam, DeriveSubAccountsResult, - DerivedKeyResult, EncryptDataToIpfsParam, EncryptDataToIpfsResult, ExistsJsonParam, + DerivedKeyResult, EncryptDataToIpfsParam, EncryptDataToIpfsResult, + EthBatchPersonalSignParam, EthBatchPersonalSignResult, ExistsJsonParam, ExistsKeystoreResult, ExistsMnemonicParam, ExistsPrivateKeyParam, ExportJsonParam, ExportJsonResult, ExportMnemonicResult, ExportPrivateKeyParam, ExportPrivateKeyResult, GeneralResult, GetExtendedPublicKeysParam, GetExtendedPublicKeysResult, GetPublicKeysParam, @@ -4874,4 +4878,50 @@ mod tests { } }) } + + #[test] + #[serial] + fn test_eth_batch_personal_sign_by_private_key() { + run_test(|| { + let wallet = import_default_pk_store(); + let param = EthBatchPersonalSignParam { + id: wallet.id.to_string(), + key: Some(api::eth_batch_personal_sign_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), + data: vec![ + "Hello imToken".to_string(), + "0xef678007d18427e6022059dbc264f27507cd1ffc".to_string(), + ], + }; + let sign_result = call_api("eth_batch_personal_sign", param).unwrap(); + let ret: EthBatchPersonalSignResult = + EthBatchPersonalSignResult::decode(sign_result.as_slice()).unwrap(); + assert_eq!(ret.signatures[0], "0x1be38ff0ab0e6d97cba73cf61421f0641628be8ee91dcb2f73315e7fdf4d0e2770b0cb3cc7350426798d43f0fb05602664a28bb2c9fcf46a07fa1c8c4e322ec01b".to_string()); + assert_eq!(ret.signatures[1], "0xb12a1c9d3a7bb722d952366b06bd48cb35bdf69065dee92351504c3716a782493c697de7b5e59579bdcc624aa277f8be5e7f42dc65fe7fcd4cc68fef29ff28c21b".to_string()); + }); + } + + #[test] + #[serial] + fn test_eth_batch_personal_sign_by_hd() { + run_test(|| { + let wallet = import_default_wallet(); + let param = EthBatchPersonalSignParam { + id: wallet.id.to_string(), + key: Some(api::eth_batch_personal_sign_param::Key::Password( + TEST_PASSWORD.to_owned(), + )), + data: vec![ + "Hello imToken".to_string(), + "0xef678007d18427e6022059dbc264f27507cd1ffc".to_string(), + ], + }; + let sign_result = call_api("eth_batch_personal_sign", param).unwrap(); + let ret: EthBatchPersonalSignResult = + EthBatchPersonalSignResult::decode(sign_result.as_slice()).unwrap(); + assert_eq!(ret.signatures[0], "0xb270b1e5ee1345c693b7b4fd7f5287f6b6372059c89590dfcadc4edf94ec9293296d3e205495f1468953a4f893cd6798b66301a51571fe2a419e75d5755b5bc91c".to_string()); + assert_eq!(ret.signatures[1], "0x19671e4c92847629fa5bbba59e70402f54366e61db0555984a83cc512413fc462d6be1508f1accdee6ad0040ed7401ac2db33d17e1aa4e66bedcb9f75c250cfa1b".to_string()); + }); + } }