From 33b55485b5295eb9101ff961559ae863525a648c Mon Sep 17 00:00:00 2001 From: gupnik Date: Thu, 27 Feb 2025 17:38:09 +0530 Subject: [PATCH 01/72] [FFI]: Don't panic if file was already deleted (#4292) --- rust/tw_macros/src/tw_ffi.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rust/tw_macros/src/tw_ffi.rs b/rust/tw_macros/src/tw_ffi.rs index f0ed2227cdc..fed4eb46e0a 100644 --- a/rust/tw_macros/src/tw_ffi.rs +++ b/rust/tw_macros/src/tw_ffi.rs @@ -134,8 +134,7 @@ pub fn tw_ffi(attr: TokenStream2, item: TokenStream2) -> Result { Ok(contents) => match serde_yaml::from_str(&contents) { Ok(config) => config, Err(_) => { - fs::remove_file(&yaml_file_path) - .expect("Failed to delete invalid YAML file"); + let _ = fs::remove_file(&yaml_file_path); TWConfig { class, static_functions: vec![], From 9ede1155072a8c2ea50f7bc7fecf2f8603b336e9 Mon Sep 17 00:00:00 2001 From: Robert Gabriel Jakabosky Date: Tue, 4 Mar 2025 17:54:01 +0800 Subject: [PATCH 02/72] Split Polymesh chain from Polkadot impl. (#4229) * Add SCALE support for String * Add SCALE support for BTreeSet/Map. * Split Polymesh chain from Polkadot impl. * Some cleanup of the Polymesh call encoder. * Use Polymesh protobuf instead of Polkadot. * Add permissions support. * Refactor. Move types into different module. * Add support for leave identity as key. * Fix custom call indices for staking batch calls. * Refactor batched call support. * Remove dead code. * Allow enum variants to re-use the same index for backwards compatibility. * Remove `multi_address` flag. Only support MultiAddress. * Fix batch call index. * Add some batch tests. * Code cleanup. * Add mobile tests. * Cleanup error conversion code. * Fix RewardDestination. * Add missing check_metadata field. * Move Polymesh signing tests over from Polkadot tests. * cargo clippy. * Fix Kotlin test. * Try fixing iOS test. * Fix test_address_derive call. * Fix Clang-tidy error. * Fix swift tests. * Fix json format. * Remove extra to_string calls. * Use common types from Polkadot.proto. * Add test for Staking.rebond * Update Android and swift tests to use Era from Polkadot. * Improve code coverage and remove dead code. * Some more code coverage. * Removed more dead code and improved code coverage. --- .../blockchains/CoinAddressDerivationTests.kt | 1 + .../polymesh/TestPolymeshAddress.kt | 30 + .../polymesh/TestPolymeshSigner.kt | 64 ++ docs/registry.md | 1 + include/TrustWalletCore/TWBlockchain.h | 1 + include/TrustWalletCore/TWCoinType.h | 1 + .../core/test/CoinAddressDerivationTests.kt | 1 + registry.json | 30 + rust/Cargo.lock | 17 + rust/Cargo.toml | 1 + .../tw_polkadot/src/call_encoder/mod.rs | 6 +- rust/chains/tw_polkadot/tests/extrinsic.rs | 143 +--- rust/chains/tw_polymesh/Cargo.toml | 16 + .../src/call_encoder.rs} | 234 +++--- rust/chains/tw_polymesh/src/entry.rs | 150 ++++ rust/chains/tw_polymesh/src/lib.rs | 29 + rust/chains/tw_polymesh/src/types.rs | 312 ++++++++ rust/chains/tw_polymesh/tests/extrinsic.rs | 732 ++++++++++++++++++ rust/frameworks/tw_substrate/src/extrinsic.rs | 5 - rust/frameworks/tw_substrate/src/lib.rs | 7 - rust/tw_coin_registry/Cargo.toml | 1 + rust/tw_coin_registry/src/blockchain_type.rs | 1 + rust/tw_coin_registry/src/dispatcher.rs | 3 + rust/tw_scale/src/lib.rs | 78 ++ rust/tw_scale/src/macros.rs | 8 +- rust/tw_tests/tests/chains/polkadot/mod.rs | 16 +- .../tests/chains/polkadot/polkadot_sign.rs | 238 +----- rust/tw_tests/tests/chains/polymesh/mod.rs | 155 ++++ .../tests/chains/polymesh/polymesh_address.rs | 35 +- .../tests/chains/polymesh/polymesh_compile.rs | 490 ++++++++++++ .../tests/chains/polymesh/polymesh_sign.rs | 379 +++++++++ .../tests/coin_address_derivation_test.rs | 1 + src/Coin.cpp | 3 + src/Polymesh/Entry.h | 17 + src/proto/Polkadot.proto | 57 +- src/proto/Polymesh.proto | 319 ++++++++ swift/Tests/Blockchains/PolymeshTests.swift | 52 ++ swift/Tests/CoinAddressDerivationTests.swift | 3 + tests/chains/Polkadot/TWAnyAddressTests.cpp | 22 - tests/chains/Polkadot/TWAnySignerTests.cpp | 1 - tests/chains/Polymesh/TWAnyAddressTests.cpp | 100 +++ tests/chains/Polymesh/TWAnySignerTests.cpp | 181 +++++ tests/chains/Polymesh/TWCoinTypeTests.cpp | 29 + tests/common/CoinAddressDerivationTests.cpp | 3 + 44 files changed, 3378 insertions(+), 595 deletions(-) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshAddress.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshSigner.kt create mode 100644 rust/chains/tw_polymesh/Cargo.toml rename rust/chains/{tw_polkadot/src/call_encoder/polymesh.rs => tw_polymesh/src/call_encoder.rs} (60%) create mode 100644 rust/chains/tw_polymesh/src/entry.rs create mode 100644 rust/chains/tw_polymesh/src/lib.rs create mode 100644 rust/chains/tw_polymesh/src/types.rs create mode 100644 rust/chains/tw_polymesh/tests/extrinsic.rs create mode 100644 rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs create mode 100644 rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs create mode 100644 src/Polymesh/Entry.h create mode 100644 src/proto/Polymesh.proto create mode 100644 swift/Tests/Blockchains/PolymeshTests.swift create mode 100644 tests/chains/Polymesh/TWAnyAddressTests.cpp create mode 100644 tests/chains/Polymesh/TWAnySignerTests.cpp create mode 100644 tests/chains/Polymesh/TWCoinTypeTests.cpp diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt index 94adc9f0ad5..af4411e737e 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt @@ -102,6 +102,7 @@ class CoinAddressDerivationTests { ACALA -> assertEquals("25GGezx3LWFQj6HZpYzoWoVzLsHojGtybef3vthC9nd19ms3", address) KUSAMA -> assertEquals("G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY", address) POLKADOT -> assertEquals("13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk", address) + POLYMESH -> assertEquals("2DHK8VhBpacs9quk78AVP9TmmcG5iXi2oKtZqneSNsVXxCKw", address) PIVX -> assertEquals("D81AqC8zKma3Cht4TbVuh4jyVVyLkZULCm", address) KAVA -> assertEquals("kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87", address) CARDANO -> assertEquals("addr1qyr8jjfnypp95eq74aqzn7ss687ehxclgj7mu6gratmg3mul2040vt35dypp042awzsjk5xm3zr3zm5qh7454uwdv08s84ray2", address) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshAddress.kt new file mode 100644 index 00000000000..a7e77d9a3e0 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshAddress.kt @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.polymesh + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestPolymeshAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + + val key = PrivateKey("0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4".toHexByteArray()) + val pubkey = key.publicKeyEd25519 + val address = AnyAddress(pubkey, CoinType.POLYMESH) + val expected = AnyAddress("2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys", CoinType.POLYMESH) + + assertEquals(pubkey.data().toHex(), "0x4bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshSigner.kt new file mode 100644 index 00000000000..116c5b76d40 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshSigner.kt @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.polymesh + +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType.POLYMESH +import wallet.core.jni.proto.Polkadot +import wallet.core.jni.proto.Polymesh +import wallet.core.jni.proto.Polymesh.SigningOutput + +class TestPolymeshSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + val genesisHashStr = "0x6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063".toHexBytesInByteString() + // Private key for testing. DO NOT USE, since this is public. + val TestKey1 = "0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4".toHexBytesInByteString() + + @Test + fun PolymeshTransactionSigning() { + // https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04 + + // Step 1: Prepare input. + val blockHashStr = "77d32517dcc7b74501096afdcff3af72008a2c489e17083f56629d195e5c6a1d".toHexBytesInByteString() + + var call = Polymesh.Balance.Transfer.newBuilder().apply { + toAddress = "2CpqFh8VnwJAjenw4xSUWCaaJ2QwGdhnCikoSEczMhjgqyj7" + value = "0x0F4240".toHexBytesInByteString() + } + + val input = Polymesh.SigningInput.newBuilder().apply { + genesisHash = genesisHashStr + blockHash = blockHashStr + era = Polkadot.Era.newBuilder().apply { + blockNumber = 16_102_106 + period = 64 + }.build() + network = POLYMESH.ss58Prefix() + nonce = 1 + specVersion = 7000005 + transactionVersion = 7 + privateKey = TestKey1 + runtimeCall = Polymesh.RuntimeCall.newBuilder().apply { + balanceCall = Polymesh.Balance.newBuilder().apply { + transfer = call.build() + }.build() + }.build() + } + + val output = AnySigner.sign(input.build(), POLYMESH, SigningOutput.parser()) + val encoded = Numeric.toHexString(output.encoded.toByteArray()) + + val expected = "0x390284004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00e9b4742a2b66931e0cf29f6811e4d44545b4f278a667b9eb1217c4b2de8763c8037e4501dd4a21179b737beb33415f458788f2d1093b527cae8bee8b2d55210ba501040005000010b713ceeb165c1ac7c450f5b138a6da0eba50bb18849f5b8e83985daa45a87e02093d00" + assertEquals(encoded, expected) + } +} diff --git a/docs/registry.md b/docs/registry.md index 2c0bce5d0bc..196c3d31653 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -64,6 +64,7 @@ This list is generated from [./registry.json](../registry.json) | 508 | MultiversX | eGLD | | | | 529 | Secret | SCRT | | | | 564 | Agoric | BLD | | | +| 595 | Polymesh | POLYX | | | | 607 | TON | TON | | | | 637 | Aptos | APT | | | | 714 | BNB Beacon Chain | BNB | | | diff --git a/include/TrustWalletCore/TWBlockchain.h b/include/TrustWalletCore/TWBlockchain.h index 355134bda05..519cf9c4429 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.h @@ -68,6 +68,7 @@ enum TWBlockchain { TWBlockchainBitcoinCash = 55, TWBlockchainPactus = 56, TWBlockchainKomodo = 57, + TWBlockchainPolymesh = 58, // Substrate }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index da009c8f1ff..b81e22582ea 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -188,6 +188,7 @@ enum TWCoinType { TWCoinTypeZkLinkNova = 810180, TWCoinTypePactus = 21888, TWCoinTypeSonic = 10000146, + TWCoinTypePolymesh = 595, // end_of_tw_coin_type_marker_do_not_modify }; diff --git a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt index 77417a3bc42..44a3417d139 100644 --- a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt +++ b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt @@ -95,6 +95,7 @@ class CoinAddressDerivationTests { Acala -> "25GGezx3LWFQj6HZpYzoWoVzLsHojGtybef3vthC9nd19ms3" Kusama -> "G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY" Polkadot -> "13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk" + Polymesh -> "2DHK8VhBpacs9quk78AVP9TmmcG5iXi2oKtZqneSNsVXxCKw" Pivx -> "D81AqC8zKma3Cht4TbVuh4jyVVyLkZULCm" Kava -> "kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87" Cardano -> "addr1qyr8jjfnypp95eq74aqzn7ss687ehxclgj7mu6gratmg3mul2040vt35dypp042awzsjk5xm3zr3zm5qh7454uwdv08s84ray2" diff --git a/registry.json b/registry.json index c4815092bb7..e53e559ab61 100644 --- a/registry.json +++ b/registry.json @@ -4844,5 +4844,35 @@ "rpc": "https://docs.pactus.org/api/http", "documentation": "https://docs.pactus.org/" } + }, + { + "id": "polymesh", + "name": "Polymesh", + "coinId": 595, + "symbol": "POLYX", + "decimals": 6, + "blockchain": "Polymesh", + "derivation": [ + { + "path": "m/44'/595'/0'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "addressHasher": "keccak256", + "ss58Prefix": 12, + "explorer": { + "url": "https://polymesh.subscan.io/", + "txPath": "/extrinsic/", + "accountPath": "/account/", + "sampleTx": "0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04", + "sampleAccount": "2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv" + }, + "info": { + "url": "https://polymesh.network/", + "source": "https://github.com/PolymeshAssociation/Polymesh", + "rpc": "wss://rpc.polymesh.network/", + "documentation": "https://developers.polymesh.network/" + } } ] diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 9bb86fb9dd8..fa5852beabf 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1893,6 +1893,7 @@ dependencies = [ "tw_native_injective", "tw_pactus", "tw_polkadot", + "tw_polymesh", "tw_ripple", "tw_ronin", "tw_solana", @@ -2201,6 +2202,22 @@ dependencies = [ "tw_substrate", ] +[[package]] +name = "tw_polymesh" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_number", + "tw_proto", + "tw_scale", + "tw_ss58_address", + "tw_substrate", +] + [[package]] name = "tw_proto" version = "0.1.0" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 87f80d4f1f4..09ab546a720 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -16,6 +16,7 @@ members = [ "chains/tw_native_injective", "chains/tw_pactus", "chains/tw_polkadot", + "chains/tw_polymesh", "chains/tw_ripple", "chains/tw_ronin", "chains/tw_solana", diff --git a/rust/chains/tw_polkadot/src/call_encoder/mod.rs b/rust/chains/tw_polkadot/src/call_encoder/mod.rs index db5cb90c2b7..6b2548e3a38 100644 --- a/rust/chains/tw_polkadot/src/call_encoder/mod.rs +++ b/rust/chains/tw_polkadot/src/call_encoder/mod.rs @@ -1,4 +1,4 @@ -use crate::{ctx_from_tw, KUSAMA, POLKADOT, POLYMESH}; +use crate::{ctx_from_tw, KUSAMA, POLKADOT}; use tw_proto::Polkadot::Proto::{ self, mod_Balance::{BatchAssetTransfer, BatchTransfer, OneOfmessage_oneof as BalanceVariant}, @@ -19,9 +19,6 @@ use generic::*; pub mod polkadot; use polkadot::*; -pub mod polymesh; -use polymesh::*; - pub fn validate_call_index(call_index: &Option) -> EncodeResult { let index = match call_index { Some(CallIndices { @@ -56,7 +53,6 @@ impl CallEncoder { let encoder = match ctx.network { POLKADOT => PolkadotCallEncoder::new_boxed(ctx), KUSAMA => KusamaCallEncoder::new_boxed(ctx), - POLYMESH => PolymeshCallEncoder::new_boxed(ctx), _ => PolkadotCallEncoder::new_boxed(ctx), }; Ok(Self { encoder }) diff --git a/rust/chains/tw_polkadot/tests/extrinsic.rs b/rust/chains/tw_polkadot/tests/extrinsic.rs index f576982c449..1a379ff1d4d 100644 --- a/rust/chains/tw_polkadot/tests/extrinsic.rs +++ b/rust/chains/tw_polkadot/tests/extrinsic.rs @@ -5,7 +5,6 @@ use tw_encoding::hex::ToHex; use tw_number::U256; use tw_proto::Polkadot::Proto; use tw_proto::Polkadot::Proto::mod_Balance::{AssetTransfer, BatchAssetTransfer, Transfer}; -use tw_proto::Polkadot::Proto::mod_Identity::mod_AddAuthorization::{AuthData, Data}; use tw_proto::Polkadot::Proto::mod_Staking::{ Bond, BondExtra, Chill, Nominate, Rebond, Unbond, WithdrawUnbonded, }; @@ -27,37 +26,6 @@ fn custom_call_indices(module: u8, method: u8) -> Option { }) } -fn polymesh_identity_call( - call: Proto::mod_Identity::OneOfmessage_oneof, -) -> Proto::mod_SigningInput::OneOfmessage_oneof { - Proto::mod_SigningInput::OneOfmessage_oneof::polymesh_call(Proto::PolymeshCall { - message_oneof: Proto::mod_PolymeshCall::OneOfmessage_oneof::identity_call( - Proto::Identity { - message_oneof: call, - }, - ), - }) -} - -fn polymesh_add_auth_call( - add_auth: Proto::mod_Identity::AddAuthorization, -) -> Proto::mod_SigningInput::OneOfmessage_oneof { - polymesh_identity_call(Proto::mod_Identity::OneOfmessage_oneof::add_authorization( - add_auth, - )) -} - -fn polymesh_join_identity(auth_id: u64) -> Proto::mod_SigningInput::OneOfmessage_oneof<'static> { - polymesh_identity_call( - Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key( - Proto::mod_Identity::JoinIdentityAsKey { - call_indices: None, - auth_id, - }, - ), - ) -} - fn balance_call( call: Proto::mod_Balance::OneOfmessage_oneof, ) -> Proto::mod_SigningInput::OneOfmessage_oneof { @@ -75,122 +43,25 @@ fn staking_call( } #[test] -fn polymesh_encode_transfer_with_memo() { - // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x0501004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e0204014d454d4f20504144444544205749544820535041434553000000000000000000 - +fn polkadot_encode_transfer() { let input = Proto::SigningInput { - network: 12, + network: 0, multi_address: true, message_oneof: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { - to_address: "2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF".into(), + to_address: "14ixj163bkk2UEKLEXsEWosuFNuijpqEWZbX5JzN4yMHbUVD".into(), value: Cow::Owned(U256::from(1u64).to_big_endian().to_vec()), - memo: "MEMO PADDED WITH SPACES".into(), - call_indices: custom_call_indices(0x05, 0x01), - })), - ..Default::default() - }; - - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!( - encoded.to_hex(), - "0501004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e0204014d454d4f20504144444544205749544820535041434553000000000000000000" - ); -} - -#[test] -fn polymesh_encode_authorization_join_identity() { - // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000 - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { - target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), ..Default::default() - }), - ..Default::default() - }; - - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!( - encoded.to_hex(), - "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000" - ); -} - -#[test] -fn polymesh_encode_authorization_join_identity_with_zero_data() { - // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320501000100010000 - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { - target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), - data: Some(AuthData { - asset: Some(Data { - data: (&[0x00]).into(), - }), - extrinsic: Some(Data { - data: (&[0x00]).into(), - }), - portfolio: Some(Data { - data: (&[0x00]).into(), - }), - }), - ..Default::default() - }), - ..Default::default() - }; - - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!( - encoded.to_hex(), - "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320501000100010000" - ); -} - -#[test] -fn polymesh_encode_authorization_join_identity_allowing_everything() { - // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000 - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { - target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), - data: Some(AuthData { - asset: None, - extrinsic: None, - portfolio: None, - }), - ..Default::default() - }), + })), ..Default::default() }; let encoded = encode_input(&input).expect("error encoding call"); assert_eq!( encoded.to_hex(), - "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000" + "050000a4b558a0342ae6e379a7ed00d23ff505f1101646cb279844496ad608943eda0d04" ); } -#[test] -fn polymesh_encode_identity() { - // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x07040b13000000000000 - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - message_oneof: polymesh_join_identity(4875), - ..Default::default() - }; - - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!(encoded.to_hex(), "07040b13000000000000"); -} - #[test] fn statemint_encode_asset_transfer() { // tx on mainnet @@ -318,7 +189,7 @@ fn encode_staking_chill() { #[test] fn encode_staking_bond_with_controller() { let input = Proto::SigningInput { - network: 12, + network: 0, multi_address: true, message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond(Bond { controller: "13wQDQTMM6E9g5WD27e6UsWWTwHLaW763FQxnkbVaoKmsBQy".into(), @@ -332,7 +203,7 @@ fn encode_staking_bond_with_controller() { let encoded = encode_input(&input).expect("error encoding call"); assert_eq!( encoded.to_hex(), - "11000081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c914652310002" + "07000081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c914652310002" ); } diff --git a/rust/chains/tw_polymesh/Cargo.toml b/rust/chains/tw_polymesh/Cargo.toml new file mode 100644 index 00000000000..1b9e2cfefd9 --- /dev/null +++ b/rust/chains/tw_polymesh/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "tw_polymesh" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_encoding = { path = "../../tw_encoding" } +tw_hash = { path = "../../tw_hash" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_number = { path = "../../tw_number" } +tw_proto = { path = "../../tw_proto" } +tw_scale = { path = "../../tw_scale" } +tw_ss58_address = { path = "../../tw_ss58_address" } +tw_substrate = { path = "../../frameworks/tw_substrate" } diff --git a/rust/chains/tw_polkadot/src/call_encoder/polymesh.rs b/rust/chains/tw_polymesh/src/call_encoder.rs similarity index 60% rename from rust/chains/tw_polkadot/src/call_encoder/polymesh.rs rename to rust/chains/tw_polymesh/src/call_encoder.rs index dde4a047308..848e86b58f7 100644 --- a/rust/chains/tw_polkadot/src/call_encoder/polymesh.rs +++ b/rust/chains/tw_polymesh/src/call_encoder.rs @@ -1,45 +1,40 @@ use std::str::FromStr; +use crate::ctx_from_tw; +use crate::types::*; use tw_coin_entry::error::prelude::*; -use tw_hash::H256; use tw_number::U256; -use tw_proto::Polkadot::Proto::{ +use tw_proto::Polkadot::Proto::{mod_CallIndices::OneOfvariant as CallIndicesVariant, CallIndices}; +use tw_proto::Polymesh::Proto::{ + self, mod_Balance::{OneOfmessage_oneof as BalanceVariant, Transfer}, - mod_Identity::{AddAuthorization, JoinIdentityAsKey, OneOfmessage_oneof as IdentityVariant}, - mod_PolymeshCall::OneOfmessage_oneof as PolymeshVariant, + mod_Identity::{ + mod_AddAuthorization::mod_Authorization::OneOfauth_oneof as AuthVariant, AddAuthorization, + JoinIdentityAsKey, LeaveIdentityAsKey, OneOfmessage_oneof as IdentityVariant, + }, + mod_RuntimeCall::OneOfpallet_oneof as RuntimeCallVariant, mod_Staking::{ Bond, BondExtra, Chill, Nominate, OneOfmessage_oneof as StakingVariant, Rebond, Unbond, WithdrawUnbonded, }, - Balance, Identity, Staking, + mod_Utility::{BatchKind, OneOfmessage_oneof as UtilityVariant}, + Balance, Identity, Staking, Utility, }; -use tw_scale::{impl_enum_scale, impl_struct_scale, Compact, RawOwned, ToScale}; +use tw_scale::{impl_enum_scale, Compact, RawOwned, ToScale}; use tw_ss58_address::SS58Address; use tw_substrate::address::SubstrateAddress; - -use super::*; - -impl_struct_scale!( - #[derive(Clone, Debug)] - pub struct Memo(H256); -); - -impl Memo { - pub fn new(memo: &str) -> Self { - let memo = memo.as_bytes(); - let mut bytes = [0; 32]; - let len = memo.len().min(32); - bytes[0..len].copy_from_slice(&memo[0..len]); - - Self(bytes.into()) - } +use tw_substrate::*; + +fn validate_call_index(call_index: &Option) -> EncodeResult { + let index = match call_index { + Some(CallIndices { + variant: CallIndicesVariant::custom(c), + }) => Some((c.module_index, c.method_index)), + _ => None, + }; + CallIndex::from_tw(index) } -impl_struct_scale!( - #[derive(Clone, Debug)] - pub struct IdentityId(H256); -); - impl_enum_scale!( #[derive(Clone, Debug)] pub enum PolymeshBalances { @@ -89,21 +84,10 @@ impl PolymeshBalances { } } -impl_enum_scale!( - #[derive(Clone, Debug)] - pub enum Signatory { - Identity(IdentityId) = 0x00, - Account(AccountId) = 0x01, - } -); - impl_enum_scale!( #[derive(Clone, Debug)] pub enum AuthorizationData { - JoinIdentity { - // TODO: Polymesh permissions. - permissions: RawOwned, - } = 0x05, + JoinIdentity { permissions: Permissions } = 0x05, } ); @@ -113,6 +97,7 @@ impl_enum_scale!( JoinIdentity { auth_id: u64, } = 0x04, + LeaveIdentity = 0x05, AddAuthorization { target: Signatory, data: AuthorizationData, @@ -122,52 +107,43 @@ impl_enum_scale!( ); impl PolymeshIdentity { - fn encode_join_identity(join: &JoinIdentityAsKey) -> WithCallIndexResult { - let ci = validate_call_index(&join.call_indices)?; + fn encode_join_identity(msg: &JoinIdentityAsKey) -> WithCallIndexResult { + let ci = validate_call_index(&msg.call_indices)?; Ok(ci.wrap(Self::JoinIdentity { - auth_id: join.auth_id, + auth_id: msg.auth_id, })) } - fn encode_add_authorization(auth: &AddAuthorization) -> WithCallIndexResult { - let ci = validate_call_index(&auth.call_indices)?; - let target = - SS58Address::from_str(&auth.target).map_err(|_| EncodeError::InvalidAddress)?; - let mut data = Vec::new(); - if let Some(auth_data) = &auth.data { - if let Some(asset) = &auth_data.asset { - data.push(0x01); - data.extend_from_slice(&asset.data); - } else { - data.push(0x00); - } - - if let Some(extrinsic) = &auth_data.extrinsic { - data.push(0x01); - data.extend_from_slice(&extrinsic.data); - } else { - data.push(0x00); - } + fn encode_leave_identity(msg: &LeaveIdentityAsKey) -> WithCallIndexResult { + let ci = validate_call_index(&msg.call_indices)?; + Ok(ci.wrap(Self::LeaveIdentity)) + } - if let Some(portfolio) = &auth_data.portfolio { - data.push(0x01); - data.extend_from_slice(&portfolio.data); - } else { - data.push(0x00); + fn encode_add_authorization(msg: &AddAuthorization) -> WithCallIndexResult { + let ci = validate_call_index(&msg.call_indices)?; + let target = SS58Address::from_str(&msg.target).map_err(|_| EncodeError::InvalidAddress)?; + let data = if let Some(auth) = &msg.authorization { + match &auth.auth_oneof { + AuthVariant::join_identity(perms) => AuthorizationData::JoinIdentity { + permissions: perms.try_into().map_err(|_| EncodeError::InvalidValue)?, + }, + AuthVariant::None => { + return Err(EncodeError::NotSupported) + .into_tw() + .context("Unsupported Authorization"); + }, } } else { - // Mark everything as authorized (asset, extrinsic, portfolio) - data.push(0x00); - data.push(0x00); - data.push(0x00); - } + return Err(EncodeError::NotSupported) + .into_tw() + .context("Missing Authorization"); + }; + Ok(ci.wrap(Self::AddAuthorization { target: Signatory::Account(SubstrateAddress(target)), - data: AuthorizationData::JoinIdentity { - permissions: RawOwned(data), - }, - expiry: if auth.expiry > 0 { - Some(auth.expiry) + data, + expiry: if msg.expiry > 0 { + Some(msg.expiry) } else { None }, @@ -177,6 +153,7 @@ impl PolymeshIdentity { pub fn encode_call(ident: &Identity) -> WithCallIndexResult { match &ident.message_oneof { IdentityVariant::join_identity_as_key(t) => Self::encode_join_identity(t), + IdentityVariant::leave_identity_as_key(t) => Self::encode_leave_identity(t), IdentityVariant::add_authorization(a) => Self::encode_add_authorization(a), _ => Err(EncodeError::NotSupported) .into_tw() @@ -208,7 +185,7 @@ impl_enum_scale!( Chill = 0x06, Rebond { value: Compact, - } = 0x18, + } = 0x13, } ); @@ -307,54 +284,97 @@ impl PolymeshStaking { } } +impl_enum_scale!( + #[derive(Clone, Debug)] + pub enum PolymeshUtility { + Batch { calls: Vec } = 0x00, + BatchAll { calls: Vec } = 0x02, + ForceBatch { calls: Vec } = 0x04, + } +); + +impl PolymeshUtility { + pub fn encode_call(encoder: &mut CallEncoder, u: &Utility) -> WithCallIndexResult { + if encoder.batch_depth > 0 { + return Err(EncodeError::NotSupported) + .into_tw() + .context("Nested batch calls not allowed"); + } + encoder.batch_depth += 1; + match &u.message_oneof { + UtilityVariant::batch(b) => { + let ci = validate_call_index(&b.call_indices)?; + let calls = b + .calls + .iter() + .map(|call| encoder.encode_runtime_call(call)) + .collect::>>()?; + encoder.batch_depth -= 1; + let batch = match b.kind { + BatchKind::StopOnError => Self::Batch { calls }, + BatchKind::Atomic => Self::BatchAll { calls }, + BatchKind::Optimistic => Self::ForceBatch { calls }, + }; + Ok(ci.wrap(batch)) + }, + _ => Err(EncodeError::NotSupported) + .into_tw() + .context("Unsupported utility call"), + } + } +} + impl_enum_scale!( #[derive(Clone, Debug)] pub enum PolymeshCall { Balances(PolymeshBalances) = 0x05, Identity(PolymeshIdentity) = 0x07, Staking(PolymeshStaking) = 0x11, - Utility(GenericUtility) = 0x29, + Utility(PolymeshUtility) = 0x29, } ); -pub struct PolymeshCallEncoder; +pub struct CallEncoder { + pub batch_depth: u32, +} + +impl CallEncoder { + pub fn from_ctx(_ctx: &SubstrateContext) -> Self { + Self { batch_depth: 0 } + } -impl PolymeshCallEncoder { - pub fn new_boxed(_ctx: &SubstrateContext) -> Box { - Box::new(Self) + pub fn encode_input(input: &'_ Proto::SigningInput<'_>) -> EncodeResult { + let ctx = ctx_from_tw(input)?; + let mut encoder = Self::from_ctx(&ctx); + let call = input + .runtime_call + .as_ref() + .ok_or(EncodeError::InvalidValue) + .into_tw() + .context("Missing runtime call")?; + encoder.encode_runtime_call(call) } -} -impl TWPolkadotCallEncoder for PolymeshCallEncoder { - fn encode_call(&self, msg: &SigningVariant<'_>) -> EncodeResult { - let call = match msg { - SigningVariant::balance_call(b) => { - PolymeshBalances::encode_call(b)?.map(PolymeshCall::Balances) + pub fn encode_runtime_call(&mut self, call: &Proto::RuntimeCall) -> EncodeResult { + let call = match &call.pallet_oneof { + RuntimeCallVariant::balance_call(msg) => { + PolymeshBalances::encode_call(msg)?.map(PolymeshCall::Balances) }, - SigningVariant::polymesh_call(msg) => match &msg.message_oneof { - PolymeshVariant::identity_call(msg) => { - PolymeshIdentity::encode_call(msg)?.map(PolymeshCall::Identity) - }, - PolymeshVariant::None => { - return Err(EncodeError::NotSupported) - .into_tw() - .context("Polymesh call variant is None"); - }, + RuntimeCallVariant::identity_call(msg) => { + PolymeshIdentity::encode_call(msg)?.map(PolymeshCall::Identity) + }, + RuntimeCallVariant::staking_call(msg) => { + PolymeshStaking::encode_call(msg)?.map(PolymeshCall::Staking) }, - SigningVariant::staking_call(s) => { - PolymeshStaking::encode_call(s)?.map(PolymeshCall::Staking) + RuntimeCallVariant::utility_call(msg) => { + PolymeshUtility::encode_call(self, msg)?.map(PolymeshCall::Utility) }, - SigningVariant::None => { + RuntimeCallVariant::None => { return Err(EncodeError::NotSupported) .into_tw() - .context("Staking call variant is None"); + .context("Runtime call variant is None"); }, }; Ok(RawOwned(call.to_scale())) } - - fn encode_batch(&self, calls: Vec) -> EncodeResult { - let call = PolymeshCall::Utility(GenericUtility::BatchAll { calls }); - Ok(RawOwned(call.to_scale())) - } } diff --git a/rust/chains/tw_polymesh/src/entry.rs b/rust/chains/tw_polymesh/src/entry.rs new file mode 100644 index 00000000000..843a18b52b6 --- /dev/null +++ b/rust/chains/tw_polymesh/src/entry.rs @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::ctx_from_tw; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_keypair::ed25519::sha512::{KeyPair, PublicKey}; +use tw_number::U256; +use tw_proto::Polymesh::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_scale::{RawOwned, ToScale}; +use tw_ss58_address::SS58Address; +use tw_substrate::*; + +use crate::call_encoder::CallEncoder; + +pub struct PolymeshEntry; + +impl PolymeshEntry { + #[inline] + fn get_keypair_impl( + &self, + _coin: &dyn CoinContext, + input: &Proto::SigningInput<'_>, + ) -> SigningResult { + Ok(KeyPair::try_from(input.private_key.as_ref())?) + } + + #[inline] + fn build_transaction_impl( + &self, + _coin: &dyn CoinContext, + public_key: Option, + input: &Proto::SigningInput<'_>, + ) -> EncodeResult { + let ctx = ctx_from_tw(input)?; + let mut encoder = CallEncoder::from_ctx(&ctx); + let call = input + .runtime_call + .as_ref() + .ok_or(EncodeError::InvalidValue) + .into_tw() + .context("Missing runtime call")?; + let call = encoder.encode_runtime_call(call)?; + let era = match &input.era { + Some(era) => Era::mortal(era.period, era.block_number), + None => Era::immortal(), + }; + let genesis_hash = input.genesis_hash.as_ref().try_into().unwrap_or_default(); + let current_hash = input.block_hash.as_ref().try_into().unwrap_or_default(); + let tip = U256::from_big_endian_slice(&input.tip) + .map_err(|_| EncodeError::InvalidValue)? + .try_into() + .map_err(|_| EncodeError::InvalidValue)?; + + let mut builder = TransactionBuilder::new(true, call); + // Add chain extensions. + builder.extension(CheckVersion(input.spec_version)); + builder.extension(CheckVersion(input.transaction_version)); + builder.extension(CheckGenesis(genesis_hash)); + builder.extension(CheckEra { era, current_hash }); + builder.extension(CheckNonce::new(input.nonce as u32)); + builder.extension(ChargeTransactionPayment::new(tip)); + if let Some(public_key) = public_key { + let account = SubstrateAddress( + SS58Address::from_public_key(&public_key, ctx.network).map_err(|e| { + TWError::new(EncodeError::InvalidAddress).context(format!("{e:?}")) + })?, + ); + builder.set_account(account); + } + Ok(builder) + } + + #[inline] + fn signing_output_impl( + &self, + _coin: &dyn CoinContext, + result: SigningResult, + ) -> SigningResult> { + let encoded = result?.to_scale(); + Ok(Proto::SigningOutput { + encoded: encoded.into(), + ..Default::default() + }) + } + + #[inline] + fn presigning_output_impl( + &self, + _coin: &dyn CoinContext, + result: SigningResult, + ) -> SigningResult> { + let pre_image = result?.to_scale(); + Ok(CompilerProto::PreSigningOutput { + // `pre_image` is already hashed if it is larger then 256 bytes. + data_hash: pre_image.clone().into(), + data: pre_image.into(), + ..Default::default() + }) + } +} + +impl SubstrateCoinEntry for PolymeshEntry { + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + #[inline] + fn get_keypair( + &self, + coin: &dyn CoinContext, + input: &Proto::SigningInput<'_>, + ) -> SigningResult { + self.get_keypair_impl(coin, input) + } + + #[inline] + fn build_transaction( + &self, + coin: &dyn CoinContext, + public_key: Option, + input: &Self::SigningInput<'_>, + ) -> SigningResult { + self.build_transaction_impl(coin, public_key, input) + .map_err(|e| e.map_err(SigningErrorType::from)) + } + + #[inline] + fn signing_output( + &self, + coin: &dyn CoinContext, + result: SigningResult, + ) -> Self::SigningOutput { + self.signing_output_impl(coin, result) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + #[inline] + fn presigning_output( + &self, + coin: &dyn CoinContext, + result: SigningResult, + ) -> Self::PreSigningOutput { + self.presigning_output_impl(coin, result) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } +} diff --git a/rust/chains/tw_polymesh/src/lib.rs b/rust/chains/tw_polymesh/src/lib.rs new file mode 100644 index 00000000000..cc814de9bbd --- /dev/null +++ b/rust/chains/tw_polymesh/src/lib.rs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_proto::Polymesh::Proto; +use tw_ss58_address::NetworkId; +use tw_substrate::*; + +pub mod call_encoder; +pub mod entry; +pub mod types; + +pub const POLYMESH_PREFIX: u16 = 12; +pub const POLYMESH: NetworkId = NetworkId::new_unchecked(POLYMESH_PREFIX); + +pub fn ctx_from_tw(input: &'_ Proto::SigningInput<'_>) -> EncodeResult { + let network = + NetworkId::try_from(input.network as u16).map_err(|_| EncodeError::InvalidNetworkId)?; + let spec_version = input.spec_version; + + Ok(SubstrateContext { + multi_address: true, + network, + spec_version, + transaction_version: input.transaction_version, + fee_asset_id: None, + check_metadata: false, + }) +} diff --git a/rust/chains/tw_polymesh/src/types.rs b/rust/chains/tw_polymesh/src/types.rs new file mode 100644 index 00000000000..6778d08dc26 --- /dev/null +++ b/rust/chains/tw_polymesh/src/types.rs @@ -0,0 +1,312 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use tw_coin_entry::error::prelude::*; +use tw_hash::{Hash, H256}; +use tw_proto::Polkadot::Proto::RewardDestination as TWRewardDestination; +use tw_proto::Polymesh::Proto::{ + mod_SecondaryKeyPermissions::{ + AssetPermissions as TWAssetPermissions, ExtrinsicPermissions as TWExtrinsicPermissions, + PalletPermissions as TWPalletPermissions, PortfolioPermissions as TWPortfolioPermissions, + RestrictionKind as TWRestrictionKind, + }, + AssetId as TWAssetId, IdentityId as TWIdentityId, PortfolioId as TWPortfolioId, + SecondaryKeyPermissions, +}; +use tw_scale::{impl_enum_scale, impl_struct_scale, ToScale}; + +use super::*; + +impl_struct_scale!( + #[derive(Clone, Debug)] + pub struct Memo(H256); +); + +impl Memo { + pub fn new(memo: &str) -> Self { + let memo = memo.as_bytes(); + let mut bytes = [0; 32]; + let len = memo.len().min(32); + bytes[0..len].copy_from_slice(&memo[0..len]); + + Self(bytes.into()) + } +} + +pub type H128 = Hash<16>; + +impl_struct_scale!( + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] + pub struct AssetId(H128); +); + +impl TryFrom<&TWAssetId<'_>> for AssetId { + type Error = TWError; + + fn try_from(id: &TWAssetId) -> Result { + let did = H128::try_from(id.id.as_ref()) + .map_err(|_| EncodeError::InvalidValue) + .into_tw() + .context("Expected 16 byte AssetId")?; + Ok(Self(did)) + } +} + +impl_struct_scale!( + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] + pub struct IdentityId(H256); +); + +impl TryFrom<&TWIdentityId<'_>> for IdentityId { + type Error = TWError; + + fn try_from(id: &TWIdentityId) -> Result { + let did = H256::try_from(id.id.as_ref()) + .map_err(|_| EncodeError::InvalidValue) + .into_tw() + .context("Expected 32 byte IdentityId")?; + Ok(Self(did)) + } +} + +impl_enum_scale!( + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] + pub enum PortfolioKind { + #[default] + Default = 0x00, + User(u64) = 0x01, + } +); + +impl_struct_scale!( + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] + pub struct PortfolioId { + did: IdentityId, + kind: PortfolioKind, + } +); + +impl TryFrom<&TWPortfolioId<'_>> for PortfolioId { + type Error = TWError; + + fn try_from(portfolio: &TWPortfolioId) -> Result { + Ok(Self { + did: portfolio + .identity + .as_ref() + .ok_or(EncodeError::InvalidValue) + .into_tw() + .context("Missing portfolio identity")? + .try_into()?, + kind: if portfolio.default { + PortfolioKind::Default + } else { + PortfolioKind::User(portfolio.user) + }, + }) + } +} + +impl_enum_scale!( + #[derive(Clone, Debug)] + pub enum Signatory { + Identity(IdentityId) = 0x00, + Account(AccountId) = 0x01, + } +); + +impl_enum_scale!( + #[derive(Clone, Debug, Default, PartialEq, Eq)] + pub enum RestrictionKind { + #[default] + Whole = 0x00, + These = 0x01, + Except = 0x02, + } +); + +impl From for RestrictionKind { + fn from(kind: TWRestrictionKind) -> Self { + match kind { + TWRestrictionKind::Whole => Self::Whole, + TWRestrictionKind::These => Self::These, + TWRestrictionKind::Except => Self::Except, + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct AssetPermissions { + kind: RestrictionKind, + assets: BTreeSet, +} + +impl ToScale for AssetPermissions { + fn to_scale_into(&self, data: &mut Vec) { + self.kind.to_scale_into(data); + if self.kind != RestrictionKind::Whole { + self.assets.to_scale_into(data); + } + } +} + +impl TryFrom<&TWAssetPermissions<'_>> for AssetPermissions { + type Error = TWError; + + fn try_from(perms: &TWAssetPermissions) -> Result { + Ok(Self { + kind: perms.kind.into(), + assets: perms + .assets + .iter() + .map(|asset| asset.try_into()) + .collect::>>()?, + }) + } +} + +#[derive(Clone, Debug, Default)] +pub struct PortfolioPermissions { + kind: RestrictionKind, + portfolios: BTreeSet, +} + +impl ToScale for PortfolioPermissions { + fn to_scale_into(&self, data: &mut Vec) { + self.kind.to_scale_into(data); + if self.kind != RestrictionKind::Whole { + self.portfolios.to_scale_into(data); + } + } +} + +impl TryFrom<&TWPortfolioPermissions<'_>> for PortfolioPermissions { + type Error = TWError; + + fn try_from(perms: &TWPortfolioPermissions) -> Result { + Ok(Self { + kind: perms.kind.into(), + portfolios: perms + .portfolios + .iter() + .map(|portfolio| portfolio.try_into()) + .collect::>>()?, + }) + } +} + +#[derive(Clone, Debug, Default)] +pub struct PalletPermissions { + kind: RestrictionKind, + extrinsic_names: BTreeSet, +} + +impl ToScale for PalletPermissions { + fn to_scale_into(&self, data: &mut Vec) { + self.kind.to_scale_into(data); + if self.kind != RestrictionKind::Whole { + self.extrinsic_names.to_scale_into(data); + } + } +} + +impl From<&TWPalletPermissions<'_>> for PalletPermissions { + fn from(perms: &TWPalletPermissions) -> Self { + Self { + kind: perms.kind.into(), + extrinsic_names: perms + .extrinsic_names + .iter() + .map(|name| name.to_string()) + .collect(), + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct ExtrinsicPermissions { + kind: RestrictionKind, + pallets: BTreeMap, +} + +impl ToScale for ExtrinsicPermissions { + fn to_scale_into(&self, data: &mut Vec) { + self.kind.to_scale_into(data); + if self.kind != RestrictionKind::Whole { + self.pallets.to_scale_into(data); + } + } +} + +impl From<&TWExtrinsicPermissions<'_>> for ExtrinsicPermissions { + fn from(perms: &TWExtrinsicPermissions) -> Self { + Self { + kind: perms.kind.into(), + pallets: perms + .pallets + .iter() + .map(|pallet| (pallet.pallet_name.to_string(), pallet.into())) + .collect(), + } + } +} + +impl_struct_scale!( + #[derive(Clone, Debug, Default)] + pub struct Permissions { + asset: AssetPermissions, + extrinsic: ExtrinsicPermissions, + portfolio: PortfolioPermissions, + } +); + +impl TryFrom<&SecondaryKeyPermissions<'_>> for Permissions { + type Error = TWError; + + fn try_from(perms: &SecondaryKeyPermissions) -> Result { + Ok(Self { + asset: if let Some(perms) = &perms.asset { + perms.try_into()? + } else { + AssetPermissions::default() + }, + extrinsic: if let Some(perms) = &perms.extrinsic { + perms.into() + } else { + ExtrinsicPermissions::default() + }, + portfolio: if let Some(perms) = &perms.portfolio { + perms.try_into()? + } else { + PortfolioPermissions::default() + }, + }) + } +} + +impl_enum_scale!( + #[derive(Clone, Debug)] + pub enum AuthorizationData { + JoinIdentity { permissions: Permissions } = 0x05, + } +); + +impl_enum_scale!( + #[derive(Clone, Debug)] + pub enum RewardDestination { + Staked = 0x00, + Stash = 0x01, + Controller = 0x02, + Account(AccountId) = 0x03, + None = 0x04, + } +); + +impl RewardDestination { + pub fn from_tw(dest: &TWRewardDestination) -> EncodeResult { + match dest { + TWRewardDestination::STAKED => Ok(Self::Staked), + TWRewardDestination::STASH => Ok(Self::Stash), + TWRewardDestination::CONTROLLER => Ok(Self::Controller), + } + } +} diff --git a/rust/chains/tw_polymesh/tests/extrinsic.rs b/rust/chains/tw_polymesh/tests/extrinsic.rs new file mode 100644 index 00000000000..777efbedce7 --- /dev/null +++ b/rust/chains/tw_polymesh/tests/extrinsic.rs @@ -0,0 +1,732 @@ +use std::borrow::Cow; +use std::default::Default; + +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_number::U256; +use tw_proto::Polkadot::Proto::RewardDestination; +use tw_proto::Polymesh::Proto::{ + self, + mod_Balance::{OneOfmessage_oneof as BalanceVariant, Transfer}, + mod_Identity::{ + mod_AddAuthorization::{mod_Authorization::OneOfauth_oneof as AuthVariant, Authorization}, + AddAuthorization, JoinIdentityAsKey, OneOfmessage_oneof as IdentityVariant, + }, + mod_RuntimeCall::OneOfpallet_oneof as CallVariant, + mod_SecondaryKeyPermissions::{ + AssetPermissions, ExtrinsicPermissions, PalletPermissions, PortfolioPermissions, + RestrictionKind, + }, + mod_Staking::{ + Bond, BondExtra, Chill, Nominate, OneOfmessage_oneof as StakingVariant, Rebond, Unbond, + WithdrawUnbonded, + }, + mod_Utility::{Batch, BatchKind, OneOfmessage_oneof as UtilityVariant}, + AssetId, Balance, Identity, IdentityId, PortfolioId, RuntimeCall, SecondaryKeyPermissions, + Staking, Utility, +}; + +use tw_polymesh::call_encoder::CallEncoder; +use tw_substrate::EncodeError; + +fn expect_encoded(input: &Proto::SigningInput<'_>, expected_value: &str) { + let encoded = CallEncoder::encode_input(input).expect("error encoding call"); + assert_eq!(encoded.0.to_hex(), expected_value); +} + +fn polymesh_identity_call(call: IdentityVariant) -> RuntimeCall<'_> { + RuntimeCall { + pallet_oneof: CallVariant::identity_call(Identity { + message_oneof: call, + }), + } +} + +fn polymesh_add_auth_call(add_auth: AddAuthorization) -> RuntimeCall<'_> { + polymesh_identity_call(IdentityVariant::add_authorization(add_auth)) +} + +fn polymesh_join_identity(auth_id: u64) -> RuntimeCall<'static> { + polymesh_identity_call(IdentityVariant::join_identity_as_key(JoinIdentityAsKey { + call_indices: None, + auth_id, + })) +} + +fn balance_call(call: BalanceVariant) -> RuntimeCall<'_> { + RuntimeCall { + pallet_oneof: CallVariant::balance_call(Balance { + message_oneof: call, + }), + } +} + +fn staking_call(call: StakingVariant) -> RuntimeCall<'_> { + RuntimeCall { + pallet_oneof: CallVariant::staking_call(Staking { + message_oneof: call, + }), + } +} + +fn batch_calls(kind: BatchKind, calls: Vec>) -> RuntimeCall<'static> { + RuntimeCall { + pallet_oneof: CallVariant::utility_call(Utility { + message_oneof: UtilityVariant::batch(Batch { + kind, + calls, + ..Default::default() + }), + }), + } +} + +fn build_input(runtime_call: RuntimeCall<'_>) -> Proto::SigningInput<'_> { + Proto::SigningInput { + network: 12, + transaction_version: 7, + runtime_call: Some(runtime_call), + ..Default::default() + } +} + +/// Test POLYX transfer with memo. +#[test] +fn polymesh_encode_transfer_with_memo() { + // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x0501004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e0204014d454d4f20504144444544205749544820535041434553000000000000000000 + + let input = build_input(balance_call( + Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF".into(), + value: Cow::Owned(U256::from(1u64).to_big_endian().to_vec()), + // The memo field is padded with nulls. + memo: "MEMO PADDED WITH SPACES".into(), + ..Default::default() + }), + )); + + expect_encoded(&input, "0501004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e0204014d454d4f20504144444544205749544820535041434553000000000000000000"); +} + +/// Test add authorization to join identity with default permissions (`Whole` meaning all permissions). +#[test] +fn polymesh_encode_authorization_join_identity() { + // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000 + + let input = build_input(polymesh_add_auth_call(AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions::default()), + }), + ..Default::default() + })); + + expect_encoded( + &input, + "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000", + ); +} + +/// Test add authorization with expiry. +#[test] +fn polymesh_encode_authorization_with_expiry() { + // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c13205000000012a00000000000000 + + let input = build_input(polymesh_add_auth_call(AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions::default()), + }), + expiry: 42, + ..Default::default() + })); + + expect_encoded( + &input, + "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c13205000000012a00000000000000" + ); +} + +/// Test add authorization to join identity with no permissions. +#[test] +fn polymesh_encode_authorization_join_identity_with_zero_data() { + // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320501000100010000 + + let input = build_input(polymesh_add_auth_call(AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { + // No asset permissions. + asset: Some(AssetPermissions { + kind: RestrictionKind::These, + assets: vec![], + }), + // No extrinsic permissions. + extrinsic: Some(ExtrinsicPermissions { + kind: RestrictionKind::These, + pallets: vec![], + }), + // No portfolio permissions. + portfolio: Some(PortfolioPermissions { + kind: RestrictionKind::These, + portfolios: vec![], + }), + }), + }), + ..Default::default() + })); + + expect_encoded( + &input, + "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320501000100010000", + ); +} + +/// Test add authorization to join identity with custom permissions +#[test] +fn polymesh_encode_authorization_join_identity_with_custom_permissions() { + // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c132050104cadc557ef87f4410b6a4bf446901930e010414417373657401045872656769737465725f756e697175655f7469636b65720108010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001010000000000000000 + + // asset + let asset = "0xcadc557ef87f4410b6a4bf446901930e" + .decode_hex() + .expect("valid asset hex"); + // Identity + let did = "0x0100000000000000000000000000000000000000000000000000000000000000" + .decode_hex() + .expect("valid did hex"); + + let input = build_input(polymesh_add_auth_call(AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { + // Allow one asset permission. + asset: Some(AssetPermissions { + kind: RestrictionKind::These, + assets: vec![AssetId { id: asset.into() }], + }), + // Allow one extrinsic permissions. + extrinsic: Some(ExtrinsicPermissions { + kind: RestrictionKind::These, + pallets: vec![PalletPermissions { + pallet_name: "Asset".into(), + kind: RestrictionKind::These, + extrinsic_names: vec!["register_unique_ticker".into()], + }], + }), + // Allow some portfolios permissions. + portfolio: Some(PortfolioPermissions { + kind: RestrictionKind::These, + portfolios: vec![ + // Default portfolio. + PortfolioId { + identity: Some(IdentityId { + id: did.clone().into(), + }), + default: true, + user: 0, + }, + // User portfolio 1. + PortfolioId { + identity: Some(IdentityId { id: did.into() }), + default: false, + user: 1, + }, + ], + }), + }), + }), + ..Default::default() + })); + + expect_encoded( + &input, + "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c132050104cadc557ef87f4410b6a4bf446901930e010414417373657401045872656769737465725f756e697175655f7469636b65720108010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001010000000000000000" + ); +} + +/// Test add authorization to join identity with all permissions. Each permission is set to `None`, which defaults to `Whole`. +#[test] +fn polymesh_encode_authorization_join_identity_allowing_everything() { + // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000 + + let input = build_input(polymesh_add_auth_call(AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { + // All asset permissions. + asset: None, + // All extrinsic permissions. + extrinsic: None, + // All portfolio permissions. + portfolio: None, + }), + }), + ..Default::default() + })); + + expect_encoded( + &input, + "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000", + ); +} + +/// Test accepting a join identity authorization. +#[test] +fn polymesh_encode_identity_join_identity() { + // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x07040b13000000000000 + + let input = build_input(polymesh_join_identity(4875)); + + expect_encoded(&input, "07040b13000000000000"); +} + +/// Test staking nominate. +#[test] +fn encode_staking_nominate() { + let input = build_input(staking_call(StakingVariant::nominate(Nominate { + nominators: vec![ + "2DxgKKS53wsAeETAZXhmT5A1bTt7h1aV4bKdtkMDwwSzSMXm".into(), + "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), + ], + call_indices: None, + }))); + + expect_encoded(&input, "1105080042ef301451c7f596f974daec8ca1234f66809905d13e16c18e23896b0c57e53e00ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148"); +} + +/// Test staking chill. +#[test] +fn encode_staking_chill() { + let input = build_input(staking_call(StakingVariant::chill(Chill { + call_indices: None, + }))); + + expect_encoded(&input, "1106"); +} + +/// Test staking bond. +#[test] +fn encode_staking_bond() { + let input = build_input(staking_call(StakingVariant::bond(Bond { + controller: "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), + value: U256::from(808081u64).to_big_endian().to_vec().into(), + reward_destination: RewardDestination::STAKED, + call_indices: None, + }))); + + expect_encoded( + &input, + "110000ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e671484652310000", + ); +} + +/// Test staking bond extra. +#[test] +fn encode_staking_bond_extra() { + let input = build_input(staking_call(StakingVariant::bond_extra(BondExtra { + value: U256::from(808081u64).to_big_endian().to_vec().into(), + call_indices: None, + }))); + + expect_encoded(&input, "110146523100"); +} + +/// Test staking rebond. +#[test] +fn encode_staking_rebond() { + let input = build_input(staking_call(StakingVariant::rebond(Rebond { + value: U256::from(808081u64).to_big_endian().to_vec().into(), + call_indices: None, + }))); + + expect_encoded(&input, "111346523100"); +} + +/// Test staking unbond. +#[test] +fn encode_staking_unbond() { + let input = build_input(staking_call(StakingVariant::unbond(Unbond { + value: U256::from(808081u64).to_big_endian().to_vec().into(), + call_indices: None, + }))); + + expect_encoded(&input, "110246523100"); +} + +/// Test staking withdraw unbonded. +#[test] +fn encode_staking_withdraw_unbonded() { + let input = build_input(staking_call(StakingVariant::withdraw_unbonded( + WithdrawUnbonded { + slashing_spans: 84, + call_indices: None, + }, + ))); + + expect_encoded(&input, "110354000000"); +} + +/// Test atomic batching staking calls bond and nominate. +#[test] +fn encode_staking_batch_bond_and_nominate() { + let input = build_input(batch_calls( + BatchKind::Atomic, + vec![ + staking_call(StakingVariant::bond(Bond { + controller: "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), + value: U256::from(808081u64).to_big_endian().to_vec().into(), + reward_destination: RewardDestination::STAKED, + call_indices: None, + })), + staking_call(StakingVariant::nominate(Nominate { + nominators: vec![ + "2DxgKKS53wsAeETAZXhmT5A1bTt7h1aV4bKdtkMDwwSzSMXm".into(), + "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), + ], + call_indices: None, + })), + ], + )); + + expect_encoded( + &input, + "290208110000ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e6714846523100001105080042ef301451c7f596f974daec8ca1234f66809905d13e16c18e23896b0c57e53e00ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148" + ); +} + +/// Test atomic batching of staking calls chill and unbond. +#[test] +fn encode_staking_batch_chill_and_unbond() { + let input = build_input(batch_calls( + BatchKind::Atomic, + vec![ + staking_call(StakingVariant::chill(Chill { call_indices: None })), + staking_call(StakingVariant::unbond(Unbond { + value: U256::from(808081u64).to_big_endian().to_vec().into(), + call_indices: None, + })), + ], + )); + + expect_encoded(&input, "2902081106110246523100"); +} + +/// Test optimistic batch of POLYX transfers. +#[test] +fn encode_batch_transfers() { + let input = build_input(batch_calls( + BatchKind::Optimistic, + vec![ + balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF".into(), + value: Cow::Owned(U256::from(1u64).to_big_endian().to_vec()), + ..Default::default() + })), + balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys".into(), + value: Cow::Owned(U256::from(2u64).to_big_endian().to_vec()), + // The memo field is padded with nulls. + memo: "MEMO PADDED WITH SPACES".into(), + ..Default::default() + })), + ], + )); + + expect_encoded( + &input, + "2904080500004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e02040501004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c08014d454d4f20504144444544205749544820535041434553000000000000000000", + ); +} + +/// Test stop on first error batch of POLYX transfers. +#[test] +fn encode_batch_transfers_stop_on_first_error() { + let input = build_input(batch_calls( + BatchKind::StopOnError, + vec![ + balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF".into(), + value: Cow::Owned(U256::from(1u64).to_big_endian().to_vec()), + ..Default::default() + })), + balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys".into(), + value: Cow::Owned(U256::from(2u64).to_big_endian().to_vec()), + // The memo field is padded with nulls. + memo: "MEMO PADDED WITH SPACES".into(), + ..Default::default() + })), + ], + )); + + expect_encoded( + &input, + "2900080500004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e02040501004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c08014d454d4f20504144444544205749544820535041434553000000000000000000", + ); +} + +/// Test that nesting of batch calls is not allowed. +#[test] +fn encode_nested_batch_calls() { + let input = build_input(batch_calls( + BatchKind::Atomic, + vec![ + balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF".into(), + value: Cow::Owned(U256::from(1u64).to_big_endian().to_vec()), + ..Default::default() + })), + batch_calls( + BatchKind::Atomic, + vec![balance_call( + Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys".into(), + value: Cow::Owned(U256::from(2u64).to_big_endian().to_vec()), + // The memo field is padded with nulls. + memo: "MEMO PADDED WITH SPACES".into(), + ..Default::default() + }), + )], + ), + ], + )); + + let tw_err = + CallEncoder::encode_input(&input).expect_err("nested batch calls should not be allowed"); + assert_eq!(tw_err.error_type(), &EncodeError::NotSupported); + // Ensure the error message contains the expected context. + let context = format!("{:?}", tw_err); + assert!(context.contains("Nested batch calls not allowed")); +} + +fn expect_encode_err(call: RuntimeCall<'_>, err: EncodeError) { + let input = build_input(call); + let res = CallEncoder::encode_input(&input).expect_err("The call should not be supported"); + assert_eq!(res.error_type(), &err); +} + +/// Test "Unsupported X call" errors. +#[test] +fn unsupported_calls() { + // Invalid runtime call. + expect_encode_err( + RuntimeCall { + pallet_oneof: CallVariant::None, + }, + EncodeError::NotSupported, + ); + + // Invalid balance call. + expect_encode_err( + balance_call(Proto::mod_Balance::OneOfmessage_oneof::None), + EncodeError::NotSupported, + ); + + // Invalid staking call. + expect_encode_err( + staking_call(Proto::mod_Staking::OneOfmessage_oneof::None), + EncodeError::NotSupported, + ); + + // Invalid Utility call. + expect_encode_err( + RuntimeCall { + pallet_oneof: CallVariant::utility_call(Utility { + message_oneof: UtilityVariant::None, + }), + }, + EncodeError::NotSupported, + ); + + // Invalid Identity call. + expect_encode_err( + polymesh_identity_call(Proto::mod_Identity::OneOfmessage_oneof::None), + EncodeError::NotSupported, + ); + + // Invalid Polymesh add authorization type. + expect_encode_err( + polymesh_add_auth_call(AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::None, + }), + ..Default::default() + }), + EncodeError::NotSupported, + ); + + // Invalid Polymesh add authorization (no authorization) + expect_encode_err( + polymesh_add_auth_call(AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + authorization: None, + ..Default::default() + }), + EncodeError::NotSupported, + ); + + // Invalid asset id in permissions. + expect_encode_err( + polymesh_add_auth_call(AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { + // One invalid asset permission. + asset: Some(AssetPermissions { + kind: RestrictionKind::Except, + assets: vec![AssetId { + id: vec![0u8].into(), + }], + }), + ..Default::default() + }), + }), + ..Default::default() + }), + EncodeError::InvalidValue, + ); + + // Invalid identity id in portfolio permissions. + expect_encode_err( + polymesh_add_auth_call(AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { + // One invalid portfolio permission. + portfolio: Some(PortfolioPermissions { + kind: RestrictionKind::Except, + portfolios: vec![PortfolioId { + identity: Some(IdentityId { id: vec![0].into() }), + default: true, + user: 0, + }], + }), + ..Default::default() + }), + }), + ..Default::default() + }), + EncodeError::InvalidValue, + ); +} + +/// Test invalid address errors. +#[test] +fn invalid_address() { + // Invalid account in POLYX transfer + expect_encode_err( + balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "BAD".into(), + value: Cow::Owned(U256::from(1u64).to_big_endian().to_vec()), + ..Default::default() + })), + EncodeError::InvalidAddress, + ); + + // Invalid Polymesh add authorization target. + expect_encode_err( + polymesh_add_auth_call(AddAuthorization { + target: "BAD".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions::default()), + }), + ..Default::default() + }), + EncodeError::InvalidAddress, + ); + + // Invalid controller address in bond. + expect_encode_err( + staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond(Bond { + controller: "BAD".into(), + value: U256::from(808081u64).to_big_endian().to_vec().into(), + reward_destination: RewardDestination::STAKED, + call_indices: None, + })), + EncodeError::InvalidAddress, + ); + + // Invalid address in nomination. + expect_encode_err( + staking_call(Proto::mod_Staking::OneOfmessage_oneof::nominate(Nominate { + nominators: vec!["BAD".into()], + call_indices: None, + })), + EncodeError::InvalidAddress, + ); +} + +fn test_invalid_value(value: Vec) { + // Invalid balance in POLYX transfer. + expect_encode_err( + balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF".into(), + // value is too long. + value: value.clone().into(), + ..Default::default() + })), + EncodeError::InvalidValue, + ); + + // Invalid bond amount. + expect_encode_err( + staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond(Bond { + controller: "2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys".into(), + value: value.clone().into(), + reward_destination: RewardDestination::STAKED, + call_indices: None, + })), + EncodeError::InvalidValue, + ); + + // Invalid bond extra amount. + expect_encode_err( + staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond_extra( + BondExtra { + value: value.clone().into(), + call_indices: None, + }, + )), + EncodeError::InvalidValue, + ); + + // Invalid unbond amount. + expect_encode_err( + staking_call(Proto::mod_Staking::OneOfmessage_oneof::unbond(Unbond { + value: value.clone().into(), + call_indices: None, + })), + EncodeError::InvalidValue, + ); + + // Invalid rebond amount. + expect_encode_err( + staking_call(Proto::mod_Staking::OneOfmessage_oneof::rebond(Rebond { + value: value.clone().into(), + call_indices: None, + })), + EncodeError::InvalidValue, + ); +} + +/// Test invalid value errors. +#[test] +fn invalid_value() { + // Test with value is not a valid `U256`. + test_invalid_value(vec![0u8; 33]); + + // Invalid balance in POLYX transfer (value is too larger for `u128`) + test_invalid_value(U256::MAX.to_big_endian().to_vec()); +} + +/// Test invalid network id. +#[test] +fn invalid_network_id() { + let input = Proto::SigningInput { + network: 0xFFFF, + ..Default::default() + }; + let res = CallEncoder::encode_input(&input).expect_err("The call should not be supported"); + assert_eq!(res.error_type(), &EncodeError::InvalidNetworkId); +} diff --git a/rust/frameworks/tw_substrate/src/extrinsic.rs b/rust/frameworks/tw_substrate/src/extrinsic.rs index 93bc9625232..579c34a040d 100644 --- a/rust/frameworks/tw_substrate/src/extrinsic.rs +++ b/rust/frameworks/tw_substrate/src/extrinsic.rs @@ -67,11 +67,6 @@ impl CallIndex { Self::from_tw(call_index) } - /// Returns true if the CallIndex contains valid indices. - pub fn has_call_index(&self) -> bool { - self.0.is_some() - } - /// Wraps a SCALE-encodable value with this CallIndex. pub fn wrap(self, value: T) -> WithCallIndex { WithCallIndex { diff --git a/rust/frameworks/tw_substrate/src/lib.rs b/rust/frameworks/tw_substrate/src/lib.rs index 30e1eae4b22..c44adb652d4 100644 --- a/rust/frameworks/tw_substrate/src/lib.rs +++ b/rust/frameworks/tw_substrate/src/lib.rs @@ -58,11 +58,4 @@ impl SubstrateContext { pub fn multi_address(&self, account: AccountId) -> MultiAddress { MultiAddress::new(account, self.multi_address) } - - pub fn multi_addresses(&self, accounts: Vec) -> Vec { - accounts - .into_iter() - .map(|account| MultiAddress::new(account, self.multi_address)) - .collect() - } } diff --git a/rust/tw_coin_registry/Cargo.toml b/rust/tw_coin_registry/Cargo.toml index 256b14691eb..d3617a4c475 100644 --- a/rust/tw_coin_registry/Cargo.toml +++ b/rust/tw_coin_registry/Cargo.toml @@ -30,6 +30,7 @@ tw_native_evmos = { path = "../chains/tw_native_evmos" } tw_native_injective = { path = "../chains/tw_native_injective" } tw_pactus = { path = "../chains/tw_pactus" } tw_polkadot = { path = "../chains/tw_polkadot" } +tw_polymesh = { path = "../chains/tw_polymesh" } tw_ripple = { path = "../chains/tw_ripple" } tw_ronin = { path = "../chains/tw_ronin" } tw_solana = { path = "../chains/tw_solana" } diff --git a/rust/tw_coin_registry/src/blockchain_type.rs b/rust/tw_coin_registry/src/blockchain_type.rs index b08b00616f3..146743ec386 100644 --- a/rust/tw_coin_registry/src/blockchain_type.rs +++ b/rust/tw_coin_registry/src/blockchain_type.rs @@ -25,6 +25,7 @@ pub enum BlockchainType { NativeInjective, Pactus, Polkadot, + Polymesh, Ripple, Ronin, Solana, diff --git a/rust/tw_coin_registry/src/dispatcher.rs b/rust/tw_coin_registry/src/dispatcher.rs index 82457ef6125..419be6b9eb2 100644 --- a/rust/tw_coin_registry/src/dispatcher.rs +++ b/rust/tw_coin_registry/src/dispatcher.rs @@ -24,6 +24,7 @@ use tw_native_evmos::entry::NativeEvmosEntry; use tw_native_injective::entry::NativeInjectiveEntry; use tw_pactus::entry::PactusEntry; use tw_polkadot::entry::PolkadotEntry; +use tw_polymesh::entry::PolymeshEntry; use tw_ripple::entry::RippleEntry; use tw_ronin::entry::RoninEntry; use tw_solana::entry::SolanaEntry; @@ -52,6 +53,7 @@ const NATIVE_EVMOS: NativeEvmosEntry = NativeEvmosEntry; const NATIVE_INJECTIVE: NativeInjectiveEntry = NativeInjectiveEntry; const PACTUS: PactusEntry = PactusEntry; const POLKADOT: SubstrateEntry = SubstrateEntry(PolkadotEntry); +const POLYMESH: SubstrateEntry = SubstrateEntry(PolymeshEntry); const RIPPLE: RippleEntry = RippleEntry; const RONIN: RoninEntry = RoninEntry; const SOLANA: SolanaEntry = SolanaEntry; @@ -80,6 +82,7 @@ pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult Ok(&NATIVE_INJECTIVE), BlockchainType::Pactus => Ok(&PACTUS), BlockchainType::Polkadot => Ok(&POLKADOT), + BlockchainType::Polymesh => Ok(&POLYMESH), BlockchainType::Ripple => Ok(&RIPPLE), BlockchainType::Ronin => Ok(&RONIN), BlockchainType::Solana => Ok(&SOLANA), diff --git a/rust/tw_scale/src/lib.rs b/rust/tw_scale/src/lib.rs index 2da23537483..dec8344929b 100644 --- a/rust/tw_scale/src/lib.rs +++ b/rust/tw_scale/src/lib.rs @@ -128,6 +128,13 @@ where } } +impl ToScale for String { + fn to_scale_into(&self, out: &mut Vec) { + self.as_bytes().to_scale_into(out) + } +} + +/// RawOwned is used to wrap data that is already encoded in SCALE format. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct RawOwned(pub Vec); @@ -143,6 +150,35 @@ impl ToScale for RawOwned { } } +// Implement ToScale for BTreeSet collection. +impl ToScale for std::collections::BTreeSet +where + T: ToScale, +{ + fn to_scale_into(&self, out: &mut Vec) { + Compact(self.len()).to_scale_into(out); + for ts in self.iter() { + ts.to_scale_into(out); + } + } +} + +// Implement ToScale for BTreeMap collection. +impl ToScale for std::collections::BTreeMap +where + K: ToScale, + V: ToScale, +{ + fn to_scale_into(&self, out: &mut Vec) { + Compact(self.len()).to_scale_into(out); + for (k, v) in self.iter() { + k.to_scale_into(out); + v.to_scale_into(out); + } + } +} + +// Implement ToScale for Vec collection. impl ToScale for Vec where T: ToScale, @@ -152,6 +188,7 @@ where } } +// Implement ToScale for references to types that implement ToScale. impl ToScale for &T { fn to_scale_into(&self, out: &mut Vec) { (*self).to_scale_into(out) @@ -395,4 +432,45 @@ mod tests { &[0x18, 0x04, 0x00, 0x08, 0x00, 0x0f, 0x00, 0x10, 0x00, 0x17, 0x00, 0x2a, 0x00], ); } + + // Test SCALE encoding of String + #[test] + fn test_string() { + assert_eq!("".to_string().to_scale(), &[0x00]); + assert_eq!( + "hello".to_string().to_scale(), + &[0x14, 0x68, 0x65, 0x6c, 0x6c, 0x6f] + ); + assert_eq!( + "hello world".to_string().to_scale(), + &[0x2c, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64] + ); + } + + // Test SCALE encoding of BTreeSet + #[test] + fn test_btree_set() { + use std::collections::BTreeSet; + let mut set = BTreeSet::new(); + set.insert(10u8); + set.insert(30u8); + set.insert(20u8); + // The values are encoded in sorted order. + assert_eq!(set.to_scale(), &[0x0c, 10, 20, 30]); + } + + // Test SCALE encoding of BTreeMap + #[test] + fn test_btree_map() { + use std::collections::BTreeMap; + let mut map = BTreeMap::new(); + map.insert(30u8, 300u16); + map.insert(10u8, 100u16); + map.insert(20u8, 200u16); + // The keys/value pairs are encoded in sorted order (by key order). + assert_eq!( + map.to_scale(), + &[0x0c, 10, 0x64, 0x00, 20, 0xc8, 0x00, 30, 0x2c, 0x01] + ); + } } diff --git a/rust/tw_scale/src/macros.rs b/rust/tw_scale/src/macros.rs index 3e5e345ba8d..3340790eda1 100644 --- a/rust/tw_scale/src/macros.rs +++ b/rust/tw_scale/src/macros.rs @@ -117,7 +117,7 @@ macro_rules! impl_enum_scale { $( $variant_field_name : $variant_field_ty ),+ - })? = $variant_index, + })?, )* } @@ -200,6 +200,8 @@ mod tests { Variant10 = 10, /// Struct variant. Struct { id: u8, id2: u8 } = 11, + /// Struct variant v2. Variants can use the same index. This allows for backwards compatibility. + StructV2 { id: u8, id2: u16 } = 11, } ); @@ -212,5 +214,9 @@ mod tests { TestEnum::Struct { id: 1, id2: 2 }.to_scale(), &[0x0B, 0x01, 0x02] ); + assert_eq!( + TestEnum::StructV2 { id: 1, id2: 2 }.to_scale(), + &[0x0B, 0x01, 0x02, 0x00] + ); } } diff --git a/rust/tw_tests/tests/chains/polkadot/mod.rs b/rust/tw_tests/tests/chains/polkadot/mod.rs index 7dd7c4a1b8a..5b4b2509331 100644 --- a/rust/tw_tests/tests/chains/polkadot/mod.rs +++ b/rust/tw_tests/tests/chains/polkadot/mod.rs @@ -18,8 +18,6 @@ mod polkadot_sign; mod polkadot_transaction_util; const GENESIS_HASH: &str = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"; -const POLYMESH_GENESIS_HASH: &str = - "6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"; const PRIVATE_KEY: &str = "abf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115"; const PRIVATE_KEY_IOS: &str = "37932b086586a6675e66e562fe68bd3eeea4177d066619c602fe3efc290ada62"; const PRIVATE_KEY_2: &str = "70a794d4f1019c3ce002f33062f45029c4f930a56b3d20ec477f7668c6bbc37f"; @@ -95,7 +93,7 @@ pub fn helper_encode_and_compile( (preimage, signed) } -pub fn balance_call( +fn balance_call( call: Proto::mod_Balance::OneOfmessage_oneof, ) -> Proto::mod_SigningInput::OneOfmessage_oneof { Proto::mod_SigningInput::OneOfmessage_oneof::balance_call(Proto::Balance { @@ -110,15 +108,3 @@ pub fn staking_call( message_oneof: call, }) } - -pub fn polymesh_call( - call: Proto::mod_Identity::OneOfmessage_oneof, -) -> Proto::mod_SigningInput::OneOfmessage_oneof { - Proto::mod_SigningInput::OneOfmessage_oneof::polymesh_call(Proto::PolymeshCall { - message_oneof: Proto::mod_PolymeshCall::OneOfmessage_oneof::identity_call( - Proto::Identity { - message_oneof: call, - }, - ), - }) -} diff --git a/rust/tw_tests/tests/chains/polkadot/polkadot_sign.rs b/rust/tw_tests/tests/chains/polkadot/polkadot_sign.rs index 98b8631c1d0..d12a3ac2a5a 100644 --- a/rust/tw_tests/tests/chains/polkadot/polkadot_sign.rs +++ b/rust/tw_tests/tests/chains/polkadot/polkadot_sign.rs @@ -3,9 +3,8 @@ // Copyright © 2017 Trust Wallet. use crate::chains::polkadot::{ - balance_call, helper_encode, helper_encode_and_maybe_sign, helper_sign, polymesh_call, - staking_call, ACCOUNT_2, GENESIS_HASH, POLYMESH_GENESIS_HASH, PRIVATE_KEY, PRIVATE_KEY_2, - PRIVATE_KEY_IOS, PRIVATE_KEY_POLKADOT, + balance_call, helper_encode, helper_encode_and_maybe_sign, helper_sign, staking_call, + ACCOUNT_2, GENESIS_HASH, PRIVATE_KEY, PRIVATE_KEY_2, PRIVATE_KEY_IOS, PRIVATE_KEY_POLKADOT, }; use std::borrow::Cow; use tw_any_coin::any_address::AnyAddress; @@ -427,148 +426,6 @@ fn test_polkadot_sign_chill_and_unbond() { ); } -// TEST(TWAnySignerPolkadot, PolymeshEncodeAndSign) -#[test] -fn test_polymesh_encode_and_sign() { - // tx on mainnet - // https://polymesh.subscan.io/extrinsic/0x9a4283cc38f7e769c53ad2d1c5cf292fc85a740ec1c1aa80c180847e51928650 - - let block_hash = "898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102" - .decode_hex() - .unwrap(); - let genesis_hash = POLYMESH_GENESIS_HASH.decode_hex().unwrap(); - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - nonce: 1, - block_hash: block_hash.into(), - genesis_hash: genesis_hash.into(), - spec_version: 3010, - transaction_version: 2, - era: Some(Proto::Era { - block_number: 4298130, - period: 64, - }), - message_oneof: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { - to_address: "2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW".into(), - value: Cow::Owned(U256::from(1000000u64).to_big_endian().to_vec()), - // The original C++ test had the wrong memo, since it didn't space pad the memo to 32 bytes. - memo: "MEMO PADDED WITH SPACES ".into(), - call_indices: custom_call_indices(0x05, 0x01), - ..Default::default() - })), - ..Default::default() - }; - - let public_key = "4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"; - let signature = "0791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f"; - - // Compile and verify the ED25519 signature. - let (preimage, signed) = - helper_encode_and_compile(CoinType::Polkadot, input, signature, public_key, true); - - assert_eq!(preimage, "050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f2050414444454420574954482053504143455320202020202020202025010400c20b0000020000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); - // This signed tranaction is different from the original C++ test, but matches the transaction on Polymesh. - assert_eq!(signed, "bd0284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee000791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f25010400050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f20504144444544205749544820535041434553202020202020202020"); -} - -// TEST(TWAnySignerPolkadot, PolymeshEncodeBondAndNominate) -#[test] -fn test_polymesh_encode_bond_and_nominate() { - // tx on mainnet - // https://polymesh.subscan.io/extrinsic/0xd516d4cb1f5ade29e557586e370e98c141c90d87a0b7547d98c6580eb2afaeeb - - let block_hash = "ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8" - .decode_hex() - .unwrap(); - let genesis_hash = POLYMESH_GENESIS_HASH.decode_hex().unwrap(); - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - nonce: 0, - block_hash: block_hash.into(), - genesis_hash: genesis_hash.into(), - spec_version: 6003050, - transaction_version: 4, - era: Some(Proto::Era { - block_number: 15742961, - period: 64, - }), - message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond_and_nominate( - Proto::mod_Staking::BondAndNominate { - controller: "2EYbDVDVWiFbXZWJgqGDJsiH5MfNeLr5fxqH3tX84LQZaETG".into(), - value: Cow::Owned(U256::from(4000000u64).to_big_endian().to_vec()), // 4.0 POLYX - reward_destination: Proto::RewardDestination::STAKED.into(), - nominators: vec!["2Gw8mSc4CUMxXMKEDqEsumQEXE5yTF8ACq2KdHGuigyXkwtz".into()], - call_indices: custom_call_indices(0x29, 0x02), - bond_call_indices: custom_call_indices(0x11, 0x00), - nominate_call_indices: custom_call_indices(0x11, 0x05), - ..Default::default() - }, - )), - ..Default::default() - }; - - let preimage = helper_encode(CoinType::Polkadot, &input); - - assert_eq!(preimage, "2902081100005ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a290610224f4000011050400c6766ff780e1f506e41622f7798ec9323ab3b8bea43767d8c107e1e920581958150300006a995b00040000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8"); - - // Can't compile a transaction with an SR25519 signature. - /* - // The public key is an SR25519 key and the signature is an SR25519 signature. - let public_key = "5ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a29061"; - let signature = "685a2fd4b1bdf7775c55eb97302a0f86b0c10848fd9db3a7f6bbe912c4c2fa28bed16f6032852ec14f27f0553523dd2fc181a6dca79f19f9c7ed6cb660cf6480"; - - let (preimage, signed) = - helper_encode_and_compile(CoinType::Polkadot, input, signature, public_key, true); - assert_eq!(signed, "d90284005ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a2906101685a2fd4b1bdf7775c55eb97302a0f86b0c10848fd9db3a7f6bbe912c4c2fa28bed16f6032852ec14f27f0553523dd2fc181a6dca79f19f9c7ed6cb660cf6480150300002902081100005ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a290610224f4000011050400c6766ff780e1f506e41622f7798ec9323ab3b8bea43767d8c107e1e920581958"); - */ -} - -// TEST(TWAnySignerPolkadot, PolymeshEncodeChillAndUnbond) -#[test] -fn test_polymesh_encode_chill_and_unbond() { - // extrinsic on mainnet - // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x29020811061102027a030a - - let block_hash = "ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8" - .decode_hex() - .unwrap(); - let genesis_hash = POLYMESH_GENESIS_HASH.decode_hex().unwrap(); - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - nonce: 0, - block_hash: block_hash.into(), - genesis_hash: genesis_hash.into(), - spec_version: 6003050, - transaction_version: 4, - era: Some(Proto::Era { - block_number: 15742961, - period: 64, - }), - message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::chill_and_unbond( - Proto::mod_Staking::ChillAndUnbond { - value: Cow::Owned(U256::from(42000000u64).to_big_endian().to_vec()), // 42.0 POLYX - call_indices: custom_call_indices(0x29, 0x02), - chill_call_indices: custom_call_indices(0x11, 0x06), - unbond_call_indices: custom_call_indices(0x11, 0x02), - ..Default::default() - }, - )), - ..Default::default() - }; - - let preimage = helper_encode(CoinType::Polkadot, &input); - - assert_eq!( - preimage, - "29020811061102027a030a150300006a995b00040000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8"); -} - // TEST(TWAnySignerPolkadot, Statemint_encodeTransaction_transfer) #[test] fn test_statemint_encode_transaction_transfer() { @@ -802,97 +659,6 @@ fn test_statemint_encode_transaction_usdt_transfer_keep_alive() { assert_eq!(signed, "5102840081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c9100d22583408806c005a24caf16f2084691f4c6dcb6015e6645adc86fc1474369b0e0b7dbcc0ef25b17eae43844aff6fb42a0b279a19e822c76043cac015be5e40a00200001c00700003206011f0050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a0600"); } -// TEST(TWAnySignerPolkadot, encodeTransaction_Add_authorization) -#[test] -fn test_encode_transaction_add_authorization() { - // tx on mainnet - // https://polymesh.subscan.io/extrinsic/0x7d9b9109027b36b72d37ba0648cb70e5254524d3d6752cc6b41601f4bdfb1af0 - - let block_hash = "ce0c2109db498e45abf8fd447580dcfa7b7a07ffc2bfb1a0fbdd1af3e8816d2b" - .decode_hex() - .unwrap(); - let genesis_hash = POLYMESH_GENESIS_HASH.decode_hex().unwrap(); - - // Set empty "These". - let empty = Proto::mod_Identity::mod_AddAuthorization::Data { - data: vec![0x00u8].into(), - }; - let input = Proto::SigningInput { - network: 12, - multi_address: true, - nonce: 5, - block_hash: block_hash.into(), - genesis_hash: genesis_hash.into(), - spec_version: 3010, - transaction_version: 2, - era: Some(Proto::Era { - block_number: 4395451, - period: 64, - }), - message_oneof: polymesh_call(Proto::mod_Identity::OneOfmessage_oneof::add_authorization( - Proto::mod_Identity::AddAuthorization { - target: "2HEVN4PHYKj7B1krQ9bctAQXZxHQQkANVNCcfbdYk2gZ4cBR".into(), - data: Some(Proto::mod_Identity::mod_AddAuthorization::AuthData { - asset: Some(empty.clone()), - extrinsic: Some(empty.clone()), - portfolio: Some(empty.clone()), - }), - call_indices: custom_call_indices(0x07, 0x0d), - ..Default::default() - }, - )), - ..Default::default() - }; - - let public_key = "4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"; - let signature = "81e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01"; - let (_preimage, signed) = - helper_encode_and_compile(CoinType::Polkadot, input, signature, public_key, true); - assert_eq!(signed, "490284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee0081e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01b5031400070d01d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d0610540501000100010000"); -} - -// TEST(TWAnySignerPolkadot, encodeTransaction_JoinIdentityAsKey) -#[test] -fn test_encode_transaction_join_identity_as_key() { - // tx on mainnet - // https://polymesh.subscan.io/extrinsic/0x9d7297d8b38af5668861996cb115f321ed681989e87024fda64eae748c2dc542 - - let block_hash = "45c80153c47f5d16acc7a66d473870e8d4574437a7d8c813f47da74cae3812c2" - .decode_hex() - .unwrap(); - let genesis_hash = POLYMESH_GENESIS_HASH.decode_hex().unwrap(); - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - nonce: 0, - block_hash: block_hash.into(), - genesis_hash: genesis_hash.into(), - spec_version: 3010, - transaction_version: 2, - era: Some(Proto::Era { - block_number: 4395527, - period: 64, - }), - message_oneof: polymesh_call( - Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key( - Proto::mod_Identity::JoinIdentityAsKey { - auth_id: 21435, - call_indices: custom_call_indices(0x07, 0x05), - ..Default::default() - }, - ), - ), - ..Default::default() - }; - - let public_key = "d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054"; - let signature = "7f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006"; - let (_preimage, signed) = - helper_encode_and_compile(CoinType::Polkadot, input, signature, public_key, true); - assert_eq!(signed, "c5018400d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054007f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006750000000705bb53000000000000"); -} - // TEST(TWAnySignerPolkadot, Kusama_SignBond_NoController) #[test] fn test_kusama_sign_bond_no_controller() { diff --git a/rust/tw_tests/tests/chains/polymesh/mod.rs b/rust/tw_tests/tests/chains/polymesh/mod.rs index 157974d25e6..3e5734b9d15 100644 --- a/rust/tw_tests/tests/chains/polymesh/mod.rs +++ b/rust/tw_tests/tests/chains/polymesh/mod.rs @@ -1 +1,156 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_any_coin::test_utils::sign_utils::AnySignerHelper; +use tw_any_coin::test_utils::sign_utils::{CompilerHelper, PreImageHelper}; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_keypair::ed25519::{sha512::PublicKey, Signature}; +use tw_keypair::traits::VerifyingKeyTrait; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Polkadot::Proto as PolkadotProto; +use tw_proto::Polymesh::Proto::{self, SigningInput}; +use tw_proto::TxCompiler::Proto::{self as CompilerProto, PreSigningOutput}; + mod polymesh_address; +mod polymesh_compile; +mod polymesh_sign; + +pub const GENESIS_HASH: &str = "0x6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"; +pub const TESTNET_GENESIS_HASH: &str = + "0x2ace05e703aa50b48c0ccccfc8b424f7aab9a1e2c424ed12e45d20b1e8ffd0d6"; +/// Private key for testing. DO NOT USE, since this is public. +pub const PRIVATE_KEY_1: &str = + "0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4"; +pub const PUBLIC_KEY_1: &str = "2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys"; +pub const PUBLIC_KEY_HEX_1: &str = + "0x4bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c"; + +pub const PUBLIC_KEY_2: &str = "2CpqFh8VnwJAjenw4xSUWCaaJ2QwGdhnCikoSEczMhjgqyj7"; + +fn custom_call_indices(module: u8, method: u8) -> Option { + Some(PolkadotProto::CallIndices { + variant: PolkadotProto::mod_CallIndices::OneOfvariant::custom( + PolkadotProto::CustomCallIndices { + module_index: module as i32, + method_index: method as i32, + }, + ), + }) +} + +fn helper_sign(coin: CoinType, input: SigningInput<'_>) -> String { + let mut signer = AnySignerHelper::::default(); + let signed_output = signer.sign(coin, input); + assert_eq!(signed_output.error, SigningError::OK); + + signed_output.encoded.to_hex() +} + +fn helper_encode(coin: CoinType, input: &SigningInput<'_>) -> String { + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(coin, input); + + assert_eq!(preimage_output.error, SigningError::OK); + preimage_output.data.to_hex() +} + +fn helper_encode_and_compile( + coin: CoinType, + input: Proto::SigningInput, + signature: &str, + public_key: &str, + ed25519: bool, +) -> (String, String) { + // Step 1: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(coin, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + let preimage = preimage_output.data.to_hex(); + + // Step 2: Compile transaction info + + // Simulate signature, normally obtained from signature server + let signature_bytes = signature.decode_hex().unwrap(); + let public_key = public_key.decode_hex().unwrap(); + + // Verify signature (pubkey & hash & signature) + if !ed25519 { + let signature = Signature::try_from(signature_bytes.as_slice()).unwrap(); + let public = PublicKey::try_from(public_key.as_slice()).unwrap(); + assert!(public.verify(signature, preimage_output.data.into())); + } + + // Compile transaction info + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile(coin, &input, vec![signature_bytes], vec![public_key]); + assert_eq!(output.error, SigningError::OK); + let signed = output.encoded.to_hex(); + + (preimage, signed) +} + +fn balance_call(call: Proto::mod_Balance::OneOfmessage_oneof) -> Proto::RuntimeCall<'_> { + Proto::RuntimeCall { + pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::balance_call(Proto::Balance { + message_oneof: call, + }), + } +} + +fn identity_call(call: Proto::mod_Identity::OneOfmessage_oneof) -> Proto::RuntimeCall<'_> { + Proto::RuntimeCall { + pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::identity_call(Proto::Identity { + message_oneof: call, + }), + } +} + +fn identity_add_auth_call( + add_auth: Proto::mod_Identity::AddAuthorization, +) -> Proto::RuntimeCall<'_> { + identity_call(Proto::mod_Identity::OneOfmessage_oneof::add_authorization( + add_auth, + )) +} + +fn identity_join_identity( + auth_id: u64, + call_indices: Option, +) -> Proto::RuntimeCall<'static> { + identity_call( + Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key( + Proto::mod_Identity::JoinIdentityAsKey { + call_indices, + auth_id, + }, + ), + ) +} + +fn staking_call(call: Proto::mod_Staking::OneOfmessage_oneof) -> Proto::RuntimeCall<'_> { + Proto::RuntimeCall { + pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::staking_call(Proto::Staking { + message_oneof: call, + }), + } +} + +fn batch_calls( + kind: Proto::mod_Utility::BatchKind, + calls: Vec>, +) -> Proto::RuntimeCall<'static> { + Proto::RuntimeCall { + pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::utility_call(Proto::Utility { + message_oneof: Proto::mod_Utility::OneOfmessage_oneof::batch( + Proto::mod_Utility::Batch { + kind, + calls, + ..Default::default() + }, + ), + }), + } +} diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_address.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_address.rs index 15957c6c549..6fc4c67fdea 100644 --- a/rust/tw_tests/tests/chains/polymesh/polymesh_address.rs +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_address.rs @@ -2,32 +2,59 @@ // // Copyright © 2017 Trust Wallet. +use crate::chains::polymesh::{PRIVATE_KEY_1, PUBLIC_KEY_1, PUBLIC_KEY_2, PUBLIC_KEY_HEX_1}; use tw_any_coin::test_utils::address_utils::{ - test_address_ss58_is_invalid, test_address_ss58_is_valid, + test_address_derive, test_address_get_data, test_address_invalid, test_address_normalization, + test_address_ss58_is_invalid, test_address_ss58_is_valid, test_address_valid, KeyType, }; use tw_coin_registry::coin_type::CoinType; +#[test] +fn test_polymesh_address_derive() { + test_address_derive( + CoinType::Polymesh, + KeyType::PrivateKey(PRIVATE_KEY_1), + PUBLIC_KEY_1, + ); +} + +#[test] +fn test_polymesh_address_normalization() { + test_address_normalization(CoinType::Polymesh, PUBLIC_KEY_1, PUBLIC_KEY_1); +} + #[test] fn test_polymesh_address_is_valid() { // Polymesh test_address_ss58_is_valid( - CoinType::Polkadot, + CoinType::Polymesh, "2DxwekgWwK7sqVeuXGmaXLZUvwnewLTs2rvU2CFKLgvvYwCG", 12, // Polymesh ss58 ); + test_address_valid(CoinType::Polymesh, PUBLIC_KEY_1); + test_address_valid(CoinType::Polymesh, PUBLIC_KEY_2); } #[test] fn test_polymesh_address_invalid() { // Substrate ed25519 test_address_ss58_is_invalid( - CoinType::Polkadot, + CoinType::Polymesh, "5FqqU2rytGPhcwQosKRtW1E3ha6BJKAjHgtcodh71dSyXhoZ", 12, // Polymesh ss58 ); test_address_ss58_is_invalid( - CoinType::Polkadot, + CoinType::Polymesh, "JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A", 12, // Polymesh ss58 ); + test_address_invalid( + CoinType::Polymesh, + "5HUUBD9nsjaKKUVB3XV87CcjcEDu7sDH2G32NAj6uNqgWp9G", + ); +} + +#[test] +fn test_polymesh_address_get_data() { + test_address_get_data(CoinType::Polymesh, PUBLIC_KEY_1, PUBLIC_KEY_HEX_1); } diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs new file mode 100644 index 00000000000..d44d8fbac30 --- /dev/null +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::chains::polymesh::{ + balance_call, identity_call, staking_call, GENESIS_HASH, PUBLIC_KEY_1, PUBLIC_KEY_2, + PUBLIC_KEY_HEX_1, TESTNET_GENESIS_HASH, +}; +use std::borrow::Cow; +use tw_any_coin::test_utils::sign_utils::{CompilerHelper, PreImageHelper}; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_keypair::ed25519::{sha512::PublicKey, Signature}; +use tw_keypair::traits::VerifyingKeyTrait; +use tw_number::U256; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Polkadot::Proto::Era; +use tw_proto::Polymesh::Proto::{ + self, + mod_Balance::Transfer, + mod_Identity::{ + mod_AddAuthorization::{mod_Authorization::OneOfauth_oneof as AuthVariant, Authorization}, + AddAuthorization, JoinIdentityAsKey, LeaveIdentityAsKey, + }, + mod_SecondaryKeyPermissions::{ + AssetPermissions, ExtrinsicPermissions, PortfolioPermissions, RestrictionKind, + }, + mod_Staking::{OneOfmessage_oneof as StakingVariant, Rebond}, + SecondaryKeyPermissions, +}; +use tw_proto::TxCompiler::Proto as CompilerProto; + +// Test compile of AddAuthorization: JoinIdentity +#[test] +fn test_polymesh_compile_add_authorization() { + // https://polymesh.subscan.io/extrinsic/16102080-1 + + // Step 1: Prepare input. + let block_hash = "b569a6fcba97252a9987f7beac2fe6dbb560b78a45e623be1e2f54fe18778512" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 11, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_000_005, + transaction_version: 7, + era: Some(Era { + block_number: 16_102_074, + period: 64, + }), + runtime_call: Some(identity_call( + Proto::mod_Identity::OneOfmessage_oneof::add_authorization(AddAuthorization { + target: PUBLIC_KEY_1.into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { + // No asset permissions. + asset: Some(AssetPermissions { + kind: RestrictionKind::These, + assets: vec![], + }), + // No extrinsic permissions. + extrinsic: Some(ExtrinsicPermissions { + kind: RestrictionKind::These, + pallets: vec![], + }), + // No portfolio permissions. + portfolio: Some(PortfolioPermissions { + kind: RestrictionKind::These, + portfolios: vec![], + }), + }), + }), + ..Default::default() + }), + )), + ..Default::default() + }; + + // Step 2: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + + assert_eq!(preimage_output.data.to_hex(), "070a014bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c0501000100010000a5032c00c5cf6a00070000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063b569a6fcba97252a9987f7beac2fe6dbb560b78a45e623be1e2f54fe18778512"); + + // SR25519 signature is not supported yet. +} + +// Test compile of JoinIdentityAsKey transaction. +#[test] +fn test_polymesh_compile_join_identity() { + // https://polymesh.subscan.io/extrinsic/16102090-1 + + // Step 1: Prepare input. + let block_hash = "cd19ce1ee3d725d5a62f29c41925d25f0655043e579231d24fb0175268b7e340" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 0, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_000_005, + transaction_version: 7, + era: Some(Era { + block_number: 16_102_087, + period: 64, + }), + runtime_call: Some(identity_call( + Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key(JoinIdentityAsKey { + auth_id: 52_188, + ..Default::default() + }), + )), + ..Default::default() + }; + + // Step 2: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + + assert_eq!(preimage_output.data.to_hex(), "0704dccb00000000000075000000c5cf6a00070000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063cd19ce1ee3d725d5a62f29c41925d25f0655043e579231d24fb0175268b7e340"); + + // Step 3: Compile transaction info + + // Simulate signature, normally obtained from signature server + let signature_bytes = "b40292db45bc8f910b580a586ff81f6c1655fc928d0bf0f41929385b26fda364985d9dee576dec47712a215bb7f70f4c926d1853533cdb693a45c65e8c017904".decode_hex().unwrap(); + let signature = Signature::try_from(signature_bytes.as_slice()).unwrap(); + let public_key = PUBLIC_KEY_HEX_1.decode_hex().unwrap(); + let public = PublicKey::try_from(public_key.as_slice()).unwrap(); + + // Verify signature (pubkey & hash & signature) + assert!(public.verify(signature, preimage_output.data.into())); + + // Compile transaction info + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile( + CoinType::Polymesh, + &input, + vec![signature_bytes], + vec![public_key], + ); + assert_eq!(output.error, SigningError::OK); + + assert_eq!( + output.encoded.to_hex(), + "c50184004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00b40292db45bc8f910b580a586ff81f6c1655fc928d0bf0f41929385b26fda364985d9dee576dec47712a215bb7f70f4c926d1853533cdb693a45c65e8c017904750000000704dccb000000000000" + ); +} + +#[test] +fn test_polymesh_compile_transfer() { + // https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04 + + // Step 1: Prepare input. + let block_hash = "77d32517dcc7b74501096afdcff3af72008a2c489e17083f56629d195e5c6a1d" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + let value = 1_000_000u64; // 1.0 POLYX + + let input = Proto::SigningInput { + network: 12, + nonce: 1, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_000_005, + transaction_version: 7, + era: Some(Era { + block_number: 16_102_106, + period: 64, + }), + runtime_call: Some(balance_call( + Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: PUBLIC_KEY_2.into(), + value: Cow::Owned(U256::from(value).to_big_endian().to_vec()), + ..Default::default() + }), + )), + ..Default::default() + }; + + // Step 2: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + + assert_eq!( + preimage_output.data.to_hex(), + "05000010b713ceeb165c1ac7c450f5b138a6da0eba50bb18849f5b8e83985daa45a87e02093d00a5010400c5cf6a00070000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f406377d32517dcc7b74501096afdcff3af72008a2c489e17083f56629d195e5c6a1d" + ); + + // Step 3: Compile transaction info + + // Simulate signature, normally obtained from signature server + let signature_bytes = "e9b4742a2b66931e0cf29f6811e4d44545b4f278a667b9eb1217c4b2de8763c8037e4501dd4a21179b737beb33415f458788f2d1093b527cae8bee8b2d55210b".decode_hex().unwrap(); + let signature = Signature::try_from(signature_bytes.as_slice()).unwrap(); + let public_key = PUBLIC_KEY_HEX_1.decode_hex().unwrap(); + let public = PublicKey::try_from(public_key.as_slice()).unwrap(); + + // Verify signature (pubkey & hash & signature) + assert!(public.verify(signature, preimage_output.data.into())); + + // Compile transaction info + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile( + CoinType::Polymesh, + &input, + vec![signature_bytes], + vec![public_key], + ); + assert_eq!(output.error, SigningError::OK); + + assert_eq!( + output.encoded.to_hex(), + "390284004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00e9b4742a2b66931e0cf29f6811e4d44545b4f278a667b9eb1217c4b2de8763c8037e4501dd4a21179b737beb33415f458788f2d1093b527cae8bee8b2d55210ba501040005000010b713ceeb165c1ac7c450f5b138a6da0eba50bb18849f5b8e83985daa45a87e02093d00" + ); +} + +// Test Leave identity transaction. +#[test] +fn test_polymesh_compile_leave_identity() { + // https://polymesh.subscan.io/extrinsic/16102113-1 + + // Step 1: Prepare input. + let block_hash = "6651325ae8f7c1726f8a610827b5e4300a504081d5fc85c17199d95bb6d9605c" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 2, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_000_005, + transaction_version: 7, + era: Some(Era { + block_number: 16_102_110, + period: 64, + }), + runtime_call: Some(identity_call( + Proto::mod_Identity::OneOfmessage_oneof::leave_identity_as_key(LeaveIdentityAsKey { + ..Default::default() + }), + )), + ..Default::default() + }; + + // Step 2: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + + assert_eq!(preimage_output.data.to_hex(), "0705e5010800c5cf6a00070000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f40636651325ae8f7c1726f8a610827b5e4300a504081d5fc85c17199d95bb6d9605c"); + + // Step 3: Compile transaction info + + // Simulate signature, normally obtained from signature server + let signature_bytes = "57232b54338939c6d9742f7a982cc668b45933bbabcb1df100f5e25ec0879eed803c04bcea28734f5e4e034f0f02aac0a8b81dcc860ddcc6b910458fc8cddb08".decode_hex().unwrap(); + let signature = Signature::try_from(signature_bytes.as_slice()).unwrap(); + let public_key = PUBLIC_KEY_HEX_1.decode_hex().unwrap(); + let public = PublicKey::try_from(public_key.as_slice()).unwrap(); + + // Verify signature (pubkey & hash & signature) + assert!(public.verify(signature, preimage_output.data.into())); + + // Compile transaction info + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile( + CoinType::Polymesh, + &input, + vec![signature_bytes], + vec![public_key], + ); + assert_eq!(output.error, SigningError::OK); + + assert_eq!( + output.encoded.to_hex(), + "a50184004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c0057232b54338939c6d9742f7a982cc668b45933bbabcb1df100f5e25ec0879eed803c04bcea28734f5e4e034f0f02aac0a8b81dcc860ddcc6b910458fc8cddb08e50108000705" + ); +} + +/// Test Staking.Rebond +#[test] +fn test_polymesh_compile_staking_rebond() { + // https://polymesh-testnet.subscan.io/extrinsic/17691406-1 + + // Step 1: Prepare the input data. + let block_hash = "7e8792533670a2063359ddd17e14923e4b37768261df38ae02d45ffef84dc6fd" + .decode_hex() + .unwrap(); + let genesis_hash = TESTNET_GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 2, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_001_000, + transaction_version: 7, + era: Some(Era { + block_number: 17_691_403, + period: 64, + }), + runtime_call: Some(staking_call(StakingVariant::rebond(Rebond { + value: Cow::Owned(U256::from(42_000000u64).to_big_endian().to_vec()), // 42 POLYX + call_indices: None, + }))), + ..Default::default() + }; + + // Step 2: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + + assert_eq!(preimage_output.data.to_hex(), "1113027a030ab5000800a8d36a00070000002ace05e703aa50b48c0ccccfc8b424f7aab9a1e2c424ed12e45d20b1e8ffd0d67e8792533670a2063359ddd17e14923e4b37768261df38ae02d45ffef84dc6fd"); + + // Step 3: Compile transaction info + + // Simulate signature, normally obtained from signature server + let signature_bytes = "9ef68cca8ed897bf10e8aa5a8dd1dbafbd78e379167f7ba294a02ff059b88f8ca9d9530fb508d29a6e48a7c55e46f67e8267b447414b2ebfaf68a1abca1a040f".decode_hex().unwrap(); + let signature = Signature::try_from(signature_bytes.as_slice()).unwrap(); + let public_key = PUBLIC_KEY_HEX_1.decode_hex().unwrap(); + let public = PublicKey::try_from(public_key.as_slice()).unwrap(); + + // Verify signature (pubkey & hash & signature) + assert!(public.verify(signature, preimage_output.data.into())); + + // Compile transaction info + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile( + CoinType::Polymesh, + &input, + vec![signature_bytes], + vec![public_key], + ); + assert_eq!(output.error, SigningError::OK); + + assert_eq!( + output.encoded.to_hex(), + "b50184004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c009ef68cca8ed897bf10e8aa5a8dd1dbafbd78e379167f7ba294a02ff059b88f8ca9d9530fb508d29a6e48a7c55e46f67e8267b447414b2ebfaf68a1abca1a040fb50008001113027a030a" + ); +} + +/// Test Immortal Era +#[test] +fn test_immortal_era_polyx_transfer() { + // https://polymesh-testnet.subscan.io/extrinsic/17760249-1 + + // Step 1: Prepare the input data. + let genesis_hash = TESTNET_GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 3, + block_hash: genesis_hash.clone().into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_001_000, + transaction_version: 7, + era: None, + runtime_call: Some(balance_call( + Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "5ECU7u4xW4UnBpndxZ3bcDXpHzuxsMujuVuKnFh174pKk3nf".into(), + value: Cow::Owned(U256::from(1_000_000u64).to_big_endian().to_vec()), + ..Default::default() + }), + )), + ..Default::default() + }; + + // Step 2: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + + assert_eq!( + preimage_output.data.to_hex(), + "0500005e642f954a7f8129e62ac03afaf094d0a0e56a4255ced9db189aa13a3649ce6c02093d00000c00a8d36a00070000002ace05e703aa50b48c0ccccfc8b424f7aab9a1e2c424ed12e45d20b1e8ffd0d62ace05e703aa50b48c0ccccfc8b424f7aab9a1e2c424ed12e45d20b1e8ffd0d6" + ); + + // Step 3: Compile transaction info + + // Simulate signature, normally obtained from signature server + let signature_bytes = "07123e08cbfb78611e1690ea84ea5ce4599598502a4a8cee9d3fb4d727192ee3b380618d05c7e10865efa2284b433856d4c814eb9c841237a9634c88d0171503".decode_hex().unwrap(); + let signature = Signature::try_from(signature_bytes.as_slice()).unwrap(); + let public_key = PUBLIC_KEY_HEX_1.decode_hex().unwrap(); + let public = PublicKey::try_from(public_key.as_slice()).unwrap(); + + // Verify signature (pubkey & hash & signature) + assert!(public.verify(signature, preimage_output.data.into())); + + // Compile transaction info + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile( + CoinType::Polymesh, + &input, + vec![signature_bytes], + vec![public_key], + ); + assert_eq!(output.error, SigningError::OK); + + assert_eq!( + output.encoded.to_hex(), + "350284004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c0007123e08cbfb78611e1690ea84ea5ce4599598502a4a8cee9d3fb4d727192ee3b380618d05c7e10865efa2284b433856d4c814eb9c841237a9634c88d0171503000c000500005e642f954a7f8129e62ac03afaf094d0a0e56a4255ced9db189aa13a3649ce6c02093d00" + ); +} + +/// Test invalid signing input. +#[test] +fn test_invalid_signing_input() { + // Step 1: Prepare the input data. + let genesis_hash = TESTNET_GENESIS_HASH.decode_hex().unwrap(); + + let mut input = Proto::SigningInput { + network: 12, + nonce: 3, + block_hash: genesis_hash.clone().into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_001_000, + transaction_version: 7, + era: None, + runtime_call: Some(balance_call( + Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "5ECU7u4xW4UnBpndxZ3bcDXpHzuxsMujuVuKnFh174pKk3nf".into(), + value: Cow::Owned(U256::from(1_000_000u64).to_big_endian().to_vec()), + ..Default::default() + }), + )), + ..Default::default() + }; + + // Test invalid U256 in `tip`, too many bytes. + { + let mut pre_imager = PreImageHelper::::default(); + input.tip = vec![0u8; 33].into(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + assert_eq!(preimage_output.error, SigningError::Error_input_parse); + } + + // Test invalid `tip`, too large for `u128`. + { + let mut pre_imager = PreImageHelper::::default(); + input.tip = U256::MAX.to_big_endian().to_vec().into(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + assert_eq!(preimage_output.error, SigningError::Error_input_parse); + } + input.tip = U256::from(0u64).to_big_endian().to_vec().into(); + + // Test invalid network id + { + let mut pre_imager = PreImageHelper::::default(); + input.network = 0xffff; + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + assert_eq!(preimage_output.error, SigningError::Error_invalid_params); + } + input.network = 12; + + // Test missing runtime call + { + let mut pre_imager = PreImageHelper::::default(); + input.runtime_call = None; + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + assert_eq!(preimage_output.error, SigningError::Error_input_parse); + } + + // Test invalid runtime call variant. + { + let mut pre_imager = PreImageHelper::::default(); + input.runtime_call = Some(Proto::RuntimeCall { + pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::None, + }); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + assert_eq!(preimage_output.error, SigningError::Error_not_supported); + } +} diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs new file mode 100644 index 00000000000..80032341cfd --- /dev/null +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::chains::polymesh::{ + balance_call, batch_calls, custom_call_indices, helper_encode, helper_encode_and_compile, + helper_sign, identity_add_auth_call, identity_call, identity_join_identity, staking_call, + GENESIS_HASH, PRIVATE_KEY_1, PUBLIC_KEY_2, +}; +use std::borrow::Cow; +use tw_any_coin::test_utils::sign_utils::AnySignerHelper; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::DecodeHex; +use tw_number::U256; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Polkadot::Proto::{Era, RewardDestination}; +use tw_proto::Polymesh::Proto::{ + self, + mod_Balance::{OneOfmessage_oneof as BalanceVariant, Transfer}, + mod_Identity::{ + mod_AddAuthorization::{mod_Authorization::OneOfauth_oneof as AuthVariant, Authorization}, + AddAuthorization, JoinIdentityAsKey, OneOfmessage_oneof as IdentityVariant, + }, + mod_SecondaryKeyPermissions::{ + AssetPermissions, ExtrinsicPermissions, PortfolioPermissions, RestrictionKind, + }, + mod_Staking::{Bond, Chill, Nominate, OneOfmessage_oneof as StakingVariant, Unbond}, + mod_Utility::BatchKind, + SecondaryKeyPermissions, +}; + +/// Test a join identity as key transaction. +#[test] +fn test_polymesh_sign_join_identity() { + // join identity + // https://polymesh.subscan.io/extrinsic/16102090-1 + + // Step 1: Prepare input. + let private_key = PRIVATE_KEY_1.decode_hex().unwrap(); + let block_hash = "cd19ce1ee3d725d5a62f29c41925d25f0655043e579231d24fb0175268b7e340" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + private_key: private_key.into(), + nonce: 0, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_000_005, + transaction_version: 7, + era: Some(Era { + block_number: 16_102_087, + period: 64, + }), + runtime_call: Some(identity_call(IdentityVariant::join_identity_as_key( + JoinIdentityAsKey { + auth_id: 52_188, + ..Default::default() + }, + ))), + ..Default::default() + }; + + let signed = helper_sign(CoinType::Polymesh, input); + + assert_eq!( + signed, + "c50184004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00b40292db45bc8f910b580a586ff81f6c1655fc928d0bf0f41929385b26fda364985d9dee576dec47712a215bb7f70f4c926d1853533cdb693a45c65e8c017904750000000704dccb000000000000" + ); +} + +/// Test invalid input when signing. +#[test] +fn test_polymesh_sign_invalid_input() { + // Step 1: Prepare input. + let private_key = PRIVATE_KEY_1.decode_hex().unwrap(); + let block_hash = "cd19ce1ee3d725d5a62f29c41925d25f0655043e579231d24fb0175268b7e340" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + private_key: private_key.into(), + nonce: 0, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_000_005, + transaction_version: 7, + runtime_call: None, + ..Default::default() + }; + + let mut signer = AnySignerHelper::::default(); + let signed_output = signer.sign(CoinType::Polymesh, input); + assert_eq!(signed_output.error, SigningError::Error_input_parse); +} + +/// Test a simple POLYX transfer. +#[test] +fn test_polymesh_sign_transfer() { + // https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04 + + // Step 1: Prepare input. + let private_key = PRIVATE_KEY_1.decode_hex().unwrap(); + let block_hash = "77d32517dcc7b74501096afdcff3af72008a2c489e17083f56629d195e5c6a1d" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + let value = 1_000_000u64; // 1.0 POLYX + + let input = Proto::SigningInput { + network: 12, + private_key: private_key.into(), + nonce: 1, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_000_005, + transaction_version: 7, + era: Some(Era { + block_number: 16_102_106, + period: 64, + }), + runtime_call: Some(balance_call(BalanceVariant::transfer(Transfer { + to_address: PUBLIC_KEY_2.into(), + value: Cow::Owned(U256::from(value).to_big_endian().to_vec()), + ..Default::default() + }))), + ..Default::default() + }; + + let signed = helper_sign(CoinType::Polymesh, input); + + assert_eq!( + signed, + "390284004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00e9b4742a2b66931e0cf29f6811e4d44545b4f278a667b9eb1217c4b2de8763c8037e4501dd4a21179b737beb33415f458788f2d1093b527cae8bee8b2d55210ba501040005000010b713ceeb165c1ac7c450f5b138a6da0eba50bb18849f5b8e83985daa45a87e02093d00" + ); +} + +// TEST(TWAnySignerPolkadot, PolymeshEncodeAndSign) +#[test] +fn test_polymesh_encode_and_sign() { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x9a4283cc38f7e769c53ad2d1c5cf292fc85a740ec1c1aa80c180847e51928650 + + let block_hash = "898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 1, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 3010, + transaction_version: 2, + era: Some(Era { + block_number: 4298130, + period: 64, + }), + runtime_call: Some(balance_call(BalanceVariant::transfer(Transfer { + to_address: "2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW".into(), + value: Cow::Owned(U256::from(1000000u64).to_big_endian().to_vec()), + // The original C++ test had the wrong memo, since it didn't space pad the memo to 32 bytes. + memo: "MEMO PADDED WITH SPACES ".into(), + ..Default::default() + }))), + ..Default::default() + }; + + let public_key = "4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"; + let signature = "0791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f"; + + // Compile and verify the ED25519 signature. + let (preimage, signed) = + helper_encode_and_compile(CoinType::Polymesh, input, signature, public_key, true); + + assert_eq!(preimage, "050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f2050414444454420574954482053504143455320202020202020202025010400c20b0000020000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); + // This signed tranaction is different from the original C++ test, but matches the transaction on Polymesh. + assert_eq!(signed, "bd0284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee000791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f25010400050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f20504144444544205749544820535041434553202020202020202020"); +} + +// TEST(TWAnySignerPolkadot, PolymeshEncodeBondAndNominate) +#[test] +fn test_polymesh_encode_bond_and_nominate() { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0xd516d4cb1f5ade29e557586e370e98c141c90d87a0b7547d98c6580eb2afaeeb + + let block_hash = "ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 0, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 6003050, + transaction_version: 4, + era: Some(Era { + block_number: 15742961, + period: 64, + }), + runtime_call: Some(batch_calls( + BatchKind::Atomic, + vec![ + staking_call(StakingVariant::bond(Bond { + controller: "2EYbDVDVWiFbXZWJgqGDJsiH5MfNeLr5fxqH3tX84LQZaETG".into(), + value: Cow::Owned(U256::from(4000000u64).to_big_endian().to_vec()), // 4.0 POLYX + reward_destination: RewardDestination::STAKED.into(), + call_indices: None, + })), + staking_call(StakingVariant::nominate(Nominate { + nominators: vec!["2Gw8mSc4CUMxXMKEDqEsumQEXE5yTF8ACq2KdHGuigyXkwtz".into()], + call_indices: None, + })), + ], + )), + ..Default::default() + }; + + let preimage = helper_encode(CoinType::Polymesh, &input); + + assert_eq!(preimage, "2902081100005ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a290610224f4000011050400c6766ff780e1f506e41622f7798ec9323ab3b8bea43767d8c107e1e920581958150300006a995b00040000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8"); + + // Can't compile a transaction with an SR25519 signature. + /* + // The public key is an SR25519 key and the signature is an SR25519 signature. + let public_key = "5ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a29061"; + let signature = "685a2fd4b1bdf7775c55eb97302a0f86b0c10848fd9db3a7f6bbe912c4c2fa28bed16f6032852ec14f27f0553523dd2fc181a6dca79f19f9c7ed6cb660cf6480"; + + let (preimage, signed) = + helper_encode_and_compile(CoinType::Polymesh, input, signature, public_key, true); + assert_eq!(signed, "d90284005ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a2906101685a2fd4b1bdf7775c55eb97302a0f86b0c10848fd9db3a7f6bbe912c4c2fa28bed16f6032852ec14f27f0553523dd2fc181a6dca79f19f9c7ed6cb660cf6480150300002902081100005ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a290610224f4000011050400c6766ff780e1f506e41622f7798ec9323ab3b8bea43767d8c107e1e920581958"); + */ +} + +// TEST(TWAnySignerPolkadot, PolymeshEncodeChillAndUnbond) +#[test] +fn test_polymesh_encode_chill_and_unbond() { + // extrinsic on mainnet + // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x29020811061102027a030a + + let block_hash = "ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 0, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 6003050, + transaction_version: 4, + era: Some(Era { + block_number: 15742961, + period: 64, + }), + runtime_call: Some(batch_calls( + BatchKind::Atomic, + vec![ + staking_call(StakingVariant::chill(Chill { call_indices: None })), + staking_call(StakingVariant::unbond(Unbond { + value: Cow::Owned(U256::from(42000000u64).to_big_endian().to_vec()), // 42.0 POLYX + call_indices: None, + })), + ], + )), + ..Default::default() + }; + + let preimage = helper_encode(CoinType::Polymesh, &input); + + assert_eq!( + preimage, + "29020811061102027a030a150300006a995b00040000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8"); +} + +// TEST(TWAnySignerPolkadot, encodeTransaction_Add_authorization) +#[test] +fn test_encode_transaction_add_authorization() { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x7d9b9109027b36b72d37ba0648cb70e5254524d3d6752cc6b41601f4bdfb1af0 + + let block_hash = "ce0c2109db498e45abf8fd447580dcfa7b7a07ffc2bfb1a0fbdd1af3e8816d2b" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 5, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 3010, + transaction_version: 2, + era: Some(Era { + block_number: 4395451, + period: 64, + }), + runtime_call: Some(identity_add_auth_call(AddAuthorization { + target: "2HEVN4PHYKj7B1krQ9bctAQXZxHQQkANVNCcfbdYk2gZ4cBR".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { + // No asset permissions. + asset: Some(AssetPermissions { + kind: RestrictionKind::These, + // Set empty "These". + assets: vec![], + }), + // No extrinsic permissions. + extrinsic: Some(ExtrinsicPermissions { + kind: RestrictionKind::These, + // Set empty "These". + pallets: vec![], + }), + // No portfolio permissions. + portfolio: Some(PortfolioPermissions { + kind: RestrictionKind::These, + // Set empty "These". + portfolios: vec![], + }), + }), + }), + // Old Polymesh v4.x call indices. + call_indices: custom_call_indices(0x07, 0x0d), + ..Default::default() + })), + ..Default::default() + }; + + let public_key = "4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"; + let signature = "81e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01"; + let (_preimage, signed) = + helper_encode_and_compile(CoinType::Polymesh, input, signature, public_key, true); + assert_eq!(signed, "490284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee0081e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01b5031400070d01d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d0610540501000100010000"); +} + +// TEST(TWAnySignerPolkadot, encodeTransaction_JoinIdentityAsKey) +#[test] +fn test_encode_transaction_join_identity_as_key() { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x9d7297d8b38af5668861996cb115f321ed681989e87024fda64eae748c2dc542 + + let block_hash = "45c80153c47f5d16acc7a66d473870e8d4574437a7d8c813f47da74cae3812c2" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 0, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 3010, + transaction_version: 2, + era: Some(Era { + block_number: 4395527, + period: 64, + }), + runtime_call: Some(identity_join_identity( + 21435, + // Old Polymesh v4.x call indices. + custom_call_indices(0x07, 0x05), + )), + ..Default::default() + }; + + let public_key = "d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054"; + let signature = "7f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006"; + let (_preimage, signed) = + helper_encode_and_compile(CoinType::Polymesh, input, signature, public_key, true); + assert_eq!(signed, "c5018400d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054007f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006750000000705bb53000000000000"); +} diff --git a/rust/tw_tests/tests/coin_address_derivation_test.rs b/rust/tw_tests/tests/coin_address_derivation_test.rs index 17808b548e1..026b260128c 100644 --- a/rust/tw_tests/tests/coin_address_derivation_test.rs +++ b/rust/tw_tests/tests/coin_address_derivation_test.rs @@ -161,6 +161,7 @@ fn test_coin_address_derivation() { CoinType::Zcash => "t1SUmK7UVqTXQDpEAqHcJWBv7pNUFHJRpDx", CoinType::Zelcash => "t1SUmK7UVqTXQDpEAqHcJWBv7pNUFHJRpDx", CoinType::Komodo => "RHtMPHweTxYNhBYUN2nJTu9QKyjm7MRKsF", + CoinType::Polymesh => "2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv", CoinType::XRP => "r9cwJ8hM13jodBBGtioB44FUZ5HwWGwqfX", CoinType::Groestlcoin => "grs1qten42eesehw0ktddcp0fws7d3ycsqez35034a2", CoinType::Decred => "DsbEmWV6ZZBsUJY2vVi5u7H62GUfBFPBfoF", diff --git a/src/Coin.cpp b/src/Coin.cpp index 615153e83cb..f0067766347 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -69,6 +69,7 @@ #include "BitcoinCash/Entry.h" #include "Pactus/Entry.h" #include "Komodo/Entry.h" +#include "Polymesh/Entry.h" // end_of_coin_includes_marker_do_not_modify using namespace TW; @@ -131,6 +132,7 @@ NativeInjective::Entry NativeInjectiveDP; BitcoinCash::Entry BitcoinCashDP; Pactus::Entry PactusDP; Komodo::Entry KomodoDP; +Polymesh::Entry PolymeshDP; // end_of_coin_dipatcher_declarations_marker_do_not_modify CoinEntry* coinDispatcher(TWCoinType coinType) { @@ -195,6 +197,7 @@ CoinEntry* coinDispatcher(TWCoinType coinType) { case TWBlockchainBitcoinCash: entry = &BitcoinCashDP; break; case TWBlockchainPactus: entry = &PactusDP; break; case TWBlockchainKomodo: entry = &KomodoDP; break; + case TWBlockchainPolymesh: entry = &PolymeshDP; break; // end_of_coin_dipatcher_switch_marker_do_not_modify default: entry = nullptr; break; diff --git a/src/Polymesh/Entry.h b/src/Polymesh/Entry.h new file mode 100644 index 00000000000..e32f339be46 --- /dev/null +++ b/src/Polymesh/Entry.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "rust/RustCoinEntry.h" + +namespace TW::Polymesh { + +/// Entry point for Polymesh coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry : public Rust::RustCoinEntry { +}; + +} // namespace TW::Polymesh + diff --git a/src/proto/Polkadot.proto b/src/proto/Polkadot.proto index 719ccdddef5..59f8aa00ba4 100644 --- a/src/proto/Polkadot.proto +++ b/src/proto/Polkadot.proto @@ -218,60 +218,6 @@ message Staking { } } -// Identity module -message Identity { - // Identity::join_identity_as_key call - message JoinIdentityAsKey { - // call indices - CallIndices call_indices = 1; - - // auth id - uint64 auth_id = 2; - } - - // Identity::add_authorization call - message AddAuthorization { - message Data { - bytes data = 1; - } - - message AuthData { - // authorization data, empty means all permissions, null means no permissions - Data asset = 1; - - // authorization data, empty means all permissions, null means no permissions - Data extrinsic = 2; - - // authorization data, empty means all permissions, null means no permissions - Data portfolio = 3; - } - - // call indices - CallIndices call_indices = 1; - - // address that will be added to the Identity - string target = 2; - - // authorization data, null means all permissions - AuthData data = 3; - - // expire time, unix seconds - uint64 expiry = 4; - } - - oneof message_oneof { - JoinIdentityAsKey join_identity_as_key = 1; - AddAuthorization add_authorization = 2; - } -} - -// Polymesh call -message PolymeshCall { - oneof message_oneof { - Identity identity_call = 2; - } -} - // Input data necessary to create a signed transaction. message SigningInput { // Recent block hash, or genesis hash if era is not set @@ -308,7 +254,6 @@ message SigningInput { oneof message_oneof { Balance balance_call = 11; Staking staking_call = 12; - PolymeshCall polymesh_call = 13; } } @@ -322,4 +267,4 @@ message SigningOutput { // error code description string error_message = 3; -} +} \ No newline at end of file diff --git a/src/proto/Polymesh.proto b/src/proto/Polymesh.proto new file mode 100644 index 00000000000..05d69d11dfe --- /dev/null +++ b/src/proto/Polymesh.proto @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.Polymesh.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; +import "Polkadot.proto"; + +// Balance transfer transaction +message Balance { + // transfer + message Transfer { + // destination address + string to_address = 1; + + // amount (uint256, serialized big endian) + bytes value = 2; + + // max 32 chars + string memo = 3; + + // call indices + Polkadot.Proto.CallIndices call_indices = 4; + } + + oneof message_oneof { + Transfer transfer = 1; + } +} + +// Staking transaction +message Staking { + // Bond to a controller + message Bond { + // controller ID (optional) + string controller = 1; + + // amount (uint256, serialized big endian) + bytes value = 2; + + // destination for rewards + Polkadot.Proto.RewardDestination reward_destination = 3; + + // call indices + Polkadot.Proto.CallIndices call_indices = 4; + } + + // Bond extra amount + message BondExtra { + // amount (uint256, serialized big endian) + bytes value = 1; + + // call indices + Polkadot.Proto.CallIndices call_indices = 2; + } + + // Unbond + message Unbond { + // amount (uint256, serialized big endian) + bytes value = 1; + + // call indices + Polkadot.Proto.CallIndices call_indices = 2; + } + + // Rebond + message Rebond { + // amount (uint256, serialized big endian) + bytes value = 1; + + // call indices + Polkadot.Proto.CallIndices call_indices = 2; + } + + // Withdraw unbonded amounts + message WithdrawUnbonded { + int32 slashing_spans = 1; + + // call indices + Polkadot.Proto.CallIndices call_indices = 2; + } + + // Nominate + message Nominate { + // list of nominators + repeated string nominators = 1; + + // call indices + Polkadot.Proto.CallIndices call_indices = 2; + } + + // Chill + message Chill { + // call indices + Polkadot.Proto.CallIndices call_indices = 1; + } + + // Payload messsage + oneof message_oneof { + Bond bond = 1; + BondExtra bond_extra = 2; + Unbond unbond = 3; + WithdrawUnbonded withdraw_unbonded = 4; + Nominate nominate = 5; + Chill chill = 6; + Rebond rebond = 7; + } +} + +message IdentityId { + // 32 byte identity id. + bytes id = 1; +} + +message AssetId { + // 16 byte asset id. + bytes id = 1; +} + +message PortfolioId { + // IdentityId of the portfolio owner. + IdentityId identity = 1; + // If `default` is true ignore the `user` field. + bool default = 2; + // The users portfolio number. (ignored if `default = true`) + uint64 user = 3; +} + +message SecondaryKeyPermissions { + enum RestrictionKind { + Whole = 0; + These = 1; + Except = 2; + } + + message AssetPermissions { + RestrictionKind kind = 1; + repeated AssetId assets = 2; + } + + message PortfolioPermissions { + RestrictionKind kind = 1; + repeated PortfolioId portfolios = 2; + } + + message PalletPermissions { + string pallet_name = 1; + RestrictionKind kind = 2; + repeated string extrinsic_names = 3; + } + + message ExtrinsicPermissions { + RestrictionKind kind = 1; + repeated PalletPermissions pallets = 2; + } + + // The assets permissions of the secondary key. + AssetPermissions asset = 1; + + // The pallet/extrinsic permissions of the secondary key. + ExtrinsicPermissions extrinsic = 2; + + // The portfolio permissions of the secondary key. + PortfolioPermissions portfolio = 3; +} + +// Identity module +message Identity { + // Identity::join_identity_as_key call + message JoinIdentityAsKey { + // call indices + Polkadot.Proto.CallIndices call_indices = 1; + + // auth id + uint64 auth_id = 2; + } + + // Identity::leave_identity_as_key call + message LeaveIdentityAsKey { + // call indices + Polkadot.Proto.CallIndices call_indices = 1; + } + + // Identity::add_authorization call + message AddAuthorization { + message Authorization { + // Authorization data. + oneof auth_oneof { + // AttestPrimaryKeyRotation(IdentityId) = 1 + // RotatePrimaryKey = 2 + // TransferTicker(Ticker) = 3 + // AddMultiSigSigner(AccountId) = 4 + // TransferAssetOwnership(AssetId) = 5 + SecondaryKeyPermissions join_identity = 6; + // PortfolioCustody(PortfolioId) = 7 + // BecomeAgent(AssetId, AgentGroup) = 8 + // AddRelayerPayingKey(AccountId, AccountId, Balance) = 9 + // RotatePrimaryKeyToSecondary(Permissions) = 10 + } + } + + // call indices + Polkadot.Proto.CallIndices call_indices = 1; + + // address that will be added to the Identity + string target = 2; + + // Authorization. + Authorization authorization = 3; + + // expire time, unix seconds + uint64 expiry = 4; + } + + oneof message_oneof { + JoinIdentityAsKey join_identity_as_key = 1; + AddAuthorization add_authorization = 2; + LeaveIdentityAsKey leave_identity_as_key = 3; + } +} + +// Utility pallet transaction +message Utility { + enum BatchKind { + // Batch multiple calls, stoping on the first error. + // + // Each call in the batch is executed in its own transaction. + // When one call fails only that transaction will be rolled back + // and any following calls in the batch will be skipped. + StopOnError = 0; + // Batch multiple calls and execute them in a single atomic transaction. + // The whole transaction will rollback if any of the calls fail. + Atomic = 1; + // Batch multiple calls. Unlike `Batch` this will continue even + // if one of the calls failed. + // + // Each call in the batch is executed in its own transaction. + // When a call fails its transaction will be rolled back and the error + // will be emitted in an event. + // + // Execution will continue until all calls in the batch have been executed. + Optimistic = 2; + } + + message Batch { + // The type of batch. + BatchKind kind = 1; + + // batched calls. + repeated RuntimeCall calls = 2; + + // call indices + Polkadot.Proto.CallIndices call_indices = 3; + } + + oneof message_oneof { + Batch batch = 1; + } +} + +// Polymesh runtime call. +message RuntimeCall { + // Top-level pallets. + oneof pallet_oneof { + Balance balance_call = 1; + Staking staking_call = 2; + Identity identity_call = 3; + Utility utility_call = 4; + } +} + +// Input data necessary to create a signed transaction. +message SigningInput { + // Recent block hash, or genesis hash if era is not set + bytes block_hash = 1; + + // Genesis block hash (identifies the chain) + bytes genesis_hash = 2; + + // Current account nonce + uint64 nonce = 3; + + // Specification version, e.g. 26. + uint32 spec_version = 4; + + // Transaction version, e.g. 5. + uint32 transaction_version = 5; + + // Optional tip to pay, big integer + bytes tip = 6; + + // Optional time validity limit, recommended, for replay-protection. Empty means Immortal. + Polkadot.Proto.Era era = 7; + + // The secret private key used for signing (32 bytes). + bytes private_key = 8; + + // Network type + uint32 network = 9; + + // Payload call + RuntimeCall runtime_call = 10; +} + +// Result containing the signed and encoded transaction. +message SigningOutput { + // Signed and encoded transaction bytes. + bytes encoded = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; +} \ No newline at end of file diff --git a/swift/Tests/Blockchains/PolymeshTests.swift b/swift/Tests/Blockchains/PolymeshTests.swift new file mode 100644 index 00000000000..bbd695266b7 --- /dev/null +++ b/swift/Tests/Blockchains/PolymeshTests.swift @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class PolymeshTests: XCTestCase { + let genesisHash = Data(hexString: "0x6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063")! + // Private key for testing. DO NOT USE, since this is public. + let testKey1 = Data(hexString: "0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4")! + + func testAddress() { + let key = PrivateKey(data: Data(hexString: "0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4")!)! + let pubkey = key.getPublicKeyEd25519() + let address = AnyAddress(publicKey: pubkey, coin: .polymesh) + let addressFromString = AnyAddress(string: "2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys", coin: .polymesh)! + + XCTAssertEqual(pubkey.data.hexString, "4bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSignTransfer() { + // https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04 + + // Step 1: Prepare input. + let blockHash = Data(hexString: "77d32517dcc7b74501096afdcff3af72008a2c489e17083f56629d195e5c6a1d")! + + let input = PolymeshSigningInput.with { + $0.genesisHash = genesisHash + $0.blockHash = blockHash + $0.nonce = 1 + $0.specVersion = 7000005 + $0.network = CoinType.polymesh.ss58Prefix + $0.transactionVersion = 7 + $0.privateKey = testKey1 + $0.era = PolkadotEra.with { + $0.blockNumber = 16102106 + $0.period = 64 + } + $0.runtimeCall = PolymeshRuntimeCall.with { + $0.balanceCall.transfer = PolymeshBalance.Transfer.with { + $0.toAddress = "2CpqFh8VnwJAjenw4xSUWCaaJ2QwGdhnCikoSEczMhjgqyj7" + $0.value = Data(hexString: "0x0F4240")! // 1.0 POLYX + } + } + } + let output: PolymeshSigningOutput = AnySigner.sign(input: input, coin: .polymesh) + + XCTAssertEqual(output.encoded.hexString, "390284004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00e9b4742a2b66931e0cf29f6811e4d44545b4f278a667b9eb1217c4b2de8763c8037e4501dd4a21179b737beb33415f458788f2d1093b527cae8bee8b2d55210ba501040005000010b713ceeb165c1ac7c450f5b138a6da0eba50bb18849f5b8e83985daa45a87e02093d00") + } +} diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 24631acb24a..fa1a04747fe 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -204,6 +204,9 @@ class CoinAddressDerivationTests: XCTestCase { case .polkadot: let expectedResult = "13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .polymesh: + let expectedResult = "2DHK8VhBpacs9quk78AVP9TmmcG5iXi2oKtZqneSNsVXxCKw" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .qtum: let expectedResult = "QhceuaTdeCZtcxmVc6yyEDEJ7Riu5gWFoF" assertCoinDerivation(coin, expectedResult, derivedAddress, address) diff --git a/tests/chains/Polkadot/TWAnyAddressTests.cpp b/tests/chains/Polkadot/TWAnyAddressTests.cpp index 5b57279f2e3..2a18ba00c41 100644 --- a/tests/chains/Polkadot/TWAnyAddressTests.cpp +++ b/tests/chains/Polkadot/TWAnyAddressTests.cpp @@ -18,7 +18,6 @@ namespace TW::Polkadot::tests { extern uint32_t polkadotPrefix; extern uint32_t kusamaPrefix; extern uint32_t astarPrefix; -extern uint32_t polymeshPrefix; extern uint32_t parallelPrefix; TEST(PolkadotAddress, Validation) { @@ -40,10 +39,6 @@ TEST(PolkadotAddress, Validation) { ASSERT_TRUE(TWAnyAddressIsValidSS58(STRING("cEYtw6AVMB27hFUs4gVukajLM7GqxwxUfJkbPY3rNToHMcCgb").get(), TWCoinTypePolkadot, 64)); ASSERT_FALSE(TWAnyAddressIsValidSS58(STRING("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A").get(), TWCoinTypePolkadot, 64)); - - // Polymesh - ASSERT_TRUE(TWAnyAddressIsValidSS58(STRING("2DxwekgWwK7sqVeuXGmaXLZUvwnewLTs2rvU2CFKLgvvYwCG").get(), TWCoinTypePolkadot, polymeshPrefix)); - ASSERT_FALSE(TWAnyAddressIsValidSS58(STRING("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A").get(), TWCoinTypePolkadot, polymeshPrefix)); } TEST(PolkadotAddress, FromPrivateKey) { @@ -85,15 +80,6 @@ TEST(PolkadotAddress, FromPublicKeyWithPrefix) { const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); EXPECT_TRUE(TWStringEqual(addressStr.get(), addressParallel.get())); } - - // polymesh - publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f83").get(), TWPublicKeyTypeED25519)); - const auto addressPolymesh = STRING("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); - { - const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58WithPublicKey(publicKey.get(), TWCoinTypePolkadot, polymeshPrefix)); - const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); - EXPECT_TRUE(TWStringEqual(addressStr.get(), addressPolymesh.get())); - } } TEST(PolkadotAddress, FromString) { @@ -117,14 +103,6 @@ TEST(PolkadotAddress, FromStringWithPrefix) { const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); EXPECT_TRUE(TWStringEqual(addressStr.get(), addressParallel.get())); } - - // polymesh - auto addressPolymesh = STRING("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); - { - const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58(addressPolymesh.get(), TWCoinTypePolkadot, polymeshPrefix)); - const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); - EXPECT_TRUE(TWStringEqual(addressStr.get(), addressPolymesh.get())); - } } } // namespace TW::Polkadot::tests diff --git a/tests/chains/Polkadot/TWAnySignerTests.cpp b/tests/chains/Polkadot/TWAnySignerTests.cpp index 8cff6ef551b..7c6328d4d98 100644 --- a/tests/chains/Polkadot/TWAnySignerTests.cpp +++ b/tests/chains/Polkadot/TWAnySignerTests.cpp @@ -24,7 +24,6 @@ namespace TW::Polkadot::tests { uint32_t polkadotPrefix = ss58Prefix(TWCoinTypePolkadot); uint32_t kusamaPrefix = ss58Prefix(TWCoinTypeKusama); uint32_t astarPrefix = 5; -uint32_t polymeshPrefix = 12; uint32_t parallelPrefix = 172; auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); diff --git a/tests/chains/Polymesh/TWAnyAddressTests.cpp b/tests/chains/Polymesh/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..1602ff3e1a0 --- /dev/null +++ b/tests/chains/Polymesh/TWAnyAddressTests.cpp @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include +#include + +#include "TestUtilities.h" +#include +#include + +using namespace TW; + +namespace TW::Polymesh::tests { +extern uint32_t polymeshPrefix; + +TEST(TWPolymesh, Address) { + auto string = STRING("2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypePolymesh)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867"); +} + +TEST(PolymeshAddress, Validation) { + // Substrate ed25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("5FqqU2rytGPhcwQosKRtW1E3ha6BJKAjHgtcodh71dSyXhoZ").get(), TWCoinTypePolymesh)); + // Bitcoin + ASSERT_FALSE(TWAnyAddressIsValid(STRING("1ES14c7qLb5CYhLMUekctxLgc1FV2Ti9DA").get(), TWCoinTypePolymesh)); + // Kusama ed25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("FHKAe66mnbk8ke8zVWE9hFVFrJN1mprFPVmD5rrevotkcDZ").get(), TWCoinTypePolymesh)); + // Kusama secp256k1 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("FxQFyTorsjVsjjMyjdgq8w5vGx8LiA1qhWbRYcFijxKKchx").get(), TWCoinTypePolymesh)); + // Kusama sr25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("EJ5UJ12GShfh7EWrcNZFLiYU79oogdtXFUuDDZzk7Wb2vCe").get(), TWCoinTypePolymesh)); + + // Polkadot ed25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu").get(), TWCoinTypePolymesh)); + // Polkadot sr25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("15AeCjMpcSt3Fwa47jJBd7JzQ395Kr2cuyF5Zp4UBf1g9ony").get(), TWCoinTypePolymesh)); + + // Polymesh + ASSERT_TRUE(TWAnyAddressIsValid(STRING("2DxwekgWwK7sqVeuXGmaXLZUvwnewLTs2rvU2CFKLgvvYwCG").get(), TWCoinTypePolymesh)); + ASSERT_FALSE(TWAnyAddressIsValid(STRING("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A").get(), TWCoinTypePolymesh)); + ASSERT_TRUE(TWAnyAddressIsValidSS58(STRING("2DxwekgWwK7sqVeuXGmaXLZUvwnewLTs2rvU2CFKLgvvYwCG").get(), TWCoinTypePolymesh, polymeshPrefix)); + ASSERT_FALSE(TWAnyAddressIsValidSS58(STRING("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A").get(), TWCoinTypePolymesh, polymeshPrefix)); +} + +TEST(PolymeshAddress, FromPrivateKey) { + // subkey phrase `chief menu kingdom stereo hope hazard into island bag trick egg route` + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("0x612d82bc053d1b4729057688ecb1ebf62745d817ddd9b595bc822f5f2ba0e41a").get())); + const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypePolymesh)); + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypePolymesh)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), STRING("2GmLy7KywpsV5fDpZfJMcgGgzoJWyrEA3Wc3fDmsoq5iqtBT").get())); +} + +TEST(PolymeshAddress, FromPublicKey) { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("0xbeff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908").get(), TWPublicKeyTypeED25519)); + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypePolymesh)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), STRING("2GmLy7KywpsV5fDpZfJMcgGgzoJWyrEA3Wc3fDmsoq5iqtBT").get())); +} + +TEST(PolymeshAddress, FromPublicKeyWithPrefix) { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f83").get(), TWPublicKeyTypeED25519)); + const auto addressPolymesh = STRING("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); + { + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58WithPublicKey(publicKey.get(), TWCoinTypePolymesh, polymeshPrefix)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), addressPolymesh.get())); + } +} + +TEST(PolymeshAddress, FromString) { + auto string = STRING("2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypePolymesh)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867"); +} + +TEST(PolymeshAddress, FromStringWithPrefix) { + // polymesh + auto addressPolymesh = STRING("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); + { + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58(addressPolymesh.get(), TWCoinTypePolymesh, polymeshPrefix)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), addressPolymesh.get())); + } +} + +} // namespace TW::Polymesh::tests \ No newline at end of file diff --git a/tests/chains/Polymesh/TWAnySignerTests.cpp b/tests/chains/Polymesh/TWAnySignerTests.cpp new file mode 100644 index 00000000000..cd4937ae4da --- /dev/null +++ b/tests/chains/Polymesh/TWAnySignerTests.cpp @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "AnyAddress.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/Polymesh.pb.h" +#include "proto/TransactionCompiler.pb.h" +#include "uint256.h" + +#include +#include +#include +#include +#include + +#include "TestUtilities.h" +#include +#include + +using namespace TW; +using namespace TW::Polymesh; + +namespace TW::Polymesh::tests { +uint32_t polymeshPrefix = 12; + +Data helper_encodeTransaction(TWCoinType coin, const Proto::SigningInput& input, const Data& pubKey, const Data& signature) { + auto txInputData = data(input.SerializeAsString()); + auto txInputDataPtr = WRAPD(TWDataCreateWithBytes(txInputData.data(), txInputData.size())); + + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, txInputDataPtr.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&pubKey)).get())); + + Polymesh::Proto::SigningOutput output; + output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get())); + EXPECT_EQ(output.error(), Common::Proto::OK); + + return data(output.encoded()); +} + +TEST(TWAnySignerPolymesh, PolymeshEncodeAndSign) { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x9a4283cc38f7e769c53ad2d1c5cf292fc85a740ec1c1aa80c180847e51928650 + + /// Step 1: Prepare transaction input (protobuf) + const auto coin = TWCoinTypePolymesh; + + Polymesh::Proto::SigningInput input; + input.set_network(12); + auto blockHash = parse_hex("898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); + auto vGenesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(1UL); + input.set_spec_version(3010u); + input.set_transaction_version(2u); + + auto* era = input.mutable_era(); + era->set_block_number(4298130UL); + era->set_period(64UL); + + auto* transfer = input.mutable_runtime_call()->mutable_balance_call()->mutable_transfer(); + transfer->set_to_address("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); + auto value = store(1000000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_memo("MEMO PADDED WITH SPACES"); + + auto* callIndices = transfer->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x05); + callIndices->set_method_index(0x01); + + /// Step 2: Obtain preimage hash + auto txInputData = data(input.SerializeAsString()); + auto txInputDataPtr = WRAPD(TWDataCreateWithBytes(txInputData.data(), txInputData.size())); + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputDataPtr.get())); + auto preImageHash = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHash.data(), int(preImageHash.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + const auto preImage = data(preSigningOutput.data()); + + ASSERT_EQ(hex(preImage), "050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f2050414444454420574954482053504143455300000000000000000025010400c20b0000020000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); + + auto pubKey = parse_hex("4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"); + auto signature = parse_hex("0791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f"); + + /// Step 3: Compile transaction info + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, txInputDataPtr.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&pubKey)).get())); + + const auto ExpectedTx = + "bd0284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee000791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f25010400050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f20504144444544205749544820535041434553000000000000000000"; + { + Polymesh::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); + + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } +} + +TEST(TWAnySignerPolymesh, encodeTransaction_Add_authorization) { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x7d9b9109027b36b72d37ba0648cb70e5254524d3d6752cc6b41601f4bdfb1af0 + + Polymesh::Proto::SigningInput input; + input.set_network(12); + auto blockHash = parse_hex("ce0c2109db498e45abf8fd447580dcfa7b7a07ffc2bfb1a0fbdd1af3e8816d2b"); + auto vGenesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(5UL); + input.set_spec_version(3010U); + input.set_transaction_version(2U); + + auto* era = input.mutable_era(); + era->set_block_number(4395451UL); + era->set_period(64UL); + + auto* addAuthorization = input.mutable_runtime_call()->mutable_identity_call()->mutable_add_authorization(); + addAuthorization->set_target("2HEVN4PHYKj7B1krQ9bctAQXZxHQQkANVNCcfbdYk2gZ4cBR"); + auto* keyPerms = addAuthorization->mutable_authorization()->mutable_join_identity(); + // Set empty "These". + auto* assets = keyPerms->mutable_asset(); + assets->set_kind(Polymesh::Proto::SecondaryKeyPermissions_RestrictionKind_These); + auto* extrinsics = keyPerms->mutable_extrinsic(); + extrinsics->set_kind(Polymesh::Proto::SecondaryKeyPermissions_RestrictionKind_These); + auto* portfolios = keyPerms->mutable_portfolio(); + portfolios->set_kind(Polymesh::Proto::SecondaryKeyPermissions_RestrictionKind_These); + + auto* callIndices = addAuthorization->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x07); + callIndices->set_method_index(0x0d); + + auto pubKey = parse_hex("4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"); + auto signature = parse_hex("81e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01"); + auto encoded = helper_encodeTransaction(TWCoinTypePolymesh, input, pubKey, signature); + ASSERT_EQ(hex(encoded), "490284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee0081e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01b5031400070d01d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d0610540501000100010000"); +} + +TEST(TWAnySignerPolymesh, encodeTransaction_JoinIdentityAsKey) { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x9d7297d8b38af5668861996cb115f321ed681989e87024fda64eae748c2dc542 + + Polymesh::Proto::SigningInput input; + input.set_network(12); + auto blockHash = parse_hex("45c80153c47f5d16acc7a66d473870e8d4574437a7d8c813f47da74cae3812c2"); + auto vGenesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(0UL); + input.set_spec_version(3010U); + input.set_transaction_version(2U); + + auto* era = input.mutable_era(); + era->set_block_number(4395527UL); + era->set_period(64UL); + + auto* key = input.mutable_runtime_call()->mutable_identity_call()->mutable_join_identity_as_key(); + key->set_auth_id(21435); + auto* callIndices = key->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x07); + callIndices->set_method_index(0x05); + + auto pubKey = parse_hex("d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054"); + auto signature = parse_hex("7f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006"); + auto encoded = helper_encodeTransaction(TWCoinTypePolymesh, input, pubKey, signature); + ASSERT_EQ(hex(encoded), "c5018400d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054007f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006750000000705bb53000000000000"); +} + +} // namespace TW::Polymesh::tests diff --git a/tests/chains/Polymesh/TWCoinTypeTests.cpp b/tests/chains/Polymesh/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..4271fff9bbe --- /dev/null +++ b/tests/chains/Polymesh/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWPolymeshCoinType, TWCoinType) { + const auto coin = TWCoinTypePolymesh; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "polymesh"); + assertStringsEqual(name, "Polymesh"); + assertStringsEqual(symbol, "POLYX"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainPolymesh); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); + assertStringsEqual(txUrl, "https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04"); + assertStringsEqual(accUrl, "https://polymesh.subscan.io/account/2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv"); +} diff --git a/tests/common/CoinAddressDerivationTests.cpp b/tests/common/CoinAddressDerivationTests.cpp index c537a8d8f90..fd35b42420c 100644 --- a/tests/common/CoinAddressDerivationTests.cpp +++ b/tests/common/CoinAddressDerivationTests.cpp @@ -400,6 +400,9 @@ TEST(Coin, DeriveAddress) { case TWCoinTypePactus: EXPECT_EQ(address, "pc1rehvlc6tfn79z0zjqqaj8zas5j5h9c2fe59a4ff"); break; + case TWCoinTypePolymesh: + EXPECT_EQ(address, "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F"); + break; // end_of_coin_address_derivation_tests_marker_do_not_modify // no default branch here, intentionally, to better notice any missing coins } From e384f674c2b92f310058848998fee2a458435fc5 Mon Sep 17 00:00:00 2001 From: gupnik Date: Wed, 5 Mar 2025 22:47:23 +0530 Subject: [PATCH 03/72] Adds support for complex custom types in Rust <> CPP FFI Generator (#4294) * Adds support for complex custom types in Rust <> CPP FFI Generator * Adds method and property in tw_ffi macro * Addresses review comments --- .gitignore | 3 + codegen-v2/src/codegen/cpp/code_gen.rs | 667 ++++++++++-------- codegen-v2/src/codegen/cpp/code_gen_types.rs | 152 ++++ codegen-v2/src/codegen/cpp/mod.rs | 1 + docs/registry.md | 2 +- .../TrustWalletCore/TWCryptoBoxPublicKey.h | 45 -- .../TrustWalletCore/TWCryptoBoxSecretKey.h | 60 -- rust/Cargo.lock | 1 + rust/tw_keypair/Cargo.toml | 1 + .../src/ffi/crypto_box/public_key.rs | 23 +- .../src/ffi/crypto_box/secret_key.rs | 26 +- rust/tw_macros/src/code_gen.rs | 10 +- rust/tw_macros/src/tw_ffi.rs | 82 ++- src/interface/TWCryptoBoxPublicKey.cpp | 31 - src/interface/TWCryptoBoxSecretKey.cpp | 41 -- 15 files changed, 632 insertions(+), 513 deletions(-) create mode 100644 codegen-v2/src/codegen/cpp/code_gen_types.rs delete mode 100644 include/TrustWalletCore/TWCryptoBoxPublicKey.h delete mode 100644 include/TrustWalletCore/TWCryptoBoxSecretKey.h delete mode 100644 src/interface/TWCryptoBoxPublicKey.cpp delete mode 100644 src/interface/TWCryptoBoxSecretKey.cpp diff --git a/.gitignore b/.gitignore index 9c41a67c770..e7e97f12830 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ swift/Sources/Generated swift/wallet-core/ codegen-v2/bindings/ +src/Generated/*.h src/Generated/*.cpp include/TrustWalletCore/TWHRP.h include/TrustWalletCore/TW*Proto.h @@ -48,6 +49,8 @@ include/TrustWalletCore/TWTONMessageSigner.h include/TrustWalletCore/TWMessageSigner.h include/TrustWalletCore/TWWalletConnectRequest.h include/TrustWalletCore/TWSolanaTransaction.h +include/TrustWalletCore/TWCryptoBoxPublicKey.h +include/TrustWalletCore/TWCryptoBoxSecretKey.h # Wasm emsdk/ diff --git a/codegen-v2/src/codegen/cpp/code_gen.rs b/codegen-v2/src/codegen/cpp/code_gen.rs index 03ebc852c97..e83f3af603e 100644 --- a/codegen-v2/src/codegen/cpp/code_gen.rs +++ b/codegen-v2/src/codegen/cpp/code_gen.rs @@ -1,10 +1,9 @@ use heck::ToLowerCamelCase; -use regex::Regex; -use serde::{Deserialize, Serialize}; use std::fmt::Write as _; use std::fs; use std::io::Write; +use super::code_gen_types::*; use crate::Error::BadFormat; use crate::Result; @@ -12,74 +11,6 @@ static IN_DIR: &str = "../rust/bindings/"; static HEADER_OUT_DIR: &str = "../include/TrustWalletCore/"; static SOURCE_OUT_DIR: &str = "../src/Generated/"; -#[derive(Deserialize, Serialize, Debug)] -pub struct TWConfig { - pub class: String, - pub static_functions: Vec, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct TWStaticFunction { - pub name: String, - pub rust_name: String, - pub args: Vec, - pub return_type: String, - pub docs: Vec, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct TWArg { - pub name: String, - pub ty: String, -} - -fn convert_standard_type_to_cpp(ty: &str) -> String { - match ty { - "TWPrivateKey" => "struct TWPrivateKey".to_string(), - "TWPublicKey" => "struct TWPublicKey".to_string(), - "TWDataVector" => "struct TWDataVector".to_string(), - _ => ty.to_string(), - } -} - -fn convert_rust_type_to_cpp(ty: &str) -> String { - let trimmed = ty.replace(" ", ""); - if let Some(captures) = Regex::new(r"^Nonnull<(.+)>$") - .expect("Failed to create regex") - .captures(&trimmed) - { - format!("{} *_Nonnull", convert_standard_type_to_cpp(&captures[1])) - } else if let Some(captures) = Regex::new(r"^NonnullMut<(.+)>$") - .expect("Failed to create regex") - .captures(&trimmed) - { - format!("{} *_Nonnull", convert_standard_type_to_cpp(&captures[1])) - } else if let Some(captures) = Regex::new(r"^Nullable<(.+)>$") - .expect("Failed to create regex") - .captures(&trimmed) - { - format!("{} *_Nullable", convert_standard_type_to_cpp(&captures[1])) - } else if let Some(captures) = Regex::new(r"^NullableMut<(.+)>$") - .expect("Failed to create regex") - .captures(&trimmed) - { - format!("{} *_Nullable", convert_standard_type_to_cpp(&captures[1])) - } else { - match ty { - "u8" => "uint8_t".to_string(), - "u16" => "uint16_t".to_string(), - "u32" => "uint32_t".to_string(), - "u64" => "uint64_t".to_string(), - "i8" => "int8_t".to_string(), - "i16" => "int16_t".to_string(), - "i32" => "int32_t".to_string(), - "i64" => "int64_t".to_string(), - "TWFFICoinType" => "enum TWCoinType".to_string(), - _ => ty.to_string(), - } - } -} - fn generate_license(file: &mut std::fs::File) -> Result<()> { writeln!(file, "// SPDX-License-Identifier: Apache-2.0")?; writeln!(file, "//")?; @@ -97,27 +28,27 @@ fn generate_header_includes(file: &mut std::fs::File, info: &TWConfig) -> Result // Include headers based on argument types let mut included_headers = std::collections::HashSet::new(); - for func in &info.static_functions { - for arg in &func.args { - if arg.ty.contains("TWString") && included_headers.insert("TWString.h") { - writeln!(file, "#include \"TWString.h\"")?; - } - if arg.ty.contains("TWData") && included_headers.insert("TWData.h") { - writeln!(file, "#include \"TWData.h\"")?; - } - if arg.ty.contains("TWPrivateKey") && included_headers.insert("TWPrivateKey.h") { - writeln!(file, "#include \"TWPrivateKey.h\"")?; - } - if arg.ty.contains("TWPublicKey") && included_headers.insert("TWPublicKey.h") { - writeln!(file, "#include \"TWPublicKey.h\"")?; + for (_, func) in info.functions(true) { + for ty in func.types() { + let tw_type = TWType::from(ty); + match tw_type { + TWType::Pointer(_, header_name) => { + if header_name == info.class { + continue; + } + if included_headers.insert(header_name.clone()) { + writeln!(file, "#include \"{}.h\"", header_name)?; + } + } + TWType::Standard(ty) => { + if ty.contains("TWFFICoinType") + && included_headers.insert("TWCoinType.h".to_string()) + { + // Need to handle this case separately because it's not a pointer type + writeln!(file, "#include \"TWCoinType.h\"")?; + } + } } - if arg.ty.contains("TWDataVector") && included_headers.insert("TWDataVector.h") { - writeln!(file, "#include \"TWDataVector.h\"")?; - } - if arg.ty.contains("TWFFICoinType") && included_headers.insert("TWCoinType.h") { - writeln!(file, "#include \"TWCoinType.h\"")?; - } - // Additional type checks can be added here in the future } } @@ -131,12 +62,19 @@ fn generate_class_declaration(file: &mut std::fs::File, info: &TWConfig) -> Resu fn generate_function_signature( class_name: &str, - func: &TWStaticFunction, + func_type: TWFunctionType, + func: &TWFunction, is_declaration: bool, ) -> Result { - let return_type = convert_rust_type_to_cpp(&func.return_type); + let return_type = TWType::from(func.return_type.clone()).cpp_type(); let whether_export = if is_declaration { - "TW_EXPORT_STATIC_METHOD " + match func_type { + TWFunctionType::StaticFunction | TWFunctionType::Constructor => { + "TW_EXPORT_STATIC_METHOD " + } + TWFunctionType::Method | TWFunctionType::Destructor => "TW_EXPORT_METHOD ", + TWFunctionType::Property => "TW_EXPORT_PROPERTY ", + } } else { "" }; @@ -146,7 +84,7 @@ fn generate_function_signature( write!( &mut signature, "{} {}", - convert_rust_type_to_cpp(&arg.ty), + TWType::from(arg.ty.clone()).cpp_type(), arg.name.to_lower_camel_case() ) .map_err(|e| BadFormat(e.to_string()))?; @@ -161,9 +99,10 @@ fn generate_function_signature( fn generate_function_declaration( file: &mut std::fs::File, class_name: &str, - func: &TWStaticFunction, + func_type: TWFunctionType, + func: &TWFunction, ) -> Result<()> { - let func_dec = generate_function_signature(class_name, func, true)?; + let func_dec = generate_function_signature(class_name, func_type, func, true)?; for doc in &func.docs { writeln!(file, "/// {}", doc)?; } @@ -182,8 +121,8 @@ pub fn generate_header(info: &TWConfig) -> Result<()> { writeln!(file, "\nTW_EXTERN_C_BEGIN\n")?; generate_class_declaration(&mut file, info)?; - for func in &info.static_functions { - generate_function_declaration(&mut file, &info.class, func)?; + for (func_type, func) in info.functions(true) { + generate_function_declaration(&mut file, &info.class, func_type, func)?; } writeln!(file, "TW_EXTERN_C_END")?; @@ -193,24 +132,74 @@ pub fn generate_header(info: &TWConfig) -> Result<()> { Ok(()) } +fn generate_wrapper_header(info: &TWConfig) -> Result<()> { + let class_name = &info.class; + let wrapper_class_name = class_name.replace("TW", ""); + let file_path = format!("{SOURCE_OUT_DIR}/{}.h", wrapper_class_name); + let mut file = std::fs::File::create(&file_path)?; + + generate_license(&mut file)?; + generate_header_guard(&mut file)?; + + writeln!(file, "#include \"rust/Wrapper.h\"\n")?; + + writeln!( + file, + "using {wrapper_class_name}Ptr = std::shared_ptr;\n", + )?; + + writeln!(file, "struct {} {{", wrapper_class_name)?; + + let Some(destructor) = &info.destructor else { + panic!("No destructor found for {}", wrapper_class_name); + }; + let destructor_name = &destructor.rust_name; + writeln!( + file, + "\texplicit {wrapper_class_name}(TW::Rust::{class_name}* raw_ptr): ptr(raw_ptr, TW::Rust::{destructor_name}) {{}}\n", + )?; + + writeln!(file, "\t{wrapper_class_name}Ptr ptr;")?; + writeln!(file, "}};\n")?; + + writeln!(file, "struct {} {{", class_name)?; + writeln!(file, "\t{wrapper_class_name} impl;")?; + writeln!(file, "}};\n")?; + + Ok(()) +} + fn generate_source_includes(file: &mut std::fs::File, info: &TWConfig) -> Result<()> { writeln!(file, "#include ", info.class)?; writeln!(file, "#include \"rust/Wrapper.h\"")?; // Include headers based on argument types let mut included_headers = std::collections::HashSet::new(); - for func in &info.static_functions { - for arg in &func.args { - if arg.ty.contains("TWPrivateKey") && included_headers.insert("TWPrivateKey.h") { + for (_, func) in info.functions(true) { + for ty in func.types() { + let tw_type = TWType::from(ty); + let TWType::Pointer(_, header_name) = tw_type else { + continue; + }; + if header_name.contains("TWPrivateKey") + && included_headers.insert("TWPrivateKey.h".to_string()) + { writeln!(file, "#include \"../PrivateKey.h\"")?; - } - if arg.ty.contains("TWPublicKey") && included_headers.insert("TWPublicKey.h") { + } else if header_name.contains("TWPublicKey") + && included_headers.insert("TWPublicKey.h".to_string()) + { writeln!(file, "#include \"../PublicKey.h\"")?; - } - if arg.ty.contains("TWDataVector") && included_headers.insert("TWDataVector.h") { + } else if header_name.contains("TWDataVector") + && included_headers.insert("TWDataVector.h".to_string()) + { writeln!(file, "#include \"../DataVector.h\"")?; + } else if !header_name.contains("TWString") && !header_name.contains("TWData") { + // Do not need wrapper headers for these types + let wrapper_header_name = header_name.replace("TW", ""); + if included_headers.insert(wrapper_header_name.clone()) { + writeln!(file, "#include \"{}.h\"", wrapper_header_name)?; + } } - // Additional type checks can be added here in the future } } @@ -229,223 +218,273 @@ fn generate_function_call(args: &Vec) -> Result { Ok(func_call) } -fn generate_return_type(func: &TWStaticFunction, converted_args: &Vec) -> Result { +fn generate_return_type(func: &TWFunction, converted_args: &Vec) -> Result { let mut return_string = String::new(); - match func.return_type.replace(" ", "").as_str() { - "NullableMut" | "Nullable" => { - write!( - &mut return_string, - "\tconst Rust::TWStringWrapper result = Rust::{}{}\n\ - \tif (!result) {{ return nullptr; }}\n\ - \treturn TWStringCreateWithUTF8Bytes(result.c_str());\n", - func.rust_name, - generate_function_call(&converted_args)?.as_str() - ) - .map_err(|e| BadFormat(e.to_string()))?; - } - "NullableMut" | "Nullable" => { - write!( - &mut return_string, - "\tconst Rust::TWDataWrapper result = Rust::{}{}\n\ - \tif (!result.ptr) {{ return nullptr; }}\n\ - \tconst auto resultData = result.toDataOrDefault();\n\ - \treturn TWDataCreateWithBytes(resultData.data(), resultData.size());\n", - func.rust_name, - generate_function_call(&converted_args)?.as_str() - ) - .map_err(|e| BadFormat(e.to_string()))?; - } - "Nonnull" | "NonnullMut" => { - write!( - &mut return_string, - "\tconst Rust::TWDataWrapper result = Rust::{}{}\n\ - \tconst auto resultData = result.toDataOrDefault();\n\ - \treturn TWDataCreateWithBytes(resultData.data(), resultData.size());\n", - func.rust_name, - generate_function_call(&converted_args)?.as_str() - ) - .map_err(|e| BadFormat(e.to_string()))?; - } - "NullableMut" | "Nullable" => { - write!( - &mut return_string, - "\tconst auto result = Rust::{}{}\n\ - \tconst auto resultRustPrivateKey = Rust::wrapTWPrivateKey(result);\n\ - \tif (!resultRustPrivateKey.get()) {{ return nullptr; }}\n\ - \tconst auto resultData = Rust::tw_private_key_bytes(resultRustPrivateKey.get());\n\ - \tconst auto resultSize = Rust::tw_private_key_size(resultRustPrivateKey.get());\n\ - \tconst Data out(resultData, resultData + resultSize);\n\ - \treturn new TWPrivateKey {{ PrivateKey(out) }};\n", - func.rust_name, - generate_function_call(&converted_args)?.as_str() - ) - .map_err(|e| BadFormat(e.to_string()))?; - } - "NullableMut" | "Nullable" => { - write!( - &mut return_string, - "\tconst auto result = Rust::{}{}\n\ - \tconst auto resultRustPublicKey = Rust::wrapTWPublicKey(result);\n\ - \tif (!resultRustPublicKey.get()) {{ return nullptr; }}\n\ - \tconst auto resultData = Rust::tw_public_key_data(resultRustPublicKey.get());\n\ - \tconst Data out(resultData.data, resultData.data + resultData.size);\n\ - \treturn new TWPublicKey {{ PublicKey(out, a->impl.type) }};\n", - func.rust_name, - generate_function_call(&converted_args)?.as_str() - ) - .map_err(|e| BadFormat(e.to_string()))?; - } - ty if ty.contains("Nonnull") => { - panic!("Nonnull types are not supported in C++ except for TWData"); - } - _ => { - write!( - &mut return_string, - "\treturn Rust::{}{}\n", - func.rust_name, - generate_function_call(&converted_args)?.as_str() - ) - .map_err(|e| BadFormat(e.to_string()))?; - } + let tw_type = TWType::from(func.return_type.replace(" ", "").to_string()); + match tw_type { + TWType::Pointer(pointer_type, ty) => match (pointer_type, ty.as_str()) { + (TWPointerType::Nullable, "TWString") | (TWPointerType::NullableMut, "TWString") => { + write!( + &mut return_string, + "\tconst Rust::TWStringWrapper result = Rust::{}{}\n\ + \tif (!result) {{ return nullptr; }}\n\ + \treturn TWStringCreateWithUTF8Bytes(result.c_str());\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + (TWPointerType::NonnullMut, "TWString") | (TWPointerType::Nonnull, "TWString") => { + panic!("Nonnull TWString is not supported"); + } + (TWPointerType::NullableMut, "TWData") | (TWPointerType::Nullable, "TWData") => { + write!( + &mut return_string, + "\tconst Rust::TWDataWrapper result = Rust::{}{}\n\ + \tif (!result.ptr) {{ return nullptr; }}\n\ + \tconst auto resultData = result.toDataOrDefault();\n\ + \treturn TWDataCreateWithBytes(resultData.data(), resultData.size());\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + (TWPointerType::NonnullMut, "TWData") | (TWPointerType::Nonnull, "TWData") => { + write!( + &mut return_string, + "\tconst Rust::TWDataWrapper result = Rust::{}{}\n\ + \tconst auto resultData = result.toDataOrDefault();\n\ + \treturn TWDataCreateWithBytes(resultData.data(), resultData.size());\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + (TWPointerType::NullableMut, "TWPrivateKey") + | (TWPointerType::Nullable, "TWPrivateKey") => { + write!( + &mut return_string, + "\tconst auto result = Rust::{}{}\n\ + \tconst auto resultRustPrivateKey = Rust::wrapTWPrivateKey(result);\n\ + \tif (!resultRustPrivateKey.get()) {{ return nullptr; }}\n\ + \tconst auto resultData = Rust::tw_private_key_bytes(resultRustPrivateKey.get());\n\ + \tconst auto resultSize = Rust::tw_private_key_size(resultRustPrivateKey.get());\n\ + \tconst Data out(resultData, resultData + resultSize);\n\ + \treturn new TWPrivateKey {{ PrivateKey(out) }};\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + (TWPointerType::NonnullMut, "TWPrivateKey") + | (TWPointerType::Nonnull, "TWPrivateKey") => { + panic!("Nonnull TWPrivateKey is not supported"); + } + (TWPointerType::NullableMut, "TWPublicKey") + | (TWPointerType::Nullable, "TWPublicKey") => { + write!( + &mut return_string, + "\tconst auto result = Rust::{}{}\n\ + \tconst auto resultRustPublicKey = Rust::wrapTWPublicKey(result);\n\ + \tif (!resultRustPublicKey.get()) {{ return nullptr; }}\n\ + \tconst auto resultData = Rust::tw_public_key_data(resultRustPublicKey.get());\n\ + \tconst Data out(resultData.data, resultData.data + resultData.size);\n\ + \treturn new TWPublicKey {{ PublicKey(out, a->impl.type) }};\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + (TWPointerType::NonnullMut, "TWPublicKey") + | (TWPointerType::Nonnull, "TWPublicKey") => { + panic!("Nonnull TWPublicKey is not supported"); + } + (pointer_type, class_name) => { + let wrapper_class_name = class_name.replace("TW", ""); + let null_return = match pointer_type { + TWPointerType::Nullable | TWPointerType::NullableMut => { + "if (!resultRaw) {{ return nullptr; }}\n" + } + _ => "", + }; + write!( + &mut return_string, + "\tauto* resultRaw = Rust::{}{}\n\ + {null_return}\ + \tconst {wrapper_class_name} resultWrapped(resultRaw);\n\ + \treturn new {class_name} {{ resultWrapped }};\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + }, + TWType::Standard(ty) => match ty.as_str() { + "void" => { + write!( + &mut return_string, + "\tRust::{}{}\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + _ => { + write!( + &mut return_string, + "\treturn Rust::{}{}\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; + } + }, } Ok(return_string) } -fn generate_conversion_code_with_var_name(ty: &str, name: &str) -> Result<(String, String)> { - match ty { - "TWString *_Nonnull" => { - let mut conversion_code = String::new(); - writeln!( - &mut conversion_code, - "\tauto& {name}String = *reinterpret_cast({name});\n\ - \tconst Rust::TWStringWrapper {name}RustStr = {name}String;" - ) - .map_err(|e| BadFormat(e.to_string()))?; - Ok((conversion_code, format!("{}RustStr.get()", name))) - } - "TWString *_Nullable" => { - let mut conversion_code = String::new(); - writeln!( - &mut conversion_code, - "\tRust::TWStringWrapper {name}RustStr;\n\ - \tif ({name} != nullptr) {{\n\ +fn generate_conversion_code_with_var_name(tw_type: TWType, name: &str) -> Result<(String, String)> { + match tw_type { + TWType::Pointer(ref pointer_type, ref ty) => match (pointer_type, ty.as_str()) { + (TWPointerType::Nonnull, "TWString") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tauto& {name}String = *reinterpret_cast({name});\n\ + \tconst Rust::TWStringWrapper {name}RustStr = {name}String;" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustStr.get()", name))) + } + (TWPointerType::Nullable, "TWString") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tRust::TWStringWrapper {name}RustStr;\n\ + \tif ({name} != nullptr) {{\n\ \t\t{name}RustStr = *reinterpret_cast({name});\n\ - \t}}" - ) - .map_err(|e| BadFormat(e.to_string()))?; - Ok((conversion_code, format!("{}RustStr.get()", name))) - } - "TWData *_Nonnull" => { - let mut conversion_code = String::new(); - writeln!( - &mut conversion_code, - "\tauto& {name}Data = *reinterpret_cast({name});\n\ - \tconst Rust::TWDataWrapper {name}RustData = {name}Data;" - ) - .map_err(|e| BadFormat(e.to_string()))?; - Ok((conversion_code, format!("{}RustData.get()", name))) - } - "TWData *_Nullable" => { - let mut conversion_code = String::new(); - writeln!( - &mut conversion_code, - "\tRust::TWDataWrapper {name}RustData;\n\ - \tif ({name} != nullptr) {{\n\ - \t\t{name}RustData = *reinterpret_cast({name});\n\ - \t}}" - ) - .map_err(|e| BadFormat(e.to_string()))?; - Ok((conversion_code, format!("{}RustData.get()", name))) - } - "struct TWPrivateKey *_Nonnull" => { - let mut conversion_code = String::new(); - writeln!( - &mut conversion_code, - "\tauto &{name}PrivateKey = *reinterpret_cast({name});\n\ - \tauto* {name}RustRaw = Rust::tw_private_key_create_with_data({name}PrivateKey.bytes.data(), {name}PrivateKey.bytes.size());\n\ - \tconst auto {name}RustPrivateKey = Rust::wrapTWPrivateKey({name}RustRaw);" - ) - .map_err(|e| BadFormat(e.to_string()))?; - Ok((conversion_code, format!("{}RustPrivateKey.get()", name))) - } - "struct TWPrivateKey *_Nullable" => { - let mut conversion_code = String::new(); - writeln!( - &mut conversion_code, - "\tstd::shared_ptr {name}RustPrivateKey;\n\ - \tif ({name} != nullptr) {{\n\ + \t}}" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustStr.get()", name))) + } + (TWPointerType::Nonnull, "TWData") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tauto& {name}Data = *reinterpret_cast({name});\n\ + \tconst Rust::TWDataWrapper {name}RustData = {name}Data;" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustData.get()", name))) + } + (TWPointerType::Nullable, "TWData") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tRust::TWDataWrapper {name}RustData;\n\ + \tif ({name} != nullptr) {{\n\ + \t\t{name}RustData = *reinterpret_cast({name});\n\ + \t}}" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustData.get()", name))) + } + (TWPointerType::Nonnull, "TWPrivateKey") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tauto &{name}PrivateKey = *reinterpret_cast({name});\n\ + \tauto* {name}RustRaw = Rust::tw_private_key_create_with_data({name}PrivateKey.bytes.data(), {name}PrivateKey.bytes.size());\n\ + \tconst auto {name}RustPrivateKey = Rust::wrapTWPrivateKey({name}RustRaw);" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustPrivateKey.get()", name))) + } + (TWPointerType::Nullable, "TWPrivateKey") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tstd::shared_ptr {name}RustPrivateKey;\n\ + \tif ({name} != nullptr) {{\n\ \t\tconst auto& {name}PrivateKey = {name};\n\ \t\tauto* {name}RustRaw = Rust::tw_private_key_create_with_data({name}PrivateKey->impl.bytes.data(), {name}PrivateKey->impl.bytes.size());\n\ \t\t{name}RustPrivateKey = Rust::wrapTWPrivateKey({name}RustRaw);\n\ - \t}}" - ) - .map_err(|e| BadFormat(e.to_string()))?; - Ok((conversion_code, format!("{}RustPrivateKey.get()", name))) - } - "struct TWPublicKey *_Nonnull" => { - let mut conversion_code = String::new(); - writeln!( - &mut conversion_code, - "\tauto &{name}PublicKey = *reinterpret_cast({name});\n\ - \tconst auto {name}PublicKeyType = static_cast({name}PublicKey.type);\n\ - \tauto* {name}RustRaw = Rust::tw_public_key_create_with_data({name}PublicKey.bytes.data(), {name}PublicKey.bytes.size(), {name}PublicKeyType);\n\ - \tconst auto {name}RustPublicKey = Rust::wrapTWPublicKey({name}RustRaw);" - ) - .map_err(|e| BadFormat(e.to_string()))?; - Ok((conversion_code, format!("{}RustPublicKey.get()", name))) - } - "struct TWPublicKey *_Nullable" => { - let mut conversion_code = String::new(); - writeln!( - &mut conversion_code, - "\tstd::shared_ptr {name}RustPublicKey;\n\ - \tif ({name} != nullptr) {{\n\ + \t}}" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustPrivateKey.get()", name))) + } + (TWPointerType::Nonnull, "TWPublicKey") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tauto &{name}PublicKey = *reinterpret_cast({name});\n\ + \tconst auto {name}PublicKeyType = static_cast({name}PublicKey.type);\n\ + \tauto* {name}RustRaw = Rust::tw_public_key_create_with_data({name}PublicKey.bytes.data(), {name}PublicKey.bytes.size(), {name}PublicKeyType);\n\ + \tconst auto {name}RustPublicKey = Rust::wrapTWPublicKey({name}RustRaw);" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustPublicKey.get()", name))) + } + (TWPointerType::Nullable, "TWPublicKey") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tstd::shared_ptr {name}RustPublicKey;\n\ + \tif ({name} != nullptr) {{\n\ \t\tconst auto& {name}PublicKey = {name};\n\ \t\tconst auto {name}PublicKeyType = static_cast({name}PublicKey->impl.type);\n\ \t\tauto* {name}RustRaw = Rust::tw_public_key_create_with_data({name}PublicKey->impl.bytes.data(), {name}PublicKey->impl.bytes.size(), {name}PublicKeyType);\n\ \t\t{name}RustPublicKey = Rust::wrapTWPublicKey({name}RustRaw);\n\ - \t}}" - ) - .map_err(|e| BadFormat(e.to_string()))?; - Ok((conversion_code, format!("{}RustPublicKey.get()", name))) - } - "struct TWDataVector *_Nonnull" => { - let mut conversion_code = String::new(); - writeln!( - &mut conversion_code, - "\tconst Rust::TWDataVectorWrapper {name}RustDataVector = createFromTWDataVector({name});" - ) - .map_err(|e| BadFormat(e.to_string()))?; - Ok((conversion_code, format!("{}RustDataVector.get()", name))) - } - "struct TWDataVector *_Nullable" => { - let mut conversion_code = String::new(); - writeln!( - &mut conversion_code, - "\tstd::shared_ptr {name}RustDataVector;\n\ - \tif ({name} != nullptr) {{\n\ + \t}}" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustPublicKey.get()", name))) + } + (TWPointerType::Nonnull, "TWDataVector") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tconst Rust::TWDataVectorWrapper {name}RustDataVector = createFromTWDataVector({name});" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustDataVector.get()", name))) + } + (TWPointerType::Nullable, "TWDataVector") => { + let mut conversion_code = String::new(); + writeln!( + &mut conversion_code, + "\tstd::shared_ptr {name}RustDataVector;\n\ + \tif ({name} != nullptr) {{\n\ \t\t{name}RustDataVector = createFromTWDataVector({name});\n\ - \t}}" - ) - .map_err(|e| BadFormat(e.to_string()))?; - Ok((conversion_code, format!("{}RustDataVector.get()", name))) - } - _ => Ok(("".to_string(), name.to_string())), + \t}}" + ) + .map_err(|e| BadFormat(e.to_string()))?; + Ok((conversion_code, format!("{}RustDataVector.get()", name))) + } + _ if tw_type.cpp_type().starts_with("struct ") => { + Ok(("".to_string(), format!("{name}->impl.ptr.get()"))) + } + _ => panic!("Unsupported type: {}", tw_type.cpp_type()), + }, + TWType::Standard(_) => Ok(("".to_string(), name.to_string())), } } fn generate_function_definition( file: &mut std::fs::File, info: &TWConfig, - func: &TWStaticFunction, + func_type: TWFunctionType, + func: &TWFunction, ) -> Result<()> { - let mut func_def = generate_function_signature(&info.class, func, false)?; + let mut func_def = generate_function_signature(&info.class, func_type, func, false)?; func_def += " {\n"; let mut converted_args = vec![]; for arg in func.args.iter() { - let func_type = convert_rust_type_to_cpp(&arg.ty); + let func_type = TWType::from(arg.ty.clone()); let (conversion_code, converted_arg) = - generate_conversion_code_with_var_name(&func_type, &arg.name.to_lower_camel_case())?; + generate_conversion_code_with_var_name(func_type, &arg.name.to_lower_camel_case())?; func_def += conversion_code.as_str(); converted_args.push(converted_arg); } @@ -456,6 +495,27 @@ fn generate_function_definition( Ok(()) } +fn generate_destructor_definition( + file: &mut std::fs::File, + info: &TWConfig, + destructor: &TWFunction, +) -> Result<()> { + let function_signature = + generate_function_signature(&info.class, TWFunctionType::Destructor, destructor, false)?; + assert!( + destructor.args.len() == 1, + "Destructor must have exactly one argument" + ); + let arg_name = &destructor.args[0].name.to_lower_camel_case(); + writeln!( + file, + "{function_signature}{{\n\ + \tdelete {arg_name};\n\ + }}" + )?; + Ok(()) +} + fn generate_source(info: &TWConfig) -> Result<()> { let file_path = format!("{SOURCE_OUT_DIR}/{}.cpp", info.class); let mut file = std::fs::File::create(&file_path)?; @@ -465,8 +525,11 @@ fn generate_source(info: &TWConfig) -> Result<()> { writeln!(file, "\nusing namespace TW;\n")?; - for func in &info.static_functions { - generate_function_definition(&mut file, info, func)?; + for (func_type, func) in info.functions(false) { + generate_function_definition(&mut file, info, func_type, func)?; + } + if let Some(destructor) = &info.destructor { + generate_destructor_definition(&mut file, info, destructor)?; } file.flush()?; @@ -490,6 +553,10 @@ pub fn generate_cpp_bindings() -> Result<()> { let info: TWConfig = serde_yaml::from_str(&file_contents).expect("Failed to parse YAML file"); + if info.is_wrapped() { + generate_wrapper_header(&info)?; + } + generate_header(&info)?; generate_source(&info)?; } diff --git a/codegen-v2/src/codegen/cpp/code_gen_types.rs b/codegen-v2/src/codegen/cpp/code_gen_types.rs new file mode 100644 index 00000000000..555fa8b8e88 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/code_gen_types.rs @@ -0,0 +1,152 @@ +use regex::Regex; +use serde::{Deserialize, Serialize}; + +pub enum TWFunctionType { + StaticFunction, + Constructor, + Destructor, + Method, + Property, +} + +#[derive(Deserialize, Serialize, Debug, Default)] +pub struct TWConfig { + pub class: String, + pub static_functions: Vec, + pub constructors: Option>, + pub destructor: Option, + pub methods: Option>, + pub properties: Option>, +} + +impl TWConfig { + pub fn functions(&self, include_destructor: bool) -> Vec<(TWFunctionType, &TWFunction)> { + let mut functions = self + .static_functions + .iter() + .map(|f| (TWFunctionType::StaticFunction, f)) + .collect::>(); + if let Some(constructors) = &self.constructors { + functions.extend( + constructors + .iter() + .map(|f| (TWFunctionType::Constructor, f)), + ); + } + if let Some(methods) = &self.methods { + functions.extend(methods.iter().map(|f| (TWFunctionType::Method, f))); + } + if let Some(properties) = &self.properties { + functions.extend(properties.iter().map(|f| (TWFunctionType::Property, f))); + } + if include_destructor { + if let Some(destructor) = &self.destructor { + functions.push((TWFunctionType::Destructor, destructor)); + } + } + functions + } + + pub fn is_wrapped(&self) -> bool { + self.constructors.is_some() && self.destructor.is_some() + } +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct TWFunction { + pub name: String, + pub rust_name: String, + pub args: Vec, + pub return_type: String, + pub docs: Vec, +} + +impl TWFunction { + pub fn types(&self) -> Vec { + self.args + .iter() + .map(|arg| arg.ty.clone()) + .chain(std::iter::once(self.return_type.clone())) + .collect() + } +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct TWArg { + pub name: String, + pub ty: String, +} + +#[derive(Debug)] +pub enum TWPointerType { + Nonnull, + NonnullMut, + Nullable, + NullableMut, +} + +#[derive(Debug)] +pub enum TWType { + Pointer(TWPointerType, String), + Standard(String), +} + +impl From for TWType { + fn from(ty: String) -> Self { + let trimmed = ty.replace(" ", ""); + if let Some(captures) = Regex::new(r"^Nonnull<(.+)>$") + .expect("Failed to create regex") + .captures(&trimmed) + { + TWType::Pointer(TWPointerType::Nonnull, captures[1].to_string()) + } else if let Some(captures) = Regex::new(r"^NonnullMut<(.+)>$") + .expect("Failed to create regex") + .captures(&trimmed) + { + TWType::Pointer(TWPointerType::NonnullMut, captures[1].to_string()) + } else if let Some(captures) = Regex::new(r"^Nullable<(.+)>$") + .expect("Failed to create regex") + .captures(&trimmed) + { + TWType::Pointer(TWPointerType::Nullable, captures[1].to_string()) + } else if let Some(captures) = Regex::new(r"^NullableMut<(.+)>$") + .expect("Failed to create regex") + .captures(&trimmed) + { + TWType::Pointer(TWPointerType::NullableMut, captures[1].to_string()) + } else { + TWType::Standard(trimmed) + } + } +} + +impl TWType { + pub fn cpp_type(&self) -> String { + match self { + TWType::Standard(ty) => match ty.as_str() { + "u8" => "uint8_t".to_string(), + "u16" => "uint16_t".to_string(), + "u32" => "uint32_t".to_string(), + "u64" => "uint64_t".to_string(), + "i8" => "int8_t".to_string(), + "i16" => "int16_t".to_string(), + "i32" => "int32_t".to_string(), + "i64" => "int64_t".to_string(), + "TWFFICoinType" => "enum TWCoinType".to_string(), + _ => ty.to_string(), + }, + TWType::Pointer(pointer_type, ty) => { + let ty = match ty.as_str() { + "TWString" | "TWData" => ty.to_string(), + _ => format!("struct {}", ty), + }; + match pointer_type { + TWPointerType::Nonnull => format!("{} *_Nonnull", ty), + TWPointerType::NonnullMut => format!("{} *_Nonnull", ty), + TWPointerType::Nullable => format!("{} *_Nullable", ty), + TWPointerType::NullableMut => format!("{} *_Nullable", ty), + } + } + } + } +} diff --git a/codegen-v2/src/codegen/cpp/mod.rs b/codegen-v2/src/codegen/cpp/mod.rs index 147960dd30c..d066709dbaf 100644 --- a/codegen-v2/src/codegen/cpp/mod.rs +++ b/codegen-v2/src/codegen/cpp/mod.rs @@ -8,6 +8,7 @@ use std::path::PathBuf; pub mod blockchain_dispatcher_generator; pub mod code_gen; +pub mod code_gen_types; pub mod entry_generator; pub mod new_blockchain; pub mod new_cosmos_chain; diff --git a/docs/registry.md b/docs/registry.md index 196c3d31653..2fcea9b71a2 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -133,7 +133,7 @@ This list is generated from [./registry.json](../registry.json) | 10004689 | IoTeX EVM | IOTX | | | | 10007000 | NativeZetaChain | ZETA | | | | 10007700 | NativeCanto | CANTO | | | -| 10008217 | Kaia | KAIA | | | +| 10008217 | Kaia | KAIA | | | | 10009000 | Avalanche C-Chain | AVAX | | | | 10009001 | Evmos | EVMOS | | | | 10042170 | Arbitrum Nova | ETH | | | diff --git a/include/TrustWalletCore/TWCryptoBoxPublicKey.h b/include/TrustWalletCore/TWCryptoBoxPublicKey.h deleted file mode 100644 index e46ea72feae..00000000000 --- a/include/TrustWalletCore/TWCryptoBoxPublicKey.h +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -#include "TWBase.h" -#include "TWData.h" -#include "TWString.h" - -TW_EXTERN_C_BEGIN - -/// Public key used in `crypto_box` cryptography. -TW_EXPORT_CLASS -struct TWCryptoBoxPublicKey; - -/// Determines if the given public key is valid or not. -/// -/// \param data *non-null* byte array. -/// \return true if the public key is valid, false otherwise. -TW_EXPORT_STATIC_METHOD -bool TWCryptoBoxPublicKeyIsValid(TWData* _Nonnull data); - -/// Create a `crypto_box` public key with the given block of data. -/// -/// \param data *non-null* byte array. Expected to have 32 bytes. -/// \note Should be deleted with \tw_crypto_box_public_key_delete. -/// \return Nullable pointer to Public Key. -TW_EXPORT_STATIC_METHOD -struct TWCryptoBoxPublicKey* _Nullable TWCryptoBoxPublicKeyCreateWithData(TWData* _Nonnull data); - -/// Delete the given public key. -/// -/// \param publicKey *non-null* pointer to public key. -TW_EXPORT_METHOD -void TWCryptoBoxPublicKeyDelete(struct TWCryptoBoxPublicKey* _Nonnull publicKey); - -/// Returns the raw data of the given public-key. -/// -/// \param publicKey *non-null* pointer to a public key. -/// \return C-compatible result with a C-compatible byte array. -TW_EXPORT_PROPERTY -TWData* _Nonnull TWCryptoBoxPublicKeyData(struct TWCryptoBoxPublicKey* _Nonnull publicKey); - -TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCryptoBoxSecretKey.h b/include/TrustWalletCore/TWCryptoBoxSecretKey.h deleted file mode 100644 index f93ad92eb56..00000000000 --- a/include/TrustWalletCore/TWCryptoBoxSecretKey.h +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -#include "TWBase.h" -#include "TWCryptoBoxPublicKey.h" -#include "TWData.h" -#include "TWString.h" - -TW_EXTERN_C_BEGIN - -/// Secret key used in `crypto_box` cryptography. -TW_EXPORT_CLASS -struct TWCryptoBoxSecretKey; - -/// Determines if the given secret key is valid or not. -/// -/// \param data *non-null* byte array. -/// \return true if the secret key is valid, false otherwise. -TW_EXPORT_STATIC_METHOD -bool TWCryptoBoxSecretKeyIsValid(TWData* _Nonnull data); - -/// Create a random secret key. -/// -/// \note Should be deleted with \tw_crypto_box_secret_key_delete. -/// \return *non-null* pointer to Secret Key. -TW_EXPORT_STATIC_METHOD -struct TWCryptoBoxSecretKey* _Nonnull TWCryptoBoxSecretKeyCreate(); - -/// Create a `crypto_box` secret key with the given block of data. -/// -/// \param data *non-null* byte array. Expected to have 32 bytes. -/// \note Should be deleted with \tw_crypto_box_secret_key_delete. -/// \return Nullable pointer to Secret Key. -TW_EXPORT_STATIC_METHOD -struct TWCryptoBoxSecretKey* _Nullable TWCryptoBoxSecretKeyCreateWithData(TWData* _Nonnull data); - -/// Delete the given secret `key`. -/// -/// \param key *non-null* pointer to secret key. -TW_EXPORT_METHOD -void TWCryptoBoxSecretKeyDelete(struct TWCryptoBoxSecretKey* _Nonnull key); - -/// Returns the public key associated with the given `key`. -/// -/// \param key *non-null* pointer to the private key. -/// \return *non-null* pointer to the corresponding public key. -TW_EXPORT_METHOD -struct TWCryptoBoxPublicKey* _Nonnull TWCryptoBoxSecretKeyGetPublicKey(struct TWCryptoBoxSecretKey* _Nonnull key); - -/// Returns the raw data of the given secret-key. -/// -/// \param secretKey *non-null* pointer to a secret key. -/// \return C-compatible result with a C-compatible byte array. -TW_EXPORT_PROPERTY -TWData* _Nonnull TWCryptoBoxSecretKeyData(struct TWCryptoBoxSecretKey* _Nonnull secretKey); - -TW_EXTERN_C_END diff --git a/rust/Cargo.lock b/rust/Cargo.lock index fa5852beabf..b60b8a02478 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2093,6 +2093,7 @@ dependencies = [ "tw_encoding", "tw_hash", "tw_keypair", + "tw_macros", "tw_memory", "tw_misc", "zeroize", diff --git a/rust/tw_keypair/Cargo.toml b/rust/tw_keypair/Cargo.toml index d0e223475da..634b5ae8ea2 100644 --- a/rust/tw_keypair/Cargo.toml +++ b/rust/tw_keypair/Cargo.toml @@ -36,6 +36,7 @@ crypto_box = "0.9.1" # Starknet specific: starknet-crypto = "0.5.0" starknet-ff = "0.3.2" +tw_macros = { path = "../tw_macros" } [dev-dependencies] serde_json = "1.0" diff --git a/rust/tw_keypair/src/ffi/crypto_box/public_key.rs b/rust/tw_keypair/src/ffi/crypto_box/public_key.rs index 44caab07d63..3f1fac4e68e 100644 --- a/rust/tw_keypair/src/ffi/crypto_box/public_key.rs +++ b/rust/tw_keypair/src/ffi/crypto_box/public_key.rs @@ -5,8 +5,9 @@ #![allow(clippy::missing_safety_doc)] use crate::nacl_crypto_box::public_key::PublicKey; -use tw_memory::ffi::tw_data::TWData; -use tw_memory::ffi::RawPtrTrait; +use tw_macros::tw_ffi; +use tw_memory::ffi::{tw_data::TWData, Nonnull, NullableMut}; +use tw_memory::ffi::{NonnullMut, RawPtrTrait}; use tw_misc::{try_or_else, try_or_false}; /// Public key used in `crypto_box` cryptography. @@ -18,8 +19,9 @@ impl RawPtrTrait for TWCryptoBoxPublicKey {} /// /// \param data *non-null* byte array. /// \return true if the public key is valid, false otherwise. +#[tw_ffi(ty = static_function, class = TWCryptoBoxPublicKey, name = IsValid)] #[no_mangle] -pub unsafe extern "C" fn tw_crypto_box_public_key_is_valid(data: *const TWData) -> bool { +pub unsafe extern "C" fn tw_crypto_box_public_key_is_valid(data: Nonnull) -> bool { let bytes_ref = try_or_false!(TWData::from_ptr_as_ref(data)); PublicKey::try_from(bytes_ref.as_slice()).is_ok() } @@ -29,10 +31,11 @@ pub unsafe extern "C" fn tw_crypto_box_public_key_is_valid(data: *const TWData) /// \param data *non-null* byte array. Expected to have 32 bytes. /// \note Should be deleted with \tw_crypto_box_public_key_delete. /// \return Nullable pointer to Public Key. +#[tw_ffi(ty = constructor, class = TWCryptoBoxPublicKey, name = CreateWithData)] #[no_mangle] pub unsafe extern "C" fn tw_crypto_box_public_key_create_with_data( - data: *const TWData, -) -> *mut TWCryptoBoxPublicKey { + data: Nonnull, +) -> NullableMut { let bytes_ref = try_or_else!(TWData::from_ptr_as_ref(data), std::ptr::null_mut); let pubkey = try_or_else!( PublicKey::try_from(bytes_ref.as_slice()), @@ -44,8 +47,11 @@ pub unsafe extern "C" fn tw_crypto_box_public_key_create_with_data( /// Delete the given public key. /// /// \param public_key *non-null* pointer to public key. +#[tw_ffi(ty = destructor, class = TWCryptoBoxPublicKey, name = Delete)] #[no_mangle] -pub unsafe extern "C" fn tw_crypto_box_public_key_delete(public_key: *mut TWCryptoBoxPublicKey) { +pub unsafe extern "C" fn tw_crypto_box_public_key_delete( + public_key: NonnullMut, +) { // Take the ownership back to rust and drop the owner. let _ = TWCryptoBoxPublicKey::from_ptr(public_key); } @@ -54,10 +60,11 @@ pub unsafe extern "C" fn tw_crypto_box_public_key_delete(public_key: *mut TWCryp /// /// \param public_key *non-null* pointer to a public key. /// \return C-compatible result with a C-compatible byte array. +#[tw_ffi(ty = property, class = TWCryptoBoxPublicKey, name = Data)] #[no_mangle] pub unsafe extern "C" fn tw_crypto_box_public_key_data( - public_key: *const TWCryptoBoxPublicKey, -) -> *mut TWData { + public_key: Nonnull, +) -> NonnullMut { let pubkey_ref = try_or_else!( TWCryptoBoxPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut diff --git a/rust/tw_keypair/src/ffi/crypto_box/secret_key.rs b/rust/tw_keypair/src/ffi/crypto_box/secret_key.rs index c55503b9f88..95c81c6020f 100644 --- a/rust/tw_keypair/src/ffi/crypto_box/secret_key.rs +++ b/rust/tw_keypair/src/ffi/crypto_box/secret_key.rs @@ -6,8 +6,10 @@ use crate::ffi::crypto_box::public_key::TWCryptoBoxPublicKey; use crate::nacl_crypto_box::secret_key::SecretKey; +use tw_macros::tw_ffi; use tw_memory::ffi::tw_data::TWData; use tw_memory::ffi::RawPtrTrait; +use tw_memory::ffi::{Nonnull, NonnullMut, NullableMut}; use tw_misc::traits::ToBytesZeroizing; use tw_misc::{try_or_else, try_or_false}; @@ -20,8 +22,9 @@ impl RawPtrTrait for TWCryptoBoxSecretKey {} /// /// \param data *non-null* byte array. /// \return true if the secret key is valid, false otherwise. +#[tw_ffi(ty = static_function, class = TWCryptoBoxSecretKey, name = IsValid)] #[no_mangle] -pub unsafe extern "C" fn tw_crypto_box_secret_key_is_valid(data: *const TWData) -> bool { +pub unsafe extern "C" fn tw_crypto_box_secret_key_is_valid(data: Nonnull) -> bool { let bytes_ref = try_or_false!(TWData::from_ptr_as_ref(data)); SecretKey::try_from(bytes_ref.as_slice()).is_ok() } @@ -30,8 +33,9 @@ pub unsafe extern "C" fn tw_crypto_box_secret_key_is_valid(data: *const TWData) /// /// \note Should be deleted with \tw_crypto_box_secret_key_delete. /// \return Nullable pointer to Private Key. +#[tw_ffi(ty = constructor, class = TWCryptoBoxSecretKey, name = Create)] #[no_mangle] -pub unsafe extern "C" fn tw_crypto_box_secret_key_create() -> *mut TWCryptoBoxSecretKey { +pub unsafe extern "C" fn tw_crypto_box_secret_key_create() -> NonnullMut { TWCryptoBoxSecretKey(SecretKey::random()).into_ptr() } @@ -40,10 +44,11 @@ pub unsafe extern "C" fn tw_crypto_box_secret_key_create() -> *mut TWCryptoBoxSe /// \param data *non-null* byte array. Expected to have 32 bytes. /// \note Should be deleted with \tw_crypto_box_secret_key_delete. /// \return Nullable pointer to Secret Key. +#[tw_ffi(ty = constructor, class = TWCryptoBoxSecretKey, name = CreateWithData)] #[no_mangle] pub unsafe extern "C" fn tw_crypto_box_secret_key_create_with_data( - data: *const TWData, -) -> *mut TWCryptoBoxSecretKey { + data: Nonnull, +) -> NullableMut { let bytes_ref = try_or_else!(TWData::from_ptr_as_ref(data), std::ptr::null_mut); let secret = try_or_else!( SecretKey::try_from(bytes_ref.as_slice()), @@ -55,8 +60,9 @@ pub unsafe extern "C" fn tw_crypto_box_secret_key_create_with_data( /// Delete the given secret `key`. /// /// \param key *non-null* pointer to secret key. +#[tw_ffi(ty = destructor, class = TWCryptoBoxSecretKey, name = Delete)] #[no_mangle] -pub unsafe extern "C" fn tw_crypto_box_secret_key_delete(key: *mut TWCryptoBoxSecretKey) { +pub unsafe extern "C" fn tw_crypto_box_secret_key_delete(key: NonnullMut) { // Take the ownership back to rust and drop the owner. let _ = TWCryptoBoxSecretKey::from_ptr(key); } @@ -65,10 +71,11 @@ pub unsafe extern "C" fn tw_crypto_box_secret_key_delete(key: *mut TWCryptoBoxSe /// /// \param key *non-null* pointer to the private key. /// \return *non-null* pointer to the corresponding public key. +#[tw_ffi(ty = method, class = TWCryptoBoxSecretKey, name = GetPublicKey)] #[no_mangle] pub unsafe extern "C" fn tw_crypto_box_secret_key_get_public_key( - key: *mut TWCryptoBoxSecretKey, -) -> *mut TWCryptoBoxPublicKey { + key: NonnullMut, +) -> NonnullMut { let secret = try_or_else!( TWCryptoBoxSecretKey::from_ptr_as_ref(key), std::ptr::null_mut @@ -80,10 +87,11 @@ pub unsafe extern "C" fn tw_crypto_box_secret_key_get_public_key( /// /// \param secret_key *non-null* pointer to a secret key. /// \return C-compatible result with a C-compatible byte array. +#[tw_ffi(ty = property, class = TWCryptoBoxSecretKey, name = Data)] #[no_mangle] pub unsafe extern "C" fn tw_crypto_box_secret_key_data( - secret_key: *const TWCryptoBoxSecretKey, -) -> *mut TWData { + secret_key: Nonnull, +) -> NonnullMut { let secret_ref = try_or_else!( TWCryptoBoxSecretKey::from_ptr_as_ref(secret_key), std::ptr::null_mut diff --git a/rust/tw_macros/src/code_gen.rs b/rust/tw_macros/src/code_gen.rs index 3b311486b9b..5170d5f58c5 100644 --- a/rust/tw_macros/src/code_gen.rs +++ b/rust/tw_macros/src/code_gen.rs @@ -1,13 +1,17 @@ use serde::{Deserialize, Serialize}; -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, Default)] pub struct TWConfig { pub class: String, - pub static_functions: Vec, + pub static_functions: Vec, + pub constructors: Option>, + pub destructor: Option, + pub methods: Option>, + pub properties: Option>, } #[derive(Deserialize, Serialize, Debug)] -pub struct TWStaticFunction { +pub struct TWFunction { pub name: String, pub rust_name: String, pub args: Vec, diff --git a/rust/tw_macros/src/tw_ffi.rs b/rust/tw_macros/src/tw_ffi.rs index fed4eb46e0a..983a3b3bee4 100644 --- a/rust/tw_macros/src/tw_ffi.rs +++ b/rust/tw_macros/src/tw_ffi.rs @@ -11,7 +11,7 @@ use std::fmt; use std::fs; use std::path::Path; -use crate::code_gen::{TWArg, TWConfig, TWStaticFunction}; +use crate::code_gen::{TWArg, TWConfig, TWFunction}; pub mod keywords { use syn::custom_keyword; @@ -20,11 +20,19 @@ pub mod keywords { custom_keyword!(class); custom_keyword!(name); custom_keyword!(static_function); + custom_keyword!(constructor); + custom_keyword!(destructor); + custom_keyword!(method); + custom_keyword!(property); } #[derive(Clone, Debug, PartialEq, Eq)] pub enum TWFFIType { StaticFunction, + Constructor, + Destructor, + Method, + Property, } impl Parse for TWFFIType { @@ -33,6 +41,18 @@ impl Parse for TWFFIType { if lookahead.peek(keywords::static_function) { input.parse::()?; Ok(Self::StaticFunction) + } else if lookahead.peek(keywords::constructor) { + input.parse::()?; + Ok(Self::Constructor) + } else if lookahead.peek(keywords::destructor) { + input.parse::()?; + Ok(Self::Destructor) + } else if lookahead.peek(keywords::method) { + input.parse::()?; + Ok(Self::Method) + } else if lookahead.peek(keywords::property) { + input.parse::()?; + Ok(Self::Property) } else { Err(lookahead.error()) } @@ -43,6 +63,10 @@ impl fmt::Display for TWFFIType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { TWFFIType::StaticFunction => write!(f, "static_function"), + TWFFIType::Constructor => write!(f, "constructor"), + TWFFIType::Destructor => write!(f, "destructor"), + TWFFIType::Method => write!(f, "method"), + TWFFIType::Property => write!(f, "property"), } } } @@ -53,7 +77,7 @@ pub struct TWFFIAttrArgs { #[parse_if(_ty_keyword.is_some())] pub _eq: Option, #[parse_if(_ty_keyword.is_some())] - pub _ty: Option, + pub ty: Option, #[parse_if(_ty_keyword.is_some())] pub _comma: Option, @@ -72,6 +96,18 @@ pub struct TWFFIAttrArgs { pub name: Option, } +fn update_or_append_function(functions: &mut Option>, function: TWFunction) { + if let Some(funcs) = functions { + if let Some(idx) = funcs.iter().position(|f| f.name == function.name) { + funcs[idx] = function; + } else { + funcs.push(function); + } + } else { + *functions = Some(vec![function]); + } +} + pub fn tw_ffi(attr: TokenStream2, item: TokenStream2) -> Result { let args = parse2::(attr)?; @@ -116,7 +152,7 @@ pub fn tw_ffi(attr: TokenStream2, item: TokenStream2) -> Result { None }) .collect::>(); - let static_function = TWStaticFunction { + let function = TWFunction { name: args.name.unwrap().to_string(), rust_name: func_name, args: func_args, @@ -137,31 +173,47 @@ pub fn tw_ffi(attr: TokenStream2, item: TokenStream2) -> Result { let _ = fs::remove_file(&yaml_file_path); TWConfig { class, - static_functions: vec![], + ..Default::default() } }, }, Err(_) => TWConfig { class, - static_functions: vec![], + ..Default::default() }, } } else { TWConfig { class, - static_functions: vec![], + ..Default::default() } }; - if let Some(idx) = config - .static_functions - .iter() - .position(|f| f.name == static_function.name) - { - config.static_functions[idx] = static_function; - } else { - config.static_functions.push(static_function); + match args.ty { + Some(TWFFIType::StaticFunction) => { + if let Some(idx) = config + .static_functions + .iter() + .position(|f| f.name == function.name) + { + config.static_functions[idx] = function; + } else { + config.static_functions.push(function); + } + }, + Some(TWFFIType::Constructor) => { + update_or_append_function(&mut config.constructors, function); + }, + Some(TWFFIType::Destructor) => { + config.destructor = Some(function); + }, + Some(TWFFIType::Method) => { + update_or_append_function(&mut config.methods, function); + }, + Some(TWFFIType::Property) => { + update_or_append_function(&mut config.properties, function); + }, + _ => panic!("Invalid FFI type"), } - let yaml_output: String = serde_yaml::to_string(&config).expect("Failed to serialize to YAML"); fs::write(&yaml_file_path, yaml_output).expect("Failed to write YAML file"); diff --git a/src/interface/TWCryptoBoxPublicKey.cpp b/src/interface/TWCryptoBoxPublicKey.cpp deleted file mode 100644 index 3dc0e10ec08..00000000000 --- a/src/interface/TWCryptoBoxPublicKey.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include "TrustWalletCore/TWCryptoBoxPublicKey.h" -#include "CryptoBox.h" - -using namespace TW; - -bool TWCryptoBoxPublicKeyIsValid(TWData* _Nonnull data) { - auto& bytes = *reinterpret_cast(data); - return CryptoBox::PublicKey::isValid(bytes); -} - -struct TWCryptoBoxPublicKey* _Nullable TWCryptoBoxPublicKeyCreateWithData(TWData* _Nonnull data) { - auto& bytes = *reinterpret_cast(data); - auto publicKey = CryptoBox::PublicKey::fromBytes(bytes); - if (!publicKey) { - return nullptr; - } - return new TWCryptoBoxPublicKey { publicKey.value() }; -} - -void TWCryptoBoxPublicKeyDelete(struct TWCryptoBoxPublicKey* _Nonnull publicKey) { - delete publicKey; -} - -TWData* _Nonnull TWCryptoBoxPublicKeyData(struct TWCryptoBoxPublicKey* _Nonnull publicKey) { - auto bytes = publicKey->impl.getData(); - return TWDataCreateWithBytes(bytes.data(), bytes.size()); -} diff --git a/src/interface/TWCryptoBoxSecretKey.cpp b/src/interface/TWCryptoBoxSecretKey.cpp deleted file mode 100644 index 0a16fa6a06f..00000000000 --- a/src/interface/TWCryptoBoxSecretKey.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include "TrustWalletCore/TWCryptoBoxSecretKey.h" -#include "CryptoBox.h" - -using namespace TW; - -bool TWCryptoBoxSecretKeyIsValid(TWData* _Nonnull data) { - auto& bytes = *reinterpret_cast(data); - return CryptoBox::SecretKey::isValid(bytes); -} - -struct TWCryptoBoxSecretKey* _Nullable TWCryptoBoxSecretKeyCreateWithData(TWData* _Nonnull data) { - auto& bytes = *reinterpret_cast(data); - auto secretKey = CryptoBox::SecretKey::fromBytes(bytes); - if (!secretKey) { - return nullptr; - } - return new TWCryptoBoxSecretKey { secretKey.value() }; -} - -struct TWCryptoBoxSecretKey* _Nonnull TWCryptoBoxSecretKeyCreate() { - CryptoBox::SecretKey secretKey; - return new TWCryptoBoxSecretKey { secretKey }; -} - -void TWCryptoBoxSecretKeyDelete(struct TWCryptoBoxSecretKey* _Nonnull key) { - delete key; -} - -struct TWCryptoBoxPublicKey* TWCryptoBoxSecretKeyGetPublicKey(struct TWCryptoBoxSecretKey* _Nonnull key) { - auto publicKey = key->impl.getPublicKey(); - return new TWCryptoBoxPublicKey { publicKey }; -} - -TWData* _Nonnull TWCryptoBoxSecretKeyData(struct TWCryptoBoxSecretKey* _Nonnull secretKey) { - auto bytes = secretKey->impl.getData(); - return TWDataCreateWithBytes(bytes.data(), bytes.size()); -} From 7a6e7279eb13b3e94008dc01809aebf842ec52fd Mon Sep 17 00:00:00 2001 From: gupnik Date: Thu, 6 Mar 2025 16:57:49 +0530 Subject: [PATCH 04/72] Allows extension to import a wallet with private key (#4297) --- wasm/src/keystore/default-impl.ts | 4 +- wasm/src/keystore/types.ts | 2 +- wasm/tests/KeyStore.test.ts | 64 +++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 wasm/tests/KeyStore.test.ts diff --git a/wasm/src/keystore/default-impl.ts b/wasm/src/keystore/default-impl.ts index 2e758bc0244..e7f9bcbf8fd 100644 --- a/wasm/src/keystore/default-impl.ts +++ b/wasm/src/keystore/default-impl.ts @@ -139,11 +139,9 @@ export class Default implements Types.IKeyStore { ): Promise { return this.load(id).then((wallet) => { let storedKey = this.mapStoredKey(wallet); - let hdWallet = storedKey.wallet(Buffer.from(password)); let coin = (this.core.CoinType as any).values["" + account.coin]; - let privateKey = hdWallet.getKey(coin, account.derivationPath); + let privateKey = storedKey.privateKey(coin, Buffer.from(password)); storedKey.delete(); - hdWallet.delete(); return privateKey; }); } diff --git a/wasm/src/keystore/types.ts b/wasm/src/keystore/types.ts index 688401c2134..63f9ba13961 100644 --- a/wasm/src/keystore/types.ts +++ b/wasm/src/keystore/types.ts @@ -6,7 +6,7 @@ import { CoinType, Derivation, PrivateKey, StoredKeyEncryption } from "../wallet export enum WalletType { Mnemonic = "mnemonic", - PrivateKey = "privateKey", + PrivateKey = "private-key", WatchOnly = "watchOnly", Hardware = "hardware", } diff --git a/wasm/tests/KeyStore.test.ts b/wasm/tests/KeyStore.test.ts new file mode 100644 index 00000000000..27bbc3ea17f --- /dev/null +++ b/wasm/tests/KeyStore.test.ts @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; +import { KeyStore } from "../dist"; + +describe("KeyStore", () => { + it("test get key", async () => { + const { CoinType, StoredKeyEncryption, HexCoding } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + + const storage = new KeyStore.FileSystemStorage("/tmp"); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ], StoredKeyEncryption.aes128Ctr); + + const account = wallet.activeAccounts[0]; + const key = await keystore.getKey(wallet.id, password, account); + + assert.equal( + HexCoding.encode(key.data()), + "0xd2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f" + ); + + const inputPrivateKey = Buffer.from("9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c", "hex"); + wallet = await keystore.importKey(inputPrivateKey, "Coolw", password, CoinType.bitcoin, StoredKeyEncryption.aes128Ctr); + + const account2 = wallet.activeAccounts[0]; + const key2 = await keystore.getKey(wallet.id, password, account2); + + assert.equal( + HexCoding.encode(key2.data()), + "0x9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c" + ); + }).timeout(10000); + + it("test export", async () => { + const { CoinType, StoredKeyEncryption, HexCoding } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + + const storage = new KeyStore.FileSystemStorage("/tmp"); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ], StoredKeyEncryption.aes128Ctr); + + const exported = await keystore.export(wallet.id, password); + assert.equal(exported, mnemonic); + + const inputPrivateKey = Buffer.from("9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c", "hex"); + wallet = await keystore.importKey(inputPrivateKey, "Coolw", password, CoinType.bitcoin, StoredKeyEncryption.aes128Ctr); + + const exported2 = await keystore.export(wallet.id, password); + assert.equal(HexCoding.encode(exported2), "0x9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c"); + }).timeout(10000); +}); From 37dd7eda5a3efaafce4aeaa06c7b86732d7a263b Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Sat, 8 Mar 2025 00:14:41 +0700 Subject: [PATCH 05/72] fix(xrp): Fix Ripple.proto Kotlin compatibility (#4299) Some parameters were changed from int64 to uint32 during the recent XRP Rust migration. However `protoc` generates Kotlin bindings with using signed integers only, so overflow happens on Kotlin side * Ripple.OperationPayment.destinationTag is now uint64 * Ripple.OperationEscrowCreate.destinationTag is now uint64 * Ripple.OperationEscrowCreate.cancelAfter is now uint64 * Ripple.OperationEscrowCreate.finishAfter is now uint64 * Ripple.SigningInput.flags is now uint64 * Ripple.SigningInput.sourceTag is now uint64 --- .../tw_ripple/src/modules/protobuf_builder.rs | 36 +++++++++++++++---- .../tests/chains/ripple/ripple_sign.rs | 2 +- src/proto/Ripple.proto | 26 ++++++++------ 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/rust/chains/tw_ripple/src/modules/protobuf_builder.rs b/rust/chains/tw_ripple/src/modules/protobuf_builder.rs index e5749695f60..90d7ed94248 100644 --- a/rust/chains/tw_ripple/src/modules/protobuf_builder.rs +++ b/rust/chains/tw_ripple/src/modules/protobuf_builder.rs @@ -152,7 +152,9 @@ impl<'a> ProtobufBuilder<'a> { .to_classic_address() .into_tw() .context("Error converting 'Payment.destination' to a Classic address")?; - let destination_tag = payment.destination_tag.zero_or_some(); + let destination_tag = payment + .destination_tag + .try_into_u32_optional("destinationTag")?; self.prepare_builder()? .payment(amount, destination, destination_tag) @@ -200,9 +202,15 @@ impl<'a> ProtobufBuilder<'a> { .escrow_create( native_amount, destination, - escrow_create.destination_tag.zero_or_some(), - escrow_create.cancel_after.zero_or_some(), - escrow_create.finish_after.zero_or_some(), + escrow_create + .destination_tag + .try_into_u32_optional("destinationTag")?, + escrow_create + .cancel_after + .try_into_u32_optional("cancelAfter")?, + escrow_create + .finish_after + .try_into_u32_optional("finishAfter")?, condition, ) .map(TransactionType::EscrowCreate) @@ -323,13 +331,13 @@ impl<'a> ProtobufBuilder<'a> { let mut builder = TransactionBuilder::default(); builder .fee(fee) - .flags(self.input.flags) + .flags(self.input.flags.try_into_u32("inputFlags")?) .sequence(self.input.sequence) .last_ledger_sequence(self.input.last_ledger_sequence) .account_str(self.input.account.as_ref())? .signing_pub_key(&signing_public_key); if self.input.source_tag != 0 { - builder.source_tag(self.input.source_tag); + builder.source_tag(self.input.source_tag.try_into_u32("sourceTag")?); } Ok(builder) } @@ -369,3 +377,19 @@ impl<'a> ProtobufBuilder<'a> { }) } } + +trait AsUint32: TryInto { + fn try_into_u32(self, param: &str) -> SigningResult { + self.try_into() + .map_err(|_| SigningError::new(SigningErrorType::Error_invalid_params)) + .with_context(|| format!("'{param}' must fit u32")) + } + + /// Tries to convert `self` as `u32`. + /// Returns error if `self` doesn't fit `u32` type, or returns `None` if `self` is 0. + fn try_into_u32_optional(self, param: &str) -> SigningResult> { + self.try_into_u32(param).map(u32::zero_or_some) + } +} + +impl AsUint32 for T where T: TryInto {} diff --git a/rust/tw_tests/tests/chains/ripple/ripple_sign.rs b/rust/tw_tests/tests/chains/ripple/ripple_sign.rs index dae815bdbb0..57e983dcb37 100644 --- a/rust/tw_tests/tests/chains/ripple/ripple_sign.rs +++ b/rust/tw_tests/tests/chains/ripple/ripple_sign.rs @@ -10,7 +10,7 @@ use tw_proto::Ripple::Proto; use tw_proto::Ripple::Proto::mod_OperationPayment::OneOfamount_oneof as AmountType; use tw_proto::Ripple::Proto::mod_SigningInput::OneOfoperation_oneof as OperationType; -const SELL_NFTOKEN_FLAG: u32 = 0x00000001; +const SELL_NFTOKEN_FLAG: u64 = 0x00000001; #[test] fn test_ripple_sign_xrp_payment_0() { diff --git a/src/proto/Ripple.proto b/src/proto/Ripple.proto index 4962acac13f..5d8e0f89bab 100644 --- a/src/proto/Ripple.proto +++ b/src/proto/Ripple.proto @@ -37,7 +37,8 @@ message OperationPayment { string destination = 3; // A Destination Tag - uint32 destination_tag = 4; + // It must fit uint32 + uint64 destination_tag = 4; } // https://xrpl.org/escrowcreate.html @@ -49,13 +50,16 @@ message OperationEscrowCreate { string destination = 2; // Destination Tag - uint32 destination_tag = 3; + // It must fit uint32 + uint64 destination_tag = 3; // Escrow expire time - uint32 cancel_after = 4; + // It must fit uint32 + uint64 cancel_after = 4; // Escrow release time - uint32 finish_after = 5; + // It must fit uint32 + uint64 finish_after = 5; // Hex-encoded crypto condition // https://datatracker.ietf.org/doc/html/draft-thomas-crypto-conditions-02#section-8.1 @@ -86,13 +90,13 @@ message OperationEscrowFinish { string fulfillment = 4; } -// https://xrpl.org/nftokenburn.html +// https://xrpl.org/nftokenburn.html message OperationNFTokenBurn { // Hex-encoded H256 NFTokenId string nftoken_id = 1; } -// https://xrpl.org/nftokencreateoffer.html +// https://xrpl.org/nftokencreateoffer.html message OperationNFTokenCreateOffer { // Hex-encoded Hash256 NFTokenId string nftoken_id = 1; @@ -101,13 +105,13 @@ message OperationNFTokenCreateOffer { string destination = 2; } -// https://xrpl.org/nftokenacceptoffer.html +// https://xrpl.org/nftokenacceptoffer.html message OperationNFTokenAcceptOffer { // Hex-encoded Hash256 NFTokenOffer string sell_offer = 1; } -// https://xrpl.org/nftokencanceloffer.html +// https://xrpl.org/nftokencanceloffer.html message OperationNFTokenCancelOffer { // Hex-encoded Vector256 NFTokenOffers repeated string token_offers = 1; @@ -128,7 +132,8 @@ message SigningInput { string account = 4; // Transaction flags, optional - uint32 flags = 5; + // It must fit uint32 + uint64 flags = 5; // The secret private key used for signing (32 bytes). bytes private_key = 6; @@ -167,7 +172,8 @@ message SigningInput { // Arbitrary integer used to identify the reason for this payment, or a sender on whose behalf this transaction is made. // Conventionally, a refund should specify the initial payment's SourceTag as the refund payment's DestinationTag. - uint32 source_tag = 25; + // It must fit uint32. + uint64 source_tag = 25; } // Result containing the signed and encoded transaction. From 80829656a0bfb67f4932f3a274e0f3b3138761a2 Mon Sep 17 00:00:00 2001 From: gupnik Date: Tue, 11 Mar 2025 21:48:35 +0530 Subject: [PATCH 06/72] [ETH]: Adds support for EIP 7702 authorization hash (#4304) * [ETH]: Adds support for EIP 7702 authorization hash * Uses TWStrings to pass chainId and nonce * Minor fix * Addresses review comments * Minor fix --- .../core/app/blockchains/ethereum/TestBarz.kt | 10 +++++++++ include/TrustWalletCore/TWBarz.h | 11 ++++++++++ src/Ethereum/Barz.cpp | 21 +++++++++++++++++++ src/Ethereum/Barz.h | 2 +- src/interface/TWBarz.cpp | 10 ++++++++- swift/Tests/BarzTests.swift | 9 ++++++++ tests/chains/Ethereum/BarzTests.cpp | 11 +++++++++- 7 files changed, 71 insertions(+), 3 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt index 235cd442b73..e2930c99271 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt @@ -226,4 +226,14 @@ class TestBarz { assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x84d0464f5a2b191e06295443970ecdcd2d18f565d0d52b5a79443192153770ab"); assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1E6c542ebC7c960c6A155A9094DB838cEf842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}") } + + @Test + fun testAuthorizationHash() { + val chainId = "0x01".toHexByteArray() + val contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" + val nonce = "0x01".toHexByteArray() + + val authorizationHash = WCBarz.getAuthorizationHash(chainId, contractAddress, nonce) + assertEquals(Numeric.toHexString(authorizationHash), "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9") // Verified with viem + } } diff --git a/include/TrustWalletCore/TWBarz.h b/include/TrustWalletCore/TWBarz.h index 1454e219b5d..5a9b3ad4ec7 100644 --- a/include/TrustWalletCore/TWBarz.h +++ b/include/TrustWalletCore/TWBarz.h @@ -55,4 +55,15 @@ TWData *_Nonnull TWBarzGetPrefixedMsgHash(TWData* _Nonnull msgHash, TWString* _N /// \return The encoded bytes of diamondCut function call TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWBarzGetDiamondCutCode(TWData *_Nonnull input); + +/// Computes an Authorization hash in [EIP-7702 format](https://eips.ethereum.org/EIPS/eip-7702) +/// `keccak256('0x05' || rlp([chain_id, address, nonce]))`. +/// +/// \param chainId The chainId of the network +/// \param contractAddress The address of the contract to be authorized +/// \param nonce The nonce of the transaction +/// \return The authorization hash +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWBarzGetAuthorizationHash(TWData* _Nonnull chainId, TWString* _Nonnull contractAddress, TWData* _Nonnull nonce); + TW_EXTERN_C_END diff --git a/src/Ethereum/Barz.cpp b/src/Ethereum/Barz.cpp index 6002af3692b..00906e53d02 100644 --- a/src/Ethereum/Barz.cpp +++ b/src/Ethereum/Barz.cpp @@ -12,6 +12,8 @@ #include "../proto/Barz.pb.h" #include "AsnParser.h" #include "Base64.h" +#include "../proto/EthereumRlp.pb.h" +#include "RLP.h" namespace TW::Barz { @@ -218,4 +220,23 @@ Data getDiamondCutCode(const Proto::DiamondCutInput& input) { return encoded; } +Data getAuthorizationHash(const Data& chainId, const std::string& contractAddress, const Data& nonce) { + EthereumRlp::Proto::EncodingInput input; + auto* list = input.mutable_item()->mutable_list(); + + list->add_items()->set_number_u256(chainId.data(), chainId.size()); + + list->add_items()->set_address(contractAddress); + + list->add_items()->set_number_u256(nonce.data(), nonce.size()); + + auto dataOut = Ethereum::RLP::encode(input); + + Data encoded; + append(encoded, parse_hex("0x05")); + append(encoded, dataOut); + + return Hash::keccak256(encoded); +} + } // namespace TW::Barz diff --git a/src/Ethereum/Barz.h b/src/Ethereum/Barz.h index f29afc4eeed..f7893ae92f8 100644 --- a/src/Ethereum/Barz.h +++ b/src/Ethereum/Barz.h @@ -16,5 +16,5 @@ Data getInitCode(const std::string& factoryAddress, const PublicKey& publicKey, Data getFormattedSignature(const Data& signature, const Data challenge, const Data& authenticatorData, const std::string& clientDataJSON); Data getPrefixedMsgHash(const Data msgHash, const std::string& barzAddress, const uint32_t chainId); Data getDiamondCutCode(const Proto::DiamondCutInput& input); // action should be one of 0, 1, 2. 0 = Add, 1 = Remove, 2 = Replace - +Data getAuthorizationHash(const Data& chainId, const std::string& contractAddress, const Data& nonce); } diff --git a/src/interface/TWBarz.cpp b/src/interface/TWBarz.cpp index 60da9690279..52140d23c2e 100644 --- a/src/interface/TWBarz.cpp +++ b/src/interface/TWBarz.cpp @@ -57,4 +57,12 @@ TWData *_Nonnull TWBarzGetDiamondCutCode(TWData *_Nonnull input) { const auto diamondCutCode = TW::Barz::getDiamondCutCode(inputProto); return TWDataCreateWithData(&diamondCutCode); -} \ No newline at end of file +} + +TWData *_Nonnull TWBarzGetAuthorizationHash(TWData* _Nonnull chainId, TWString* _Nonnull contractAddress, TWString* _Nonnull nonce) { + const auto& chainIdData = *reinterpret_cast(chainId); + const auto& contractAddressStr = *reinterpret_cast(contractAddress); + const auto& nonceData = *reinterpret_cast(nonce); + const auto authorizationHash = TW::Barz::getAuthorizationHash(chainIdData, contractAddressStr, nonceData); + return TWDataCreateWithData(&authorizationHash); +} diff --git a/swift/Tests/BarzTests.swift b/swift/Tests/BarzTests.swift index c5a40f64363..c30d27e070c 100644 --- a/swift/Tests/BarzTests.swift +++ b/swift/Tests/BarzTests.swift @@ -185,6 +185,15 @@ class BarzTests: XCTestCase { XCTAssertEqual(output.preHash.hexString, "84d0464f5a2b191e06295443970ecdcd2d18f565d0d52b5a79443192153770ab") XCTAssertEqual(String(data: output.encoded, encoding: .utf8), "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1E6c542ebC7c960c6A155A9094DB838cEf842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}") } + + func testAuthorizationHash() { + let chainId = Data(hexString: "0x01")! + let contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" + let nonce = Data(hexString: "0x01")! + + let authorizationHash = Barz.getAuthorizationHash(chainId: chainId, contractAddress: contractAddress, nonce: nonce) + XCTAssertEqual(authorizationHash.hexString, "3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9") // Verified with viem + } let factory = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800" let diamondCutFacet = "0x312382b3B302bDcC0711fe710314BE298426296f" diff --git a/tests/chains/Ethereum/BarzTests.cpp b/tests/chains/Ethereum/BarzTests.cpp index 479726d7393..e01936d1ffa 100644 --- a/tests/chains/Ethereum/BarzTests.cpp +++ b/tests/chains/Ethereum/BarzTests.cpp @@ -15,7 +15,6 @@ #include namespace TW::Barz::tests { - // https://testnet.bscscan.com/tx/0x6c6e1fe81c722c0abce1856b9b4e078ab2cad06d51f2d1b04945e5ba2286d1b4 TEST(Barz, GetInitCode) { const PublicKey& publicKey = PublicKey(parse_hex("0x04e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b02"), TWPublicKeyTypeNIST256p1Extended); @@ -414,5 +413,15 @@ TEST(Barz, GetDiamondCutCodeWithLongInitData) { } } +TEST(Barz, GetAuthorizationHash) { + { + const auto chainId = store(uint256_t(1)); + const auto contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; + const auto nonce = store(uint256_t(1)); + + const auto& authorizationHash = Barz::getAuthorizationHash(chainId, contractAddress, nonce); + ASSERT_EQ(hexEncoded(authorizationHash), "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9"); // Verified with viem + } } +} From 6f9366071c226b900cf6ab37d730dd9ae417e994 Mon Sep 17 00:00:00 2001 From: gupnik Date: Mon, 17 Mar 2025 20:05:28 +0530 Subject: [PATCH 07/72] [ETH]: Adds support for User Operation v0.7 to enable EIP-7702 compatible ERC 4337 transactions (#4306) * [ETH]: Adds support for User Operation v0.7 * Uses non-optional fields in proto for now * Minor fix * Introduces PackedUserOperation and aligns fields with ERC4337 spec * Adds one_of for User Operation * Adds tests * Adds API to get encoded hash * Addresses review comment * Uses encodeParams * Adds API to get signed hash * Adds API to sign authorisation hash * Addresses review comment --- include/TrustWalletCore/TWBarz.h | 30 ++ rust/tw_evm/src/modules/tx_builder.rs | 125 ++++++- rust/tw_evm/src/transaction/mod.rs | 1 + .../src/transaction/user_operation_v0_7.rs | 343 ++++++++++++++++++ rust/tw_evm/tests/barz.rs | 62 +++- src/Ethereum/Barz.cpp | 85 +++++ src/Ethereum/Barz.h | 3 + src/interface/TWBarz.cpp | 27 ++ src/proto/Ethereum.proto | 40 +- tests/chains/Ethereum/BarzTests.cpp | 43 +++ 10 files changed, 747 insertions(+), 12 deletions(-) create mode 100644 rust/tw_evm/src/transaction/user_operation_v0_7.rs diff --git a/include/TrustWalletCore/TWBarz.h b/include/TrustWalletCore/TWBarz.h index 5a9b3ad4ec7..6c3f2585af7 100644 --- a/include/TrustWalletCore/TWBarz.h +++ b/include/TrustWalletCore/TWBarz.h @@ -66,4 +66,34 @@ TWData *_Nonnull TWBarzGetDiamondCutCode(TWData *_Nonnull input); TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWBarzGetAuthorizationHash(TWData* _Nonnull chainId, TWString* _Nonnull contractAddress, TWData* _Nonnull nonce); +/// Returns the signed authorization hash +/// +/// \param chainId The chainId of the network +/// \param contractAddress The address of the contract to be authorized +/// \param nonce The nonce of the transaction +/// \param privateKey The private key +/// \return A json string of the signed authorization +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWBarzSignAuthorization(TWData* _Nonnull chainId, TWString* _Nonnull contractAddress, TWData* _Nonnull nonce, TWString* _Nonnull privateKey); + +/// Returns the encoded hash of the user operation +/// +/// \param chainId The chainId of the network +/// \param wallet The address of the wallet +/// \param version The version of the wallet +/// \param typeHash The type hash of the transaction +/// \param domainSeparatorHash The domain separator hash of the wallet +/// \param hash The hash of the user operation +/// \return The encoded hash of the user operation +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWBarzGetEncodedHash(TWData* _Nonnull chainId, TWString* _Nonnull wallet, TWString* _Nonnull version, TWString* _Nonnull typeHash, TWString* _Nonnull domainSeparatorHash, TWString* _Nonnull hash); + +/// Signs a message using the private key +/// +/// \param hash The hash to sign +/// \param privateKey The private key +/// \return The signature +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWBarzGetSignedHash(TWString* _Nonnull hash, TWString* _Nonnull privateKey); + TW_EXTERN_C_END diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs index b4a141a1737..932d87f31f2 100644 --- a/rust/tw_evm/src/modules/tx_builder.rs +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -13,6 +13,7 @@ use crate::transaction::access_list::{Access, AccessList}; use crate::transaction::transaction_eip1559::TransactionEip1559; use crate::transaction::transaction_non_typed::TransactionNonTyped; use crate::transaction::user_operation::UserOperation; +use crate::transaction::user_operation_v0_7::UserOperationV0_7; use crate::transaction::UnsignedTransactionBox; use std::marker::PhantomData; use std::str::FromStr; @@ -31,6 +32,7 @@ impl TxBuilder { pub fn tx_from_proto( input: &Proto::SigningInput<'_>, ) -> SigningResult> { + use Proto::mod_SigningInput::OneOfuser_operation_oneof as UserOp; use Proto::mod_Transaction::OneOftransaction_oneof as Tx; use Proto::TransactionMode as TxMode; @@ -148,8 +150,18 @@ impl TxBuilder { let payload = Erc4337SimpleAccount::encode_execute_batch(calls) .map_err(abi_to_signing_error)?; - return Self::user_operation_from_proto(input, payload) - .map(UserOperation::into_boxed); + return match &input.user_operation_oneof { + UserOp::user_operation_v0_7(user_op_v0_7) => { + Self::user_operation_v0_7_from_proto(input, user_op_v0_7, payload) + .map(UserOperationV0_7::into_boxed) + }, + UserOp::user_operation(user_op) => { + Self::user_operation_from_proto(input, user_op, payload) + .map(UserOperation::into_boxed) + }, + UserOp::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("No user operation specified"), + }; }, Tx::None => { return SigningError::err(SigningErrorType::Error_invalid_params) @@ -176,7 +188,18 @@ impl TxBuilder { }) .map_err(abi_to_signing_error)?; - Self::user_operation_from_proto(input, payload)?.into_boxed() + match &input.user_operation_oneof { + UserOp::user_operation_v0_7(user_op_v0_7) => { + Self::user_operation_v0_7_from_proto(input, user_op_v0_7, payload) + .map(UserOperationV0_7::into_boxed) + }, + UserOp::user_operation(user_op) => { + Self::user_operation_from_proto(input, user_op, payload) + .map(UserOperation::into_boxed) + }, + UserOp::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("No user operation specified"), + }? }, }; Ok(tx) @@ -268,13 +291,9 @@ impl TxBuilder { fn user_operation_from_proto( input: &Proto::SigningInput, + user_op: &Proto::UserOperation, erc4337_payload: Data, ) -> SigningResult { - let Some(ref user_op) = input.user_operation else { - return SigningError::err(CommonError::Error_invalid_params) - .context("No user operation specified"); - }; - let nonce = U256::from_big_endian_slice(&input.nonce) .into_tw() .context("Invalid nonce")?; @@ -321,6 +340,96 @@ impl TxBuilder { }) } + fn user_operation_v0_7_from_proto( + input: &Proto::SigningInput, + user_op_v0_7: &Proto::UserOperationV0_7, + erc4337_payload: Data, + ) -> SigningResult { + let sender = Self::parse_address(user_op_v0_7.sender.as_ref()) + .context("Invalid User Operation sender")?; + + let nonce = U256::from_big_endian_slice(&input.nonce) + .into_tw() + .context("Invalid nonce")?; + + let factory = Self::parse_address_optional(user_op_v0_7.factory.as_ref()) + .context("Invalid factory address")?; + + let call_data_gas_limit = U256::from_big_endian_slice(&input.gas_limit) + .into_tw() + .context("Invalid gas limit")? + .try_into() + .into_tw() + .context("Gas limit exceeds u128")?; + + let verification_gas_limit = + U256::from_big_endian_slice(&user_op_v0_7.verification_gas_limit) + .into_tw() + .context("Invalid verification gas limit")? + .try_into() + .into_tw() + .context("Verification gas limit exceeds u128")?; + + let pre_verification_gas = U256::from_big_endian_slice(&user_op_v0_7.pre_verification_gas) + .into_tw() + .context("Invalid pre-verification gas")?; + + let max_fee_per_gas = U256::from_big_endian_slice(&input.max_fee_per_gas) + .into_tw() + .context("Invalid max fee per gas")? + .try_into() + .into_tw() + .context("Max fee per gas exceeds u128")?; + + let max_priority_fee_per_gas = + U256::from_big_endian_slice(&input.max_inclusion_fee_per_gas) + .into_tw() + .context("Invalid max inclusion fee per gas")? + .try_into() + .into_tw() + .context("Max inclusion fee per gas exceeds u128")?; + + let paymaster = Self::parse_address_optional(user_op_v0_7.paymaster.as_ref()) + .context("Invalid paymaster address")?; + + let paymaster_verification_gas_limit = + U256::from_big_endian_slice(&user_op_v0_7.paymaster_verification_gas_limit) + .into_tw() + .context("Invalid paymaster verification gas limit")? + .try_into() + .into_tw() + .context("Paymaster verification gas limit exceeds u128")?; + + let paymaster_post_op_gas_limit = + U256::from_big_endian_slice(&user_op_v0_7.paymaster_post_op_gas_limit) + .into_tw() + .context("Invalid paymaster post-op gas limit")? + .try_into() + .into_tw() + .context("Paymaster post-op gas limit exceeds u128")?; + + let entry_point = Self::parse_address(user_op_v0_7.entry_point.as_ref()) + .context("Invalid entry point")?; + + Ok(UserOperationV0_7 { + sender, + nonce, + factory, + factory_data: user_op_v0_7.factory_data.to_vec(), + call_data: erc4337_payload, + call_data_gas_limit, + verification_gas_limit, + pre_verification_gas, + max_fee_per_gas, + max_priority_fee_per_gas, + paymaster, + paymaster_verification_gas_limit, + paymaster_post_op_gas_limit, + paymaster_data: user_op_v0_7.paymaster_data.to_vec(), + entry_point, + }) + } + fn parse_address(addr: &str) -> SigningResult
{ Context::Address::from_str(addr) .map(Context::Address::into) diff --git a/rust/tw_evm/src/transaction/mod.rs b/rust/tw_evm/src/transaction/mod.rs index 9d4ba81b32b..c3d63b789d8 100644 --- a/rust/tw_evm/src/transaction/mod.rs +++ b/rust/tw_evm/src/transaction/mod.rs @@ -21,6 +21,7 @@ pub mod signature; pub mod transaction_eip1559; pub mod transaction_non_typed; pub mod user_operation; +pub mod user_operation_v0_7; pub trait TransactionCommon { fn payload(&self) -> Data; diff --git a/rust/tw_evm/src/transaction/user_operation_v0_7.rs b/rust/tw_evm/src/transaction/user_operation_v0_7.rs new file mode 100644 index 00000000000..ccf1b08369f --- /dev/null +++ b/rust/tw_evm/src/transaction/user_operation_v0_7.rs @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::abi::encode::encode_tokens; +use crate::abi::non_empty_array::NonEmptyBytes; +use crate::abi::token::Token; +use crate::address::Address; +use crate::transaction::signature::Signature; +use crate::transaction::{SignedTransaction, TransactionCommon, UnsignedTransaction}; +use serde::Serialize; +use tw_coin_entry::error::prelude::*; +use tw_encoding::hex; +use tw_hash::sha3::keccak256; +use tw_hash::H256; +use tw_memory::Data; +use tw_number::U256; + +pub struct PackedUserOperation { + pub sender: Address, + pub nonce: U256, + pub init_code: Data, + pub call_data: Data, + pub account_gas_limits: Data, + pub pre_verification_gas: U256, + pub gas_fees: Data, + pub paymaster_and_data: Data, +} + +impl PackedUserOperation { + // Taken from https://github.com/alchemyplatform/rundler/blob/0caa06ce10717a2214c554995a8fb9f4bd18fa4b/crates/types/src/user_operation/v0_7.rs#L1017 + fn new(user_op: &UserOperationV0_7) -> Self { + let init_code = if let Some(factory) = user_op.factory { + let mut init_code = factory.bytes().into_vec(); + init_code.extend_from_slice(&user_op.factory_data); + init_code + } else { + vec![] + }; + + let account_gas_limits = + concat_u128_be(user_op.verification_gas_limit, user_op.call_data_gas_limit); + let gas_fees = concat_u128_be(user_op.max_fee_per_gas, user_op.max_priority_fee_per_gas); + + let paymaster_and_data = if let Some(paymaster) = user_op.paymaster { + let mut paymaster_and_data = paymaster.bytes().into_vec(); + paymaster_and_data + .extend_from_slice(&user_op.paymaster_verification_gas_limit.to_be_bytes()); + paymaster_and_data + .extend_from_slice(&user_op.paymaster_post_op_gas_limit.to_be_bytes()); + paymaster_and_data.extend_from_slice(&user_op.paymaster_data); + paymaster_and_data + } else { + vec![] + }; + + Self { + sender: user_op.sender, + nonce: user_op.nonce, + init_code, + call_data: user_op.call_data.clone(), + account_gas_limits: account_gas_limits.to_vec(), + pre_verification_gas: user_op.pre_verification_gas, + gas_fees: gas_fees.to_vec(), + paymaster_and_data, + } + } + + fn pre_hash(&self, chain_id: U256, entry_point: Address) -> H256 { + let encode_hash = keccak256(&self.encode()); + let encode_hash = + NonEmptyBytes::new(encode_hash).expect("keccak256 must not return an empty hash"); + + let tokens = [ + Token::FixedBytes(encode_hash), + Token::Address(entry_point), + Token::u256(chain_id), + ]; + let encoded = encode_tokens(&tokens); + let pre_hash = keccak256(&encoded); + H256::try_from(pre_hash.as_slice()).expect("keccak256 returns 32 bytes") + } + + fn encode(&self) -> Data { + let init_code_hash = keccak256(&self.init_code); + let init_code_hash = + NonEmptyBytes::new(init_code_hash).expect("keccak256 must not return an empty hash"); + + let call_data_hash = keccak256(&self.call_data); + let call_data_hash = + NonEmptyBytes::new(call_data_hash).expect("keccak256 must not return an empty hash"); + + let paymaster_and_data_hash = keccak256(&self.paymaster_and_data); + let paymaster_and_data_hash = NonEmptyBytes::new(paymaster_and_data_hash) + .expect("keccak256 must not return an empty hash"); + + let account_gas_limits = NonEmptyBytes::new(self.account_gas_limits.clone()) + .expect("keccak256 must not return an empty hash"); + + let gas_fees = NonEmptyBytes::new(self.gas_fees.clone()) + .expect("keccak256 must not return an empty hash"); + + let tokens = [ + Token::Address(self.sender), + Token::u256(self.nonce), + Token::FixedBytes(init_code_hash), + Token::FixedBytes(call_data_hash), + Token::FixedBytes(account_gas_limits), + Token::u256(self.pre_verification_gas), + Token::FixedBytes(gas_fees), + Token::FixedBytes(paymaster_and_data_hash), + ]; + + encode_tokens(&tokens) + } +} + +pub struct UserOperationV0_7 { + pub sender: Address, + pub nonce: U256, + pub factory: Option
, + pub factory_data: Data, + pub call_data: Data, + pub call_data_gas_limit: u128, + pub verification_gas_limit: u128, + pub pre_verification_gas: U256, + pub max_fee_per_gas: u128, + pub max_priority_fee_per_gas: u128, + pub paymaster: Option
, + pub paymaster_verification_gas_limit: u128, + pub paymaster_post_op_gas_limit: u128, + pub paymaster_data: Data, + + pub entry_point: Address, +} + +impl TransactionCommon for UserOperationV0_7 { + #[inline] + fn payload(&self) -> Data { + self.call_data.clone() + } +} + +impl UnsignedTransaction for UserOperationV0_7 { + type SignedTransaction = SignedUserOperationV0_7; + + fn pre_hash(&self, chain_id: U256) -> H256 { + PackedUserOperation::new(self).pre_hash(chain_id, self.entry_point) + } + + fn encode(&self, _chain_id: U256) -> Data { + PackedUserOperation::new(self).encode() + } + + #[inline] + fn try_into_signed( + self, + signature: tw_keypair::ecdsa::secp256k1::Signature, + _chain_id: U256, + ) -> SigningResult { + Ok(SignedUserOperationV0_7 { + unsigned: self, + signature: Signature::new(signature), + }) + } +} + +pub struct SignedUserOperationV0_7 { + unsigned: UserOperationV0_7, + signature: Signature, +} + +impl TransactionCommon for SignedUserOperationV0_7 { + #[inline] + fn payload(&self) -> Data { + self.unsigned.call_data.clone() + } +} + +impl SignedTransaction for SignedUserOperationV0_7 { + type Signature = Signature; + + fn encode(&self) -> Data { + let mut signature = self.signature.to_rsv_bytes(); + signature[64] += 27; + + let prefix = true; + let tx = SignedUserOperationV0_7Serde { + sender: self.unsigned.sender.to_string(), + nonce: self.unsigned.nonce.to_string(), + factory: self + .unsigned + .factory + .map(|addr| addr.to_string()) + .unwrap_or_default(), + factory_data: hex::encode(&self.unsigned.factory_data, prefix), + call_data: hex::encode(&self.unsigned.call_data, prefix), + call_data_gas_limit: self.unsigned.call_data_gas_limit.to_string(), + verification_gas_limit: self.unsigned.verification_gas_limit.to_string(), + pre_verification_gas: self.unsigned.pre_verification_gas.to_string(), + max_fee_per_gas: self.unsigned.max_fee_per_gas.to_string(), + max_priority_fee_per_gas: self.unsigned.max_priority_fee_per_gas.to_string(), + paymaster: self + .unsigned + .paymaster + .map(|addr| addr.to_string()) + .unwrap_or_default(), + paymaster_verification_gas_limit: self + .unsigned + .paymaster_verification_gas_limit + .to_string(), + paymaster_post_op_gas_limit: self.unsigned.paymaster_post_op_gas_limit.to_string(), + paymaster_data: hex::encode(&self.unsigned.paymaster_data, prefix), + signature: hex::encode(signature.as_slice(), prefix), + }; + serde_json::to_string(&tx) + .expect("Simple structure should never fail on serialization") + .into_bytes() + } + + #[inline] + fn signature(&self) -> &Self::Signature { + &self.signature + } +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct SignedUserOperationV0_7Serde { + sender: String, + nonce: String, + factory: String, + factory_data: String, + call_data: String, + call_data_gas_limit: String, + verification_gas_limit: String, + pre_verification_gas: String, + max_fee_per_gas: String, + max_priority_fee_per_gas: String, + paymaster: String, + paymaster_verification_gas_limit: String, + paymaster_post_op_gas_limit: String, + paymaster_data: String, + signature: String, +} + +fn concat_u128_be(a: u128, b: u128) -> [u8; 32] { + let a = a.to_be_bytes(); + let b = b.to_be_bytes(); + std::array::from_fn(|i| { + if let Some(i) = i.checked_sub(a.len()) { + b[i] + } else { + a[i] + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + use tw_encoding::hex::{DecodeHex, ToHex}; + + // Replicates https://github.com/alchemyplatform/rundler/blob/0caa06ce10717a2214c554995a8fb9f4bd18fa4b/crates/types/src/user_operation/v0_7.rs#L1189 + #[test] + fn test_encode_user_operation() { + let chain_id = U256::from(11155111u64); + let entry_point = Address::from("0x0000000071727De22E5E9d8BAf0edAc6f37da032"); + + let user_op = PackedUserOperation { + sender: Address::from("0xb292Cf4a8E1fF21Ac27C4f94071Cd02C022C414b"), + nonce: U256::from_str("0xF83D07238A7C8814A48535035602123AD6DBFA63000000000000000000000001").unwrap(), + init_code: Vec::default(), + call_data: "e9ae5c530000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001d8b292cf4a8e1ff21ac27c4f94071cd02c022c414b00000000000000000000000000000000000000000000000000000000000000009517e29f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000ad6330089d9a1fe89f4020292e1afe9969a5a2fc00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018e2fbe8980000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000800000000000000000000000002372912728f93ab3daaaebea4f87e6e28476d987000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000".decode_hex().unwrap(), + account_gas_limits: "000000000000000000000000000114fc0000000000000000000000000012c9b5".decode_hex().unwrap(), + pre_verification_gas: U256::from(48916u64), + gas_fees: "000000000000000000000000524121000000000000000000000000109a4a441a".decode_hex().unwrap(), + paymaster_and_data: Vec::default(), + }; + + let encoded = hex::encode(user_op.encode(), false); + let expected = "000000000000000000000000b292cf4a8e1ff21ac27c4f94071cd02c022c414bf83d07238a7c8814a48535035602123ad6dbfa63000000000000000000000001c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470f1b8863cae5d3c89d78ce8e239e0c416de4c9224226a78fbb6c4af63ed0eebf7000000000000000000000000000114fc0000000000000000000000000012c9b5000000000000000000000000000000000000000000000000000000000000bf14000000000000000000000000524121000000000000000000000000109a4a441ac5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; + assert_eq!(encoded, expected); + + let pre_hash = user_op.pre_hash(chain_id, entry_point); + let expected_pre_hash = + H256::from("e486401370d145766c3cf7ba089553214a1230d38662ae532c9b62eb6dadcf7e"); + assert_eq!(pre_hash, expected_pre_hash); + } + + #[test] + fn test_encode_user_operation_2() { + let chain_id = U256::from(31337u64); + let entry_point = Address::from("0x0000000071727De22E5E9d8BAf0edAc6f37da032"); + + let user_op = UserOperationV0_7 { + sender: Address::from("0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"), + nonce: U256::from(0u64), + factory: Some(Address::from("0xf471789937856d80e589f5996cf8b0511ddd9de4")), + factory_data: "f471789937856d80e589f5996cf8b0511ddd9de4".decode_hex().unwrap(), + call_data: "00".decode_hex().unwrap(), + call_data_gas_limit: 100000u128, + verification_gas_limit: 100000u128, + pre_verification_gas: U256::from(1000000u64), + max_fee_per_gas: 100000u128, + max_priority_fee_per_gas: 100000u128, + paymaster: Some(Address::from("0xf62849f9a0b5bf2913b396098f7c7019b51a820a")), + paymaster_verification_gas_limit: 99999u128, + paymaster_post_op_gas_limit: 88888u128, + paymaster_data: "00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b".decode_hex().unwrap(), + entry_point: Address::from("0x0000000071727De22E5E9d8BAf0edAc6f37da032"), + }; + let packed_user_op = PackedUserOperation::new(&user_op); + + let expected_packed_user_op = PackedUserOperation { + sender: Address::from("0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"), + nonce: U256::from(0u64), + init_code: "f471789937856d80e589f5996cf8b0511ddd9de4f471789937856d80e589f5996cf8b0511ddd9de4".decode_hex().unwrap(), + call_data: "00".decode_hex().unwrap(), + account_gas_limits:concat_u128_be(100000u128, 100000u128).to_vec(), + pre_verification_gas: U256::from(1000000u64), + gas_fees: concat_u128_be(100000u128, 100000u128).to_vec(), + paymaster_and_data: "f62849f9a0b5bf2913b396098f7c7019b51a820a0000000000000000000000000001869f00000000000000000000000000015b3800000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b".decode_hex().unwrap(), + }; + assert_eq!(packed_user_op.init_code, expected_packed_user_op.init_code); + assert_eq!( + packed_user_op.paymaster_and_data, + expected_packed_user_op.paymaster_and_data + ); + + let pre_hash = packed_user_op.pre_hash(chain_id, entry_point); + assert_eq!( + H256::to_hex(&pre_hash), + "648c415fb11c6fad4b274a0aea51e808e6e3ad2e500d5e76ee2fb61e7ea4d07c" + ); + let expected_pre_hash = expected_packed_user_op.pre_hash(chain_id, entry_point); + assert_eq!( + H256::to_hex(&expected_pre_hash), + "648c415fb11c6fad4b274a0aea51e808e6e3ad2e500d5e76ee2fb61e7ea4d07c" + ); + } +} diff --git a/rust/tw_evm/tests/barz.rs b/rust/tw_evm/tests/barz.rs index 2b4a250fd26..5fd6ac38dc7 100644 --- a/rust/tw_evm/tests/barz.rs +++ b/rust/tw_evm/tests/barz.rs @@ -5,6 +5,7 @@ use std::borrow::Cow; use tw_coin_entry::error::prelude::*; use tw_encoding::hex; +use tw_encoding::hex::DecodeHex; use tw_evm::abi::prebuild::erc20::Erc20; use tw_evm::address::Address; use tw_evm::evm_context::StandardEvmContext; @@ -43,7 +44,9 @@ fn test_barz_transfer_account_deployed() { transaction: Some(Proto::Transaction { transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), }), - user_operation: Some(user_op), + user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation( + user_op, + ), ..Proto::SigningInput::default() }; @@ -96,7 +99,9 @@ fn test_barz_transfer_account_not_deployed() { transaction: Some(Proto::Transaction { transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), }), - user_operation: Some(user_op), + user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation( + user_op, + ), ..Proto::SigningInput::default() }; @@ -173,7 +178,9 @@ fn test_barz_batched_account_deployed() { Proto::mod_Transaction::Batch { calls }, ), }), - user_operation: Some(user_op), + user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation( + user_op, + ), ..Proto::SigningInput::default() }; @@ -190,3 +197,52 @@ fn test_barz_batched_account_deployed() { "84d0464f5a2b191e06295443970ecdcd2d18f565d0d52b5a79443192153770ab" ); } + +#[test] +fn test_barz_transfer_account_deployed_v0_7() { + let private_key = + hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(0x23_86f2_6fc1_0000), + data: Cow::default(), + }; + let user_op = Proto::UserOperationV0_7 { + entry_point: "0x0000000071727De22E5E9d8BAf0edAc6f37da032".into(), + sender: "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029".into(), + pre_verification_gas: U256::from(1000000u64).to_big_endian_compact().into(), + verification_gas_limit: U256::from(100000u128).to_big_endian_compact().into(), + factory: "0xf471789937856d80e589f5996cf8b0511ddd9de4".into(), + factory_data: "f471789937856d80e589f5996cf8b0511ddd9de4".decode_hex().unwrap().into(), + paymaster: "0xf62849f9a0b5bf2913b396098f7c7019b51a820a".into(), + paymaster_verification_gas_limit: U256::from(99999u128).to_big_endian_compact().into(), + paymaster_post_op_gas_limit: U256::from(88888u128).to_big_endian_compact().into(), + paymaster_data: "00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b".decode_hex().unwrap().into(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(31337u64), + nonce: U256::encode_be_compact(0u64), + tx_mode: Proto::TransactionMode::UserOp, + gas_limit: U256::from(100000u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), + max_inclusion_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), + to_address: "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9".into(), + private_key: private_key.into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + user_operation_oneof: + Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation_v0_7(user_op), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + assert_eq!( + hex::encode(output.pre_hash, false), + "f177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb" + ); +} diff --git a/src/Ethereum/Barz.cpp b/src/Ethereum/Barz.cpp index 00906e53d02..9d1cef7dee7 100644 --- a/src/Ethereum/Barz.cpp +++ b/src/Ethereum/Barz.cpp @@ -14,6 +14,8 @@ #include "Base64.h" #include "../proto/EthereumRlp.pb.h" #include "RLP.h" +#include "PrivateKey.h" +#include namespace TW::Barz { @@ -239,4 +241,87 @@ Data getAuthorizationHash(const Data& chainId, const std::string& contractAddres return Hash::keccak256(encoded); } +std::vector getRSVY(const Data& hash, const std::string& privateKey) { + auto privateKeyData = parse_hex(privateKey); + auto privateKeyObj = PrivateKey(privateKeyData); + auto signature = privateKeyObj.sign(hash, TWCurveSECP256k1); + if (signature.empty()) { + return {}; + } + // Extract r, s, v values from the signature + Data r; + Data s; + Data v; + Data yParity; + // r value (first 32 bytes) + append(r, subData(signature, 0, 32)); + // s value (next 32 bytes) + append(s, subData(signature, 32, 32)); + // v value (last byte, should be 0 or 1, add 27 to make it 27 or 28) + uint8_t vValue = signature[64] + 27; + append(v, vValue); + // yParity value (last byte) + uint8_t yParityValue = signature[64]; + append(yParity, yParityValue); + + return {r, s, v, yParity}; +} + +std::string signAuthorization(const Data& chainId, const std::string& contractAddress, const Data& nonce, const std::string& privateKey) { + auto authorizationHash = getAuthorizationHash(chainId, contractAddress, nonce); + auto rsvy = getRSVY(authorizationHash, privateKey); + + nlohmann::json jsonObj; + jsonObj["chainId"] = hexEncoded(chainId); + jsonObj["address"] = contractAddress; + jsonObj["nonce"] = hexEncoded(nonce); + jsonObj["yParity"] = hexEncoded(rsvy[3]); + jsonObj["r"] = hexEncoded(rsvy[0]); + jsonObj["s"] = hexEncoded(rsvy[1]); + + return jsonObj.dump(); +} + +Data getEncodedHash(const Data& chainId, const std::string& wallet, const std::string& version, const std::string& typeHash, const std::string& domainSeparatorHash, const std::string& hash) { + // Create domain separator: keccak256(abi.encode(BIZ_DOMAIN_SEPARATOR_HASH, block.chainid, wallet, "v0.1.0")) + auto domainSeparator = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { + std::make_shared(parse_hex(domainSeparatorHash)), + std::make_shared(chainId), + std::make_shared(wallet), + std::make_shared(version) + }); + if (!domainSeparator.has_value()) { + return {}; + } + Data domainSeparatorEncodedHash = Hash::keccak256(domainSeparator.value()); + + // Create message hash: keccak256(abi.encode(typeHash, keccak256(abi.encode(hash)))) + Data encodedHash; + Ethereum::ABI::ValueEncoder::encodeBytes(parse_hex(hash), encodedHash); + Data innerHash = Hash::keccak256(encodedHash); + + Data messageData; + Ethereum::ABI::ValueEncoder::encodeBytes(parse_hex(typeHash), messageData); + Ethereum::ABI::ValueEncoder::encodeBytes(innerHash, messageData); + Data messageHash = Hash::keccak256(messageData); + + // Final hash: keccak256(abi.encodePacked("\x19\x01", domainSeparator, messageHash)) + Data encoded; + append(encoded, parse_hex("0x1901")); + append(encoded, domainSeparatorEncodedHash); + append(encoded, messageHash); + return Hash::keccak256(encoded); +} + +Data getSignedHash(const std::string& hash, const std::string& privateKey) { + auto rsvy = getRSVY(parse_hex(hash), privateKey); + + Data result; + append(result, rsvy[0]); + append(result, rsvy[1]); + append(result, rsvy[2]); + + return result; +} + } // namespace TW::Barz diff --git a/src/Ethereum/Barz.h b/src/Ethereum/Barz.h index f7893ae92f8..d6c2907d6dd 100644 --- a/src/Ethereum/Barz.h +++ b/src/Ethereum/Barz.h @@ -17,4 +17,7 @@ Data getFormattedSignature(const Data& signature, const Data challenge, const Da Data getPrefixedMsgHash(const Data msgHash, const std::string& barzAddress, const uint32_t chainId); Data getDiamondCutCode(const Proto::DiamondCutInput& input); // action should be one of 0, 1, 2. 0 = Add, 1 = Remove, 2 = Replace Data getAuthorizationHash(const Data& chainId, const std::string& contractAddress, const Data& nonce); +std::string signAuthorization(const Data& chainId, const std::string& contractAddress, const Data& nonce, const std::string& privateKey); +Data getEncodedHash(const Data& chainId, const std::string& wallet, const std::string& version, const std::string& typeHash, const std::string& domainSeparatorHash, const std::string& hash); +Data getSignedHash(const std::string& hash, const std::string& privateKey); } diff --git a/src/interface/TWBarz.cpp b/src/interface/TWBarz.cpp index 52140d23c2e..0c6719e84eb 100644 --- a/src/interface/TWBarz.cpp +++ b/src/interface/TWBarz.cpp @@ -66,3 +66,30 @@ TWData *_Nonnull TWBarzGetAuthorizationHash(TWData* _Nonnull chainId, TWString* const auto authorizationHash = TW::Barz::getAuthorizationHash(chainIdData, contractAddressStr, nonceData); return TWDataCreateWithData(&authorizationHash); } + +TWString *_Nonnull TWBarzSignAuthorization(TWData* _Nonnull chainId, TWString* _Nonnull contractAddress, TWData* _Nonnull nonce, TWString* _Nonnull privateKey) { + const auto& chainIdData = *reinterpret_cast(chainId); + const auto& contractAddressStr = *reinterpret_cast(contractAddress); + const auto& nonceData = *reinterpret_cast(nonce); + const auto& privateKeyStr = *reinterpret_cast(privateKey); + const auto signedAuthorization = TW::Barz::signAuthorization(chainIdData, contractAddressStr, nonceData, privateKeyStr); + return TWStringCreateWithUTF8Bytes(signedAuthorization.c_str()); +} + +TWData *_Nonnull TWBarzGetEncodedHash(TWData* _Nonnull chainId, TWString* _Nonnull wallet, TWString* _Nonnull version, TWString* _Nonnull typeHash, TWString* _Nonnull domainSeparatorHash, TWString* _Nonnull hash) { + const auto& chainIdData = *reinterpret_cast(chainId); + const auto& walletStr = *reinterpret_cast(wallet); + const auto& versionStr = *reinterpret_cast(version); + const auto& typeHashStr = *reinterpret_cast(typeHash); + const auto& domainSeparatorHashStr = *reinterpret_cast(domainSeparatorHash); + const auto& hashStr = *reinterpret_cast(hash); + const auto encodedHash = TW::Barz::getEncodedHash(chainIdData, walletStr, versionStr, typeHashStr, domainSeparatorHashStr, hashStr); + return TWDataCreateWithData(&encodedHash); +} + +TWData *_Nonnull TWBarzGetSignedHash(TWString* _Nonnull hash, TWString* _Nonnull privateKey) { + const auto& hashStr = *reinterpret_cast(hash); + const auto& privateKeyStr = *reinterpret_cast(privateKey); + const auto signedHash = TW::Barz::getSignedHash(hashStr, privateKeyStr); + return TWDataCreateWithData(&signedHash); +} diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index cfe413cb54b..37949d836fc 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -133,6 +133,39 @@ message UserOperation { bytes paymaster_and_data = 6; } +// EIP-7702 compatible ERC-4337 structure that describes a transaction to be sent on behalf of a user +message UserOperationV0_7 { + // Entry point contract address + string entry_point = 1; + + // Account factory contract address + string factory = 2; + + // Account factory data + bytes factory_data = 3; + + // Account logic contract address + string sender = 4; + + // The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata + bytes pre_verification_gas = 5; + + // The amount of gas to allocate for the verification step + bytes verification_gas_limit = 6; + + // Address of paymaster + string paymaster = 7; + + // The amount of gas to allocate for the paymaster verification step + bytes paymaster_verification_gas_limit = 8; + + // The amount of gas to allocate for paymaster post ops + bytes paymaster_post_op_gas_limit = 9; + + // Paymaster data + bytes paymaster_data = 10; +} + // An item of the [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) access list. message Access { // Address to be accessed by the transaction. @@ -179,7 +212,12 @@ message SigningInput { Transaction transaction = 10; // UserOperation for ERC-4337 wallets - UserOperation user_operation = 11; + oneof user_operation_oneof { + UserOperation user_operation = 11; + + // EIP-7702 compatible + UserOperationV0_7 user_operation_v0_7 = 13; + } // Optional list of addresses and storage keys that the transaction plans to access. // Used in `TransactionMode::Enveloped` only. diff --git a/tests/chains/Ethereum/BarzTests.cpp b/tests/chains/Ethereum/BarzTests.cpp index e01936d1ffa..4eb2adae5fb 100644 --- a/tests/chains/Ethereum/BarzTests.cpp +++ b/tests/chains/Ethereum/BarzTests.cpp @@ -424,4 +424,47 @@ TEST(Barz, GetAuthorizationHash) { } } +TEST(Barz, SignAuthorization) { + { + const auto chainId = store(uint256_t(1)); + const auto contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; + const auto nonce = store(uint256_t(1)); + const auto privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; + + const auto signedAuthorization = Barz::signAuthorization(chainId, contractAddress, nonce, privateKey); + auto json = nlohmann::json::parse(signedAuthorization); + // Verified with viem + ASSERT_EQ(json["chainId"], hexEncoded(chainId)); + ASSERT_EQ(json["address"], contractAddress); + ASSERT_EQ(json["nonce"], hexEncoded(nonce)); + ASSERT_EQ(json["yParity"], hexEncoded(store(uint256_t(1)))); + ASSERT_EQ(json["r"], "0x2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710"); + ASSERT_EQ(json["s"], "0x5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c"); + } +} + +TEST(Barz, GetEncodedHash) { + { + const auto chainId = store(uint256_t(31337), 32); + std::cout << "chainId: " << hexEncoded(chainId) << std::endl; + const auto wallet = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"; + const auto version = "v0.1.0"; + const auto typeHash = "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867"; + const auto domainSeparatorHash = "0x293ce8821a350a49f08b53d14e10112c36c7fbf3b8eb7078497893f3ea477f6b"; + const auto hash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb"; + + const auto& encodedHash = Barz::getEncodedHash(chainId, wallet, version, typeHash, domainSeparatorHash, hash); + ASSERT_EQ(hexEncoded(encodedHash), "0x59ebb8c4e48c115eeaf2ea7d3a0802754462761c5019df8d2a38effb226191d5"); + } +} + +TEST(Barz, GetSignedHash) { + { + const auto hash = "0x59ebb8c4e48c115eeaf2ea7d3a0802754462761c5019df8d2a38effb226191d5"; + const auto privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; + const auto signedHash = Barz::getSignedHash(hash, privateKey); + ASSERT_EQ(hexEncoded(signedHash), "0x34a7792a140f52358925a57bca8ea936d70133b285396040ac0507597ed5c70a3148964ba1e0b32b8f59fbd9c098a4ec2b9ae5e5739ce4aeccae0f73279d50da1b"); + } +} + } From f044ce659c48ca3824cdf63d00f25cf931e5281b Mon Sep 17 00:00:00 2001 From: gupnik Date: Tue, 18 Mar 2025 15:05:55 +0530 Subject: [PATCH 08/72] [ETH]: Adds Kotlin tests for EIP-7702 Auth and UserOpV07 (#4313) --- .../core/app/blockchains/ethereum/TestBarz.kt | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt index e2930c99271..abb9ec5b741 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt @@ -236,4 +236,73 @@ class TestBarz { val authorizationHash = WCBarz.getAuthorizationHash(chainId, contractAddress, nonce) assertEquals(Numeric.toHexString(authorizationHash), "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9") // Verified with viem } + + @Test + fun testSignAuthorization() { + val chainId = "0x01".toHexByteArray() + val contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" + val nonce = "0x01".toHexByteArray() + val privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8" + + val signedAuthorization = WCBarz.signAuthorization(chainId, contractAddress, nonce, privateKey) + val json = org.json.JSONObject(signedAuthorization) + + // Verified with viem + assertEquals(Numeric.toHexString(chainId), json.getString("chainId")) + assertEquals(contractAddress, json.getString("address")) + assertEquals(Numeric.toHexString(nonce), json.getString("nonce")) + assertEquals("0x01", json.getString("yParity")) + assertEquals("0x2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710", json.getString("r")) + assertEquals("0x5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c", json.getString("s")) + } + + @Test + fun testBarzTransferAccountDeployedV07() { + val transfer = Ethereum.Transaction.Transfer.newBuilder().apply { + amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + data = ByteString.EMPTY + }.build() + + val userOpV07 = Ethereum.UserOperationV0_7.newBuilder().apply { + entryPoint = "0x0000000071727De22E5E9d8BAf0edAc6f37da032" + sender = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029" + preVerificationGas = ByteString.copyFrom("0xF4240".toHexByteArray()) // 1000000 + verificationGasLimit = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 + factory = "0xf471789937856d80e589f5996cf8b0511ddd9de4" + factoryData = ByteString.copyFrom("0xf471789937856d80e589f5996cf8b0511ddd9de4".toHexByteArray()) + paymaster = "0xf62849f9a0b5bf2913b396098f7c7019b51a820a" + paymasterVerificationGasLimit = ByteString.copyFrom("0x1869F".toHexByteArray()) // 99999 + paymasterPostOpGasLimit = ByteString.copyFrom("0x15B38".toHexByteArray()) // 88888 + paymasterData = ByteString.copyFrom( + "0x00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b" + .toHexByteArray() + ) + }.build() + + // Create signing input + val signingInput = Ethereum.SigningInput.newBuilder().apply { + privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray()).data()) + chainId = ByteString.copyFrom("0x7A69".toHexByteArray()) // 31337 + nonce = ByteString.copyFrom("0x00".toHexByteArray()) + txMode = Ethereum.TransactionMode.UserOp + gasLimit = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 + maxFeePerGas = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 + maxInclusionFeePerGas = ByteString.copyFrom("0x186A0".toHexByteArray()) + toAddress = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9" + + transaction = Ethereum.Transaction.newBuilder().apply { + this.transfer = transfer + }.build() + + userOperationV07 = userOpV07 + }.build() + + val output = AnySigner.sign(signingInput, CoinType.ETHEREUM, Ethereum.SigningOutput.parser()) + + assertEquals( + "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb", + Numeric.toHexString(output.preHash.toByteArray()) + ) + } + } From df42d6b8940896da753230208d49fc613c63bc89 Mon Sep 17 00:00:00 2001 From: gupnik Date: Tue, 18 Mar 2025 16:40:54 +0530 Subject: [PATCH 09/72] [ETH]: Adds UserOp Hash Encoding and Signing tests as well (#4314) * [ETH]: Adds Kotlin tests for EIP-7702 Auth and UserOpV07 * Adds userhash encoding and signing as well --- .../core/app/blockchains/ethereum/TestBarz.kt | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt index abb9ec5b741..ff31c515533 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt @@ -258,6 +258,9 @@ class TestBarz { @Test fun testBarzTransferAccountDeployedV07() { + val chainIdByteArray = "0x7A69".toHexByteArray() // 31337 + val wallet = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029" + val transfer = Ethereum.Transaction.Transfer.newBuilder().apply { amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) data = ByteString.EMPTY @@ -265,7 +268,7 @@ class TestBarz { val userOpV07 = Ethereum.UserOperationV0_7.newBuilder().apply { entryPoint = "0x0000000071727De22E5E9d8BAf0edAc6f37da032" - sender = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029" + sender = wallet preVerificationGas = ByteString.copyFrom("0xF4240".toHexByteArray()) // 1000000 verificationGasLimit = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 factory = "0xf471789937856d80e589f5996cf8b0511ddd9de4" @@ -282,7 +285,7 @@ class TestBarz { // Create signing input val signingInput = Ethereum.SigningInput.newBuilder().apply { privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray()).data()) - chainId = ByteString.copyFrom("0x7A69".toHexByteArray()) // 31337 + chainId = ByteString.copyFrom(chainIdByteArray) // 31337 nonce = ByteString.copyFrom("0x00".toHexByteArray()) txMode = Ethereum.TransactionMode.UserOp gasLimit = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 @@ -303,6 +306,26 @@ class TestBarz { "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb", Numeric.toHexString(output.preHash.toByteArray()) ) - } + val version = "v0.1.0" + val typeHash = "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867" + val domainSeparatorHash = "0x293ce8821a350a49f08b53d14e10112c36c7fbf3b8eb7078497893f3ea477f6b" + val hash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb" + + val encodedHash = WCBarz.getEncodedHash(chainIdByteArray, wallet, version, typeHash, domainSeparatorHash, hash) + assertEquals( + "0x59ebb8c4e48c115eeaf2ea7d3a0802754462761c5019df8d2a38effb226191d5", + Numeric.toHexString(encodedHash) + ) + + val privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8" + val signedHash = WCBarz.getSignedHash( + "0x59ebb8c4e48c115eeaf2ea7d3a0802754462761c5019df8d2a38effb226191d5", + privateKey + ) + assertEquals( + "0x34a7792a140f52358925a57bca8ea936d70133b285396040ac0507597ed5c70a3148964ba1e0b32b8f59fbd9c098a4ec2b9ae5e5739ce4aeccae0f73279d50da1b", + Numeric.toHexString(signedHash) + ) + } } From b8c1fab52958952daf09469a730f7a4d47d21756 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Fri, 21 Mar 2025 19:42:59 +0700 Subject: [PATCH 10/72] feat(eip7702): Add Biz Smart Contract Account Type (#4319) * fix(eip7702): Add `UserOperationMode` * Add `erc4337.biz_account.abi.json` ABI * fix(eip7702): Add `test_barz_transfer_erc7702_eoa` test * fix(eip7702): Fix `Biz.execute4337Ops()` * fix(eip7702): Minor changes * fix(eip7702): Rename `UserOperationMode` to `SCAccountType` --- rust/tw_evm/src/abi/prebuild/erc4337.rs | 74 ++- .../resource/erc4337.biz_account.abi.json | 515 ++++++++++++++++++ rust/tw_evm/src/modules/tx_builder.rs | 75 +-- rust/tw_evm/tests/barz.rs | 147 ++++- src/proto/Ethereum.proto | 12 + 5 files changed, 786 insertions(+), 37 deletions(-) create mode 100644 rust/tw_evm/src/abi/prebuild/resource/erc4337.biz_account.abi.json diff --git a/rust/tw_evm/src/abi/prebuild/erc4337.rs b/rust/tw_evm/src/abi/prebuild/erc4337.rs index c6ef8eb76a2..3dd1b74ac10 100644 --- a/rust/tw_evm/src/abi/prebuild/erc4337.rs +++ b/rust/tw_evm/src/abi/prebuild/erc4337.rs @@ -3,21 +3,26 @@ // Copyright © 2017 Trust Wallet. use crate::abi::contract::Contract; +use crate::abi::param_token::NamedToken; use crate::abi::param_type::ParamType; use crate::abi::token::Token; -use crate::abi::AbiResult; +use crate::abi::{AbiError, AbiErrorKind, AbiResult}; use crate::address::Address; use lazy_static::lazy_static; +use tw_coin_entry::error::prelude::{OrTWError, ResultContext}; use tw_memory::Data; use tw_number::U256; /// Generated via https://remix.ethereum.org /// https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol const ERC4337_SIMPLE_ACCOUNT_ABI: &str = include_str!("resource/erc4337.simple_account.abi.json"); +const ERC4337_BIZ_ACCOUNT_ABI: &str = include_str!("resource/erc4337.biz_account.abi.json"); lazy_static! { static ref ERC4337_SIMPLE_ACCOUNT: Contract = serde_json::from_str(ERC4337_SIMPLE_ACCOUNT_ABI).unwrap(); + static ref ERC4337_BIZ_ACCOUNT: Contract = + serde_json::from_str(ERC4337_BIZ_ACCOUNT_ABI).unwrap(); } pub struct ExecuteArgs { @@ -38,6 +43,15 @@ impl Erc4337SimpleAccount { ]) } + pub fn encode_execute_4337_op(args: ExecuteArgs) -> AbiResult { + let func = ERC4337_BIZ_ACCOUNT.function("execute4337Op")?; + func.encode_input(&[ + Token::Address(args.to), + Token::u256(args.value), + Token::Bytes(args.data), + ]) + } + pub fn encode_execute_batch(args: I) -> AbiResult where I: IntoIterator, @@ -66,4 +80,62 @@ impl Erc4337SimpleAccount { Token::array(ParamType::Bytes, datas), ]) } + + pub fn encode_execute_4337_ops(args: I) -> AbiResult + where + I: IntoIterator, + { + let func = ERC4337_BIZ_ACCOUNT.function("execute4337Ops")?; + + // `tuple[]`, where each item is a tuple of (address, uint256, bytes). + let array_param = func + .inputs + .first() + .or_tw_err(AbiErrorKind::Error_internal) + .context("'Biz.execute4337Ops()' should contain only one argument")?; + + let ParamType::Array { + kind: array_elem_type, + } = array_param.kind.clone() + else { + return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { + format!( + "'Biz.execute4337Ops()' input argument should be an array, found: {:?}", + array_param.kind + ) + }); + }; + + let ParamType::Tuple { + params: tuple_params, + } = array_elem_type.as_ref() + else { + return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { + format!( + "'Biz.execute4337Ops()' input argument should be an array of tuples, found: {array_elem_type:?}", + ) + }); + }; + + if tuple_params.len() != 3 { + return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { + format!( + "'Biz.execute4337Ops()' input argument should be an array of tuples with 3 elements, found: {}", tuple_params.len() + ) + }); + } + + let array_tokens = args + .into_iter() + .map(|call| Token::Tuple { + params: vec![ + NamedToken::with_param_and_token(&tuple_params[0], Token::Address(call.to)), + NamedToken::with_param_and_token(&tuple_params[1], Token::u256(call.value)), + NamedToken::with_param_and_token(&tuple_params[2], Token::Bytes(call.data)), + ], + }) + .collect(); + + func.encode_input(&[Token::array(*array_elem_type, array_tokens)]) + } } diff --git a/rust/tw_evm/src/abi/prebuild/resource/erc4337.biz_account.abi.json b/rust/tw_evm/src/abi/prebuild/resource/erc4337.biz_account.abi.json new file mode 100644 index 00000000000..137ae43cac3 --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/resource/erc4337.biz_account.abi.json @@ -0,0 +1,515 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "_bizGuard", + "type": "address", + "internalType": "contract BizGuard" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "bizGuard", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract BizGuard" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "entryPoint", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "execute", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "execute4337Op", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "execute4337Ops", + "inputs": [ + { + "name": "calls", + "type": "tuple[]", + "internalType": "struct Biz.Call[]", + "components": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "executeBatch", + "inputs": [ + { + "name": "calls", + "type": "tuple[]", + "internalType": "struct Biz.Call[]", + "components": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "executeWithSignature", + "inputs": [ + { + "name": "calls", + "type": "tuple[]", + "internalType": "struct Biz.Call[]", + "components": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "isValidSignature", + "inputs": [ + { + "name": "hash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "isValid", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "onERC1155BatchReceived", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "onERC1155Received", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "onERC721Received", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "onTokenTransfer", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "tokensReceived", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "validateUserOp", + "inputs": [ + { + "name": "userOp", + "type": "tuple", + "internalType": "struct Biz.PackedUserOperation", + "components": [ + { + "name": "sender", + "type": "address", + "internalType": "address" + }, + { + "name": "nonce", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "initCode", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "callData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "accountGasLimits", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "preVerificationGas", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "gasFees", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "paymasterAndData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "userOpHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "missingAccountFunds", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "validationData", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "error", + "name": "ECDSAInvalidSignature", + "inputs": [] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureLength", + "inputs": [ + { + "name": "length", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureS", + "inputs": [ + { + "name": "s", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "ERC4337Disabled", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidERC4337Flag", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidSignature", + "inputs": [] + }, + { + "type": "error", + "name": "OnlyEntryPoint", + "inputs": [] + }, + { + "type": "error", + "name": "OnlySelf", + "inputs": [] + } +] \ No newline at end of file diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs index 932d87f31f2..516f375024b 100644 --- a/rust/tw_evm/src/modules/tx_builder.rs +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -23,6 +23,10 @@ use tw_memory::Data; use tw_number::U256; use tw_proto::Common::Proto::SigningError as CommonError; use tw_proto::Ethereum::Proto; +use Proto::mod_SigningInput::OneOfuser_operation_oneof as UserOp; +use Proto::mod_Transaction::OneOftransaction_oneof as Tx; +use Proto::SCAccountType; +use Proto::TransactionMode as TxMode; pub struct TxBuilder { _phantom: PhantomData, @@ -32,10 +36,6 @@ impl TxBuilder { pub fn tx_from_proto( input: &Proto::SigningInput<'_>, ) -> SigningResult> { - use Proto::mod_SigningInput::OneOfuser_operation_oneof as UserOp; - use Proto::mod_Transaction::OneOftransaction_oneof as Tx; - use Proto::TransactionMode as TxMode; - let Some(ref transaction) = input.transaction else { return SigningError::err(CommonError::Error_invalid_params) .context("No transaction specified"); @@ -147,21 +147,15 @@ impl TxBuilder { .iter() .map(Self::erc4337_execute_call_from_proto) .collect::, _>>()?; - let payload = Erc4337SimpleAccount::encode_execute_batch(calls) - .map_err(abi_to_signing_error)?; - - return match &input.user_operation_oneof { - UserOp::user_operation_v0_7(user_op_v0_7) => { - Self::user_operation_v0_7_from_proto(input, user_op_v0_7, payload) - .map(UserOperationV0_7::into_boxed) + let user_op_payload = match input.user_operation_mode { + SCAccountType::SimpleAccount => { + Erc4337SimpleAccount::encode_execute_batch(calls) }, - UserOp::user_operation(user_op) => { - Self::user_operation_from_proto(input, user_op, payload) - .map(UserOperation::into_boxed) - }, - UserOp::None => SigningError::err(SigningErrorType::Error_invalid_params) - .context("No user operation specified"), - }; + SCAccountType::Biz4337 => Erc4337SimpleAccount::encode_execute_4337_ops(calls), + } + .map_err(abi_to_signing_error)?; + + return Self::user_operation_from_proto(input, user_op_payload); }, Tx::None => { return SigningError::err(SigningErrorType::Error_invalid_params) @@ -180,31 +174,42 @@ impl TxBuilder { let to = to .or_tw_err(SigningErrorType::Error_invalid_address) .context("No contract/destination address specified")?; - // Payload should match the ERC4337 standard. - let payload = Erc4337SimpleAccount::encode_execute(ExecuteArgs { + let args = ExecuteArgs { to, value: eth_amount, data: payload, - }) - .map_err(abi_to_signing_error)?; + }; - match &input.user_operation_oneof { - UserOp::user_operation_v0_7(user_op_v0_7) => { - Self::user_operation_v0_7_from_proto(input, user_op_v0_7, payload) - .map(UserOperationV0_7::into_boxed) - }, - UserOp::user_operation(user_op) => { - Self::user_operation_from_proto(input, user_op, payload) - .map(UserOperation::into_boxed) - }, - UserOp::None => SigningError::err(SigningErrorType::Error_invalid_params) - .context("No user operation specified"), - }? + let user_op_payload = match input.user_operation_mode { + SCAccountType::SimpleAccount => Erc4337SimpleAccount::encode_execute(args), + SCAccountType::Biz4337 => Erc4337SimpleAccount::encode_execute_4337_op(args), + } + .map_err(abi_to_signing_error)?; + Self::user_operation_from_proto(input, user_op_payload)? }, }; Ok(tx) } + #[inline] + fn user_operation_from_proto( + input: &Proto::SigningInput<'_>, + user_op_payload: Data, + ) -> SigningResult> { + match input.user_operation_oneof { + UserOp::user_operation_v0_7(ref user_op_v0_7) => { + Self::user_operation_v0_7_from_proto(input, user_op_v0_7, user_op_payload) + .map(UserOperationV0_7::into_boxed) + }, + UserOp::user_operation(ref user_op) => { + Self::user_operation_v0_6_from_proto(input, user_op, user_op_payload) + .map(UserOperation::into_boxed) + }, + UserOp::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("No user operation specified"), + } + } + #[inline] fn erc4337_execute_call_from_proto( call: &Proto::mod_Transaction::mod_Batch::BatchedCall, @@ -289,7 +294,7 @@ impl TxBuilder { }) } - fn user_operation_from_proto( + fn user_operation_v0_6_from_proto( input: &Proto::SigningInput, user_op: &Proto::UserOperation, erc4337_payload: Data, diff --git a/rust/tw_evm/tests/barz.rs b/rust/tw_evm/tests/barz.rs index 5fd6ac38dc7..b795d34607b 100644 --- a/rust/tw_evm/tests/barz.rs +++ b/rust/tw_evm/tests/barz.rs @@ -10,6 +10,7 @@ use tw_evm::abi::prebuild::erc20::Erc20; use tw_evm::address::Address; use tw_evm::evm_context::StandardEvmContext; use tw_evm::modules::signer::Signer; +use tw_misc::traits::ToBytesVec; use tw_number::U256; use tw_proto::Ethereum::Proto; @@ -199,7 +200,7 @@ fn test_barz_batched_account_deployed() { } #[test] -fn test_barz_transfer_account_deployed_v0_7() { +fn test_barz_transfer_account_not_deployed_v0_7() { let private_key = hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); @@ -246,3 +247,147 @@ fn test_barz_transfer_account_deployed_v0_7() { "f177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb" ); } + +#[test] +fn test_barz_transfer_erc7702_eoa() { + let private_key = + hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); + + let user_op = Proto::UserOperationV0_7 { + entry_point: "0x0000000071727De22E5E9d8BAf0edAc6f37da032".into(), + sender: "0x2EF648D7C03412B832726fd4683E2625deA047Ba".into(), + pre_verification_gas: U256::from(1000000u64).to_big_endian_compact().into(), + verification_gas_limit: U256::from(100000u128).to_big_endian_compact().into(), + paymaster: "0xb0086171AC7b6BD4D046580bca6d6A4b0835c232".into(), + paymaster_verification_gas_limit: U256::from(99999u128).to_big_endian_compact().into(), + paymaster_post_op_gas_limit: U256::from(88888u128).to_big_endian_compact().into(), + // Dummy paymaster data. + paymaster_data: "00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b".decode_hex().unwrap().into(), + ..Proto::UserOperationV0_7::default() + }; + + let approve = Proto::mod_Transaction::ERC20Approve { + // Paymaster address. + spender: "0xb0086171AC7b6BD4D046580bca6d6A4b0835c232".into(), + amount: U256::encode_be_compact(655_360_197_115_136_u64), + }; + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(31337u64), + nonce: U256::encode_be_compact(0u64), + tx_mode: Proto::TransactionMode::UserOp, + gas_limit: U256::from(100000u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), + max_inclusion_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), + // USDT token. + to_address: "0xdac17f958d2ee523a2206206994597c13d831ec7".into(), + private_key: private_key.into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc20_approve( + approve, + ), + }), + user_operation_oneof: + Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation_v0_7(user_op), + user_operation_mode: Proto::SCAccountType::Biz4337, + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + assert_eq!( + hex::encode(output.pre_hash, false), + "68109b9caf49f7971b689307c9a77ceec46e4b8fa88421c4276dd846f782d92c" + ); + + let user_op: serde_json::Value = serde_json::from_slice(&output.encoded).unwrap(); + assert_eq!(user_op["callData"], "0x76276c82000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd0000000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_barz_transfer_erc7702_eoa_batch() { + let private_key = + hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); + + let user_op = Proto::UserOperationV0_7 { + entry_point: "0x0000000071727De22E5E9d8BAf0edAc6f37da032".into(), + sender: "0x2EF648D7C03412B832726fd4683E2625deA047Ba".into(), + pre_verification_gas: U256::from(1000000u64).to_big_endian_compact().into(), + verification_gas_limit: U256::from(100000u128).to_big_endian_compact().into(), + paymaster: "0xb0086171AC7b6BD4D046580bca6d6A4b0835c232".into(), + paymaster_verification_gas_limit: U256::from(99999u128).to_big_endian_compact().into(), + paymaster_post_op_gas_limit: U256::from(88888u128).to_big_endian_compact().into(), + // Dummy paymaster data. + paymaster_data: "00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b".decode_hex().unwrap().into(), + ..Proto::UserOperationV0_7::default() + }; + + let mut calls = Vec::with_capacity(2); + + // ERC20 approve. At least one of the calls should be an ERC20.approve() + // so paymaster can collect tokens to cover the fees. + { + // Paymaster address. + let recipient = Address::from("0xb0086171AC7b6BD4D046580bca6d6A4b0835c232"); + let amount = U256::from(655_360_197_115_136_u64); + let payload = Erc20::approve(recipient, amount).unwrap(); + + calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + // USDT + address: "0xdac17f958d2ee523a2206206994597c13d831ec7".into(), + amount: Cow::default(), + payload: payload.into(), + }); + } + + // ERC20 transfer. Regular transaction. + { + let recipient = Address::from("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); + let amount = U256::from(0x8AC7_2304_89E8_0000_u64); + let payload = Erc20::transfer(recipient, amount).unwrap(); + + calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + address: "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266".into(), + amount: Cow::default(), + payload: payload.into(), + }); + } + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(31337u64), + nonce: U256::encode_be_compact(0u64), + tx_mode: Proto::TransactionMode::UserOp, + gas_limit: U256::from(100000u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), + max_inclusion_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), + // USDT token. + to_address: "0xdac17f958d2ee523a2206206994597c13d831ec7".into(), + private_key: private_key.into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::batch( + Proto::mod_Transaction::Batch { calls }, + ), + }), + user_operation_oneof: + Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation_v0_7(user_op), + user_operation_mode: Proto::SCAccountType::Biz4337, + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!( + output.error, + SigningErrorType::OK, + "{}", + output.error_message + ); + + assert_eq!( + hex::encode(output.pre_hash, false), + "f6340068891dc3eb78959993151c421dde23982b3a1407c0dbbd62c2c22c3cb8" + ); + + let user_op: serde_json::Value = serde_json::from_slice(&output.encoded).unwrap(); + assert_eq!(user_op["callData"], "0x26da7d880000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd000000000000000000000000000000000000000000000000000000000000000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b1266000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000"); +} diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index 37949d836fc..60d8a222238 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -174,6 +174,15 @@ message Access { repeated bytes stored_keys = 2; } +// Smart Contract account type. +enum SCAccountType { + // ERC-4337 compatible smart contract wallet. + // https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/accounts/SimpleAccount.sol + SimpleAccount = 0; + // Biz smart contract (Trust Wallet specific). + Biz4337 = 1; +} + // Input data necessary to create a signed transaction. // Legacy and EIP2718/EIP1559 transactions supported, see TransactionMode. message SigningInput { @@ -222,6 +231,9 @@ message SigningInput { // Optional list of addresses and storage keys that the transaction plans to access. // Used in `TransactionMode::Enveloped` only. repeated Access access_list = 12; + + // Smart contract account type. Used in `TransactionMode::UserOp` only. + SCAccountType user_operation_mode = 14; } // Result containing the signed and encoded transaction. From 2c1e0feb1f439c6128ddab3817e6d490d995bce6 Mon Sep 17 00:00:00 2001 From: Yeferson Licet <111311418+y3fers0n@users.noreply.github.com> Date: Mon, 24 Mar 2025 08:20:16 -0300 Subject: [PATCH 11/72] fix: tron message sign (#4326) --- .../blockchains/tron/TestTronMessageSigner.kt | 2 +- src/Tron/MessageSigner.cpp | 2 +- src/Tron/MessageSigner.h | 2 +- swift/Tests/Blockchains/TronTests.swift | 2 +- tests/chains/Tron/TronMessageSignerTests.cpp | 16 ++++++++++++++-- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronMessageSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronMessageSigner.kt index 23cfb2302d3..7730a59f9ca 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronMessageSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronMessageSigner.kt @@ -20,7 +20,7 @@ class TestTronMessageSigner { val privateKey = PrivateKey(data) val msg = "Hello World" val signature = TronMessageSigner.signMessage(privateKey, msg) - assertEquals("9bb6d11ec8a6a3fb686a8f55b123e7ec4e9746a26157f6f9e854dd72f5683b450397a7b0a9653865658de8f9243f877539882891bad30c7286c3bf5622b900471b", signature) + assertEquals("bc0753c070cc55693097df11bc11e1a7c4bd5e1a40b9dc94c75568e59bcc9d6b50a7873ef25b469e494490a54de37327b4bc7fc825c81a377b555e34fb7261ba1c", signature) val pubKey = privateKey.getPublicKey(CoinType.TRON) assertTrue(TronMessageSigner.verifyMessage(pubKey, msg, signature)) } diff --git a/src/Tron/MessageSigner.cpp b/src/Tron/MessageSigner.cpp index 8fe0d8619a6..32af140daf0 100644 --- a/src/Tron/MessageSigner.cpp +++ b/src/Tron/MessageSigner.cpp @@ -14,7 +14,7 @@ namespace TW::Tron { Data generateMessage(const std::string& message) { std::string prefix(1, MessageSigner::TronPrefix); std::stringstream ss; - ss << prefix << MessageSigner::MessagePrefix << message; + ss << prefix << MessageSigner::MessagePrefix << message.length() << message; Data signableMessage = Hash::keccak256(data(ss.str())); return signableMessage; } diff --git a/src/Tron/MessageSigner.h b/src/Tron/MessageSigner.h index e6e7bf2a9a9..ed016f66b4b 100644 --- a/src/Tron/MessageSigner.h +++ b/src/Tron/MessageSigner.h @@ -24,7 +24,7 @@ class MessageSigner { /// \param signature signature to verify the message against /// \return true if the message match the signature, false otherwise static bool verifyMessage(const PublicKey& publicKey, const std::string& message, const std::string& signature) noexcept; - static constexpr auto MessagePrefix = "TRON Signed Message:\n32"; + static constexpr auto MessagePrefix = "TRON Signed Message:\n"; static constexpr std::uint8_t TronPrefix{0x19}; }; diff --git a/swift/Tests/Blockchains/TronTests.swift b/swift/Tests/Blockchains/TronTests.swift index 9c5c7d6ebea..67ae3f91242 100644 --- a/swift/Tests/Blockchains/TronTests.swift +++ b/swift/Tests/Blockchains/TronTests.swift @@ -79,7 +79,7 @@ class TronTests: XCTestCase { let privateKey = PrivateKey(data: Data(hexString: "75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")!)! let msg = "Hello World" let signature = TronMessageSigner.signMessage(privateKey: privateKey, message: msg) - XCTAssertEqual(signature, "9bb6d11ec8a6a3fb686a8f55b123e7ec4e9746a26157f6f9e854dd72f5683b450397a7b0a9653865658de8f9243f877539882891bad30c7286c3bf5622b900471b") + XCTAssertEqual(signature, "bc0753c070cc55693097df11bc11e1a7c4bd5e1a40b9dc94c75568e59bcc9d6b50a7873ef25b469e494490a54de37327b4bc7fc825c81a377b555e34fb7261ba1c") let pubKey = privateKey.getPublicKey(coinType: .tron) XCTAssertTrue(TronMessageSigner.verifyMessage(pubKey: pubKey, message: msg, signature: signature)) } diff --git a/tests/chains/Tron/TronMessageSignerTests.cpp b/tests/chains/Tron/TronMessageSignerTests.cpp index 4b0940a5db6..548e44f394d 100644 --- a/tests/chains/Tron/TronMessageSignerTests.cpp +++ b/tests/chains/Tron/TronMessageSignerTests.cpp @@ -15,9 +15,16 @@ namespace TW::Tron { PrivateKey tronKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")); auto msg = "Hello World"; auto signature = Tron::MessageSigner::signMessage(tronKey, msg); - ASSERT_EQ(signature, "9bb6d11ec8a6a3fb686a8f55b123e7ec4e9746a26157f6f9e854dd72f5683b450397a7b0a9653865658de8f9243f877539882891bad30c7286c3bf5622b900471b"); auto pubKey = tronKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + + ASSERT_EQ(signature, "bc0753c070cc55693097df11bc11e1a7c4bd5e1a40b9dc94c75568e59bcc9d6b50a7873ef25b469e494490a54de37327b4bc7fc825c81a377b555e34fb7261ba1c"); ASSERT_TRUE(Tron::MessageSigner::verifyMessage(pubKey, msg, signature)); + + auto msg2 = "A much longer message to test the signing and verification process"; + auto signature2 = Tron::MessageSigner::signMessage(tronKey, msg2); + + ASSERT_EQ(signature2, "93aee5f753cf889e0749c74dd0c5996cce889883ae079e09ede462e16d65d06a4f43d1ed2745e9f3c1690695628269bd58f057a4a93953cc50e66b4a05bc0f451b"); + ASSERT_TRUE(Tron::MessageSigner::verifyMessage(pubKey, msg2, signature2)); } TEST(TWTronMessageSigner, SignAndVerifyLegacy) { @@ -27,7 +34,12 @@ namespace TW::Tron { const auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeTron)); const auto signature = WRAPS(TWTronMessageSignerSignMessage(privateKey.get(), message.get())); - EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "9bb6d11ec8a6a3fb686a8f55b123e7ec4e9746a26157f6f9e854dd72f5683b450397a7b0a9653865658de8f9243f877539882891bad30c7286c3bf5622b900471b"); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "bc0753c070cc55693097df11bc11e1a7c4bd5e1a40b9dc94c75568e59bcc9d6b50a7873ef25b469e494490a54de37327b4bc7fc825c81a377b555e34fb7261ba1c"); EXPECT_TRUE(TWTronMessageSignerVerifyMessage(pubKey.get(), message.get(), signature.get())); + + const auto message2 = STRING("A much longer message to test the signing and verification process"); + const auto signature2 = WRAPS(TWTronMessageSignerSignMessage(privateKey.get(), message2.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature2.get())), "93aee5f753cf889e0749c74dd0c5996cce889883ae079e09ede462e16d65d06a4f43d1ed2745e9f3c1690695628269bd58f057a4a93953cc50e66b4a05bc0f451b"); + EXPECT_TRUE(TWTronMessageSignerVerifyMessage(pubKey.get(), message2.get(), signature2.get())); } } From fb2f5fa3a154718da49df637479a93caf2ce0b5e Mon Sep 17 00:00:00 2001 From: gupnik Date: Mon, 24 Mar 2025 19:22:25 +0530 Subject: [PATCH 12/72] Adds ability to specify the curve while constructing Private Key (#4324) * Adds ability to specify the curve while constructing Private Key * Adds signing functions without a curve * Migrates to new API * Use TWCoinTypeCurve * Adds Curve --- src/Aeternity/Signer.cpp | 4 +- src/Aion/Signer.cpp | 4 +- src/Algorand/Signer.cpp | 6 +- src/Bitcoin/SigningInput.cpp | 2 +- src/Cardano/Signer.cpp | 6 +- src/Decred/Signer.cpp | 6 +- src/EOS/Signer.cpp | 10 ++-- src/Ethereum/Barz.cpp | 4 +- src/Everscale/Signer.cpp | 2 +- src/FIO/TransactionBuilder.cpp | 2 +- src/Filecoin/Signer.cpp | 6 +- src/HDWallet.cpp | 14 ++--- src/Harmony/Signer.cpp | 4 +- src/Hedera/Signer.cpp | 2 +- src/IOST/Account.cpp | 6 +- src/Icon/Signer.cpp | 4 +- src/ImmutableX/StarkKey.cpp | 4 +- src/IoTeX/Signer.cpp | 8 +-- src/Keystore/StoredKey.cpp | 4 +- src/MultiversX/Signer.cpp | 4 +- src/NEAR/Serialization.cpp | 2 +- src/NEAR/Signer.cpp | 4 +- src/NEO/Signer.cpp | 4 +- src/NULS/Signer.cpp | 4 +- src/Nano/Signer.cpp | 4 +- src/Nebulas/Signer.cpp | 4 +- src/Nervos/Signer.cpp | 2 +- src/Nimiq/Signer.cpp | 2 +- src/Oasis/Signer.cpp | 6 +- src/Ontology/Oep4TxBuilder.cpp | 4 +- src/Ontology/OngTxBuilder.cpp | 8 +-- src/Ontology/OntTxBuilder.cpp | 4 +- src/PrivateKey.cpp | 55 +++++++++++++++++- src/PrivateKey.h | 37 ++++++++++++ src/Stellar/Signer.cpp | 4 +- src/Tezos/BinaryCoding.cpp | 2 +- src/Tezos/Signer.cpp | 4 +- src/Theta/Signer.cpp | 4 +- src/Tron/Signer.cpp | 8 +-- src/VeChain/Signer.cpp | 4 +- src/Waves/Signer.cpp | 4 +- src/Zilliqa/Signer.cpp | 4 +- swift/Podfile.lock | 2 +- tests/chains/Aeternity/SignerTests.cpp | 8 +-- tests/chains/Aion/SignerTests.cpp | 4 +- tests/chains/Algorand/SignerTests.cpp | 10 ++-- .../Algorand/TransactionCompilerTests.cpp | 4 +- tests/chains/Aptos/AddressTests.cpp | 2 +- tests/chains/Aptos/CompilerTests.cpp | 8 +-- .../chains/Bitcoin/TWBitcoinSigningTests.cpp | 58 +++++++++---------- tests/chains/Bitcoin/TxComparisonHelper.cpp | 2 +- tests/chains/BitcoinDiamond/SignerTests.cpp | 6 +- .../BitcoinDiamond/TWAnySignerTests.cpp | 2 +- tests/chains/BitcoinGold/TWAddressTests.cpp | 2 +- tests/chains/Cardano/SigningTests.cpp | 2 +- tests/chains/Cardano/StakingTests.cpp | 10 ++-- tests/chains/Cardano/VoteDelegationTests.cpp | 8 +-- tests/chains/Cosmos/AddressTests.cpp | 4 +- tests/chains/Cosmos/SignerTests.cpp | 2 +- tests/chains/Cosmos/THORChain/SwapTests.cpp | 16 ++--- tests/chains/Cosmos/THORChain/TWSwapTests.cpp | 2 +- tests/chains/Decred/AddressTests.cpp | 2 +- tests/chains/Decred/SignerTests.cpp | 10 ++-- .../Decred/TransactionCompilerTests.cpp | 2 +- tests/chains/EOS/TransactionTests.cpp | 4 +- tests/chains/Ethereum/AddressTests.cpp | 2 +- tests/chains/MultiversX/SignerTests.cpp | 38 ++++++------ tests/chains/MultiversX/TWAnySignerTests.cpp | 2 +- .../MultiversX/TransactionCompilerTests.cpp | 6 +- tests/chains/NEO/SignerTests.cpp | 4 +- tests/chains/Nano/SignerTests.cpp | 26 ++++----- tests/chains/Nebl/SignerTests.cpp | 4 +- tests/chains/Nebl/TWAnySignerTests.cpp | 2 +- tests/chains/Nebl/TransactionBuilderTests.cpp | 2 +- .../chains/Nebl/TransactionCompilerTests.cpp | 2 +- tests/chains/Nebulas/TransactionTests.cpp | 4 +- tests/chains/Tezos/SignerTests.cpp | 4 +- tests/chains/Theta/SignerTests.cpp | 2 +- tests/chains/VeChain/SignerTests.cpp | 2 +- tests/chains/WAX/TWAnySignerTests.cpp | 2 +- tests/chains/Waves/LeaseTests.cpp | 8 +-- tests/chains/Waves/SignerTests.cpp | 2 +- tests/chains/Waves/TransactionTests.cpp | 6 +- tests/chains/XRP/TransactionCompilerTests.cpp | 2 +- tests/chains/Zcash/AddressTests.cpp | 4 +- .../chains/Zcash/TWZcashTransactionTests.cpp | 2 +- .../chains/Zcash/TransactionCompilerTests.cpp | 2 +- tests/chains/Zen/AddressTests.cpp | 2 +- tests/chains/Zen/SignerTests.cpp | 2 +- tests/chains/Zilliqa/AddressTests.cpp | 2 +- tests/chains/Zilliqa/SignerTests.cpp | 6 +- tests/common/Bech32AddressTests.cpp | 14 ++--- .../common/HDWallet/HDWalletInternalTests.cpp | 2 +- tests/common/PrivateKeyTests.cpp | 56 +++++++++++++++++- tests/common/PublicKeyTests.cpp | 40 +++++++------ tests/interface/TWPublicKeyTests.cpp | 4 +- .../interface/TWTransactionCompilerTests.cpp | 4 +- walletconsole/lib/Address.cpp | 2 +- walletconsole/lib/Keys.cpp | 2 +- 99 files changed, 426 insertions(+), 284 deletions(-) diff --git a/src/Aeternity/Signer.cpp b/src/Aeternity/Signer.cpp index ec22012eb88..47b6ff36aea 100644 --- a/src/Aeternity/Signer.cpp +++ b/src/Aeternity/Signer.cpp @@ -15,7 +15,7 @@ using namespace TW; namespace TW::Aeternity { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); std::string sender_id = input.from_address(); std::string recipient_id = input.to_address(); std::string payload = input.payload(); @@ -34,7 +34,7 @@ Proto::SigningOutput Signer::sign(const TW::PrivateKey& privateKey, Transaction& auto msg = buildMessageToSign(txRlp); /// sign ed25519 - auto sigRaw = privateKey.sign(msg, TWCurveED25519); + auto sigRaw = privateKey.sign(msg); auto signature = Identifiers::prefixSignature + Base58::encodeCheck(sigRaw); /// encode the message using rlp diff --git a/src/Aion/Signer.cpp b/src/Aion/Signer.cpp index 8fe86f3904f..b79afa26bed 100644 --- a/src/Aion/Signer.cpp +++ b/src/Aion/Signer.cpp @@ -9,7 +9,7 @@ using namespace TW; namespace TW::Aion { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); auto transaction = Signer::buildTransaction(input); Signer::sign(key, transaction); @@ -23,7 +23,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexcept { auto encoded = transaction.encode(); auto hashData = Hash::blake2b(encoded, 32); - auto hashSignature = privateKey.sign(hashData, TWCurveED25519); + auto hashSignature = privateKey.sign(hashData); auto publicKeyData = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; // Aion signature = pubKeyBytes + signatureBytes diff --git a/src/Algorand/Signer.cpp b/src/Algorand/Signer.cpp index 71f44b6fcce..02a7bf0b3e7 100644 --- a/src/Algorand/Signer.cpp +++ b/src/Algorand/Signer.cpp @@ -18,11 +18,11 @@ const std::string ASSET_TRANSACTION = "axfer"; Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto protoOutput = Proto::SigningOutput(); - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); auto pubkey = key.getPublicKey(TWPublicKeyTypeED25519); auto preImageData = Signer::preImage(pubkey, input); - auto signature = key.sign(preImageData, TWCurveED25519); + auto signature = key.sign(preImageData); return Signer::encodeTransaction(signature, pubkey, input); } @@ -37,7 +37,7 @@ Data Signer::sign(const PrivateKey& privateKey, const BaseTransaction& transacti Data data; append(data, TRANSACTION_TAG); append(data, transaction.serialize()); - auto signature = privateKey.sign(data, TWCurveED25519); + auto signature = privateKey.sign(data); return {signature.begin(), signature.end()}; } diff --git a/src/Bitcoin/SigningInput.cpp b/src/Bitcoin/SigningInput.cpp index e7cd22d8417..8ef598e9201 100644 --- a/src/Bitcoin/SigningInput.cpp +++ b/src/Bitcoin/SigningInput.cpp @@ -17,7 +17,7 @@ SigningInput::SigningInput(const Proto::SigningInput& input) { toAddress = input.to_address(); changeAddress = input.change_address(); for (auto&& key : input.private_key()) { - privateKeys.emplace_back(key); + privateKeys.emplace_back(key, TWCurveSECP256k1); } for (auto&& script : input.scripts()) { scripts[script.first] = Script(script.second.begin(), script.second.end()); diff --git a/src/Cardano/Signer.cpp b/src/Cardano/Signer.cpp index 42d51f9568e..886499d5286 100644 --- a/src/Cardano/Signer.cpp +++ b/src/Cardano/Signer.cpp @@ -129,7 +129,7 @@ Common::Proto::SigningError Signer::assembleSignatures(std::vector, Common::Proto::SigningError> Signer::signStep(Bitcoin: return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); } } else { - pubkey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1).bytes; + pubkey = PrivateKey(key, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes; } auto signature = createSignature(transactionToSign, script, key, data, index); @@ -263,7 +263,7 @@ Data Signer::createSignature(const Transaction& transaction, const Bitcoin::Scri return externalSignature; } - auto pk = PrivateKey(key); + auto pk = PrivateKey(key, TWCurveSECP256k1); auto signature = pk.signAsDER(Data(begin(sighash), end(sighash))); if (script.empty()) { return {}; @@ -275,7 +275,7 @@ Data Signer::createSignature(const Transaction& transaction, const Bitcoin::Scri Data Signer::keyForPublicKeyHash(const Data& hash) const { for (auto& key : input.private_key()) { - auto publicKey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); + auto publicKey = PrivateKey(key, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1); auto keyHash = TW::Hash::ripemd(TW::Hash::blake256(publicKey.bytes)); if (std::equal(std::begin(keyHash), std::end(keyHash), std::begin(hash), std::end(hash))) { return Data(key.begin(), key.end()); diff --git a/src/EOS/Signer.cpp b/src/EOS/Signer.cpp index dc8019fbeed..1c1286e17c4 100644 --- a/src/EOS/Signer.cpp +++ b/src/EOS/Signer.cpp @@ -17,6 +17,8 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(chainId); auto tx = signer.buildTx(input); + // values for Legacy and ModernK1 + TWCurve curve = TWCurveSECP256k1; // get key type EOS::Type type = Type::Legacy; switch (input.private_key_type()) { @@ -29,6 +31,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { break; case Proto::KeyType::MODERNR1: + curve = TWCurveNIST256p1; type = Type::ModernR1; break; default: @@ -36,7 +39,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { } // sign the transaction with a Signer - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), curve); signer.sign(key, type, tx); // Pack the transaction and add the json encoding to Signing outputput @@ -55,17 +58,14 @@ void Signer::sign(const PrivateKey& privateKey, Type type, Transaction& transact throw std::invalid_argument("Invalid transaction!"); } - // values for Legacy and ModernK1 - TWCurve curve = TWCurveSECP256k1; auto canonicalChecker = isCanonical; // Values for ModernR1 if (type == Type::ModernR1) { - curve = TWCurveNIST256p1; canonicalChecker = nullptr; } - const Data result = privateKey.sign(hash(transaction), curve, canonicalChecker); + const Data result = privateKey.sign(hash(transaction), canonicalChecker); transaction.signatures.emplace_back(Signature(result, type)); } diff --git a/src/Ethereum/Barz.cpp b/src/Ethereum/Barz.cpp index 9d1cef7dee7..54af0c466dc 100644 --- a/src/Ethereum/Barz.cpp +++ b/src/Ethereum/Barz.cpp @@ -243,8 +243,8 @@ Data getAuthorizationHash(const Data& chainId, const std::string& contractAddres std::vector getRSVY(const Data& hash, const std::string& privateKey) { auto privateKeyData = parse_hex(privateKey); - auto privateKeyObj = PrivateKey(privateKeyData); - auto signature = privateKeyObj.sign(hash, TWCurveSECP256k1); + auto privateKeyObj = PrivateKey(privateKeyData, TWCurveSECP256k1); + auto signature = privateKeyObj.sign(hash); if (signature.empty()) { return {}; } diff --git a/src/Everscale/Signer.cpp b/src/Everscale/Signer.cpp index 33e5ee368b0..b3cbcbcdc57 100644 --- a/src/Everscale/Signer.cpp +++ b/src/Everscale/Signer.cpp @@ -12,7 +12,7 @@ namespace TW::Everscale { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto key = PrivateKey(input.private_key()); + auto key = PrivateKey(input.private_key(), TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto protoOutput = Proto::SigningOutput(); diff --git a/src/FIO/TransactionBuilder.cpp b/src/FIO/TransactionBuilder.cpp index 4a890d81705..3061accf7b2 100644 --- a/src/FIO/TransactionBuilder.cpp +++ b/src/FIO/TransactionBuilder.cpp @@ -70,7 +70,7 @@ string TransactionBuilder::actionName(const Proto::SigningInput& input) { } string TransactionBuilder::sign(Proto::SigningInput in) { - PrivateKey privateKey(in.private_key()); + PrivateKey privateKey(in.private_key(), TWCurveSECP256k1); PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); Address owner(publicKey); diff --git a/src/Filecoin/Signer.cpp b/src/Filecoin/Signer.cpp index d0d72f4dd2c..8db3a82cebd 100644 --- a/src/Filecoin/Signer.cpp +++ b/src/Filecoin/Signer.cpp @@ -77,7 +77,7 @@ Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& pub Proto::SigningOutput Signer::signSecp256k1(const Proto::SigningInput& input) { // Load private key and transaction from Protobuf input. - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto pubkey = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended); auto transaction = Signer::buildTx(pubkey, input); @@ -123,7 +123,7 @@ Transaction Signer::buildTx(const PublicKey& publicKey, const Proto::SigningInpu /// https://github.com/filecoin-project/lotus/blob/ce17546a762eef311069e13410d15465d832a45e/chain/messagesigner/messagesigner.go#L197-L211 Proto::SigningOutput Signer::signDelegated(const Proto::SigningInput& input) { // Load private key from Protobuf input. - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); // Load the transaction params. @@ -172,7 +172,7 @@ Proto::SigningOutput Signer::signDelegated(const Proto::SigningInput& input) { auto preHash = data(ethOutput.data_hash()); // Sign transaction as an Ethereum EIP1559 native transfer. - Data signature = privateKey.sign(preHash, TWCurveSECP256k1); + Data signature = privateKey.sign(preHash); // Generate a Filecoin signed message. Transaction filecoinTransaction( diff --git a/src/HDWallet.cpp b/src/HDWallet.cpp index 597484381b0..a329f6634da 100644 --- a/src/HDWallet.cpp +++ b/src/HDWallet.cpp @@ -147,14 +147,14 @@ template PrivateKey HDWallet::getMasterKey(TWCurve curve) const { auto node = getMasterNode(*this, curve); auto data = Data(node.private_key, node.private_key + PrivateKey::_size); - return PrivateKey(data); + return PrivateKey(data, curve); } template PrivateKey HDWallet::getMasterKeyExtension(TWCurve curve) const { auto node = getMasterNode(*this, curve); auto data = Data(node.private_key_extension, node.private_key_extension + PrivateKey::_size); - return PrivateKey(data); + return PrivateKey(data, curve); } template @@ -173,7 +173,7 @@ PrivateKey HDWallet::getKeyByCurve(TWCurve curve, const DerivationPath case TWPrivateKeyTypeCardano: { if (derivationPath.indices.size() < 4 || derivationPath.indices[3].value > 1) { // invalid derivation path - return PrivateKey(Data(PrivateKey::cardanoKeySize)); + return PrivateKey(Data(PrivateKey::cardanoKeySize), curve); } const DerivationPath stakingPath = cardanoStakingDerivationPath(derivationPath); @@ -188,7 +188,7 @@ PrivateKey HDWallet::getKeyByCurve(TWCurve curve, const DerivationPath auto chainCode2 = Data(node2.chain_code, node2.chain_code + PrivateKey::_size); TW::memzero(&node); - return PrivateKey(pkData, extData, chainCode, pkData2, extData2, chainCode2); + return PrivateKey(pkData, extData, chainCode, pkData2, extData2, chainCode2, curve); } case TWPrivateKeyTypeDefault: default: @@ -196,9 +196,9 @@ PrivateKey HDWallet::getKeyByCurve(TWCurve curve, const DerivationPath auto data = Data(node.private_key, node.private_key + PrivateKey::_size); TW::memzero(&node); if (curve == TWCurveStarkex) { - return ImmutableX::getPrivateKeyFromEthPrivKey(PrivateKey(data)); + return ImmutableX::getPrivateKeyFromEthPrivKey(PrivateKey(data, curve)); } - return PrivateKey(data); + return PrivateKey(data, curve); } } @@ -312,7 +312,7 @@ std::optional HDWallet::getPrivateKeyFromExtended(const st hdnode_private_ckd(&node, path.change()); hdnode_private_ckd(&node, path.address()); - return PrivateKey(Data(node.private_key, node.private_key + 32)); + return PrivateKey(Data(node.private_key, node.private_key + 32), curve); } template diff --git a/src/Harmony/Signer.cpp b/src/Harmony/Signer.cpp index 79f59e80f08..8a1cb459306 100644 --- a/src/Harmony/Signer.cpp +++ b/src/Harmony/Signer.cpp @@ -86,7 +86,7 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { } Proto::SigningOutput Signer::signTransaction(const Proto::SigningInput& input) { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto transaction = Signer::buildUnsignedTransaction(input); @@ -131,7 +131,7 @@ uint8_t Signer::getEnum() noexcept { template Proto::SigningOutput Signer::signStaking(const Proto::SigningInput &input, function func) { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto stakingTx = buildUnsignedStakingTransaction(input, func); auto signer = Signer(uint256_t(load(input.chain_id()))); diff --git a/src/Hedera/Signer.cpp b/src/Hedera/Signer.cpp index 47628377551..13d2a93f9e0 100644 --- a/src/Hedera/Signer.cpp +++ b/src/Hedera/Signer.cpp @@ -86,7 +86,7 @@ static inline Proto::SigningOutput sign(const proto::TransactionBody& body, cons namespace TW::Hedera { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); auto body = internals::transactionBodyPrerequisites(input); switch (input.body().data_case()) { diff --git a/src/IOST/Account.cpp b/src/IOST/Account.cpp index 1aa3c91f5b7..24b17e61a54 100644 --- a/src/IOST/Account.cpp +++ b/src/IOST/Account.cpp @@ -47,15 +47,15 @@ Account::Account(const Proto::AccountInfo& account) { } Data Account::sign(const Data& digest, TWCurve curve) const { - return PrivateKey(activeKey).sign(digest, curve); + return PrivateKey(activeKey, curve).sign(digest); } Data Account::publicActiveKey() const { - return PrivateKey(activeKey).getPublicKey(TWPublicKeyTypeED25519).bytes; + return PrivateKey(activeKey, TWCurveED25519).getPublicKey(TWPublicKeyTypeED25519).bytes; } Data Account::publicOwnerKey() const { - return PrivateKey(ownerKey).getPublicKey(TWPublicKeyTypeED25519).bytes; + return PrivateKey(ownerKey, TWCurveED25519).getPublicKey(TWPublicKeyTypeED25519).bytes; } std::string Account::address(const std::string& publickey) { diff --git a/src/Icon/Signer.cpp b/src/Icon/Signer.cpp index e537a72740d..e24f30feeb7 100644 --- a/src/Icon/Signer.cpp +++ b/src/Icon/Signer.cpp @@ -79,8 +79,8 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Proto::SigningOutput Signer::sign() const noexcept { const auto hash = Hash::sha3_256(Signer::preImage()); - const auto key = PrivateKey(input.private_key()); - const auto signature = key.sign(hash, TWCurveSECP256k1); + const auto key = PrivateKey(input.private_key(), TWCurveSECP256k1); + const auto signature = key.sign(hash); auto output = Proto::SigningOutput(); output.set_signature(signature.data(), signature.size()); diff --git a/src/ImmutableX/StarkKey.cpp b/src/ImmutableX/StarkKey.cpp index 51dec1d669f..5adb08cc96e 100644 --- a/src/ImmutableX/StarkKey.cpp +++ b/src/ImmutableX/StarkKey.cpp @@ -34,11 +34,11 @@ std::string grindKey(const Data& seed) { PrivateKey getPrivateKeyFromSeed(const Data& seed, const DerivationPath& path) { auto key = HDWallet<32>::bip32DeriveRawSeed(TWCoinTypeEthereum, seed, path); auto data = parse_hex(grindKey(key.bytes), true); - return PrivateKey(data); + return PrivateKey(data, TWCurveStarkex); } PrivateKey getPrivateKeyFromEthPrivKey(const PrivateKey& ethPrivKey) { - return PrivateKey(parse_hex(ImmutableX::grindKey(ethPrivKey.bytes), true)); + return PrivateKey(parse_hex(ImmutableX::grindKey(ethPrivKey.bytes), true), TWCurveStarkex); } PrivateKey getPrivateKeyFromRawSignature(const Data& signature, const DerivationPath& derivationPath) { diff --git a/src/IoTeX/Signer.cpp b/src/IoTeX/Signer.cpp index 75c0d2cf7a6..9c139888d1c 100644 --- a/src/IoTeX/Signer.cpp +++ b/src/IoTeX/Signer.cpp @@ -16,17 +16,17 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { } Data Signer::sign() const { - auto key = PrivateKey(input.privatekey()); - return key.sign(hash(), TWCurveSECP256k1); + auto key = PrivateKey(input.privatekey(), TWCurveSECP256k1); + return key.sign(hash()); } Proto::SigningOutput Signer::build() const { auto signedAction = Proto::Action(); signedAction.mutable_core()->MergeFrom(action); - auto key = PrivateKey(input.privatekey()); + auto key = PrivateKey(input.privatekey(), TWCurveSECP256k1); auto pk = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended).bytes; signedAction.set_senderpubkey(pk.data(), pk.size()); - auto sig = key.sign(hash(), TWCurveSECP256k1); + auto sig = key.sign(hash()); signedAction.set_signature(sig.data(), sig.size()); auto output = Proto::SigningOutput(); diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index b216461fe31..31ed44c0933 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -61,7 +61,7 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na StoredKey key = createWithPrivateKey(name, password, privateKeyData, encryption); const auto derivationPath = TW::derivationPath(coin); const auto pubKeyType = TW::publicKeyType(coin); - const auto pubKey = PrivateKey(privateKeyData).getPublicKey(pubKeyType); + const auto pubKey = PrivateKey(privateKeyData, TWCoinTypeCurve(coin)).getPublicKey(pubKeyType); const auto address = TW::deriveAddress(coin, PrivateKey(privateKeyData)); key.accounts.emplace_back(address, coin, TWDerivationDefault, derivationPath, hex(pubKey.bytes), ""); return key; @@ -261,7 +261,7 @@ const PrivateKey StoredKey::privateKey(TWCoinType coin, [[maybe_unused]] TWDeriv return wallet.getKey(coin, account.derivationPath); } // type == StoredKeyType::privateKey - return PrivateKey(payload.decrypt(password)); + return PrivateKey(payload.decrypt(password), TWCoinTypeCurve(coin)); } void StoredKey::fixAddresses(const Data& password) { diff --git a/src/MultiversX/Signer.cpp b/src/MultiversX/Signer.cpp index a2a80fdedd3..16abbd2c835 100644 --- a/src/MultiversX/Signer.cpp +++ b/src/MultiversX/Signer.cpp @@ -15,9 +15,9 @@ namespace TW::MultiversX { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { TransactionFactory factory; - auto privateKey = PrivateKey(input.private_key()); + auto privateKey = PrivateKey(input.private_key(), TWCurveED25519); auto signableAsData = buildUnsignedTxBytes(input); - auto signature = privateKey.sign(signableAsData, TWCurveED25519); + auto signature = privateKey.sign(signableAsData); return buildSigningOutput(input, signature); } diff --git a/src/NEAR/Serialization.cpp b/src/NEAR/Serialization.cpp index f2272383ede..f1aac366a41 100644 --- a/src/NEAR/Serialization.cpp +++ b/src/NEAR/Serialization.cpp @@ -164,7 +164,7 @@ static void writeAction(Data& data, const Proto::Action& action) { Data transactionData(const Proto::SigningInput& input) { Data data; writeString(data, input.signer_id()); - auto key = PrivateKey(input.private_key()); + auto key = PrivateKey(input.private_key(), TWCurveED25519); auto public_key = key.getPublicKey(TWPublicKeyTypeED25519); auto public_key_proto = Proto::PublicKey(); public_key_proto.set_data(public_key.bytes.data(), public_key.bytes.size()); diff --git a/src/NEAR/Signer.cpp b/src/NEAR/Signer.cpp index 9e2589ed09e..f5ae073d484 100644 --- a/src/NEAR/Signer.cpp +++ b/src/NEAR/Signer.cpp @@ -12,9 +12,9 @@ namespace TW::NEAR { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto transaction = transactionData(input); - auto key = PrivateKey(input.private_key()); + auto key = PrivateKey(input.private_key(), TWCurveED25519); auto hash = Hash::sha256(transaction); - auto signature = key.sign(hash, TWCurveED25519); + auto signature = key.sign(hash); auto output = Proto::SigningOutput(); auto signedTransaction = signedTransactionData(transaction, signature); output.set_signed_transaction(signedTransaction.data(), signedTransaction.size()); diff --git a/src/NEO/Signer.cpp b/src/NEO/Signer.cpp index df3187c9447..c3f210e08d2 100644 --- a/src/NEO/Signer.cpp +++ b/src/NEO/Signer.cpp @@ -46,7 +46,7 @@ void Signer::sign(Transaction& tx) const { } Data Signer::sign(const Data& data) const { - auto signature = getPrivateKey().sign(TW::Hash::sha256(data), TWCurveNIST256p1); + auto signature = getPrivateKey().sign(TW::Hash::sha256(data)); signature.pop_back(); return signature; } @@ -264,7 +264,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); try { auto signer = - Signer(PrivateKey(Data(input.private_key().begin(), input.private_key().end()))); + Signer(PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveNIST256p1)); Proto::TransactionPlan plan; if (input.has_plan()) { plan = input.plan(); diff --git a/src/NULS/Signer.cpp b/src/NULS/Signer.cpp index 2e1ffca2784..51b44385159 100644 --- a/src/NULS/Signer.cpp +++ b/src/NULS/Signer.cpp @@ -151,11 +151,11 @@ Data Signer::sign() const { Data txHash = calcTransactionDigest(dataRet); Data privKey = data(input.private_key()); - auto priv = PrivateKey(privKey); + auto priv = PrivateKey(privKey, TWCurveSECP256k1); auto transactionSignature = makeTransactionSignature(priv, txHash); if (Address::isValid(input.fee_payer()) && input.from() != input.fee_payer()) { Data feePayerPrivKey = data(input.fee_payer_private_key()); - auto feePayerPriv = PrivateKey(feePayerPrivKey); + auto feePayerPriv = PrivateKey(feePayerPrivKey, TWCurveSECP256k1); auto feePayerTransactionSignature = makeTransactionSignature(feePayerPriv, txHash); transactionSignature.insert(transactionSignature.end(), feePayerTransactionSignature.begin(), diff --git a/src/Nano/Signer.cpp b/src/Nano/Signer.cpp index 1dc1deeb228..3599953777f 100644 --- a/src/Nano/Signer.cpp +++ b/src/Nano/Signer.cpp @@ -115,7 +115,7 @@ std::array hashBlockData(const PublicKey& publicKey, const Proto::Sign } Signer::Signer(const Proto::SigningInput& input) - : privateKey(Data(input.private_key().begin(), input.private_key().end())), + : privateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519Blake2bNano), publicKey(privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b)), input(input), previous{previousFromInput(input)}, @@ -143,7 +143,7 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { std::array Signer::sign() const noexcept { auto digest = Data(blockHash.begin(), blockHash.end()); - auto sig = privateKey.sign(digest, TWCurveED25519Blake2bNano); + auto sig = privateKey.sign(digest); std::array signature = {0}; std::copy_n(sig.begin(), signature.size(), signature.begin()); diff --git a/src/Nebulas/Signer.cpp b/src/Nebulas/Signer.cpp index ada56c94f8b..026683667e5 100644 --- a/src/Nebulas/Signer.cpp +++ b/src/Nebulas/Signer.cpp @@ -15,7 +15,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto tx = signer.buildTransaction(input); - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); signer.sign(privateKey, tx); auto output = Proto::SigningOutput(); @@ -29,7 +29,7 @@ void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) const transaction.hash = this->hash(transaction); transaction.chainID = chainID; transaction.algorithm = 1; - transaction.signature = privateKey.sign(transaction.hash, TWCurveSECP256k1); + transaction.signature = privateKey.sign(transaction.hash); transaction.serializeToRaw(); } diff --git a/src/Nervos/Signer.cpp b/src/Nervos/Signer.cpp index d5cb006ad30..1fd9b24347b 100644 --- a/src/Nervos/Signer.cpp +++ b/src/Nervos/Signer.cpp @@ -34,7 +34,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& signingInput) noexc std::vector privateKeys; privateKeys.reserve(signingInput.private_key_size()); for (auto&& privateKey : signingInput.private_key()) { - privateKeys.emplace_back(privateKey); + privateKeys.emplace_back(PrivateKey(privateKey, TWCurveSECP256k1)); } auto error = tx.sign(privateKeys); if (error != Common::Proto::OK) { diff --git a/src/Nimiq/Signer.cpp b/src/Nimiq/Signer.cpp index e8de7375c62..fb4e6b55269 100644 --- a/src/Nimiq/Signer.cpp +++ b/src/Nimiq/Signer.cpp @@ -10,7 +10,7 @@ namespace TW::Nimiq { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); auto pubkey = key.getPublicKey(TWPublicKeyTypeED25519); std::array pubkeyBytes; std::copy(pubkey.bytes.begin(), pubkey.bytes.end(), pubkeyBytes.data()); diff --git a/src/Oasis/Signer.cpp b/src/Oasis/Signer.cpp index ea3405382a8..ca687bccf05 100644 --- a/src/Oasis/Signer.cpp +++ b/src/Oasis/Signer.cpp @@ -24,7 +24,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { } Data Signer::build() const { - auto privateKey = PrivateKey(input.private_key()); + auto privateKey = PrivateKey(input.private_key(), TWCurveED25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); if(input.has_transfer()) { @@ -53,7 +53,7 @@ Data Signer::build() const { template Data Signer::signTransaction(T& tx) const { - auto privateKey = PrivateKey(input.private_key()); + auto privateKey = PrivateKey(input.private_key(), TWCurveED25519); // The use of this context thing is explained here --> // https://docs.oasis.dev/oasis-core/common-functionality/crypto#domain-separation @@ -62,7 +62,7 @@ Data Signer::signTransaction(T& tx) const { dataToHash.insert(dataToHash.end(), encodedMessage.begin(), encodedMessage.end()); auto hash = Hash::sha512_256(dataToHash); - auto signature = privateKey.sign(hash, TWCurveED25519); + auto signature = privateKey.sign(hash); return Data(signature.begin(), signature.end()); } diff --git a/src/Ontology/Oep4TxBuilder.cpp b/src/Ontology/Oep4TxBuilder.cpp index 9041adc3b78..2ef9d41ca78 100644 --- a/src/Ontology/Oep4TxBuilder.cpp +++ b/src/Ontology/Oep4TxBuilder.cpp @@ -24,8 +24,8 @@ Data Oep4TxBuilder::balanceOf(const Ontology::Proto::SigningInput& input) { Data Oep4TxBuilder::transfer(const Ontology::Proto::SigningInput& input) { Oep4 oep4(parse_hex(input.contract())); - auto payerSigner = Signer(PrivateKey(input.payer_private_key())); - auto fromSigner = Signer(PrivateKey(input.owner_private_key())); + auto payerSigner = Signer(PrivateKey(input.payer_private_key(), TWCurveNIST256p1)); + auto fromSigner = Signer(PrivateKey(input.owner_private_key(), TWCurveNIST256p1)); auto toAddress = Address(input.to_address()); auto tranferTx = oep4.transfer(fromSigner, toAddress, input.amount(), payerSigner, input.gas_price(), input.gas_limit(), input.nonce()); diff --git a/src/Ontology/OngTxBuilder.cpp b/src/Ontology/OngTxBuilder.cpp index 2cae37b9cb1..6f6fcfc4383 100644 --- a/src/Ontology/OngTxBuilder.cpp +++ b/src/Ontology/OngTxBuilder.cpp @@ -20,8 +20,8 @@ Data OngTxBuilder::balanceOf(const Ontology::Proto::SigningInput& input) { } Data OngTxBuilder::transfer(const Ontology::Proto::SigningInput& input) { - auto payer = Signer(PrivateKey(input.payer_private_key())); - auto owner = Signer(PrivateKey(input.owner_private_key())); + auto payer = Signer(PrivateKey(input.payer_private_key(), TWCurveNIST256p1)); + auto owner = Signer(PrivateKey(input.owner_private_key(), TWCurveNIST256p1)); auto toAddress = Address(input.to_address()); auto transaction = Ong().transfer(owner, toAddress, input.amount(), payer, input.gas_price(), input.gas_limit(), input.nonce()); @@ -30,8 +30,8 @@ Data OngTxBuilder::transfer(const Ontology::Proto::SigningInput& input) { } Data OngTxBuilder::withdraw(const Ontology::Proto::SigningInput& input) { - auto payer = Signer(PrivateKey(input.payer_private_key())); - auto owner = Signer(PrivateKey(input.owner_private_key())); + auto payer = Signer(PrivateKey(input.payer_private_key(), TWCurveNIST256p1)); + auto owner = Signer(PrivateKey(input.owner_private_key(), TWCurveNIST256p1)); auto toAddress = Address(input.to_address()); auto transaction = Ong().withdraw(owner, toAddress, input.amount(), payer, input.gas_price(), input.gas_limit(), input.nonce()); diff --git a/src/Ontology/OntTxBuilder.cpp b/src/Ontology/OntTxBuilder.cpp index dc26b38e192..3da313eda46 100644 --- a/src/Ontology/OntTxBuilder.cpp +++ b/src/Ontology/OntTxBuilder.cpp @@ -20,8 +20,8 @@ Data OntTxBuilder::balanceOf(const Ontology::Proto::SigningInput& input) { } Data OntTxBuilder::transfer(const Ontology::Proto::SigningInput& input) { - auto payerSigner = Signer(PrivateKey(input.payer_private_key())); - auto fromSigner = Signer(PrivateKey(input.owner_private_key())); + auto payerSigner = Signer(PrivateKey(input.payer_private_key(), TWCurveNIST256p1)); + auto fromSigner = Signer(PrivateKey(input.owner_private_key(), TWCurveNIST256p1)); auto toAddress = Address(input.to_address()); auto tranferTx = Ont().transfer(fromSigner, toAddress, input.amount(), payerSigner, input.gas_price(), input.gas_limit(), input.nonce()); diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index 501dfc4124c..98e05e500bc 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -114,11 +114,19 @@ TWPrivateKeyType PrivateKey::getType(TWCurve curve) noexcept { } } -PrivateKey::PrivateKey(const Data& data) { - if (!isValid(data)) { + PrivateKey::PrivateKey(const Data& data) { + if (!isValid(data)) { + throw std::invalid_argument("Invalid private key data"); + } + bytes = data; + } + +PrivateKey::PrivateKey(const Data& data, TWCurve curve) { + if (!isValid(data, curve)) { throw std::invalid_argument("Invalid private key data"); } bytes = data; + _curve = curve; } PrivateKey::PrivateKey( @@ -136,6 +144,23 @@ PrivateKey::PrivateKey( append(bytes, chainCode2); } +PrivateKey::PrivateKey( + const Data& key1, const Data& extension1, const Data& chainCode1, + const Data& key2, const Data& extension2, const Data& chainCode2, + TWCurve curve) { + if (key1.size() != _size || extension1.size() != _size || chainCode1.size() != _size || + key2.size() != _size || extension2.size() != _size || chainCode2.size() != _size) { + throw std::invalid_argument("Invalid private key or extended key data"); + } + bytes = key1; + append(bytes, extension1); + append(bytes, chainCode1); + append(bytes, key2); + append(bytes, extension2); + append(bytes, chainCode2); + _curve = curve; +} + PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { Data result; switch (type) { @@ -206,6 +231,9 @@ int ecdsa_sign_digest_checked(const ecdsa_curve* curve, const uint8_t* priv_key, } Data PrivateKey::sign(const Data& digest, TWCurve curve) const { + if (_curve.has_value() && _curve.value() != curve) { + throw std::invalid_argument("Specified curve is different from the curve of the private key"); + } Data result; bool success = false; switch (curve) { @@ -256,7 +284,17 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { return result; } +Data PrivateKey::sign(const Data& digest, int (*canonicalChecker)(uint8_t by, uint8_t sig[64])) const { + if (!_curve.has_value()) { + throw std::invalid_argument("Curve is not set"); + } + return sign(digest, _curve.value(), canonicalChecker); +} + Data PrivateKey::sign(const Data& digest, TWCurve curve, int (*canonicalChecker)(uint8_t by, uint8_t sig[64])) const { + if (_curve.has_value() && _curve.value() != curve) { + throw std::invalid_argument("Specified curve is different from the curve of the private key"); + } Data result; bool success = false; switch (curve) { @@ -287,7 +325,17 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve, int (*canonicalChecker) return result; } +Data PrivateKey::sign(const Data& digest) const { + if (!_curve.has_value()) { + throw std::invalid_argument("Curve is not set"); + } + return sign(digest, _curve.value()); +} + Data PrivateKey::signAsDER(const Data& digest) const { + if (_curve.has_value() && _curve.value() != TWCurveSECP256k1) { + throw std::invalid_argument("DER signature is only supported for SECP256k1"); + } Data sig(64); bool success = ecdsa_sign_digest(&secp256k1, key().data(), digest.data(), sig.data(), nullptr, nullptr) == 0; @@ -304,6 +352,9 @@ Data PrivateKey::signAsDER(const Data& digest) const { } Data PrivateKey::signZilliqa(const Data& message) const { + if (_curve.has_value() && _curve.value() != TWCurveSECP256k1) { + throw std::invalid_argument("Zilliqa signature is only supported for SECP256k1"); + } Data sig(64); bool success = zil_schnorr_sign(&secp256k1, key().data(), message.data(), static_cast(message.size()), sig.data()) == 0; diff --git a/src/PrivateKey.h b/src/PrivateKey.h index c6ab3ddd8dc..ba0e5ebcff9 100644 --- a/src/PrivateKey.h +++ b/src/PrivateKey.h @@ -10,6 +10,8 @@ #include #include +#include + namespace TW { class PrivateKey { @@ -42,15 +44,34 @@ class PrivateKey { static TWPrivateKeyType getType(TWCurve curve) noexcept; /// Initializes a private key with an array of bytes. Size must be exact (normally 32, or 192 for extended) + /// @deprecated Use PrivateKey(const Data& data, TWCurve curve) instead explicit PrivateKey(const Data& data); + /// Initializes a private key with an array of bytes and a curve. + /// Size of the data must be exact (normally 32, or 192 for extended) + /// Signing functions will throw an exception if the provided curve is different from the one specified. + explicit PrivateKey(const Data& data, TWCurve curve); + /// Initializes a private key from a string of bytes. + /// @deprecated Use PrivateKey(const std::string& data, TWCurve curve) instead explicit PrivateKey(const std::string& data) : PrivateKey(TW::data(data)) {} + /// Initializes a private key from a string of bytes and a curve. + /// Signing functions will throw an exception if the provided curve is different from the one specified. + explicit PrivateKey(const std::string& data, TWCurve curve) : PrivateKey(TW::data(data), curve) {} + /// Initializes a Cardano style key + /// @deprecated Use PrivateKey(const Data& bytes1, const Data& extension1, const Data& chainCode1, const Data& bytes2, const Data& extension2, const Data& chainCode2, TWCurve curve) instead explicit PrivateKey( const Data& bytes1, const Data& extension1, const Data& chainCode1, const Data& bytes2, const Data& extension2, const Data& chainCode2); + + /// Initializes a Cardano style key with a specified curve. + /// Signing functions will throw an exception if the provided curve is different from the one specified. + explicit PrivateKey( + const Data& bytes1, const Data& extension1, const Data& chainCode1, + const Data& bytes2, const Data& extension2, const Data& chainCode2, + TWCurve curve); PrivateKey(const PrivateKey& other) = default; PrivateKey& operator=(const PrivateKey& other) = default; @@ -64,21 +85,37 @@ class PrivateKey { PublicKey getPublicKey(enum TWPublicKeyType type) const; /// Signs a digest using the given ECDSA curve. + /// If constructed with a curve, an exception will be thrown if the curve does not match the one specified. + /// @deprecated Use sign(const Data& digest) instead Data sign(const Data& digest, TWCurve curve) const; + /// Signs a digest using the given ECDSA curve at the time of construction. + /// IF constructed without a curve, an exception will be thrown. + Data sign(const Data& digest) const; + /// Signs a digest using the given ECDSA curve and prepends the recovery id (a la graphene) /// Only a sig that passes canonicalChecker is returned + /// If constructed with a curve, an exception will be thrown if the curve does not match the one specified. + /// @deprecated Use sign(const Data& digest, int(*canonicalChecker)(uint8_t by, uint8_t sig[64])) instead Data sign(const Data& digest, TWCurve curve, int(*canonicalChecker)(uint8_t by, uint8_t sig[64])) const; + /// Signs a digest using the given ECDSA curve and prepends the recovery id (a la graphene) + /// Only a sig that passes canonicalChecker is returned + Data sign(const Data& digest, int (*canonicalChecker)(uint8_t by, uint8_t sig[64])) const; + /// Signs a digest using the given ECDSA curve. The result is encoded with /// DER. + /// If constructed with a curve, an exception will be thrown if the curve does not match SECP256k1. Data signAsDER(const Data& digest) const; /// Signs a digest using given ECDSA curve, returns Zilliqa schnorr signature + /// If constructed with a curve, an exception will be thrown if the curve does not match SECP256k1. Data signZilliqa(const Data& message) const; /// Cleanup contents (fill with 0s), called before destruction void cleanup(); +private: + std::optional _curve = std::nullopt; }; } // namespace TW diff --git a/src/Stellar/Signer.cpp b/src/Stellar/Signer.cpp index 01ac21aa776..1718ff8b571 100644 --- a/src/Stellar/Signer.cpp +++ b/src/Stellar/Signer.cpp @@ -23,7 +23,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { std::string Signer::sign() const noexcept { - auto key = PrivateKey(Data(_input.private_key().begin(), _input.private_key().end())); + auto key = PrivateKey(Data(_input.private_key().begin(), _input.private_key().end()), TWCurveED25519); auto account = Address(_input.account()); auto encoded = encode(_input); @@ -39,7 +39,7 @@ std::string Signer::sign() const noexcept { auto hash = Hash::sha256(encodedWithHeaders); auto data = Data(hash.begin(), hash.end()); - auto sign = key.sign(data, TWCurveED25519); + auto sign = key.sign(data); auto signature = Data(); signature.insert(signature.end(), encoded.begin(), encoded.end()); diff --git a/src/Tezos/BinaryCoding.cpp b/src/Tezos/BinaryCoding.cpp index 7300157f5ca..6be3edd6442 100644 --- a/src/Tezos/BinaryCoding.cpp +++ b/src/Tezos/BinaryCoding.cpp @@ -60,7 +60,7 @@ PrivateKey parsePrivateKey(const std::string& privateKey) { throw std::invalid_argument("Invalid Public Key"); } append(pk, Data(decoded.begin() + prefix_size, decoded.end())); - return PrivateKey(pk); + return PrivateKey(pk, TWCurveSECP256k1); } } // namespace TW::Tezos diff --git a/src/Tezos/Signer.cpp b/src/Tezos/Signer.cpp index bd61436b4d7..42ec445cdd0 100644 --- a/src/Tezos/Signer.cpp +++ b/src/Tezos/Signer.cpp @@ -17,7 +17,7 @@ namespace TW::Tezos { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(); - PrivateKey key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + PrivateKey key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); Data encoded; if (input.encoded_operations().empty()) { auto operationList = Tezos::OperationList(input.operation_list().branch()); @@ -53,7 +53,7 @@ Data Signer::signData(const PrivateKey& privateKey, const Data& data) { append(watermarkedData, data); Data hash = Hash::blake2b(watermarkedData, 32); - Data signature = privateKey.sign(hash, TWCurve::TWCurveED25519); + Data signature = privateKey.sign(hash); Data signedData = Data(); append(signedData, data); diff --git a/src/Theta/Signer.cpp b/src/Theta/Signer.cpp index 020965d374a..0f25d9503c5 100755 --- a/src/Theta/Signer.cpp +++ b/src/Theta/Signer.cpp @@ -12,7 +12,7 @@ using RLP = TW::Ethereum::RLP; namespace TW::Theta { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto pkFrom = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto pkFrom = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto from = Ethereum::Address(pkFrom.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); auto transaction = Transaction( @@ -61,7 +61,7 @@ Data Signer::encode(const Transaction& transaction) const { Data Signer::sign(const PrivateKey& privateKey, const Transaction& transaction) noexcept { auto encoded = encode(transaction); auto hash = Hash::keccak256(encoded); - auto signature = privateKey.sign(hash, TWCurveSECP256k1); + auto signature = privateKey.sign(hash); return signature; } diff --git a/src/Tron/Signer.cpp b/src/Tron/Signer.cpp index 72e3783d079..d0acf6706cd 100644 --- a/src/Tron/Signer.cpp +++ b/src/Tron/Signer.cpp @@ -404,9 +404,9 @@ Data serialize(const protocol::Transaction& tx) noexcept { } Proto::SigningOutput signDirect(const Proto::SigningInput& input) { - const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto hash = parse_hex(input.txid()); - const auto signature = key.sign(hash, TWCurveSECP256k1); + const auto signature = key.sign(hash); auto output = Proto::SigningOutput(); output.set_signature(signature.data(), signature.size()); output.set_id(input.txid()); @@ -441,8 +441,8 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { const auto hash = Hash::sha256(serialize(tx)); - const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - const auto signature = key.sign(hash, TWCurveSECP256k1); + const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); + const auto signature = key.sign(hash); const auto json = transactionJSON(tx, hash, signature).dump(); diff --git a/src/VeChain/Signer.cpp b/src/VeChain/Signer.cpp index 78ddc8a81e6..de3e864318c 100644 --- a/src/VeChain/Signer.cpp +++ b/src/VeChain/Signer.cpp @@ -11,7 +11,7 @@ using namespace TW; namespace TW::VeChain { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto transaction = Transaction(); transaction.chainTag = static_cast(input.chain_tag()); transaction.blockRef = input.block_ref(); @@ -37,7 +37,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Data Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexcept { auto encoded = transaction.encode(); auto hash = Hash::blake2b(encoded, 32); - auto signature = privateKey.sign(hash, TWCurveSECP256k1); + auto signature = privateKey.sign(hash); return Data(signature.begin(), signature.end()); } diff --git a/src/Waves/Signer.cpp b/src/Waves/Signer.cpp index 2d4d6955236..a8e11f6e7cf 100644 --- a/src/Waves/Signer.cpp +++ b/src/Waves/Signer.cpp @@ -11,7 +11,7 @@ using namespace TW; namespace TW::Waves { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveCurve25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); auto transaction = Transaction(input, publicKey.bytes); @@ -26,7 +26,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Data Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexcept { try { auto bytesToSign = transaction.serializeToSign(); - auto signature = privateKey.sign(bytesToSign, TWCurveCurve25519); + auto signature = privateKey.sign(bytesToSign); return signature; } catch (...) { return Data(); diff --git a/src/Zilliqa/Signer.cpp b/src/Zilliqa/Signer.cpp index cf2003ec4f7..6db4930d251 100644 --- a/src/Zilliqa/Signer.cpp +++ b/src/Zilliqa/Signer.cpp @@ -44,7 +44,7 @@ static inline ByteArray* byteArray(const void* data, size_t size) { Data Signer::getPreImage(const Proto::SigningInput& input, Address& address) noexcept { auto internal = ZilliqaMessage::ProtoTransactionCoreInfo(); - const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); if (!Address::decode(input.to(), address)) { // invalid input address return Data(0); @@ -91,7 +91,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); Address address; const auto preImage = Signer::getPreImage(input, address); - const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); const auto pubKey = key.getPublicKey(TWPublicKeyTypeSECP256k1); const auto signature = key.signZilliqa(preImage); const auto transaction = input.transaction(); diff --git a/swift/Podfile.lock b/swift/Podfile.lock index cc5798c5639..88093ba1a0d 100644 --- a/swift/Podfile.lock +++ b/swift/Podfile.lock @@ -13,4 +13,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: aac2324ba35cdd5631cb37618cd483887bab9cfd -COCOAPODS: 1.14.3 +COCOAPODS: 1.16.2 diff --git a/tests/chains/Aeternity/SignerTests.cpp b/tests/chains/Aeternity/SignerTests.cpp index 3fe61a03851..c5038f13631 100644 --- a/tests/chains/Aeternity/SignerTests.cpp +++ b/tests/chains/Aeternity/SignerTests.cpp @@ -22,7 +22,7 @@ TEST(AeternitySigner, Sign) { uint64_t nonce = 49; auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCurveED25519); auto result = Signer::sign(privateKey, transaction); EXPECT_EQ(result.signature(), "sg_VW42qDPP3MMNFAStYaumjZz7mC7BZYpbNa15E57ejqUe7JdQFWCiX65eLNUpGMpt8tSpfgCfkYzcaFppqx7W75CrcWdC8"); @@ -39,7 +39,7 @@ TEST(AeternitySigner, SignTxWithZeroTtl) { uint64_t nonce = 49; auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCurveED25519); auto result = Signer::sign(privateKey, transaction); EXPECT_EQ(result.signature(), "sg_7qJK868bqEZ5ciC2P3WCKYfhayvKTHvPsz3bdPgpfF3Ky7yNg9f8k22A3gxjjSm9afa6JmP8TJpF4GJkFh2k7gGaog9KS"); @@ -56,7 +56,7 @@ TEST(AeternitySigner, SignTxWithZeroAmount) { uint64_t nonce = 7; auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCurveED25519); auto result = Signer::sign(privateKey, transaction); EXPECT_EQ(result.signature(), "sg_ShWvujPnyKBT1Ng2X5k6XSchVK8Bq7LYEisPMH11DUoPkXZcooBzqw81j9j5JewoFFpT9xEhUptj1azcLA21ogURYh4Lz"); @@ -73,7 +73,7 @@ TEST(AeternitySigner, SignTxWithZeroNonce) { uint64_t nonce = 0; auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCurveED25519); auto result = Signer::sign(privateKey, transaction); EXPECT_EQ(result.signature(), "sg_MaJc4ptSUhq5kH6mArszDAvu4f7PejyuhmgM6U8GEr8bRUTaSFbdFPx4C6FEYA5v5Lgwu9EToaWnHgR2xkqZ9JjHnaBpA"); diff --git a/tests/chains/Aion/SignerTests.cpp b/tests/chains/Aion/SignerTests.cpp index 84927f7dcee..ed6ba21e40e 100644 --- a/tests/chains/Aion/SignerTests.cpp +++ b/tests/chains/Aion/SignerTests.cpp @@ -14,7 +14,7 @@ TEST(AionSigner, Sign) { auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, {}); - auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")); + auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9"), TWCurveED25519); Signer::sign(privateKey, transaction); EXPECT_EQ(hex(transaction.signature), "a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); @@ -27,7 +27,7 @@ TEST(AionSigner, SignWithData) { auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, parse_hex("41494f4e0000")); - auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")); + auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9"), TWCurveED25519); Signer::sign(privateKey, transaction); EXPECT_EQ(hex(transaction.signature), "a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a736fc2642c2d62900204779aa274dba3b8712eff7a8464aa78ea52b09ece20679fe3f5edf94c84a7e0c5f93213be891bc279af927086f455167f5bc73d3046c0d"); diff --git a/tests/chains/Algorand/SignerTests.cpp b/tests/chains/Algorand/SignerTests.cpp index cc3736ccd2a..f3a1b359439 100644 --- a/tests/chains/Algorand/SignerTests.cpp +++ b/tests/chains/Algorand/SignerTests.cpp @@ -55,7 +55,7 @@ TEST(AlgorandSigner, EncodeBytes) { } TEST(AlgorandSigner, Sign) { - auto key = PrivateKey(parse_hex("c9d3cc16fecabe2747eab86b81528c6ed8b65efc1d6906d86aabc27187a1fe7c")); + auto key = PrivateKey(parse_hex("c9d3cc16fecabe2747eab86b81528c6ed8b65efc1d6906d86aabc27187a1fe7c"), TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto from = Address(publicKey); auto to = Address("UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM"); @@ -85,7 +85,7 @@ TEST(AlgorandSigner, Sign) { TEST(AlgorandSigner, SignAssetNFTTransfer) { // Successfully broadcasted: https://allo.info/tx/FFLUH4QKZHG744RIQ2AZNWZUSIIH262KZ4MEWSY4RXMWN5NMOOJA - auto key = PrivateKey(parse_hex("dc6051ffc7b3ec601bde432f6dea34d40fe3855e4181afa0f0524c42194a6da7")); + auto key = PrivateKey(parse_hex("dc6051ffc7b3ec601bde432f6dea34d40fe3855e4181afa0f0524c42194a6da7"), TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto from = Address(publicKey); auto to = Address("362T7CSXNLIOBX6J3H2SCPS4LPYFNV6DDWE6G64ZEUJ6SY5OJIR6SB5CVE"); @@ -116,7 +116,7 @@ TEST(AlgorandSigner, SignAssetNFTTransfer) { TEST(AlgorandSigner, SignAsset) { // https://explorer.bitquery.io/algorand_testnet/tx/NJ62HYO2LC222AVLIN2GW5LKIWKLGC7NZLIQ3DUL2RDVRYO2UW7A - auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04")); + auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04"), TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto from = Address(publicKey); auto to = Address("GJIWJSX2EU5RC32LKTDDXWLA2YICBHKE35RV2ZPASXZYKWUWXFLKNFSS4U"); @@ -146,7 +146,7 @@ TEST(AlgorandSigner, SignAsset) { } TEST(AlgorandSigner, SignAssetWithNote) { - auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04")); + auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04"), TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto from = Address(publicKey); auto to = Address("GJIWJSX2EU5RC32LKTDDXWLA2YICBHKE35RV2ZPASXZYKWUWXFLKNFSS4U"); @@ -173,7 +173,7 @@ TEST(AlgorandSigner, SignAssetWithNote) { TEST(AlgorandSigner, SignAssetOptIn) { // https://explorer.bitquery.io/algorand_testnet/tx/47LE2QS4B5N6IFHXOUN2MJUTCOQCHNY6AB3AJYECK4IM2VYKJDKQ - auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04")); + auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04"), TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto address = Address(publicKey); Data note; diff --git a/tests/chains/Algorand/TransactionCompilerTests.cpp b/tests/chains/Algorand/TransactionCompilerTests.cpp index e630a7205a3..5df08888b70 100644 --- a/tests/chains/Algorand/TransactionCompilerTests.cpp +++ b/tests/chains/Algorand/TransactionCompilerTests.cpp @@ -23,7 +23,7 @@ namespace TW::Algorand::tests { TEST(AlgorandCompiler, CompileWithSignatures) { /// Step 1: Prepare transaction input (protobuf) auto privateKey = parse_hex("d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b"); - auto key = PrivateKey(privateKey); + auto key = PrivateKey(privateKey, TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto note = parse_hex("68656c6c6f"); auto genesisHash = Base64::decode("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="); @@ -52,7 +52,7 @@ TEST(AlgorandCompiler, CompileWithSignatures) { EXPECT_EQ(hex(preImage), "54588aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179"); // Simulate signature, normally obtained from signature server - const auto signature = key.sign(preImage, TWCurveED25519); + const auto signature = key.sign(preImage); /// Step 3: Compile transaction info auto outputData = diff --git a/tests/chains/Aptos/AddressTests.cpp b/tests/chains/Aptos/AddressTests.cpp index b94e4222c86..6c7a952b87f 100644 --- a/tests/chains/Aptos/AddressTests.cpp +++ b/tests/chains/Aptos/AddressTests.cpp @@ -38,7 +38,7 @@ TEST(AptosAddress, Invalid) { } TEST(AptosAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("088baa019f081d6eab8dff5c447f9ce2f83c1babf3d03686299eaf6a1e89156e")); + auto privateKey = PrivateKey(parse_hex("088baa019f081d6eab8dff5c447f9ce2f83c1babf3d03686299eaf6a1e89156e"), TWCurveED25519); auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); Entry entry; auto address = entry.deriveAddress(TWCoinTypeAptos, pubkey, TWDerivationDefault, std::monostate{}); diff --git a/tests/chains/Aptos/CompilerTests.cpp b/tests/chains/Aptos/CompilerTests.cpp index ebe0ccc49ae..814e71c6f02 100644 --- a/tests/chains/Aptos/CompilerTests.cpp +++ b/tests/chains/Aptos/CompilerTests.cpp @@ -44,9 +44,9 @@ TEST(AptosCompiler, StandardTransaction) { // Sign the pre-hash data. - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec"), TWCurveED25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; - auto signature = privateKey.sign(actualDataToSign, TWCurveED25519); + auto signature = privateKey.sign(actualDataToSign); EXPECT_EQ(hex(signature), "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); // Compile the transaction. @@ -125,9 +125,9 @@ TEST(AptosCompiler, BlindTransactionJson) { // Sign the pre-hash data. - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec"), TWCurveED25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; - auto signature = privateKey.sign(actualDataToSign, TWCurveED25519); + auto signature = privateKey.sign(actualDataToSign); EXPECT_EQ(hex(signature), "42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b"); // Compile the transaction. diff --git a/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp b/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp index c54bd302026..ed56956b738 100644 --- a/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp @@ -46,7 +46,7 @@ SigningInput buildInputP2PKH(bool omitKey = false) { input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; input.coinType = TWCoinTypeBitcoin; - auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"), TWCurveSECP256k1); auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash0 = Hash::ripemd(Hash::sha256(pubKey0.bytes)); assert(hex(utxoPubkeyHash0) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); @@ -54,7 +54,7 @@ SigningInput buildInputP2PKH(bool omitKey = false) { input.privateKeys.push_back(utxoKey0); } - auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); assert(hex(utxoPubkeyHash1) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); @@ -84,7 +84,7 @@ SigningInput buildInputP2PKH(bool omitKey = false) { } TEST(BitcoinSigning, SpendMinimumAmountP2WPKH) { - auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35")); + auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35"), TWCurveSECP256k1); auto myPublicKey = myPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(myPublicKey.bytes)); @@ -474,7 +474,7 @@ TEST(BitcoinSigning, SignPlanTransactionWithDustAmount) { // If the change amount is less than "dust", there should not be a change output. TEST(BitcoinSigning, SignPlanTransactionNoChange) { - const auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35")); + const auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35"), TWCurveSECP256k1); auto myPublicKey = myPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(myPublicKey.bytes)); auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); @@ -560,7 +560,7 @@ TEST(BitcoinSigning, SignPlanTransactionNoChange) { // Not enough funds to send requested amount after UTXO dust filtering. TEST(BitcoinSigning, SignPlanTransactionNotSufficientAfterDustFiltering) { - const auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35")); + const auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35"), TWCurveSECP256k1); auto myPublicKey = myPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(myPublicKey.bytes)); auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); @@ -628,7 +628,7 @@ TEST(BitcoinSigning, SignPlanTransactionNotSufficientAfterDustFiltering) { // Deposit 0.0001 BTC from bc1q2sphzvc2uqmxqte2w9dd4gzy4sy9vvfv0me9ke to 0xa8491D40d4F71A752cA41DA0516AEd80c33a1B56 on ZETA mainnet. // https://www.zetachain.com/docs/developers/omnichain/bitcoin/#example-1-deposit-btc-into-an-account-in-zevm TEST(BitcoinSigning, SignDepositBtcToZetaChain) { - const auto myPrivateKey = PrivateKey(parse_hex("428d66be0b5a620f126a00fa67637222ce3dc9badfe5c605189520760810cfac")); + const auto myPrivateKey = PrivateKey(parse_hex("428d66be0b5a620f126a00fa67637222ce3dc9badfe5c605189520760810cfac"), TWCurveSECP256k1); auto myPublicKey = myPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(myPublicKey.bytes)); auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); @@ -795,7 +795,7 @@ TEST(BitcoinSigning, SignP2WPKH_Bip143) { input.changeAddress = "16TZ8J6Q5iZKBWizWzFAYnrsaox5Z5aBRV"; const auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); - const auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + const auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"), TWCurveSECP256k1); const auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(hex(pubKey0.bytes), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); @@ -806,7 +806,7 @@ TEST(BitcoinSigning, SignP2WPKH_Bip143) { input.privateKeys.push_back(utxoKey0); const auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); - const auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + const auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); const auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(hex(pubKey1.bytes), "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"); const auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); @@ -882,13 +882,13 @@ SigningInput buildInputP2WPKH(int64_t amount, TWBitcoinSigHashType hashType, int input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; input.coinType = TWCoinTypeBitcoin; - auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"), TWCurveSECP256k1); auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash0 = Hash::ripemd(Hash::sha256(pubKey0.bytes)); assert(hex(utxoPubkeyHash0) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); input.privateKeys.push_back(utxoKey0); - auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); assert(hex(utxoPubkeyHash1) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); @@ -1105,10 +1105,10 @@ SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType, bool omitScript input.dustCalculator = std::make_shared(50); if (!omitKeys) { - auto utxoKey0 = PrivateKey(parse_hex("ed00a0841cd53aedf89b0c616742d1d2a930f8ae2b0fb514765a17bb62c7521a")); + auto utxoKey0 = PrivateKey(parse_hex("ed00a0841cd53aedf89b0c616742d1d2a930f8ae2b0fb514765a17bb62c7521a"), TWCurveSECP256k1); input.privateKeys.push_back(utxoKey0); - auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); input.privateKeys.push_back(utxoKey1); } @@ -1532,17 +1532,17 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { input.changeAddress = "1Bd1VA2bnLjoBk4ook3H19tZWETk8s6Ym5"; auto key0 = parse_hex("730fff80e1413068a05b57d6a58261f07551163369787f349438ea38ca80fac6"); - input.privateKeys.push_back(PrivateKey(key0)); + input.privateKeys.push_back(PrivateKey(key0, TWCurveSECP256k1)); auto key1 = parse_hex("11fa3d25a17cbc22b29c44a484ba552b5a53149d106d3d853e22fdd05a2d8bb3"); - input.privateKeys.push_back(PrivateKey(key1)); + input.privateKeys.push_back(PrivateKey(key1, TWCurveSECP256k1)); auto key2 = parse_hex("77bf4141a87d55bdd7f3cd0bdccf6e9e642935fec45f2f30047be7b799120661"); - input.privateKeys.push_back(PrivateKey(key2)); + input.privateKeys.push_back(PrivateKey(key2, TWCurveSECP256k1)); auto key3 = parse_hex("14af36970f5025ea3e8b5542c0f8ebe7763e674838d08808896b63c3351ffe49"); - input.privateKeys.push_back(PrivateKey(key3)); + input.privateKeys.push_back(PrivateKey(key3, TWCurveSECP256k1)); auto key4 = parse_hex("fe9a95c19eef81dde2b95c1284ef39be497d128e2aa46916fb02d552485e0323"); - input.privateKeys.push_back(PrivateKey(key4)); + input.privateKeys.push_back(PrivateKey(key4, TWCurveSECP256k1)); auto key5 = parse_hex("428a7aee9f0c2af0cd19af3cf1c78149951ea528726989b2e83e4778d2c3f890"); - input.privateKeys.push_back(PrivateKey(key5)); + input.privateKeys.push_back(PrivateKey(key5, TWCurveSECP256k1)); auto redeemScript = Script::buildPayToWitnessScriptHash(parse_hex("a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54")); auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); @@ -1657,10 +1657,10 @@ TEST(BitcoinSigning, Sign_NegativeInvalidAddress) { input.toAddress = "THIS-IS-NOT-A-BITCOIN-ADDRESS"; input.changeAddress = "THIS-IS-NOT-A-BITCOIN-ADDRESS-EITHER"; - auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"), TWCurveSECP256k1); input.privateKeys.push_back(utxoKey0); - auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); input.privateKeys.push_back(utxoKey1); auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); @@ -1738,7 +1738,7 @@ TEST(BitcoinSigning, Plan_10input_MaxAmount) { EXPECT_TRUE(verifyPlan(plan, {1'000'000, 1'010'000, 1'020'000, 1'030'000, 1'040'000, 1'050'000, 1'060'000, 1'070'000, 1'080'000, 1'090'000}, 10'449'278, 722)); // Extend input with keys, reuse plan, Sign - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + auto privKey = PrivateKey(parse_hex(ownPrivateKey), TWCurveSECP256k1); input.privateKeys.push_back(privKey); input.plan = plan; @@ -1771,7 +1771,7 @@ TEST(BitcoinSigning, Sign_LitecoinReal_a85f) { input.toAddress = "ltc1q0dvup9kzplv6yulzgzzxkge8d35axkq4n45hum"; input.changeAddress = ownAddress; - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + auto privKey = PrivateKey(parse_hex(ownPrivateKey), TWCurveSECP256k1); input.privateKeys.push_back(privKey); auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); @@ -1861,7 +1861,7 @@ TEST(BitcoinSigning, PlanAndSign_LitecoinReal_8435) { EXPECT_TRUE(verifyPlan(plan, {3'899'774}, 1'200'000, 141)); // Extend input with keys and plan, for Sign - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + auto privKey = PrivateKey(parse_hex(ownPrivateKey), TWCurveSECP256k1); input.privateKeys.push_back(privKey); input.plan = plan; @@ -1946,7 +1946,7 @@ TEST(BitcoinSigning, Sign_ManyUtxos_400) { EXPECT_TRUE(verifyPlan(plan, subset, 300'000, 4'561)); // Extend input with keys, reuse plan, Sign - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + auto privKey = PrivateKey(parse_hex(ownPrivateKey), TWCurveSECP256k1); input.privateKeys.push_back(privKey); input.plan = plan; @@ -2015,7 +2015,7 @@ TEST(BitcoinSigning, Sign_ManyUtxos_2000) { EXPECT_TRUE(verifyPlan(plan, subset, 2'000'000, 40'943)); // Extend input with keys, reuse plan, Sign - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + auto privKey = PrivateKey(parse_hex(ownPrivateKey), TWCurveSECP256k1); input.privateKeys.push_back(privKey); input.plan = plan; @@ -2075,7 +2075,7 @@ TEST(BitcoinSigning, EncodeThreeOutput) { // add signature - auto privkey = PrivateKey(parse_hex(ownPrivateKey)); + auto privkey = PrivateKey(parse_hex(ownPrivateKey), TWCurveSECP256k1); auto pubkey = PrivateKey(privkey).getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(hex(pubkey.bytes), "036739829f2cfec79cfe6aaf1c22ecb7d4867dfd8ab4deb7121b36a00ab646caed"); @@ -2123,7 +2123,7 @@ TEST(BitcoinSigning, EncodeThreeOutput) { TEST(BitcoinSigning, RedeemExtendedPubkeyUTXO) { auto wif = "L4BeKzm3AHDUMkxLRVKTSVxkp6Hz9FcMQPh18YCKU1uioXfovzwP"; auto decoded = Base58::decodeCheck(wif); - auto key = PrivateKey(Data(decoded.begin() + 1, decoded.begin() + 33)); + auto key = PrivateKey(Data(decoded.begin() + 1, decoded.begin() + 33), TWCurveSECP256k1); auto pubkey = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended); auto hash = Hash::sha256ripemd(pubkey.bytes.data(), pubkey.bytes.size()); @@ -2191,7 +2191,7 @@ TEST(BitcoinSigning, SignP2TR_5df51e) { input.changeAddress = ownAddress; input.coinType = coin; - auto utxoKey0 = PrivateKey(parse_hex(privateKey)); + auto utxoKey0 = PrivateKey(parse_hex(privateKey), TWCurveSECP256k1); auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(hex(pubKey0.bytes), "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee"); EXPECT_EQ(SegwitAddress(pubKey0, "bc").string(), ownAddress); @@ -2313,7 +2313,7 @@ TEST(BitcoinSigning, Build_OpReturn_THORChainSwap_eb4c) { } TEST(BitcoinSigning, Sign_OpReturn_THORChainSwap) { - PrivateKey privateKey = PrivateKey(parse_hex("6bd4096fa6f08bd3af2b437244ba0ca2d35045c5233b8d6796df37e61e974de5")); + PrivateKey privateKey = PrivateKey(parse_hex("6bd4096fa6f08bd3af2b437244ba0ca2d35045c5233b8d6796df37e61e974de5"), TWCurveSECP256k1); PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto ownAddress = SegwitAddress(publicKey, "bc"); auto ownAddressString = ownAddress.string(); diff --git a/tests/chains/Bitcoin/TxComparisonHelper.cpp b/tests/chains/Bitcoin/TxComparisonHelper.cpp index f674bcaaff7..3996815dd7c 100644 --- a/tests/chains/Bitcoin/TxComparisonHelper.cpp +++ b/tests/chains/Bitcoin/TxComparisonHelper.cpp @@ -48,7 +48,7 @@ SigningInput buildSigningInput(Amount amount, int byteFee, const UTXOs& utxos, b input.dustCalculator = std::make_shared(coin); if (!omitPrivateKey) { - auto utxoKey = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto utxoKey = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); auto pubKey = utxoKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey.bytes)); assert(hex(utxoPubkeyHash) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); diff --git a/tests/chains/BitcoinDiamond/SignerTests.cpp b/tests/chains/BitcoinDiamond/SignerTests.cpp index 2b0a239d726..bf0916d7617 100644 --- a/tests/chains/BitcoinDiamond/SignerTests.cpp +++ b/tests/chains/BitcoinDiamond/SignerTests.cpp @@ -45,7 +45,7 @@ TEST(BitcoinDiamondSigner, Sign) { utxo0->set_amount(27615); auto utxoKey0 = - PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee"), TWCoinTypeCurve(TWCoinTypeBitcoinDiamond)); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeBitcoinDiamond, utxoKey0); ASSERT_EQ(utxoAddr0, "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeBitcoinDiamond); @@ -102,7 +102,7 @@ TEST(BitcoinDiamondSigner, SignSegwit) { utxo0->set_amount(27615); auto utxoKey0 = - PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee"), TWCoinTypeCurve(TWCoinTypeBitcoinDiamond)); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeBitcoinDiamond, utxoKey0, TWDerivationBitcoinSegwit); ASSERT_EQ(utxoAddr0, "bcd1q5jx6gcuxeefvejk30r0fqrr37psnpscslrrd0y"); @@ -160,7 +160,7 @@ TEST(BitcoinDiamondSigner, SignAnyoneCanPay) { utxo0->set_amount(27615); auto utxoKey0 = - PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee"), TWCoinTypeCurve(TWCoinTypeBitcoinDiamond)); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeBitcoinDiamond, utxoKey0, TWDerivationBitcoinSegwit); ASSERT_EQ(utxoAddr0, "bcd1q5jx6gcuxeefvejk30r0fqrr37psnpscslrrd0y"); diff --git a/tests/chains/BitcoinDiamond/TWAnySignerTests.cpp b/tests/chains/BitcoinDiamond/TWAnySignerTests.cpp index f1ab62557f2..f71410173e6 100644 --- a/tests/chains/BitcoinDiamond/TWAnySignerTests.cpp +++ b/tests/chains/BitcoinDiamond/TWAnySignerTests.cpp @@ -45,7 +45,7 @@ TEST(TWAnySignerBitcoinDiamond, Sign) { auto script0 = parse_hex("76a914a48da46386ce52cccad178de900c71f06130c31088ac"); utxo0->set_script(script0.data(), (int)script0.size()); - auto utxoKey0 = PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + auto utxoKey0 = PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee"), TWCoinTypeCurve(TWCoinTypeBitcoinDiamond)); input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); Bitcoin::Proto::TransactionPlan plan; diff --git a/tests/chains/BitcoinGold/TWAddressTests.cpp b/tests/chains/BitcoinGold/TWAddressTests.cpp index ec7372aee34..67d006b5b63 100644 --- a/tests/chains/BitcoinGold/TWAddressTests.cpp +++ b/tests/chains/BitcoinGold/TWAddressTests.cpp @@ -24,7 +24,7 @@ TEST(TWBitcoinGoldAddress, Valid) { } TEST(TWBitcoinGoldAddress, PubkeyToAddress) { - const auto privateKey = PrivateKey(parse_hex(PRIVATE_KEY)); + const auto privateKey = PrivateKey(parse_hex(PRIVATE_KEY), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); /// construct with public key diff --git a/tests/chains/Cardano/SigningTests.cpp b/tests/chains/Cardano/SigningTests.cpp index 83be54b7926..aa2e4fb5763 100644 --- a/tests/chains/Cardano/SigningTests.cpp +++ b/tests/chains/Cardano/SigningTests.cpp @@ -615,7 +615,7 @@ TEST(CardanoSigning, SignTransferFromLegacy) { const auto privateKeyData = parse_hex("98f266d1aac660179bc2f456033941238ee6b2beb8ed0f9f34c9902816781f5a9903d1d395d6ab887b65ea5e344ef09b449507c21a75f0ce8c59d0ed1c6764eba7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f4e890ca4eb6bec44bf751b5a843174534af64d6ad1f44e0613db78a7018781f5aa151d2997f52059466b715d8eefab30a78b874ae6ef4931fa58bb21ef8ce2423d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4"); { - const auto privKey = PrivateKey(privateKeyData); + const auto privKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano); const auto pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519Cardano); const auto addr = AddressV2(pubKey); EXPECT_EQ(addr.string(), "Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8"); diff --git a/tests/chains/Cardano/StakingTests.cpp b/tests/chains/Cardano/StakingTests.cpp index 45e9e7d13f9..1fed5d1ff81 100644 --- a/tests/chains/Cardano/StakingTests.cpp +++ b/tests/chains/Cardano/StakingTests.cpp @@ -29,7 +29,7 @@ const auto poolIdNufi = "7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a TEST(CardanoStaking, RegisterStakingKey) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); @@ -76,7 +76,7 @@ TEST(CardanoStaking, RegisterStakingKey) { TEST(CardanoStaking, DeregisterStakingKey) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); @@ -123,7 +123,7 @@ TEST(CardanoStaking, DeregisterStakingKey) { TEST(CardanoStaking, Redelegate) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); @@ -171,7 +171,7 @@ TEST(CardanoStaking, Redelegate) { TEST(CardanoStaking, RegisterAndDelegate_similar53339b) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); @@ -265,7 +265,7 @@ TEST(CardanoStaking, RegisterAndDelegate_similar53339b) { TEST(CardanoStaking, Withdraw_similarf48098) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); diff --git a/tests/chains/Cardano/VoteDelegationTests.cpp b/tests/chains/Cardano/VoteDelegationTests.cpp index 74700e2cb53..4befdedf809 100644 --- a/tests/chains/Cardano/VoteDelegationTests.cpp +++ b/tests/chains/Cardano/VoteDelegationTests.cpp @@ -25,7 +25,7 @@ const auto dRepAddressCIP105 = "drep13d6sxkyz6st9h65qqrzd8ukpywhr8swe9f6357qntgj TEST(CardanoVoteDelegation, DelegateToSpecificDRepCIP129) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); @@ -82,7 +82,7 @@ TEST(CardanoVoteDelegation, DelegateToSpecificDRepCIP129) { TEST(CardanoVoteDelegation, DelegateToSpecificDRepCIP105) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); @@ -139,7 +139,7 @@ TEST(CardanoVoteDelegation, DelegateToSpecificDRepCIP105) { TEST(CardanoVoteDelegation, DelegateToAlwaysAbstain) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); @@ -195,7 +195,7 @@ TEST(CardanoVoteDelegation, DelegateToAlwaysAbstain) { TEST(CardanoVoteDelegation, DelegateToNoConfidence) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); diff --git a/tests/chains/Cosmos/AddressTests.cpp b/tests/chains/Cosmos/AddressTests.cpp index 97034a9b590..87032b6ec58 100644 --- a/tests/chains/Cosmos/AddressTests.cpp +++ b/tests/chains/Cosmos/AddressTests.cpp @@ -25,7 +25,7 @@ TEST(CosmosAddress, Invalid) { } TEST(CosmosAddress, Cosmos_FromPublicKey) { - auto privateKey = PrivateKey(parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")); + auto privateKey = PrivateKey(parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"), TWCurveSECP256k1); auto publicKeyData = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(publicKeyData.bytes), "0257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc5"); @@ -56,7 +56,7 @@ TEST(CosmosAddress, Cosmos_Invalid) { } TEST(CosmosAddress, ThorFromPublicKey) { - auto privateKey = PrivateKey(parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e")); + auto privateKey = PrivateKey(parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"), TWCurveSECP256k1); auto publicKeyData = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(publicKeyData.bytes), "03ed997e396cf4292f5fce5a42bba41599ccd5d96e313154a7c9ea7049de317c77"); diff --git a/tests/chains/Cosmos/SignerTests.cpp b/tests/chains/Cosmos/SignerTests.cpp index 6e5570004d4..57f39e57293 100644 --- a/tests/chains/Cosmos/SignerTests.cpp +++ b/tests/chains/Cosmos/SignerTests.cpp @@ -240,7 +240,7 @@ TEST(CosmosSigner, SignIbcTransferProtobuf_817101) { amountOfFee->set_amount("12500"); auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); - EXPECT_EQ(Cosmos::Address(TWCoinTypeCosmos, PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx"); + EXPECT_EQ(Cosmos::Address(TWCoinTypeCosmos, PrivateKey(privateKey, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx"); input.set_private_key(privateKey.data(), privateKey.size()); auto output = Proto::SigningOutput(); diff --git a/tests/chains/Cosmos/THORChain/SwapTests.cpp b/tests/chains/Cosmos/THORChain/SwapTests.cpp index 44b11f6d686..b0f8f9d9d83 100644 --- a/tests/chains/Cosmos/THORChain/SwapTests.cpp +++ b/tests/chains/Cosmos/THORChain/SwapTests.cpp @@ -90,7 +90,7 @@ TEST(THORChainSwap, SwapBtcEth) { // set few fields before signing tx.set_byte_fee(20); - EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); auto& utxo = *tx.add_utxo(); Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); @@ -350,7 +350,7 @@ TEST(THORChainSwap, SwapBtcBnb) { // set few fields before signing tx.set_byte_fee(80); - EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); auto& utxo = *tx.add_utxo(); Data utxoHash = parse_hex("8eae5c3a4c75058d4e3facd5d72f18a40672bcd3d1f35ebf3094bd6c78da48eb"); @@ -801,7 +801,7 @@ TEST(THORChainSwap, SwapBnbEth) { EXPECT_EQ(hex(TW::data(tx.private_key())), ""); // set private key and few other fields - EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, PrivateKey(TestKey1Bnb)), Address1Bnb); + EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, PrivateKey(TestKey1Bnb, TWCoinTypeCurve(TWCoinTypeBinance))), Address1Bnb); tx.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); tx.set_chain_id("Binance-Chain-Tigris"); tx.set_account_number(1902570); @@ -849,7 +849,7 @@ TEST(THORChainSwap, SwapBnbRune) { EXPECT_EQ(hex(TW::data(tx.private_key())), ""); // set private key and few other fields - EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, PrivateKey(TestKey1Bnb)), Address1Bnb); + EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, PrivateKey(TestKey1Bnb, TWCoinTypeCurve(TWCoinTypeBinance))), Address1Bnb); tx.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); tx.set_chain_id("Binance-Chain-Tigris"); tx.set_account_number(1902570); @@ -901,7 +901,7 @@ TEST(THORChainSwap, SwapBusdTokenBnb) { // set private key and few other fields const Data privateKey = parse_hex("412c379cccf9d792238f0a8bd923604e00c2be11ea1de715945f6a849796362a"); - EXPECT_EQ(Binance::Address(PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "bnb1gddl87crh47wzynjx3c6pmcclzk7txlkm74x28"); + EXPECT_EQ(Binance::Address(PrivateKey(privateKey, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "bnb1gddl87crh47wzynjx3c6pmcclzk7txlkm74x28"); tx.set_private_key(privateKey.data(), privateKey.size()); tx.set_chain_id("Binance-Chain-Tigris"); tx.set_account_number(7320332); @@ -951,7 +951,7 @@ TEST(THORChainSwap, SwapBnbBnbToken) { // set private key and few other fields const Data privateKey = parse_hex("bcf8b072560dda05122c99390def2c385ec400e1a93df0657a85cf6b57a715da"); - EXPECT_EQ(Binance::Address(PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx"); + EXPECT_EQ(Binance::Address(PrivateKey(privateKey, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx"); tx.set_private_key(privateKey.data(), privateKey.size()); tx.set_chain_id("Binance-Chain-Tigris"); tx.set_account_number(1902570); @@ -1004,7 +1004,7 @@ TEST(THORChainSwap, SwapBtcEthWithAffFee) { // set few fields before signing tx.set_byte_fee(20); - EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); auto& utxo = *tx.add_utxo(); Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); @@ -1122,7 +1122,7 @@ TEST(THORChainSwap, SwapBtcNegativeMemoTooLong) { // set few fields before signing tx.set_byte_fee(20); - EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); auto& utxo = *tx.add_utxo(); Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); diff --git a/tests/chains/Cosmos/THORChain/TWSwapTests.cpp b/tests/chains/Cosmos/THORChain/TWSwapTests.cpp index 9492770c08f..1aabc6655f9 100644 --- a/tests/chains/Cosmos/THORChain/TWSwapTests.cpp +++ b/tests/chains/Cosmos/THORChain/TWSwapTests.cpp @@ -81,7 +81,7 @@ TEST(TWTHORChainSwap, SwapBtcToEth) { // sign tx input for signed full tx // set few fields before signing txInput.set_byte_fee(20); - EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); txInput.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); auto& utxo = *txInput.add_utxo(); Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); diff --git a/tests/chains/Decred/AddressTests.cpp b/tests/chains/Decred/AddressTests.cpp index 51c855ce811..0d8884dbee3 100644 --- a/tests/chains/Decred/AddressTests.cpp +++ b/tests/chains/Decred/AddressTests.cpp @@ -19,7 +19,7 @@ TEST(DecredAddress, FromPublicKey) { ASSERT_EQ(address.string(), "DsmcYVbP1Nmag2H4AS17UTvmWXmGeA7nLDx"); } { - const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")); + const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a"), TWCurveED25519); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeED25519)); EXPECT_ANY_THROW(new Address(publicKey)); } diff --git a/tests/chains/Decred/SignerTests.cpp b/tests/chains/Decred/SignerTests.cpp index e698910b17c..5e34583badf 100644 --- a/tests/chains/Decred/SignerTests.cpp +++ b/tests/chains/Decred/SignerTests.cpp @@ -19,7 +19,7 @@ namespace TW::Decred::tests { // clang-format off TEST(DecredSigner, SignP2PKH) { - const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); @@ -121,7 +121,7 @@ TEST(DecredSigner, SignP2PKH) { } TEST(DecredSigner, SignP2PK) { - const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); @@ -203,7 +203,7 @@ TEST(DecredSigner, SignP2PK) { } TEST(DecredSigner, SignP2SH) { - const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); @@ -309,7 +309,7 @@ TEST(DecredSigner, SignP2SH) { } TEST(DecredSigner, SignNegativeNoUtxo) { - const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); @@ -364,7 +364,7 @@ TEST(DecredSigner, SignNegativeNoUtxo) { } TEST(DecredSigner, SignP2PKH_NoPlan) { - const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); diff --git a/tests/chains/Decred/TransactionCompilerTests.cpp b/tests/chains/Decred/TransactionCompilerTests.cpp index 23d49b69bf1..1c9921bebc2 100644 --- a/tests/chains/Decred/TransactionCompilerTests.cpp +++ b/tests/chains/Decred/TransactionCompilerTests.cpp @@ -78,7 +78,7 @@ TEST(DecredCompiler, CompileWithSignatures) { "9e4305478d1a69ee5c89a2e234d1cf270798d447d5db983b8fc3c817afddec34"); // compile - auto publicKey = PrivateKey(utxoKey).getPublicKey(TWPublicKeyTypeSECP256k1); + auto publicKey = PrivateKey(utxoKey, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1); auto signature = parse_hex("304402206ee887c9239e5fff0048674bdfff2a8cfbeec6cd4a3ccebcc12fac44b24cc5ac0220718f7c760818fde18bc5ba8457d43d5a145cc4cf13d2a5557cba9107e9f4558d"); auto outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKey.bytes}); diff --git a/tests/chains/EOS/TransactionTests.cpp b/tests/chains/EOS/TransactionTests.cpp index 013db25ed5a..684aac0228e 100644 --- a/tests/chains/EOS/TransactionTests.cpp +++ b/tests/chains/EOS/TransactionTests.cpp @@ -77,7 +77,7 @@ TEST(EOSTransaction, Serialization) { // verify k1 sigs for (int i = 0; i < 5; i++) { - PrivateKey pk(Hash::sha256(std::string(i + 1, 'A'))); + PrivateKey pk(Hash::sha256(std::string(i + 1, 'A')), TWCurveSECP256k1); ASSERT_NO_THROW(signer.sign(pk, Type::ModernK1, tx2)); ASSERT_EQ( @@ -87,7 +87,7 @@ TEST(EOSTransaction, Serialization) { // verify r1 sigs for (int i = 0; i < 5; i++) { - PrivateKey pk(Hash::sha256(std::string(i + 1, 'A'))); + PrivateKey pk(Hash::sha256(std::string(i + 1, 'A')), TWCurveNIST256p1); ASSERT_NO_THROW(signer.sign(pk, Type::ModernR1, tx2)); ASSERT_EQ( diff --git a/tests/chains/Ethereum/AddressTests.cpp b/tests/chains/Ethereum/AddressTests.cpp index b2b320cf82e..63bee37c017 100644 --- a/tests/chains/Ethereum/AddressTests.cpp +++ b/tests/chains/Ethereum/AddressTests.cpp @@ -42,7 +42,7 @@ TEST(EthereumAddress, String) { } TEST(EthereumAddress, FromPrivateKey) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); const auto address = Address(publicKey); diff --git a/tests/chains/MultiversX/SignerTests.cpp b/tests/chains/MultiversX/SignerTests.cpp index 09a7ce4e1ad..96401c1e104 100644 --- a/tests/chains/MultiversX/SignerTests.cpp +++ b/tests/chains/MultiversX/SignerTests.cpp @@ -22,7 +22,7 @@ namespace TW::MultiversX::tests { TEST(MultiversXSigner, SignGenericAction) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); @@ -59,7 +59,7 @@ TEST(MultiversXSigner, SignGenericAction) { TEST(MultiversXSigner, SignGenericActionUnDelegate) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(6); @@ -96,7 +96,7 @@ TEST(MultiversXSigner, SignGenericActionUnDelegate) { TEST(MultiversXSigner, SignGenericActionRedelegateRewards) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); @@ -132,7 +132,7 @@ TEST(MultiversXSigner, SignGenericActionRedelegateRewards) { TEST(MultiversXSigner, SignGenericActionClaimRewards) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); @@ -168,7 +168,7 @@ TEST(MultiversXSigner, SignGenericActionClaimRewards) { TEST(MultiversXSigner, SignGenericActionWithdrawStake) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); @@ -205,7 +205,7 @@ TEST(MultiversXSigner, SignGenericActionWithdrawStake) { TEST(MultiversXSigner, SignGenericActionDelegate) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(1); @@ -259,7 +259,7 @@ TEST(MultiversXSigner, SignGenericActionJSON) { "chainId": "1" })"; - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); auto encoded = Signer::signJSON(input, privateKey.bytes); nlohmann::json expected = R"( { @@ -280,7 +280,7 @@ TEST(MultiversXSigner, SignGenericActionJSON) { TEST(MultiversXSigner, SignWithoutData) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(0); @@ -332,7 +332,7 @@ TEST(MultiversXSigner, SignJSONWithoutData) { "chainId": "1" })"; - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); auto encoded = Signer::signJSON(input, privateKey.bytes); nlohmann::json expected = R"( { @@ -354,7 +354,7 @@ TEST(MultiversXSigner, SignWithUsernames) { // https://github.com/multiversx/mx-chain-go/blob/master/examples/construction_test.go, scenario "TestConstructTransaction_Usernames". auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(89); @@ -394,7 +394,7 @@ TEST(MultiversXSigner, SignWithUsernames) { TEST(MultiversXSigner, SignWithOptions) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(89); @@ -434,7 +434,7 @@ TEST(MultiversXSigner, SignWithOptions) { TEST(MultiversXSigner, SignEGLDTransfer) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_egld_transfer()->mutable_accounts()->set_sender_nonce(7); @@ -465,7 +465,7 @@ TEST(MultiversXSigner, SignEGLDTransfer) { TEST(MultiversXSigner, SignESDTTransfer) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_esdt_transfer()->mutable_accounts()->set_sender_nonce(7); @@ -500,7 +500,7 @@ TEST(MultiversXSigner, SignESDTTransfer) { TEST(MultiversXSigner, SignESDTNFTTransfer) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_esdtnft_transfer()->mutable_accounts()->set_sender_nonce(7); @@ -536,7 +536,7 @@ TEST(MultiversXSigner, SignESDTNFTTransfer) { TEST(MultiversXSigner, SignGenericActionWithGuardian) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(42); @@ -576,7 +576,7 @@ TEST(MultiversXSigner, SignGenericActionWithGuardian) { TEST(MultiversXSigner, SignEGLDTransferWithGuardian) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_egld_transfer()->mutable_accounts()->set_sender_nonce(7); @@ -610,7 +610,7 @@ TEST(MultiversXSigner, SignEGLDTransferWithGuardian) { TEST(MultiversXSigner, SignGenericActionWithRelayer) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(42); @@ -648,7 +648,7 @@ TEST(MultiversXSigner, SignGenericActionWithRelayer) { TEST(MultiversXSigner, SignEGLDTransferWithRelayer) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_egld_transfer()->mutable_accounts()->set_sender_nonce(7); @@ -706,7 +706,7 @@ TEST(ElrondSigner, buildSigningOutput) { input.set_gas_price(1000000000); input.set_gas_limit(50000); input.set_chain_id("1"); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); auto unsignedTxBytes = Signer::buildUnsignedTxBytes(input); auto signature = privateKey.sign(unsignedTxBytes, TWCurveED25519); diff --git a/tests/chains/MultiversX/TWAnySignerTests.cpp b/tests/chains/MultiversX/TWAnySignerTests.cpp index 1b1311b1382..359b8622407 100644 --- a/tests/chains/MultiversX/TWAnySignerTests.cpp +++ b/tests/chains/MultiversX/TWAnySignerTests.cpp @@ -16,7 +16,7 @@ namespace TW::MultiversX::tests { TEST(TWAnySignerMultiversX, Sign) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); diff --git a/tests/chains/MultiversX/TransactionCompilerTests.cpp b/tests/chains/MultiversX/TransactionCompilerTests.cpp index 438add4c4c6..9df91a44432 100644 --- a/tests/chains/MultiversX/TransactionCompilerTests.cpp +++ b/tests/chains/MultiversX/TransactionCompilerTests.cpp @@ -50,7 +50,7 @@ TEST(MultiversXCompiler, CompileGenericActionWithSignatures) { auto preImage = data(preSigningOutput.data()); auto preImageHash = data(preSigningOutput.data_hash()); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); Data signature = parse_hex("4f0eb7dca9177f1849bc98b856ab4b3238a666abb3369b4fc0faba429b5c91c46b06893e841a8f411aa199c78cc456514abe39948108baf83a7be0b3fae9d70a"); // Verify signature (pubkey & hash & signature) @@ -123,7 +123,7 @@ TEST(MultiversXCompiler, CompileEGLDTransferWithSignatures) { auto preImage = data(preSigningOutput.data()); auto preImageHash = data(preSigningOutput.data_hash()); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); Data signature = parse_hex("e55ad0642c7d47806410c12b1c93eb6250ccb76f711bbf82c5963bf59b5cdfe291d8b083b75de526f20457eede0c8a1dacf65c2c0034d47560c3bab5319c4006"); // Verify signature (pubkey & hash & signature) @@ -195,7 +195,7 @@ TEST(MultiversXCompiler, CompileESDTTransferWithSignatures) { auto preImage = data(preSigningOutput.data()); auto preImageHash = data(preSigningOutput.data_hash()); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); Data signature = parse_hex("99314010bca2251e52f3a8c2efae2f02b81cb27c83edbaf553e7fb771ffbe69e99ac6304bdc8477ff6727e6e6a47b3d5e17c5537859e21d06a81ec8d632ad100"); // Verify signature (pubkey & hash & signature) diff --git a/tests/chains/NEO/SignerTests.cpp b/tests/chains/NEO/SignerTests.cpp index 55a214e09ae..d9a3b56a17e 100644 --- a/tests/chains/NEO/SignerTests.cpp +++ b/tests/chains/NEO/SignerTests.cpp @@ -31,7 +31,7 @@ TEST(NEOSigner, FromPublicPrivateKey) { } TEST(NEOSigner, SigningData) { - auto signer = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); + auto signer = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCurveNIST256p1)); auto verScript = "ba7908ddfe5a1177f2c9d3fa1d3dc71c9c289a3325b3bdd977e20c50136959ed02d1411efa5e8b897d970ef7e2325e6c0a3fdee4eb421223f0d86e455879a9ad"; auto invocationScript = string("401642b3d538e138f34b32330e381a7fe3f5151fcf958f2030991e72e2e25043143e4a1ebd239634efba279c96fa0ab04a15aa15179d73a7ef5a886ac8a06af484401642b3d538e138f34b32330e381a7fe3f5151fcf958f2030991e72e2e25043143e4a1ebd239634efba279c96fa0ab04a15aa15179d73a7ef5a886ac8a06af484401642b3d538e138f34b32330e381a7fe3f5151fcf958f2030991e72e2e25043143e4a1ebd239634efba279c96fa0ab04a15aa15179d73a7ef5a886ac8a06af484"); invocationScript = string(invocationScript.rbegin(), invocationScript.rend()); @@ -50,7 +50,7 @@ TEST(NEOAccount, validity) { } TEST(NEOSigner, SigningTransaction) { - auto privateKey = PrivateKey(parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128")); + auto privateKey = PrivateKey(parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128"), TWCurveNIST256p1); auto signer = Signer(privateKey); auto transaction = Transaction(); transaction.type = TransactionType::TT_ContractTransaction; diff --git a/tests/chains/Nano/SignerTests.cpp b/tests/chains/Nano/SignerTests.cpp index c28645d0794..359a643c59b 100644 --- a/tests/chains/Nano/SignerTests.cpp +++ b/tests/chains/Nano/SignerTests.cpp @@ -14,7 +14,7 @@ using namespace TW; namespace TW::Nano::tests { TEST(NanoSigner, sign1) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); auto input = Proto::SigningInput(); @@ -44,7 +44,7 @@ TEST(NanoSigner, sign1) { } TEST(NanoSigner, sign2) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto parentBlock = parse_hex("f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); auto input = Proto::SigningInput(); @@ -61,7 +61,7 @@ TEST(NanoSigner, sign2) { } TEST(NanoSigner, sign3) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto parentBlock = parse_hex("2568bf76336f7a415ca236dab97c1df9de951ca057a2e79df1322e647a259e7b"); const auto linkBlock = parse_hex("d7384845d2ae530b45a5dd50ee50757f988329f652781767af3f1bc2322f52b9"); @@ -92,7 +92,7 @@ TEST(NanoSigner, sign3) { } TEST(NanoSigner, sign4) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto parentBlock = parse_hex("1ca240212838d053ecaa9dceee598c52a6080067edecaeede3319eb0b7db6525"); auto input = Proto::SigningInput(); @@ -110,7 +110,7 @@ TEST(NanoSigner, sign4) { } TEST(NanoSigner, signInvalid1) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); // Missing link_block auto input = Proto::SigningInput(); @@ -122,7 +122,7 @@ TEST(NanoSigner, signInvalid1) { } TEST(NanoSigner, signInvalid2) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); // Missing representative @@ -135,7 +135,7 @@ TEST(NanoSigner, signInvalid2) { } TEST(NanoSigner, signInvalid3) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); // Missing balance @@ -148,7 +148,7 @@ TEST(NanoSigner, signInvalid3) { } TEST(NanoSigner, signInvalid4) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); // Account first block cannot be 0 balance @@ -162,7 +162,7 @@ TEST(NanoSigner, signInvalid4) { } TEST(NanoSigner, signInvalid5) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); // First block must use link_block not link_recipient auto input = Proto::SigningInput(); @@ -175,7 +175,7 @@ TEST(NanoSigner, signInvalid5) { } TEST(NanoSigner, signInvalid6) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); // Invalid representative value @@ -189,7 +189,7 @@ TEST(NanoSigner, signInvalid6) { } TEST(NanoSigner, signInvalid7) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto parentBlock = parse_hex("f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); auto input = Proto::SigningInput(); @@ -203,7 +203,7 @@ TEST(NanoSigner, signInvalid7) { } TEST(NanoSigner, buildUnsignedTxBytes) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); @@ -218,7 +218,7 @@ TEST(NanoSigner, buildUnsignedTxBytes) { } TEST(NanoSigner, buildSigningOutput) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); diff --git a/tests/chains/Nebl/SignerTests.cpp b/tests/chains/Nebl/SignerTests.cpp index eb8cad61749..637e83bff97 100644 --- a/tests/chains/Nebl/SignerTests.cpp +++ b/tests/chains/Nebl/SignerTests.cpp @@ -44,7 +44,7 @@ TEST(NeblSigner, Sign) { utxo0->mutable_out_point()->set_sequence(4294967294); utxo0->set_amount(2500000000); - auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9"), TWCoinTypeCurve(TWCoinTypeNebl)); auto script0 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); @@ -106,7 +106,7 @@ TEST(NeblSigner, SignAnyoneCanPay) { ASSERT_EQ(hex(script1.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); utxo1->set_script(script1.bytes.data(), script1.bytes.size()); - auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9"), TWCoinTypeCurve(TWCoinTypeNebl)); input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); auto plan = TransactionBuilder::plan(input); diff --git a/tests/chains/Nebl/TWAnySignerTests.cpp b/tests/chains/Nebl/TWAnySignerTests.cpp index 745d6ed792c..e420d8684b4 100644 --- a/tests/chains/Nebl/TWAnySignerTests.cpp +++ b/tests/chains/Nebl/TWAnySignerTests.cpp @@ -44,7 +44,7 @@ TEST(TWAnySignerNebl, Sign) { utxo0->mutable_out_point()->set_sequence(4294967294); utxo0->set_amount(2500000000); - auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9"), TWCoinTypeCurve(TWCoinTypeNebl)); auto script0 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); diff --git a/tests/chains/Nebl/TransactionBuilderTests.cpp b/tests/chains/Nebl/TransactionBuilderTests.cpp index 09bdcb8218d..03762bc2146 100644 --- a/tests/chains/Nebl/TransactionBuilderTests.cpp +++ b/tests/chains/Nebl/TransactionBuilderTests.cpp @@ -39,7 +39,7 @@ TEST(NeblTransactionBuilder, BuildWithTime) { utxo0->mutable_out_point()->set_sequence(4294967294); utxo0->set_amount(2500000000); - auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9"), TWCoinTypeCurve(TWCoinTypeNebl)); auto script0 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); diff --git a/tests/chains/Nebl/TransactionCompilerTests.cpp b/tests/chains/Nebl/TransactionCompilerTests.cpp index 30669f9ad36..6cfdb58ae3a 100644 --- a/tests/chains/Nebl/TransactionCompilerTests.cpp +++ b/tests/chains/Nebl/TransactionCompilerTests.cpp @@ -54,7 +54,7 @@ TEST(NeblCompiler, CompileWithSignatures) { ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); - auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9"), TWCoinTypeCurve(TWCoinTypeNebl)); input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); EXPECT_EQ(input.utxo_size(), 1); diff --git a/tests/chains/Nebulas/TransactionTests.cpp b/tests/chains/Nebulas/TransactionTests.cpp index d937cb73734..8e8c107a549 100644 --- a/tests/chains/Nebulas/TransactionTests.cpp +++ b/tests/chains/Nebulas/TransactionTests.cpp @@ -28,7 +28,7 @@ TEST(NebulasTransaction, serialize) { /* timestamp: */ 1560052938, /* payload: */ std::string()); - const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")); + const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b"), TWCurveSECP256k1); auto signer = Signer(1); signer.sign(privateKey, transaction); transaction.serializeToRaw(); @@ -49,7 +49,7 @@ TEST(NebulasTransaction, binaryPayload) { /* timestamp: */ 1560052938, /* payload: */ std::string("{\"binary\":\"test\"}")); - const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")); + const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b"), TWCurveSECP256k1); auto signer = Signer(1); signer.sign(privateKey, transaction); ASSERT_EQ(TW::Base64::encode(transaction.raw), "CiB1Oqj7bxLQMHEoNyg/vFHmsTrGdkpTf/5qFDkYPB3bkxIaGVefwtw23wEobqA40/7aIwQHghETxH4r+50aGhlXf89CeLWgHFjKu9/6tn4KNbelsMDAIIi2IhAAAAAAAAAAAJin2bgxTAAAKAcwyony5wU6PQoGYmluYXJ5EjN7IkRhdGEiOnsiZGF0YSI6WzExNiwxMDEsMTE1LDExNl0sInR5cGUiOiJCdWZmZXIifX1AAUoQAAAAAAAAAAAAAAAAAA9CQFIQAAAAAAAAAAAAAAAAAAMNQFgBYkGHXq+JWPaEyeB19bqL3QB5jyM961WLq7PMTpnGM4iLtBjCkngjS81kgPM2TE4qKDcpzqjum/NccrZtUPQLGk0MAQ=="); diff --git a/tests/chains/Tezos/SignerTests.cpp b/tests/chains/Tezos/SignerTests.cpp index 2f63e20c9e8..56f046c2f8a 100644 --- a/tests/chains/Tezos/SignerTests.cpp +++ b/tests/chains/Tezos/SignerTests.cpp @@ -22,7 +22,7 @@ TEST(TezosSigner, SignString) { append(expected, bytesToSign); append(expected, expectedSignature); - auto key = PrivateKey(parse_hex("0x2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f")); + auto key = PrivateKey(parse_hex("0x2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f"), TWCurveED25519); auto signedBytes = Signer().signData(key, bytesToSign); ASSERT_EQ(signedBytes, expected); @@ -77,7 +77,7 @@ TEST(TezosSigner, SignOperationList) { op_list.addOperation(delegateOperation); auto decodedPrivateKey = Base58::decodeCheck("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); - auto key = PrivateKey(Data(decodedPrivateKey.begin() + 4, decodedPrivateKey.end())); + auto key = PrivateKey(Data(decodedPrivateKey.begin() + 4, decodedPrivateKey.end()), TWCurveED25519); std::string expectedForgedBytesToSign = hex(op_list.forge(key)); std::string expectedSignature = "871693145f2dc72861ff6816e7ac3ce93c57611ac09a4c657a5a35270fa57153334c14cd8cae94ee228b6ef52f0e3f10948721e666318bc54b6c455404b11e03"; diff --git a/tests/chains/Theta/SignerTests.cpp b/tests/chains/Theta/SignerTests.cpp index 295ee5fc98d..778a21cb3de 100644 --- a/tests/chains/Theta/SignerTests.cpp +++ b/tests/chains/Theta/SignerTests.cpp @@ -13,7 +13,7 @@ using boost::multiprecision::uint256_t; TEST(Signer, Sign) { const auto pkFrom = - PrivateKey(parse_hex("0x93a90ea508331dfdf27fb79757d4250b4e84954927ba0073cd67454ac432c737")); + PrivateKey(parse_hex("0x93a90ea508331dfdf27fb79757d4250b4e84954927ba0073cd67454ac432c737"), TWCurveSECP256k1); const auto from = Ethereum::Address("0x2E833968E5bB786Ae419c4d13189fB081Cc43bab"); const auto to = Ethereum::Address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); auto transaction = Transaction(from, to, 10, 20, 1); diff --git a/tests/chains/VeChain/SignerTests.cpp b/tests/chains/VeChain/SignerTests.cpp index 1317cc17ee4..5b8d0135fee 100644 --- a/tests/chains/VeChain/SignerTests.cpp +++ b/tests/chains/VeChain/SignerTests.cpp @@ -23,7 +23,7 @@ TEST(Signer, Sign) { transaction.gas = 21000; transaction.nonce = 1; - auto key = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); + auto key = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"), TWCurveSECP256k1); auto signature = Signer::sign(key, transaction); ASSERT_EQ(hex(signature), "3181b1094150f8e4f51f370b805cc9c5b107504145b9e316e846d5e5dbeedb5c1c2b5d217f197a105983dfaad6a198414d5731c7447493cb6b5169907d73dbe101"); diff --git a/tests/chains/WAX/TWAnySignerTests.cpp b/tests/chains/WAX/TWAnySignerTests.cpp index f81d7663dec..414ea7bfef6 100644 --- a/tests/chains/WAX/TWAnySignerTests.cpp +++ b/tests/chains/WAX/TWAnySignerTests.cpp @@ -21,7 +21,7 @@ TEST(TWAnySignerWAX, Sign) { const auto refBlock = parse_hex("0cffaeda15039f3468398c5b4295d220fcc217f7cf96030c3729773097c6bd76"); const auto key = parse_hex("d30d185a296b9591d648cb92fe0aa8f8a42de30ed9d2a21da9e7f69c67e8e355"); - const auto pubKey = PublicKey(PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1)); + const auto pubKey = PublicKey(PrivateKey(key, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1)); const auto address = Address(pubKey); EXPECT_EQ(address.string(), "EOS7rC6zYUjuxWkiokZTrwwHqwFvZ15Qdrn5WNxMKVXtHiDDmBWog"); diff --git a/tests/chains/Waves/LeaseTests.cpp b/tests/chains/Waves/LeaseTests.cpp index 588f7f99db7..ab012c1497d 100644 --- a/tests/chains/Waves/LeaseTests.cpp +++ b/tests/chains/Waves/LeaseTests.cpp @@ -21,7 +21,7 @@ namespace TW::Waves::tests { TEST(WavesLease, serialize) { const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); auto input = Proto::SigningInput(); input.set_timestamp(int64_t(1526646497465)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); @@ -42,7 +42,7 @@ TEST(WavesLease, serialize) { TEST(WavesLease, CancelSerialize) { const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); auto input = Proto::SigningInput(); input.set_timestamp(int64_t(1568831000826)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); @@ -62,7 +62,7 @@ TEST(WavesLease, CancelSerialize) { TEST(WavesLease, jsonSerialize) { const auto privateKey = PrivateKey(parse_hex( - "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); auto input = Proto::SigningInput(); @@ -102,7 +102,7 @@ TEST(WavesLease, jsonSerialize) { TEST(WavesLease, jsonCancelSerialize) { const auto privateKey = PrivateKey(parse_hex( - "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); auto input = Proto::SigningInput(); diff --git a/tests/chains/Waves/SignerTests.cpp b/tests/chains/Waves/SignerTests.cpp index bbc63fad0c5..c4e6f61e263 100644 --- a/tests/chains/Waves/SignerTests.cpp +++ b/tests/chains/Waves/SignerTests.cpp @@ -16,7 +16,7 @@ namespace TW::Waves::tests { TEST(WavesSigner, SignTransaction) { const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); ASSERT_EQ(hex(Data(publicKeyCurve25519.bytes.begin(), publicKeyCurve25519.bytes.end())), "559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); diff --git a/tests/chains/Waves/TransactionTests.cpp b/tests/chains/Waves/TransactionTests.cpp index a71ca9c9069..5597f37259b 100644 --- a/tests/chains/Waves/TransactionTests.cpp +++ b/tests/chains/Waves/TransactionTests.cpp @@ -19,7 +19,7 @@ using namespace std; TEST(WavesTransaction, serialize) { const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); auto input = Proto::SigningInput(); input.set_timestamp(int64_t(1526641218066)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); @@ -67,7 +67,7 @@ TEST(WavesTransaction, serialize) { TEST(WavesTransaction, failedSerialize) { // 141 bytes attachment const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); auto input = Proto::SigningInput(); input.set_timestamp(int64_t(1526641218066)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); @@ -91,7 +91,7 @@ TEST(WavesTransaction, failedSerialize) { TEST(WavesTransaction, jsonSerialize) { const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); ASSERT_EQ(hex(Data(publicKeyCurve25519.bytes.begin(), publicKeyCurve25519.bytes.end())), "559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); diff --git a/tests/chains/XRP/TransactionCompilerTests.cpp b/tests/chains/XRP/TransactionCompilerTests.cpp index 7bcf106884a..0849aa99e20 100644 --- a/tests/chains/XRP/TransactionCompilerTests.cpp +++ b/tests/chains/XRP/TransactionCompilerTests.cpp @@ -24,7 +24,7 @@ TEST(RippleCompiler, CompileRippleWithSignatures) { /// Step 1: Prepare transaction input (protobuf) auto key = parse_hex("acf1bbf6264e699da0cc65d17ac03fcca6ded1522d19529df7762db46097ff9f"); auto input = TW::Ripple::Proto::SigningInput(); - auto privateKey = TW::PrivateKey(key); + auto privateKey = TW::PrivateKey(key, TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); input.mutable_op_payment()->set_amount(1000000); diff --git a/tests/chains/Zcash/AddressTests.cpp b/tests/chains/Zcash/AddressTests.cpp index 32ea339ed5f..a29a988ce7a 100644 --- a/tests/chains/Zcash/AddressTests.cpp +++ b/tests/chains/Zcash/AddressTests.cpp @@ -11,7 +11,7 @@ namespace TW::Zcash { TEST(ZcashAddress, FromPrivateKey) { - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto address = TAddress(publicKey); @@ -21,7 +21,7 @@ TEST(ZcashAddress, FromPrivateKey) { } TEST(ZcashAddress, FromPublicKey) { - const auto privateKey = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835")); + const auto privateKey = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto address = TAddress(publicKey); diff --git a/tests/chains/Zcash/TWZcashTransactionTests.cpp b/tests/chains/Zcash/TWZcashTransactionTests.cpp index 243f9554639..7126da30ce1 100644 --- a/tests/chains/Zcash/TWZcashTransactionTests.cpp +++ b/tests/chains/Zcash/TWZcashTransactionTests.cpp @@ -181,7 +181,7 @@ TEST(TWZcashTransaction, BlossomSigning) { utxo0->set_amount(27615); // real key 1p "m/44'/133'/0'/0/14" - auto utxoKey0 = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); + auto utxoKey0 = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"), TWCurveSECP256k1); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZcash, utxoKey0); auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZcash); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); diff --git a/tests/chains/Zcash/TransactionCompilerTests.cpp b/tests/chains/Zcash/TransactionCompilerTests.cpp index 6e92a26d50d..183b26e6233 100644 --- a/tests/chains/Zcash/TransactionCompilerTests.cpp +++ b/tests/chains/Zcash/TransactionCompilerTests.cpp @@ -48,7 +48,7 @@ TEST(ZcashCompiler, CompileWithSignatures) { utxo0->set_amount(27615); // real key 1p "m/44'/133'/0'/0/14" - auto utxoKey0 = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); + auto utxoKey0 = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"), TWCurveSECP256k1); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZcash, utxoKey0); auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZcash); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); diff --git a/tests/chains/Zen/AddressTests.cpp b/tests/chains/Zen/AddressTests.cpp index bef8a87317a..c0f4595c24f 100644 --- a/tests/chains/Zen/AddressTests.cpp +++ b/tests/chains/Zen/AddressTests.cpp @@ -23,7 +23,7 @@ TEST(ZenAddress, Invalid) { } TEST(ZenAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto privateKey = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a"), TWCurveSECP256k1); auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto address = Address(pubKey); ASSERT_EQ(address.string(), "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); diff --git a/tests/chains/Zen/SignerTests.cpp b/tests/chains/Zen/SignerTests.cpp index 8b054461429..5aa159115c0 100644 --- a/tests/chains/Zen/SignerTests.cpp +++ b/tests/chains/Zen/SignerTests.cpp @@ -46,7 +46,7 @@ TEST(ZenSigner, Sign) { utxo0->mutable_out_point()->set_sequence(UINT32_MAX); utxo0->set_amount(17600); - auto utxoKey0 = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto utxoKey0 = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a"), TWCoinTypeCurve(TWCoinTypeZen)); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZen, utxoKey0); ASSERT_EQ(utxoAddr0, "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZen, blockHash, blockHeight); diff --git a/tests/chains/Zilliqa/AddressTests.cpp b/tests/chains/Zilliqa/AddressTests.cpp index db26af859b7..26861fb8ca7 100644 --- a/tests/chains/Zilliqa/AddressTests.cpp +++ b/tests/chains/Zilliqa/AddressTests.cpp @@ -15,7 +15,7 @@ namespace TW::Zilliqa::tests { TEST(ZilliqaAddress, FromPrivateKey) { const auto privateKey = - PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); + PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6"), TWCurveSECP256k1); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const auto address = Address(publicKey); auto expectedAddress = "zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg"; diff --git a/tests/chains/Zilliqa/SignerTests.cpp b/tests/chains/Zilliqa/SignerTests.cpp index f8e0440307d..dec0e6f67fe 100644 --- a/tests/chains/Zilliqa/SignerTests.cpp +++ b/tests/chains/Zilliqa/SignerTests.cpp @@ -14,7 +14,7 @@ namespace TW::Zilliqa::tests { TEST(ZilliqaSigner, PreImage) { - auto privateKey = PrivateKey(parse_hex("0E891B9DFF485000C7D1DC22ECF3A583CC50328684321D61947A86E57CF6C638")); + auto privateKey = PrivateKey(parse_hex("0E891B9DFF485000C7D1DC22ECF3A583CC50328684321D61947A86E57CF6C638"), TWCurveSECP256k1); auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(pubKey.bytes), "034ae47910d58b9bde819c3cffa8de4441955508db00aa2540db8e6bf6e99abc1b"); @@ -46,7 +46,7 @@ TEST(ZilliqaSigner, PreImage) { } TEST(ZilliqaSigner, Signing) { - auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")); + auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"), TWCurveSECP256k1); auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); // 1 ZIL @@ -76,7 +76,7 @@ TEST(ZilliqaSigner, Signing) { TEST(ZilliqaSigner, SigningData) { // https://viewblock.io/zilliqa/tx/0x6228b3d7e69fc3481b84fd00e892cec359a41654f58948ff7b1b932396b00ad9 - auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")); + auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"), TWCurveSECP256k1); auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); // 10 ZIL diff --git a/tests/common/Bech32AddressTests.cpp b/tests/common/Bech32AddressTests.cpp index 11d8ec18b81..27a54f35d4e 100644 --- a/tests/common/Bech32AddressTests.cpp +++ b/tests/common/Bech32AddressTests.cpp @@ -99,33 +99,33 @@ TEST(Bech32Address, FromKeyHash) { TEST(Bech32Address, FromPublicKey) { { - auto privateKey = PrivateKey(parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832")); + auto privateKey = PrivateKey(parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(publicKey.bytes), "026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); auto address = Bech32Address("bnb", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ("bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", address.string()); } { - auto privateKey = PrivateKey(parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")); + auto privateKey = PrivateKey(parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(publicKey.bytes), "0257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc5"); auto address = Bech32Address("cosmos", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ(address.string(), "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); } { - auto privateKey = PrivateKey(parse_hex("e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94")); + auto privateKey = PrivateKey(parse_hex("e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); auto address = Bech32Address("one", Hash::HasherKeccak256, publicKey); ASSERT_EQ(address.string(), "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); } { - auto privateKey = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f")); + auto privateKey = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); auto address = Bech32Address("io", Hash::HasherKeccak256, publicKey); ASSERT_EQ(address.string(), "io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); } { - const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); + const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(publicKey.bytes), "02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d"); const auto address = Bech32Address("zil", Hash::HasherSha256, publicKey); @@ -136,7 +136,7 @@ TEST(Bech32Address, FromPublicKey) { // From same public key, but different hashes: different results TEST(Bech32Address, Hashes) { - const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); + const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6"), TWCurveSECP256k1); auto publicKey1 = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ("02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d", hex(publicKey1.bytes)); @@ -158,7 +158,7 @@ TEST(Bech32Address, Hashes) { // From same public key, but different prefixes: different results (checksum) TEST(Bech32Address, Prefixes) { - const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); + const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ("02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d", hex(publicKey.bytes)); diff --git a/tests/common/HDWallet/HDWalletInternalTests.cpp b/tests/common/HDWallet/HDWalletInternalTests.cpp index 19e8c45868f..6b0554a65d3 100644 --- a/tests/common/HDWallet/HDWalletInternalTests.cpp +++ b/tests/common/HDWallet/HDWalletInternalTests.cpp @@ -31,7 +31,7 @@ std::string nodeToHexString(const HDNode& node) { } Data publicKeyFromPrivateKey(const Data& privateKey) { - return PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1).bytes; + return PrivateKey(privateKey, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes; } TEST(HDWalletInternal, SquareDerivationRoutes) { diff --git a/tests/common/PrivateKeyTests.cpp b/tests/common/PrivateKeyTests.cpp index fa9ec36aea3..fccfebcff6f 100644 --- a/tests/common/PrivateKeyTests.cpp +++ b/tests/common/PrivateKeyTests.cpp @@ -19,7 +19,7 @@ namespace TW::tests { TEST(PrivateKey, CreateValid) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); EXPECT_TRUE(PrivateKey::isValid(privKeyData, TWCurveSECP256k1)); - auto privateKey = PrivateKey(privKeyData); + auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); EXPECT_EQ(hex(privKeyData), hex(privateKey.bytes)); } @@ -117,26 +117,29 @@ TEST(PrivateKey, Valid) { TEST(PrivateKey, PublicKey) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(privKeyData); { + const auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); EXPECT_EQ( "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867", hex(publicKey.bytes)); } { + const auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ( "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1", hex(publicKey.bytes)); } { + const auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); EXPECT_EQ( "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91", hex(publicKey.bytes)); } { + const auto privateKey = PrivateKey(privKeyData, TWCurveNIST256p1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1Extended); EXPECT_EQ( "046d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab918b4fe46ccbf56701fb210d67d91c5779468f6b3fdc7a63692b9b62543f47ae", @@ -320,4 +323,53 @@ TEST(PrivateKey, SignShortDigest) { } } +TEST(PrivateKey, SignWithDifferentCurveWorks) { + Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + // Using the deprecated constructor without specifying a curve + auto privateKey = PrivateKey(privKeyData); + Data messageData = TW::data("hello"); + Data hash = Hash::keccak256(messageData); + + // Should be able to sign with any curve when using the normal constructor + try { + auto signatureSECP256k1 = privateKey.sign(hash, TWCurveSECP256k1); + EXPECT_FALSE(signatureSECP256k1.empty()); + EXPECT_EQ(signatureSECP256k1.size(), 65ul); + + auto signatureNIST256p1 = privateKey.sign(hash, TWCurveNIST256p1); + EXPECT_FALSE(signatureNIST256p1.empty()); + EXPECT_EQ(signatureNIST256p1.size(), 65ul); + + // Verify the signatures are different for different curves + EXPECT_NE(hex(signatureSECP256k1), hex(signatureNIST256p1)); + } catch (const std::exception& e) { + FAIL() << "Unexpected exception was thrown: " << e.what(); + } +} + +TEST(PrivateKey, SignWithDifferentCurveThrows) { + Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + // Using the constructor that specifies a curve + auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); + Data messageData = TW::data("hello"); + Data hash = Hash::keccak256(messageData); + + // Should throw an exception if signing with a different curve + try { + auto _ = privateKey.sign(hash, TWCurveNIST256p1); + FAIL() << "Expected exception was not thrown"; + } catch (const std::invalid_argument& e) { + EXPECT_STREQ("Specified curve is different from the curve of the private key", e.what()); + } + + // Check that signing with the same curve works fine + try { + auto signature = privateKey.sign(hash, TWCurveSECP256k1); + EXPECT_EQ(signature.size(), 65ul); + EXPECT_TRUE(!signature.empty()); + } catch (const std::exception& e) { + FAIL() << "Unexpected exception was thrown: " << e.what(); + } +} + } // namespace TW::tests diff --git a/tests/common/PublicKeyTests.cpp b/tests/common/PublicKeyTests.cpp index d73c1c1ac8a..d69e1bc7952 100644 --- a/tests/common/PublicKeyTests.cpp +++ b/tests/common/PublicKeyTests.cpp @@ -15,7 +15,7 @@ using namespace TW; TEST(PublicKeyTests, CreateFromPrivateSecp256k1) { const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(key); + auto privateKey = PrivateKey(key, TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(publicKey.bytes.size(), 33ul); EXPECT_EQ(hex(publicKey.bytes), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); @@ -43,7 +43,7 @@ TEST(PublicKeyTests, CreateBlake) { const auto privateKeyHex = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; const auto publicKeyKeyHex = "b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"; { - auto publicKey = PrivateKey(parse_hex(privateKeyHex)).getPublicKey(TWPublicKeyTypeED25519Blake2b); + auto publicKey = PrivateKey(parse_hex(privateKeyHex), TWCurveED25519Blake2bNano).getPublicKey(TWPublicKeyTypeED25519Blake2b); EXPECT_EQ(hex(publicKey.bytes), publicKeyKeyHex); EXPECT_EQ(publicKey.bytes.size(), 32ul); } @@ -55,7 +55,7 @@ TEST(PublicKeyTests, CreateBlake) { TEST(PublicKeyTests, CompressedExtended) { const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(key); + auto privateKey = PrivateKey(key, TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1); EXPECT_EQ(publicKey.bytes.size(), 33ul); @@ -92,7 +92,7 @@ TEST(PublicKeyTests, CompressedExtended) { TEST(PublicKeyTests, CompressedExtendedNist) { const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(key); + auto privateKey = PrivateKey(key, TWCurveNIST256p1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); EXPECT_EQ(publicKey.type, TWPublicKeyTypeNIST256p1); EXPECT_EQ(publicKey.bytes.size(), 33ul); @@ -129,7 +129,7 @@ TEST(PublicKeyTests, CompressedExtendedNist) { TEST(PublicKeyTests, CompressedExtendedED25519) { const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(key); + auto privateKey = PrivateKey(key, TWCurveED25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); EXPECT_EQ(publicKey.type, TWPublicKeyTypeED25519); EXPECT_EQ(publicKey.bytes.size(), 32ul); @@ -155,32 +155,34 @@ TEST(PublicKeyTests, IsValidWrongType) { } TEST(PublicKeyTests, Verify) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - const char* message = "Hello"; const Data messageData = TW::data(message); const Data digest = Hash::sha256(messageData); { - const auto signature = privateKey.sign(digest, TWCurveSECP256k1); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); + const auto signature = privateKey.sign(digest); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_TRUE(publicKey.verify(signature, digest)); EXPECT_EQ(hex(signature), "0f5d5a9e5fc4b82a625312f3be5d3e8ad017d882de86c72c92fcefa924e894c12071772a14201a3a0debf381b5e8dea39fadb9bcabdc02ee71ab018f55bf717f01"); } { - const auto signature = privateKey.sign(digest, TWCurveED25519); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveED25519); + const auto signature = privateKey.sign(digest); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); EXPECT_TRUE(publicKey.verify(signature, digest)); EXPECT_EQ(hex(signature), "42848abf2641a731e18b8a1fb80eff341a5acebdc56faeccdcbadb960aef775192842fccec344679446daa4d02d264259c8f9aa364164ebe0ebea218581e2e03"); } { - const auto signature = privateKey.sign(digest, TWCurveED25519Blake2bNano); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveED25519Blake2bNano); + const auto signature = privateKey.sign(digest); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); EXPECT_TRUE(publicKey.verify(signature, digest)); EXPECT_EQ(hex(signature), "5c1473944cd0234ebc5a91b2966b9e707a33b936dadd149417a2e53b6b3fc97bef17b767b1690708c74d7b4c8fe48703fd44a6ef59d4cc5b9f88ba992db0a003"); } { - const auto signature = privateKey.sign(digest, TWCurveNIST256p1); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveNIST256p1); + const auto signature = privateKey.sign(digest); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1Extended); EXPECT_TRUE(publicKey.verify(signature, digest)); EXPECT_EQ(hex(signature), "2e4655831f0c60729583595c103bf0d862af6313e4326f03f512682106c792822f5a9cd21e7d4a3316c2d337e5eee649b09c34f7b4407344f0d32e8d33167d8901"); @@ -200,7 +202,7 @@ TEST(PublicKeyTests, ED25519_malleability) { } TEST(PublicKeyTests, VerifyAsDER) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const char* message = "Hello"; const Data messageData = TW::data(message); @@ -224,19 +226,19 @@ TEST(PublicKeyTests, VerifyAsDER) { } TEST(PublicKeyTests, VerifyEd25519Extended) { - const auto privateKey = PrivateKey(parse_hex("e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276")); + const auto privateKey = PrivateKey(parse_hex("e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276"), TWCurveED25519ExtendedCardano); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); const auto message = TW::data("Hello"); const auto digest = Hash::sha256(message); - const auto signature = privateKey.sign(digest, TWCurveED25519ExtendedCardano); + const auto signature = privateKey.sign(digest); const auto valid = publicKey.verify(signature, digest); EXPECT_TRUE(valid); } TEST(PublicKeyTests, VerifySchnorr) { - const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const auto privateKey = PrivateKey(key); const Data messageData = TW::data("hello schnorr"); @@ -249,7 +251,7 @@ TEST(PublicKeyTests, VerifySchnorr) { } TEST(PublicKeyTests, VerifySchnorrWrongType) { - const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const auto privateKey = PrivateKey(key); const Data messageData = TW::data("hello schnorr"); @@ -284,13 +286,13 @@ TEST(PublicKeyTests, RecoverRaw) { } TEST(PublicKeyTests, SignAndRecoverRaw) { - const auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); + const auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); EXPECT_EQ(hex(publicKey.bytes), "0463ade8ebc212b85e7e4278dc3dcb4f9cc18aab912ef5d302b5d1940e772e9e1a9213522efddad487bbd5dd7907e8e776f918e9a5e4cb51893724e9fe76792a4f"); const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); // sign - const auto signature = privateKey.sign(message, TWCurveSECP256k1); + const auto signature = privateKey.sign(message); EXPECT_EQ(hex(signature), "92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e5800"); // revocer @@ -322,7 +324,7 @@ TEST(PublicKeyTests, Recover) { "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed8090329274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"); } - const auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); + const auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); EXPECT_EQ(hex(publicKey.bytes), "0463ade8ebc212b85e7e4278dc3dcb4f9cc18aab912ef5d302b5d1940e772e9e1a9213522efddad487bbd5dd7907e8e776f918e9a5e4cb51893724e9fe76792a4f"); { diff --git a/tests/interface/TWPublicKeyTests.cpp b/tests/interface/TWPublicKeyTests.cpp index 16b707e0a95..90b060da2ad 100644 --- a/tests/interface/TWPublicKeyTests.cpp +++ b/tests/interface/TWPublicKeyTests.cpp @@ -66,7 +66,7 @@ TEST(TWPublicKeyTests, CompressedExtended) { } TEST(TWPublicKeyTests, Verify) { - const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); const char* message = "Hello"; @@ -80,7 +80,7 @@ TEST(TWPublicKeyTests, Verify) { } TEST(TWPublicKeyTests, VerifyAsDER) { - const PrivateKey key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const PrivateKey key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); const char* message = "Hello"; diff --git a/tests/interface/TWTransactionCompilerTests.cpp b/tests/interface/TWTransactionCompilerTests.cpp index 806de4959da..eb52f41d2d5 100644 --- a/tests/interface/TWTransactionCompilerTests.cpp +++ b/tests/interface/TWTransactionCompilerTests.cpp @@ -431,9 +431,9 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { // 2 private keys are needed (despite >2 UTXOs) auto key0 = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); auto key1 = parse_hex("7878787878787878787878787878787878787878787878787878787878787878"); - EXPECT_EQ(hex(PrivateKey(key0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + EXPECT_EQ(hex(PrivateKey(key0, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey0)); - EXPECT_EQ(hex(PrivateKey(key1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + EXPECT_EQ(hex(PrivateKey(key1, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey1)); *input.add_private_key() = std::string(key0.begin(), key0.end()); *input.add_private_key() = std::string(key1.begin(), key1.end()); diff --git a/walletconsole/lib/Address.cpp b/walletconsole/lib/Address.cpp index 166efc215cc..804b575309e 100644 --- a/walletconsole/lib/Address.cpp +++ b/walletconsole/lib/Address.cpp @@ -46,7 +46,7 @@ bool Address::addrPri(const string& coinid, const string& prikey_in, string& res return false; } auto ctype = (TWCoinType)coin.c; - PrivateKey priKey = PrivateKey(priDat); + PrivateKey priKey = PrivateKey(priDat, TWCoinTypeCurve(ctype)); res = TW::deriveAddress(ctype, priKey); return true; } diff --git a/walletconsole/lib/Keys.cpp b/walletconsole/lib/Keys.cpp index b30e90b98f8..7545b1e4f0c 100644 --- a/walletconsole/lib/Keys.cpp +++ b/walletconsole/lib/Keys.cpp @@ -52,7 +52,7 @@ bool Keys::pubPri(const string& coinid, const string& p, string& res) { Data privDat; try { privDat = parse_hex(p); - auto priv = PrivateKey(privDat); + auto priv = PrivateKey(privDat, TWCoinTypeCurve(TWCoinType(coin.c))); auto pub = priv.getPublicKey((TWPublicKeyType)coin.pubKeyType); res = hex(pub.bytes); _out << "Public key created, type " << (int)coin.pubKeyType << ", length " << pub.bytes.size() << endl; From 17a9e63867395df19f2b45a0cf27b0825b38e009 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Fri, 28 Mar 2025 19:59:52 +0700 Subject: [PATCH 13/72] feat(eip7702): Add `SetCode` transaction type (#4336) * fix(eip7702): Add `SetCode` transaction type * fix(eip7702): Add `Biz.executeBatch` function call * Add `AuthorizationSigner` * fix(eip7702): Fix Authorization list RLP encoding * fix(eip7702): Add `Biz.execute` and `Biz.executeBatch` tests * fix(eip7702): Add android test * [CI] Trigger CI --- .../core/app/blockchains/ethereum/TestBarz.kt | 57 ++++++ rust/tw_evm/src/abi/prebuild/biz.rs | 115 ++++++++++++ rust/tw_evm/src/abi/prebuild/erc4337.rs | 83 +-------- rust/tw_evm/src/abi/prebuild/mod.rs | 11 ++ .../src/modules/authorization_signer.rs | 91 ++++++++++ rust/tw_evm/src/modules/mod.rs | 1 + rust/tw_evm/src/modules/tx_builder.rs | 143 +++++++++++++-- rust/tw_evm/src/rlp/impls.rs | 10 ++ .../src/transaction/authorization_list.rs | 95 ++++++++++ rust/tw_evm/src/transaction/mod.rs | 2 + .../src/transaction/transaction_eip7702.rs | 165 ++++++++++++++++++ rust/tw_evm/src/transaction/user_operation.rs | 3 +- rust/tw_evm/tests/barz.rs | 141 ++++++++++++++- src/proto/Ethereum.proto | 19 +- 14 files changed, 831 insertions(+), 105 deletions(-) create mode 100644 rust/tw_evm/src/abi/prebuild/biz.rs create mode 100644 rust/tw_evm/src/modules/authorization_signer.rs create mode 100644 rust/tw_evm/src/transaction/authorization_list.rs create mode 100644 rust/tw_evm/src/transaction/transaction_eip7702.rs diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt index ff31c515533..28272709001 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt @@ -227,6 +227,63 @@ class TestBarz { assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1E6c542ebC7c960c6A155A9094DB838cEf842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}") } + // https://bscscan.com/tx/0x425eb17a8e1dee2fcee8352a772d83cbb069c2e03f2c5d9d00da3b3ef66ce48b + @Test + fun testSignEip7702EoaBatched() { + val transferFunc1 = EthereumAbiFunction("transfer") + transferFunc1.addParamAddress("0x2EF648D7C03412B832726fd4683E2625deA047Ba".toHexByteArray(), false) + // 100_000_000_000_000 + transferFunc1.addParamUInt256("0x5af3107a4000".toHexByteArray(), false) + val transferPayload1 = EthereumAbi.encode(transferFunc1) + + val transferFunc2 = EthereumAbiFunction("transfer") + transferFunc2.addParamAddress("0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e".toHexByteArray(), false) + // 500_000_000_000_000 + transferFunc2.addParamUInt256("0x1c6bf52634000".toHexByteArray(), false) + val transferPayload2 = EthereumAbi.encode(transferFunc2) + + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("0xe148e40f06ee3ba316cdb2571f33486cf879c0ffd2b279ce9f9a88c41ce962e7".toHexByteArray()).data()) + chainId = ByteString.copyFrom("0x38".toHexByteArray()) + nonce = ByteString.copyFrom("0x12".toHexByteArray()) + txMode = TransactionMode.SetCode + + gasLimit = ByteString.copyFrom("0x186a0".toHexByteArray()) + maxFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + maxInclusionFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + + transaction = Ethereum.Transaction.newBuilder().apply { + batch = Ethereum.Transaction.Batch.newBuilder().apply { + addAllCalls(listOf( + Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply { + // TWT + address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" + amount = ByteString.copyFrom("0x00".toHexByteArray()) + payload = ByteString.copyFrom(transferPayload1) + }.build(), + Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply { + // TWT + address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" + amount = ByteString.copyFrom("0x00".toHexByteArray()) + payload = ByteString.copyFrom(transferPayload2) + }.build() + )) + }.build() + }.build() + + userOperationMode = Ethereum.SCAccountType.Biz + eip7702Authority = Ethereum.Authority.newBuilder().apply { + address = "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7" + }.build() + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x00b2d13719df301927ddcbdad5b6bc6214f2007c6408df883c9ea483b45e6f44") + assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x04f9030f3812843b9aca00843b9aca00830186a0945132829820b44dc3e8586cec926a16fca0a5608480b9024434fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000002ef648d7c03412b832726fd4683e2625dea047ba00000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000c0f85cf85a3894117bc8454756456a0f83dbd130bb94d793d3f3f71380a0073afc661c158a2dccf4183f87e1e4d62b4d406af418cfd69959368ec9bec2a6a064292fd61d4d16b840470a86fc4f7a89413f9126d897f2268eb76a1d887c6d7a01a0e8bcbd96323c9d3e67b74366b2f43299100996d9e8874a6fd87186ac8f580d4ca07c25b4f0619af77fb953e8f0e4372bfbee62616ad419697516108eeb9bcebb28") + } + @Test fun testAuthorizationHash() { val chainId = "0x01".toHexByteArray() diff --git a/rust/tw_evm/src/abi/prebuild/biz.rs b/rust/tw_evm/src/abi/prebuild/biz.rs new file mode 100644 index 00000000000..36edfa95e68 --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/biz.rs @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::abi::contract::Contract; +use crate::abi::function::Function; +use crate::abi::param_token::NamedToken; +use crate::abi::param_type::ParamType; +use crate::abi::prebuild::ExecuteArgs; +use crate::abi::token::Token; +use crate::abi::{AbiError, AbiErrorKind, AbiResult}; +use lazy_static::lazy_static; +use tw_coin_entry::error::prelude::*; +use tw_memory::Data; + +const ERC4337_BIZ_ACCOUNT_ABI: &str = include_str!("resource/erc4337.biz_account.abi.json"); + +lazy_static! { + static ref ERC4337_BIZ_ACCOUNT: Contract = + serde_json::from_str(ERC4337_BIZ_ACCOUNT_ABI).unwrap(); +} + +pub struct BizAccount; + +impl BizAccount { + pub fn encode_execute(args: ExecuteArgs) -> AbiResult { + let func = ERC4337_BIZ_ACCOUNT.function("execute")?; + func.encode_input(&[ + Token::Address(args.to), + Token::u256(args.value), + Token::Bytes(args.data), + ]) + } + + pub fn encode_execute_4337_op(args: ExecuteArgs) -> AbiResult { + let func = ERC4337_BIZ_ACCOUNT.function("execute4337Op")?; + func.encode_input(&[ + Token::Address(args.to), + Token::u256(args.value), + Token::Bytes(args.data), + ]) + } + + pub fn encode_execute_batch(args: I) -> AbiResult + where + I: IntoIterator, + { + let func = ERC4337_BIZ_ACCOUNT.function("executeBatch")?; + encode_batch(func, args) + } + + pub fn encode_execute_4337_ops(args: I) -> AbiResult + where + I: IntoIterator, + { + let func = ERC4337_BIZ_ACCOUNT.function("execute4337Ops")?; + encode_batch(func, args) + } +} + +fn encode_batch(function: &Function, args: I) -> AbiResult +where + I: IntoIterator, +{ + // `tuple[]`, where each item is a tuple of (address, uint256, bytes). + let array_param = function + .inputs + .first() + .or_tw_err(AbiErrorKind::Error_internal) + .context("'Biz.execute4337Ops()' should contain only one argument")?; + + let ParamType::Array { + kind: array_elem_type, + } = array_param.kind.clone() + else { + return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { + format!( + "'Biz.execute4337Ops()' input argument should be an array, found: {:?}", + array_param.kind + ) + }); + }; + + let ParamType::Tuple { + params: tuple_params, + } = array_elem_type.as_ref() + else { + return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { + format!( + "'Biz.execute4337Ops()' input argument should be an array of tuples, found: {array_elem_type:?}", + ) + }); + }; + + if tuple_params.len() != 3 { + return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { + format!( + "'Biz.execute4337Ops()' input argument should be an array of tuples with 3 elements, found: {}", tuple_params.len() + ) + }); + } + + let array_tokens = args + .into_iter() + .map(|call| Token::Tuple { + params: vec![ + NamedToken::with_param_and_token(&tuple_params[0], Token::Address(call.to)), + NamedToken::with_param_and_token(&tuple_params[1], Token::u256(call.value)), + NamedToken::with_param_and_token(&tuple_params[2], Token::Bytes(call.data)), + ], + }) + .collect(); + + function.encode_input(&[Token::array(*array_elem_type, array_tokens)]) +} diff --git a/rust/tw_evm/src/abi/prebuild/erc4337.rs b/rust/tw_evm/src/abi/prebuild/erc4337.rs index 3dd1b74ac10..71329edfc58 100644 --- a/rust/tw_evm/src/abi/prebuild/erc4337.rs +++ b/rust/tw_evm/src/abi/prebuild/erc4337.rs @@ -3,32 +3,20 @@ // Copyright © 2017 Trust Wallet. use crate::abi::contract::Contract; -use crate::abi::param_token::NamedToken; use crate::abi::param_type::ParamType; +use crate::abi::prebuild::ExecuteArgs; use crate::abi::token::Token; -use crate::abi::{AbiError, AbiErrorKind, AbiResult}; -use crate::address::Address; +use crate::abi::AbiResult; use lazy_static::lazy_static; -use tw_coin_entry::error::prelude::{OrTWError, ResultContext}; use tw_memory::Data; -use tw_number::U256; /// Generated via https://remix.ethereum.org /// https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol const ERC4337_SIMPLE_ACCOUNT_ABI: &str = include_str!("resource/erc4337.simple_account.abi.json"); -const ERC4337_BIZ_ACCOUNT_ABI: &str = include_str!("resource/erc4337.biz_account.abi.json"); lazy_static! { static ref ERC4337_SIMPLE_ACCOUNT: Contract = serde_json::from_str(ERC4337_SIMPLE_ACCOUNT_ABI).unwrap(); - static ref ERC4337_BIZ_ACCOUNT: Contract = - serde_json::from_str(ERC4337_BIZ_ACCOUNT_ABI).unwrap(); -} - -pub struct ExecuteArgs { - pub to: Address, - pub value: U256, - pub data: Data, } pub struct Erc4337SimpleAccount; @@ -43,15 +31,6 @@ impl Erc4337SimpleAccount { ]) } - pub fn encode_execute_4337_op(args: ExecuteArgs) -> AbiResult { - let func = ERC4337_BIZ_ACCOUNT.function("execute4337Op")?; - func.encode_input(&[ - Token::Address(args.to), - Token::u256(args.value), - Token::Bytes(args.data), - ]) - } - pub fn encode_execute_batch(args: I) -> AbiResult where I: IntoIterator, @@ -80,62 +59,4 @@ impl Erc4337SimpleAccount { Token::array(ParamType::Bytes, datas), ]) } - - pub fn encode_execute_4337_ops(args: I) -> AbiResult - where - I: IntoIterator, - { - let func = ERC4337_BIZ_ACCOUNT.function("execute4337Ops")?; - - // `tuple[]`, where each item is a tuple of (address, uint256, bytes). - let array_param = func - .inputs - .first() - .or_tw_err(AbiErrorKind::Error_internal) - .context("'Biz.execute4337Ops()' should contain only one argument")?; - - let ParamType::Array { - kind: array_elem_type, - } = array_param.kind.clone() - else { - return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { - format!( - "'Biz.execute4337Ops()' input argument should be an array, found: {:?}", - array_param.kind - ) - }); - }; - - let ParamType::Tuple { - params: tuple_params, - } = array_elem_type.as_ref() - else { - return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { - format!( - "'Biz.execute4337Ops()' input argument should be an array of tuples, found: {array_elem_type:?}", - ) - }); - }; - - if tuple_params.len() != 3 { - return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { - format!( - "'Biz.execute4337Ops()' input argument should be an array of tuples with 3 elements, found: {}", tuple_params.len() - ) - }); - } - - let array_tokens = args - .into_iter() - .map(|call| Token::Tuple { - params: vec![ - NamedToken::with_param_and_token(&tuple_params[0], Token::Address(call.to)), - NamedToken::with_param_and_token(&tuple_params[1], Token::u256(call.value)), - NamedToken::with_param_and_token(&tuple_params[2], Token::Bytes(call.data)), - ], - }) - .collect(); - - func.encode_input(&[Token::array(*array_elem_type, array_tokens)]) - } } diff --git a/rust/tw_evm/src/abi/prebuild/mod.rs b/rust/tw_evm/src/abi/prebuild/mod.rs index 134b2430973..5eeb9ab85a1 100644 --- a/rust/tw_evm/src/abi/prebuild/mod.rs +++ b/rust/tw_evm/src/abi/prebuild/mod.rs @@ -2,7 +2,18 @@ // // Copyright © 2017 Trust Wallet. +use crate::address::Address; +use tw_memory::Data; +use tw_number::U256; + +pub mod biz; pub mod erc1155; pub mod erc20; pub mod erc4337; pub mod erc721; + +pub struct ExecuteArgs { + pub to: Address, + pub value: U256, + pub data: Data, +} diff --git a/rust/tw_evm/src/modules/authorization_signer.rs b/rust/tw_evm/src/modules/authorization_signer.rs new file mode 100644 index 00000000000..86d77fde04e --- /dev/null +++ b/rust/tw_evm/src/modules/authorization_signer.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::rlp::list::RlpList; +use crate::transaction::authorization_list::{Authorization, SignedAuthorization}; +use std::iter; +use tw_coin_entry::error::prelude::*; +use tw_hash::sha3::keccak256; +use tw_hash::H256; +use tw_keypair::ecdsa::secp256k1::PrivateKey; +use tw_keypair::traits::SigningKeyTrait; +use tw_number::U256; + +pub const EIP_7702_MAGIC_PREFIX: u8 = 0x05; + +pub struct AuthorizationSigner; + +impl AuthorizationSigner { + pub fn sign( + private_key: &PrivateKey, + authorization: Authorization, + ) -> SigningResult { + let hash = Self::get_authorization_hash(&authorization); + let signature = private_key.sign(hash)?; + + Ok(SignedAuthorization { + authorization, + y_parity: signature.v(), + r: U256::from_big_endian(signature.r()), + s: U256::from_big_endian(signature.s()), + }) + } + + fn get_authorization_hash(authorization: &Authorization) -> H256 { + let mut list = RlpList::new(); + list.append(&authorization.chain_id) + .append(&authorization.address) + .append(&authorization.nonce); + + let to_hash: Vec<_> = iter::once(EIP_7702_MAGIC_PREFIX) + .chain(list.finish()) + .collect(); + H256::try_from(keccak256(&to_hash).as_slice()) + .expect("keccak256 must return exactly 32 bytes") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::address::Address; + use std::str::FromStr; + use tw_encoding::hex::ToHex; + + #[test] + fn test_get_authorization_hash() { + let authorization = Authorization { + chain_id: U256::from(1_u32), + address: Address::from_str("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1").unwrap(), + nonce: U256::from(1_u32), + }; + assert_eq!( + AuthorizationSigner::get_authorization_hash(&authorization).to_hex(), + "3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9" + ); + } + + #[test] + fn test_sign_authorization() { + let authorization = Authorization { + chain_id: U256::from(1_u32), + address: Address::from_str("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1").unwrap(), + nonce: U256::from(1_u32), + }; + let private_key = PrivateKey::try_from( + "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8", + ) + .unwrap(); + let signed_authorization = AuthorizationSigner::sign(&private_key, authorization).unwrap(); + assert_eq!( + signed_authorization.r.to_big_endian().to_hex(), + "2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710" + ); + assert_eq!( + signed_authorization.s.to_big_endian().to_hex(), + "5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c" + ); + assert_eq!(signed_authorization.y_parity, 1); + } +} diff --git a/rust/tw_evm/src/modules/mod.rs b/rust/tw_evm/src/modules/mod.rs index fb246766ac1..6653db1f41f 100644 --- a/rust/tw_evm/src/modules/mod.rs +++ b/rust/tw_evm/src/modules/mod.rs @@ -3,6 +3,7 @@ // Copyright © 2017 Trust Wallet. pub mod abi_encoder; +pub mod authorization_signer; pub mod compiler; pub mod message_signer; pub mod rlp_encoder; diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs index 516f375024b..d246fc7d2b2 100644 --- a/rust/tw_evm/src/modules/tx_builder.rs +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -3,14 +3,19 @@ // Copyright © 2017 Trust Wallet. use crate::abi::abi_to_signing_error; +use crate::abi::prebuild::biz::BizAccount; use crate::abi::prebuild::erc1155::Erc1155; use crate::abi::prebuild::erc20::Erc20; -use crate::abi::prebuild::erc4337::{Erc4337SimpleAccount, ExecuteArgs}; +use crate::abi::prebuild::erc4337::Erc4337SimpleAccount; use crate::abi::prebuild::erc721::Erc721; +use crate::abi::prebuild::ExecuteArgs; use crate::address::{Address, EvmAddress}; use crate::evm_context::EvmContext; +use crate::modules::authorization_signer::AuthorizationSigner; use crate::transaction::access_list::{Access, AccessList}; +use crate::transaction::authorization_list::{Authorization, AuthorizationList}; use crate::transaction::transaction_eip1559::TransactionEip1559; +use crate::transaction::transaction_eip7702::TransactionEip7702; use crate::transaction::transaction_non_typed::TransactionNonTyped; use crate::transaction::user_operation::UserOperation; use crate::transaction::user_operation_v0_7::UserOperationV0_7; @@ -19,6 +24,7 @@ use std::marker::PhantomData; use std::str::FromStr; use tw_coin_entry::error::prelude::*; use tw_hash::H256; +use tw_keypair::ecdsa::secp256k1; use tw_memory::Data; use tw_number::U256; use tw_proto::Common::Proto::SigningError as CommonError; @@ -136,26 +142,23 @@ impl TxBuilder { (amount, payload, to_address) }, Tx::batch(ref batch) => { - if input.tx_mode != TxMode::UserOp { - return SigningError::err(SigningErrorType::Error_invalid_params) - .context("Transaction batch can be used in User Operation mode only"); - } - // Payload should match ERC4337 standard. let calls: Vec<_> = batch .calls .iter() .map(Self::erc4337_execute_call_from_proto) .collect::, _>>()?; - let user_op_payload = match input.user_operation_mode { - SCAccountType::SimpleAccount => { - Erc4337SimpleAccount::encode_execute_batch(calls) - }, - SCAccountType::Biz4337 => Erc4337SimpleAccount::encode_execute_4337_ops(calls), - } - .map_err(abi_to_signing_error)?; + let execute_payload = Self::encode_execute_batch(input.user_operation_mode, calls)?; - return Self::user_operation_from_proto(input, user_op_payload); + return match input.tx_mode { + TxMode::UserOp => Self::user_operation_from_proto(input, execute_payload), + TxMode::SetCode => { + Self::transaction_eip7702_from_proto(input, U256::zero(), execute_payload) + }, + _ => SigningError::err(SigningErrorType::Error_invalid_params).context( + "Transaction batch can be used in `UserOp` or `SetCode` modes only", + ), + }; }, Tx::None => { return SigningError::err(SigningErrorType::Error_invalid_params) @@ -180,13 +183,22 @@ impl TxBuilder { data: payload, }; - let user_op_payload = match input.user_operation_mode { - SCAccountType::SimpleAccount => Erc4337SimpleAccount::encode_execute(args), - SCAccountType::Biz4337 => Erc4337SimpleAccount::encode_execute_4337_op(args), - } - .map_err(abi_to_signing_error)?; + let user_op_payload = Self::encode_execute(input.user_operation_mode, args)?; Self::user_operation_from_proto(input, user_op_payload)? }, + TxMode::SetCode => { + let to = to + .or_tw_err(SigningErrorType::Error_invalid_address) + .context("No contract/destination address specified")?; + let args = ExecuteArgs { + to, + value: eth_amount, + data: payload, + }; + + let execute_payload = Self::encode_execute(input.user_operation_mode, args)?; + Self::transaction_eip7702_from_proto(input, eth_amount, execute_payload)? + }, }; Ok(tx) } @@ -294,6 +306,76 @@ impl TxBuilder { }) } + #[inline] + fn transaction_eip7702_from_proto( + input: &Proto::SigningInput, + eth_amount: U256, + payload: Data, + ) -> SigningResult> { + let signer_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref()) + .into_tw() + .context("Sender's private key must be provided to generate an EIP-7702 transaction")?; + let signer = Address::with_secp256k1_pubkey(&signer_key.public()); + + let nonce = U256::from_big_endian_slice(&input.nonce) + .into_tw() + .context("Invalid nonce")?; + + let gas_limit = U256::from_big_endian_slice(&input.gas_limit) + .into_tw() + .context("Invalid gas limit")?; + + let max_inclusion_fee_per_gas = + U256::from_big_endian_slice(&input.max_inclusion_fee_per_gas) + .into_tw() + .context("Invalid max inclusion fee per gas")?; + + let max_fee_per_gas = U256::from_big_endian_slice(&input.max_fee_per_gas) + .into_tw() + .context("Invalid max fee per gas")?; + + let access_list = + Self::parse_access_list(&input.access_list).context("Invalid access list")?; + + let authority: Address = input + .eip7702_authority + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("'eip7702Authority' must be provided for `SetCode` transaction")? + .address + // Parse `Address` + .parse() + .into_tw() + .context("Invalid authority address")?; + + let chain_id = U256::from_big_endian_slice(&input.chain_id) + .into_tw() + .context("Invalid chain ID")?; + + let authorization = Authorization { + chain_id, + address: authority, + // `authorization.nonce` must be incremented by 1 over `transaction.nonce`. + nonce: nonce + 1, + }; + let signed_authorization = AuthorizationSigner::sign(&signer_key, authorization)?; + let authorization_list = AuthorizationList::from(vec![signed_authorization]); + + Ok(TransactionEip7702 { + nonce, + max_inclusion_fee_per_gas, + max_fee_per_gas, + gas_limit, + // EIP-7702 transaction calls a smart contract function of the authorized address. + to: Some(signer), + amount: eth_amount, + payload, + access_list, + authorization_list, + } + .into_boxed()) + } + fn user_operation_v0_6_from_proto( input: &Proto::SigningInput, user_op: &Proto::UserOperation, @@ -435,6 +517,29 @@ impl TxBuilder { }) } + #[inline] + fn encode_execute(account_type: SCAccountType, args: ExecuteArgs) -> SigningResult { + match account_type { + SCAccountType::SimpleAccount => Erc4337SimpleAccount::encode_execute(args), + SCAccountType::Biz4337 => BizAccount::encode_execute_4337_op(args), + SCAccountType::Biz => BizAccount::encode_execute(args), + } + .map_err(abi_to_signing_error) + } + + #[inline] + fn encode_execute_batch( + account_type: SCAccountType, + calls: Vec, + ) -> SigningResult { + match account_type { + SCAccountType::SimpleAccount => Erc4337SimpleAccount::encode_execute_batch(calls), + SCAccountType::Biz4337 => BizAccount::encode_execute_4337_ops(calls), + SCAccountType::Biz => BizAccount::encode_execute_batch(calls), + } + .map_err(abi_to_signing_error) + } + fn parse_address(addr: &str) -> SigningResult
{ Context::Address::from_str(addr) .map(Context::Address::into) diff --git a/rust/tw_evm/src/rlp/impls.rs b/rust/tw_evm/src/rlp/impls.rs index 371a3a7b47e..c12afd27079 100644 --- a/rust/tw_evm/src/rlp/impls.rs +++ b/rust/tw_evm/src/rlp/impls.rs @@ -8,6 +8,16 @@ use crate::rlp::RlpEncode; use tw_hash::H256; use tw_number::U256; +impl RlpEncode for u8 { + fn rlp_append(&self, buf: &mut RlpBuffer) { + if *self == 0 { + buf.append_data(&[]) + } else { + buf.append_data(&[*self]) + } + } +} + impl RlpEncode for U256 { fn rlp_append(&self, buf: &mut RlpBuffer) { buf.append_data(&self.to_big_endian_compact()) diff --git a/rust/tw_evm/src/transaction/authorization_list.rs b/rust/tw_evm/src/transaction/authorization_list.rs new file mode 100644 index 00000000000..ea652d8a716 --- /dev/null +++ b/rust/tw_evm/src/transaction/authorization_list.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::Address; +use crate::rlp::buffer::RlpBuffer; +use crate::rlp::RlpEncode; +use tw_number::U256; + +/// Authorization for 7702 txn support. +pub struct Authorization { + /// The chain ID of the authorization. + pub chain_id: U256, + /// The address of the authorization. + pub address: Address, + /// The nonce for the authorization. + pub nonce: U256, +} + +/// Signed authorization for 7702 txn support. +pub struct SignedAuthorization { + pub authorization: Authorization, + /// y-parity of the signature. + pub y_parity: u8, + /// r part of the signature. + pub r: U256, + /// s part of the signature. + pub s: U256, +} + +impl RlpEncode for SignedAuthorization { + fn rlp_append(&self, buf: &mut RlpBuffer) { + buf.begin_list(); + + self.authorization.chain_id.rlp_append(buf); + self.authorization.address.rlp_append(buf); + self.authorization.nonce.rlp_append(buf); + self.y_parity.rlp_append(buf); + self.r.rlp_append(buf); + self.s.rlp_append(buf); + + buf.finalize_list(); + } +} + +/// [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) access list. +#[derive(Default)] +pub struct AuthorizationList(Vec); + +impl From> for AuthorizationList { + fn from(value: Vec) -> Self { + AuthorizationList(value) + } +} + +impl RlpEncode for AuthorizationList { + fn rlp_append(&self, buf: &mut RlpBuffer) { + buf.begin_list(); + + for access in self.0.iter() { + access.rlp_append(buf); + } + + buf.finalize_list(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + use tw_encoding::hex::ToHex; + + #[test] + fn test_encode_signed_authorization() { + let authorization = SignedAuthorization { + authorization: Authorization { + chain_id: U256::from(123_u32), + address: Address::from_str("0x0101010101010101010101010101010101010101").unwrap(), + nonce: U256::from(321_u32), + }, + y_parity: 3, + r: U256::from(222_u32), + s: U256::from(333_u32), + }; + + let mut buf = RlpBuffer::new(); + authorization.rlp_append(&mut buf); + let encoded = buf.finish(); + assert_eq!( + encoded.to_hex(), + "df7b9401010101010101010101010101010101010101018201410381de82014d" + ); + } +} diff --git a/rust/tw_evm/src/transaction/mod.rs b/rust/tw_evm/src/transaction/mod.rs index c3d63b789d8..6dba302df49 100644 --- a/rust/tw_evm/src/transaction/mod.rs +++ b/rust/tw_evm/src/transaction/mod.rs @@ -17,8 +17,10 @@ use tw_memory::Data; use tw_number::U256; pub mod access_list; +pub mod authorization_list; pub mod signature; pub mod transaction_eip1559; +pub mod transaction_eip7702; pub mod transaction_non_typed; pub mod user_operation; pub mod user_operation_v0_7; diff --git a/rust/tw_evm/src/transaction/transaction_eip7702.rs b/rust/tw_evm/src/transaction/transaction_eip7702.rs new file mode 100644 index 00000000000..9833bc056b2 --- /dev/null +++ b/rust/tw_evm/src/transaction/transaction_eip7702.rs @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::Address; +use crate::rlp::list::RlpList; +use crate::transaction::access_list::AccessList; +use crate::transaction::authorization_list::AuthorizationList; +use crate::transaction::signature::{EthSignature, Signature}; +use crate::transaction::{SignedTransaction, TransactionCommon, UnsignedTransaction}; +use tw_coin_entry::error::prelude::*; +use tw_keypair::ecdsa::secp256k1; +use tw_memory::Data; +use tw_number::U256; + +const EIP7702_TX_TYPE: u8 = 0x04; + +/// EIP7702 transaction. +pub struct TransactionEip7702 { + pub nonce: U256, + pub max_inclusion_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub gas_limit: U256, + pub to: Option
, + pub amount: U256, + pub payload: Data, + pub access_list: AccessList, + pub authorization_list: AuthorizationList, +} + +impl TransactionCommon for TransactionEip7702 { + #[inline] + fn payload(&self) -> Data { + self.payload.clone() + } +} + +impl UnsignedTransaction for TransactionEip7702 { + type SignedTransaction = SignedTransactionEip7702; + + #[inline] + fn encode(&self, chain_id: U256) -> Data { + encode_transaction(self, chain_id, None) + } + + #[inline] + fn try_into_signed( + self, + signature: secp256k1::Signature, + chain_id: U256, + ) -> SigningResult { + Ok(SignedTransactionEip7702 { + unsigned: self, + signature: Signature::new(signature), + chain_id, + }) + } +} + +pub struct SignedTransactionEip7702 { + unsigned: TransactionEip7702, + signature: Signature, + chain_id: U256, +} + +impl TransactionCommon for SignedTransactionEip7702 { + #[inline] + fn payload(&self) -> Data { + self.unsigned.payload.clone() + } +} + +impl SignedTransaction for SignedTransactionEip7702 { + type Signature = Signature; + + #[inline] + fn encode(&self) -> Data { + encode_transaction(&self.unsigned, self.chain_id, Some(&self.signature)) + } + + #[inline] + fn signature(&self) -> &Self::Signature { + &self.signature + } +} + +fn encode_transaction( + tx: &TransactionEip7702, + chain_id: U256, + signature: Option<&Signature>, +) -> Data { + let mut list = RlpList::new(); + list.append(&chain_id) + .append(&tx.nonce) + .append(&tx.max_inclusion_fee_per_gas) + .append(&tx.max_fee_per_gas) + .append(&tx.gas_limit) + .append(&tx.to) + .append(&tx.amount) + .append(tx.payload.as_slice()) + .append(&tx.access_list) + .append(&tx.authorization_list); + + if let Some(signature) = signature { + list.append(&signature.v()); + list.append(&signature.r()); + list.append(&signature.s()); + } + + let tx_encoded = list.finish(); + + let mut envelope = Vec::with_capacity(tx_encoded.len() + 1); + envelope.push(EIP7702_TX_TYPE); + envelope.extend_from_slice(tx_encoded.as_slice()); + envelope +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::transaction::authorization_list::{Authorization, SignedAuthorization}; + use std::str::FromStr; + use tw_encoding::hex::{DecodeHex, ToHex}; + use tw_hash::H256; + + #[test] + fn test_encode_transaction_eip7702() { + let authorization = SignedAuthorization { + authorization: Authorization { + chain_id: U256::from(6_u32), + address: Address::from_str("0x0202020202020202020202020202020202020202").unwrap(), + nonce: U256::from(2_u32), + }, + y_parity: 0, + r: U256::from_str("0x42556c4f2a3f4e4e639cca524d1da70e60881417d4643e5382ed110a52719eaf") + .unwrap(), + s: U256::from_str("0x172f591a2a763d0bd6b13d042d8c5eb66e87f129c9dc77ada66b6041012db2b3") + .unwrap(), + }; + + let tx = TransactionEip7702 { + nonce: U256::from(1_u32), + max_inclusion_fee_per_gas: U256::from(2_u32), + max_fee_per_gas: U256::from(3_u32), + gas_limit: U256::from(4_u32), + to: Some(Address::from_str("0x0101010101010101010101010101010101010101").unwrap()), + amount: U256::from(5_u32), + payload: "0x1234".decode_hex().unwrap(), + access_list: AccessList::default(), + authorization_list: AuthorizationList::from(vec![authorization]), + }; + + let r = H256::from("d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47"); + let s = H256::from("786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a"); + let v = 0x00; + let signature = secp256k1::Signature::try_from_parts(r, s, v).unwrap(); + let chain_id = U256::from(56_u32); + let signed_tx = tx.try_into_signed(signature, chain_id).unwrap(); + + assert_eq!( + signed_tx.encode().to_hex(), + "04f8c0380102030494010101010101010101010101010101010101010105821234c0f85cf85a069402020202020202020202020202020202020202020280a042556c4f2a3f4e4e639cca524d1da70e60881417d4643e5382ed110a52719eafa0172f591a2a763d0bd6b13d042d8c5eb66e87f129c9dc77ada66b6041012db2b380a0d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47a0786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a" + ); + } +} diff --git a/rust/tw_evm/src/transaction/user_operation.rs b/rust/tw_evm/src/transaction/user_operation.rs index 3f55658bb57..bf16935fcd1 100644 --- a/rust/tw_evm/src/transaction/user_operation.rs +++ b/rust/tw_evm/src/transaction/user_operation.rs @@ -162,7 +162,8 @@ struct SignedUserOperationSerde { #[cfg(test)] mod tests { use super::*; - use crate::abi::prebuild::erc4337::{Erc4337SimpleAccount, ExecuteArgs}; + use crate::abi::prebuild::erc4337::Erc4337SimpleAccount; + use crate::abi::prebuild::ExecuteArgs; #[test] fn test_encode_user_operation() { diff --git a/rust/tw_evm/tests/barz.rs b/rust/tw_evm/tests/barz.rs index b795d34607b..4b8d1ee782d 100644 --- a/rust/tw_evm/tests/barz.rs +++ b/rust/tw_evm/tests/barz.rs @@ -5,7 +5,7 @@ use std::borrow::Cow; use tw_coin_entry::error::prelude::*; use tw_encoding::hex; -use tw_encoding::hex::DecodeHex; +use tw_encoding::hex::{DecodeHex, ToHex}; use tw_evm::abi::prebuild::erc20::Erc20; use tw_evm::address::Address; use tw_evm::evm_context::StandardEvmContext; @@ -249,7 +249,7 @@ fn test_barz_transfer_account_not_deployed_v0_7() { } #[test] -fn test_barz_transfer_erc7702_eoa() { +fn test_barz_transfer_erc4337_eoa() { let private_key = hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); @@ -306,7 +306,7 @@ fn test_barz_transfer_erc7702_eoa() { } #[test] -fn test_barz_transfer_erc7702_eoa_batch() { +fn test_barz_transfer_erc4337_eoa_batch() { let private_key = hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); @@ -391,3 +391,138 @@ fn test_barz_transfer_erc7702_eoa_batch() { let user_op: serde_json::Value = serde_json::from_slice(&output.encoded).unwrap(); assert_eq!(user_op["callData"], "0x26da7d880000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd000000000000000000000000000000000000000000000000000000000000000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b1266000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000"); } + +#[test] +fn test_barz_transfer_erc7702_eoa() { + let private_key = + hex::decode("0xe148e40f06ee3ba316cdb2571f33486cf879c0ffd2b279ce9f9a88c41ce962e7").unwrap(); + + let erc20_transfer = Proto::mod_Transaction::ERC20Transfer { + to: "0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e".into(), + amount: U256::encode_be_compact(500_000_000_000_000_u64), + }; + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(56_u64), + nonce: U256::encode_be_compact(16_u64), + tx_mode: Proto::TransactionMode::SetCode, + gas_limit: U256::from(100_000_u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + max_inclusion_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + private_key: private_key.into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc20_transfer( + erc20_transfer, + ), + }), + to_address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), + user_operation_mode: Proto::SCAccountType::Biz, + eip7702_authority: Some(Proto::Authority { + address: "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7".into(), + }), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!( + output.error, + SigningErrorType::OK, + "{}", + output.error_message + ); + + assert_eq!( + output.pre_hash.to_hex(), + "8917c03bdd4be922d2163448902eb4f9be4c1fb427641d10f72331e839b00dce" + ); + // Successfully broadcasted transaction: + // https://bscscan.com/tx/0x723c6265ded49520372b4e04d66290fc946f12a48375ee0b1f01165ebe85f0e1 + assert_eq!( + output.encoded.to_hex(), + "04f901ae3810843b9aca00843b9aca00830186a0945132829820b44dc3e8586cec926a16fca0a5608480b8e4b61d27f60000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000c0f85cf85a3894117bc8454756456a0f83dbd130bb94d793d3f3f71180a0f435b376e77a6baff416c53d83992ff53d65846cb1a21686d6743dceee5e7c21a03a9eff368ecc02f1126facd76e8ae5003528ff48ddec3302ad52b06828e992f001a0303774c304ef92095bddf85dba08ea6c7d31d89adf974fe4bcf68c80aee0200aa0669244d097856a4c91433219ab9530650f7012c6118b537d193ca82de05acaac" + ); +} + +#[test] +fn test_barz_transfer_erc7702_eoa_batch() { + let private_key = + hex::decode("0xe148e40f06ee3ba316cdb2571f33486cf879c0ffd2b279ce9f9a88c41ce962e7").unwrap(); + + let mut calls = Vec::with_capacity(2); + + // ERC20 transfer #1. + { + let recipient = Address::from("0x2EF648D7C03412B832726fd4683E2625deA047Ba"); + // 0.0001 TWT + let amount = U256::from(100_000_000_000_000_u64); + let payload = Erc20::transfer(recipient, amount).unwrap(); + + calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + // TWT + address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), + amount: Cow::default(), + payload: payload.into(), + }); + } + + // ERC20 transfer #2. + { + let recipient = Address::from("0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e"); + // 0.0005 TWT + let amount = U256::from(500_000_000_000_000_u64); + let payload = Erc20::transfer(recipient, amount).unwrap(); + + calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + // TWT + address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), + amount: Cow::default(), + payload: payload.into(), + }); + } + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(56_u64), + nonce: U256::encode_be_compact(18_u64), + tx_mode: Proto::TransactionMode::SetCode, + gas_limit: U256::from(100_000_u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + max_inclusion_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + private_key: private_key.into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::batch( + Proto::mod_Transaction::Batch { calls }, + ), + }), + user_operation_mode: Proto::SCAccountType::Biz, + eip7702_authority: Some(Proto::Authority { + address: "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7".into(), + }), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!( + output.error, + SigningErrorType::OK, + "{}", + output.error_message + ); + + assert_eq!( + output.pre_hash.to_hex(), + "00b2d13719df301927ddcbdad5b6bc6214f2007c6408df883c9ea483b45e6f44" + ); + // Successfully broadcasted transaction: + // https://bscscan.com/tx/0x425eb17a8e1dee2fcee8352a772d83cbb069c2e03f2c5d9d00da3b3ef66ce48b + assert_eq!( + output.encoded.to_hex(), + "04f9030f3812843b9aca00843b9aca00830186a0945132829820b44dc3e8586cec926a16fca0a5608480b9024434fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000002ef648d7c03412b832726fd4683e2625dea047ba00000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000c0f85cf85a3894117bc8454756456a0f83dbd130bb94d793d3f3f71380a0073afc661c158a2dccf4183f87e1e4d62b4d406af418cfd69959368ec9bec2a6a064292fd61d4d16b840470a86fc4f7a89413f9126d897f2268eb76a1d887c6d7a01a0e8bcbd96323c9d3e67b74366b2f43299100996d9e8874a6fd87186ac8f580d4ca07c25b4f0619af77fb953e8f0e4372bfbee62616ad419697516108eeb9bcebb28" + ); +} diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index 60d8a222238..f2e51c469a9 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -110,6 +110,11 @@ enum TransactionMode { // EIP4337-compatible UserOperation UserOp = 2; + + // EIP-7702 transaction (with type 0x4); allows to set the code of a contract for an EOA. + // Note that `SetCode` transaction extends `Enveloped` transaction. + // https://eips.ethereum.org/EIPS/eip-7702 + SetCode = 4; } // ERC-4337 structure that describes a transaction to be sent on behalf of a user @@ -174,13 +179,21 @@ message Access { repeated bytes stored_keys = 2; } +// [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) authority. +message Authority { + // Address to be authorized, a smart contract address. + string address = 2; +} + // Smart Contract account type. enum SCAccountType { // ERC-4337 compatible smart contract wallet. // https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/accounts/SimpleAccount.sol SimpleAccount = 0; - // Biz smart contract (Trust Wallet specific). + // Biz smart contract (Trust Wallet specific) through ERC-4337 EntryPoint. Biz4337 = 1; + // Biz smart contract (Trust Wallet specific) directly through ERC-7702. + Biz = 2; } // Input data necessary to create a signed transaction. @@ -234,6 +247,10 @@ message SigningInput { // Smart contract account type. Used in `TransactionMode::UserOp` only. SCAccountType user_operation_mode = 14; + + // A smart contract to which we’re delegating to. + // Currently, we support delegation to only one authority at a time. + Authority eip7702_authority = 15; } // Result containing the signed and encoded transaction. From 73ee1b795804581efbf3e3acce9984f2504505a7 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Mon, 31 Mar 2025 21:18:51 +0700 Subject: [PATCH 14/72] feat(biz): Adjust `Barz.getEncodedHash` according to the latest changes in Biz contract (#4342) * fix(biz): Adjust `Barz.getEncodedHash` according to the latest Biz changes * fix(biz): Adjust Android test --- .../core/app/blockchains/ethereum/TestBarz.kt | 23 +++++++--- include/TrustWalletCore/TWBarz.h | 24 +++++++--- src/Ethereum/Barz.cpp | 46 +++++++++++-------- src/Ethereum/Barz.h | 17 ++++++- src/interface/TWBarz.cpp | 27 +++++++++-- tests/chains/Ethereum/BarzTests.cpp | 29 ++++++++---- 6 files changed, 118 insertions(+), 48 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt index 28272709001..c88bdc9b1f5 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt @@ -364,24 +364,35 @@ class TestBarz { Numeric.toHexString(output.preHash.toByteArray()) ) - val version = "v0.1.0" + val codeAddress = "0x2e234DAe75C793f67A35089C9d99245E1C58470b" + val codeName = "Biz" + val codeVersion = "v1.0.0" val typeHash = "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867" - val domainSeparatorHash = "0x293ce8821a350a49f08b53d14e10112c36c7fbf3b8eb7078497893f3ea477f6b" + val domainSeparatorHash = "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472" val hash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb" - val encodedHash = WCBarz.getEncodedHash(chainIdByteArray, wallet, version, typeHash, domainSeparatorHash, hash) + val encodedHash = WCBarz.getEncodedHash( + chainIdByteArray, + codeAddress, + codeName, + codeVersion, + typeHash, + domainSeparatorHash, + wallet, + hash + ) assertEquals( - "0x59ebb8c4e48c115eeaf2ea7d3a0802754462761c5019df8d2a38effb226191d5", + "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635", Numeric.toHexString(encodedHash) ) val privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8" val signedHash = WCBarz.getSignedHash( - "0x59ebb8c4e48c115eeaf2ea7d3a0802754462761c5019df8d2a38effb226191d5", + "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635", privateKey ) assertEquals( - "0x34a7792a140f52358925a57bca8ea936d70133b285396040ac0507597ed5c70a3148964ba1e0b32b8f59fbd9c098a4ec2b9ae5e5739ce4aeccae0f73279d50da1b", + "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c", Numeric.toHexString(signedHash) ) } diff --git a/include/TrustWalletCore/TWBarz.h b/include/TrustWalletCore/TWBarz.h index 6c3f2585af7..cda18a1a478 100644 --- a/include/TrustWalletCore/TWBarz.h +++ b/include/TrustWalletCore/TWBarz.h @@ -78,15 +78,25 @@ TWString *_Nonnull TWBarzSignAuthorization(TWData* _Nonnull chainId, TWString* _ /// Returns the encoded hash of the user operation /// -/// \param chainId The chainId of the network -/// \param wallet The address of the wallet -/// \param version The version of the wallet -/// \param typeHash The type hash of the transaction -/// \param domainSeparatorHash The domain separator hash of the wallet -/// \param hash The hash of the user operation +/// \param chainId The chainId of the network. +/// \param codeAddress The address of the Biz Smart Contract. +/// \param codeName The name of the Biz Smart Contract. +/// \param codeVersion The version of the Biz Smart Contract. +/// \param typeHash The type hash of the transaction. +/// \param domainSeparatorHash The domain separator hash of the wallet. +/// \param sender The address of the UserOperation sender. +/// \param userOpHash The hash of the user operation. /// \return The encoded hash of the user operation TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWBarzGetEncodedHash(TWData* _Nonnull chainId, TWString* _Nonnull wallet, TWString* _Nonnull version, TWString* _Nonnull typeHash, TWString* _Nonnull domainSeparatorHash, TWString* _Nonnull hash); +TWData *_Nonnull TWBarzGetEncodedHash( + TWData* _Nonnull chainId, + TWString* _Nonnull codeAddress, + TWString* _Nonnull codeName, + TWString* _Nonnull codeVersion, + TWString* _Nonnull typeHash, + TWString* _Nonnull domainSeparatorHash, + TWString* _Nonnull sender, + TWString* _Nonnull userOpHash); /// Signs a message using the private key /// diff --git a/src/Ethereum/Barz.cpp b/src/Ethereum/Barz.cpp index 54af0c466dc..b9b29eedd7e 100644 --- a/src/Ethereum/Barz.cpp +++ b/src/Ethereum/Barz.cpp @@ -270,7 +270,7 @@ std::vector getRSVY(const Data& hash, const std::string& privateKey) { std::string signAuthorization(const Data& chainId, const std::string& contractAddress, const Data& nonce, const std::string& privateKey) { auto authorizationHash = getAuthorizationHash(chainId, contractAddress, nonce); auto rsvy = getRSVY(authorizationHash, privateKey); - + nlohmann::json jsonObj; jsonObj["chainId"] = hexEncoded(chainId); jsonObj["address"] = contractAddress; @@ -278,33 +278,43 @@ std::string signAuthorization(const Data& chainId, const std::string& contractAd jsonObj["yParity"] = hexEncoded(rsvy[3]); jsonObj["r"] = hexEncoded(rsvy[0]); jsonObj["s"] = hexEncoded(rsvy[1]); - + return jsonObj.dump(); } -Data getEncodedHash(const Data& chainId, const std::string& wallet, const std::string& version, const std::string& typeHash, const std::string& domainSeparatorHash, const std::string& hash) { - // Create domain separator: keccak256(abi.encode(BIZ_DOMAIN_SEPARATOR_HASH, block.chainid, wallet, "v0.1.0")) +Data getEncodedHash( + const Data& chainId, + const std::string& codeAddress, + const std::string& codeName, + const std::string& codeVersion, + const std::string& typeHash, + const std::string& domainSeparatorHash, + const std::string& sender, + const std::string& userOpHash) +{ + Data codeAddressBytes32(12, 0); + append(codeAddressBytes32, parse_hex(codeAddress)); + + // Create domain separator: keccak256(abi.encode(BIZ_DOMAIN_SEPARATOR_HASH, "Biz", "v1.0.0", block.chainid, wallet, _addressToBytes32(singleton))) auto domainSeparator = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { std::make_shared(parse_hex(domainSeparatorHash)), + std::make_shared(Hash::keccak256(codeName)), + std::make_shared(Hash::keccak256(codeVersion)), std::make_shared(chainId), - std::make_shared(wallet), - std::make_shared(version) + std::make_shared(sender), + std::make_shared(codeAddressBytes32), }); if (!domainSeparator.has_value()) { return {}; } Data domainSeparatorEncodedHash = Hash::keccak256(domainSeparator.value()); - - // Create message hash: keccak256(abi.encode(typeHash, keccak256(abi.encode(hash)))) - Data encodedHash; - Ethereum::ABI::ValueEncoder::encodeBytes(parse_hex(hash), encodedHash); - Data innerHash = Hash::keccak256(encodedHash); - - Data messageData; - Ethereum::ABI::ValueEncoder::encodeBytes(parse_hex(typeHash), messageData); - Ethereum::ABI::ValueEncoder::encodeBytes(innerHash, messageData); - Data messageHash = Hash::keccak256(messageData); - + + // Create message hash: keccak256(abi.encode(typeHash, hash)) + Data messageToHash; + append(messageToHash, parse_hex(typeHash)); + append(messageToHash, parse_hex(userOpHash)); + Data messageHash = Hash::keccak256(messageToHash); + // Final hash: keccak256(abi.encodePacked("\x19\x01", domainSeparator, messageHash)) Data encoded; append(encoded, parse_hex("0x1901")); @@ -315,7 +325,7 @@ Data getEncodedHash(const Data& chainId, const std::string& wallet, const std::s Data getSignedHash(const std::string& hash, const std::string& privateKey) { auto rsvy = getRSVY(parse_hex(hash), privateKey); - + Data result; append(result, rsvy[0]); append(result, rsvy[1]); diff --git a/src/Ethereum/Barz.h b/src/Ethereum/Barz.h index d6c2907d6dd..445cbd529aa 100644 --- a/src/Ethereum/Barz.h +++ b/src/Ethereum/Barz.h @@ -17,7 +17,20 @@ Data getFormattedSignature(const Data& signature, const Data challenge, const Da Data getPrefixedMsgHash(const Data msgHash, const std::string& barzAddress, const uint32_t chainId); Data getDiamondCutCode(const Proto::DiamondCutInput& input); // action should be one of 0, 1, 2. 0 = Add, 1 = Remove, 2 = Replace Data getAuthorizationHash(const Data& chainId, const std::string& contractAddress, const Data& nonce); -std::string signAuthorization(const Data& chainId, const std::string& contractAddress, const Data& nonce, const std::string& privateKey); -Data getEncodedHash(const Data& chainId, const std::string& wallet, const std::string& version, const std::string& typeHash, const std::string& domainSeparatorHash, const std::string& hash); +std::string signAuthorization( + const Data& chainId, + const std::string& contractAddress, + const Data& nonce, + const std::string& privateKey); +Data getEncodedHash( + const Data& chainId, + const std::string& codeAddress, + const std::string& codeName, + const std::string& codeVersion, + const std::string& typeHash, + const std::string& domainSeparatorHash, + const std::string& sender, + const std::string& userOpHash); Data getSignedHash(const std::string& hash, const std::string& privateKey); + } diff --git a/src/interface/TWBarz.cpp b/src/interface/TWBarz.cpp index 0c6719e84eb..1220d65beab 100644 --- a/src/interface/TWBarz.cpp +++ b/src/interface/TWBarz.cpp @@ -76,14 +76,31 @@ TWString *_Nonnull TWBarzSignAuthorization(TWData* _Nonnull chainId, TWString* _ return TWStringCreateWithUTF8Bytes(signedAuthorization.c_str()); } -TWData *_Nonnull TWBarzGetEncodedHash(TWData* _Nonnull chainId, TWString* _Nonnull wallet, TWString* _Nonnull version, TWString* _Nonnull typeHash, TWString* _Nonnull domainSeparatorHash, TWString* _Nonnull hash) { +TWData *_Nonnull TWBarzGetEncodedHash( + TWData* _Nonnull chainId, + TWString* _Nonnull codeAddress, + TWString* _Nonnull codeName, + TWString* _Nonnull codeVersion, + TWString* _Nonnull typeHash, + TWString* _Nonnull domainSeparatorHash, + TWString* _Nonnull sender, + TWString* _Nonnull userOpHash) { const auto& chainIdData = *reinterpret_cast(chainId); - const auto& walletStr = *reinterpret_cast(wallet); - const auto& versionStr = *reinterpret_cast(version); + const auto& codeAddressStr = *reinterpret_cast(codeAddress); + const auto& codeNameStr = *reinterpret_cast(codeName); + const auto& codeVersionStr = *reinterpret_cast(codeVersion); const auto& typeHashStr = *reinterpret_cast(typeHash); const auto& domainSeparatorHashStr = *reinterpret_cast(domainSeparatorHash); - const auto& hashStr = *reinterpret_cast(hash); - const auto encodedHash = TW::Barz::getEncodedHash(chainIdData, walletStr, versionStr, typeHashStr, domainSeparatorHashStr, hashStr); + const auto& senderStr = *reinterpret_cast(sender); + const auto& userOpHashStr = *reinterpret_cast(userOpHash); + const auto encodedHash = TW::Barz::getEncodedHash( + chainIdData, + codeAddressStr, + codeNameStr, + codeVersionStr, typeHashStr, + domainSeparatorHashStr, + senderStr, + userOpHashStr); return TWDataCreateWithData(&encodedHash); } diff --git a/tests/chains/Ethereum/BarzTests.cpp b/tests/chains/Ethereum/BarzTests.cpp index 4eb2adae5fb..bb3fb750c89 100644 --- a/tests/chains/Ethereum/BarzTests.cpp +++ b/tests/chains/Ethereum/BarzTests.cpp @@ -446,24 +446,33 @@ TEST(Barz, SignAuthorization) { TEST(Barz, GetEncodedHash) { { const auto chainId = store(uint256_t(31337), 32); - std::cout << "chainId: " << hexEncoded(chainId) << std::endl; - const auto wallet = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"; - const auto version = "v0.1.0"; + const auto codeAddress = "0x2e234DAe75C793f67A35089C9d99245E1C58470b"; + const auto codeName = "Biz"; + const auto codeVersion = "v1.0.0"; const auto typeHash = "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867"; - const auto domainSeparatorHash = "0x293ce8821a350a49f08b53d14e10112c36c7fbf3b8eb7078497893f3ea477f6b"; - const auto hash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb"; - - const auto& encodedHash = Barz::getEncodedHash(chainId, wallet, version, typeHash, domainSeparatorHash, hash); - ASSERT_EQ(hexEncoded(encodedHash), "0x59ebb8c4e48c115eeaf2ea7d3a0802754462761c5019df8d2a38effb226191d5"); + const auto domainSeparatorHash = "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472"; + const auto sender = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"; + const auto userOpHash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb"; + + const auto& encodedHash = Barz::getEncodedHash( + chainId, + codeAddress, + codeName, + codeVersion, + typeHash, + domainSeparatorHash, + sender, + userOpHash); + ASSERT_EQ(hexEncoded(encodedHash), "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635"); } } TEST(Barz, GetSignedHash) { { - const auto hash = "0x59ebb8c4e48c115eeaf2ea7d3a0802754462761c5019df8d2a38effb226191d5"; + const auto hash = "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635"; const auto privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; const auto signedHash = Barz::getSignedHash(hash, privateKey); - ASSERT_EQ(hexEncoded(signedHash), "0x34a7792a140f52358925a57bca8ea936d70133b285396040ac0507597ed5c70a3148964ba1e0b32b8f59fbd9c098a4ec2b9ae5e5739ce4aeccae0f73279d50da1b"); + ASSERT_EQ(hexEncoded(signedHash), "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c"); } } From ea38450d2b9580596e85660cdd89b81aee3a5976 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Tue, 1 Apr 2025 05:52:21 +0700 Subject: [PATCH 15/72] chore(dependencies): Update `gtest` to 1.16.0 (#4343) --- tests/CMakeLists.txt | 2 +- tools/dependencies-version | 2 +- tools/download-dependencies | 4 ++-- tools/install-dependencies | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 288f2842ba8..87ec8f86384 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,7 +10,7 @@ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Add googletest directly to our build. This defines # the gtest and gtest_main targets. -add_subdirectory(${CMAKE_SOURCE_DIR}/build/local/src/gtest/googletest-release-1.11.0 +add_subdirectory(${CMAKE_SOURCE_DIR}/build/local/src/gtest/googletest-1.16.0 ${CMAKE_CURRENT_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL) diff --git a/tools/dependencies-version b/tools/dependencies-version index 90a9a778a6e..66d095d4f1a 100755 --- a/tools/dependencies-version +++ b/tools/dependencies-version @@ -1,6 +1,6 @@ #!/bin/bash -export GTEST_VERSION=1.11.0 +export GTEST_VERSION=1.16.0 export CHECK_VERSION=0.15.2 export JSON_VERSION=3.11.3 export PROTOBUF_VERSION=3.19.2 diff --git a/tools/download-dependencies b/tools/download-dependencies index bd138ca5904..2d674ab60c1 100755 --- a/tools/download-dependencies +++ b/tools/download-dependencies @@ -15,9 +15,9 @@ function download_gtest() { mkdir -p "$GTEST_DIR" cd "$GTEST_DIR" if [ ! -f release-$GTEST_VERSION.tar.gz ]; then - curl -fSsOL https://github.com/google/googletest/archive/release-$GTEST_VERSION.tar.gz + curl -fSsOL https://github.com/google/googletest/releases/download/v$GTEST_VERSION/googletest-$GTEST_VERSION.tar.gz fi - tar xzf release-$GTEST_VERSION.tar.gz + tar xzf googletest-$GTEST_VERSION.tar.gz } function download_libcheck() { diff --git a/tools/install-dependencies b/tools/install-dependencies index 3ad3d77a54d..acdcf8e56eb 100755 --- a/tools/install-dependencies +++ b/tools/install-dependencies @@ -31,7 +31,7 @@ function download_dependencies() { function build_gtest() { # Build gtest GTEST_DIR="$ROOT/build/local/src/gtest" - cd ${GTEST_DIR}/googletest-release-$GTEST_VERSION + cd ${GTEST_DIR}/googletest-$GTEST_VERSION $CMAKE -DCMAKE_INSTALL_PREFIX:PATH=$PREFIX -H. $MAKE -j4 $MAKE install From 4b63b6319d14bcf895c0b91265c89628293cb37c Mon Sep 17 00:00:00 2001 From: gupnik Date: Tue, 1 Apr 2025 13:18:54 +0530 Subject: [PATCH 16/72] [ETH]: Makes factory and paymaster optional while serialising UserOpV07 (#4345) --- .../src/transaction/user_operation_v0_7.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/rust/tw_evm/src/transaction/user_operation_v0_7.rs b/rust/tw_evm/src/transaction/user_operation_v0_7.rs index ccf1b08369f..bfe9f1a0183 100644 --- a/rust/tw_evm/src/transaction/user_operation_v0_7.rs +++ b/rust/tw_evm/src/transaction/user_operation_v0_7.rs @@ -188,11 +188,7 @@ impl SignedTransaction for SignedUserOperationV0_7 { let tx = SignedUserOperationV0_7Serde { sender: self.unsigned.sender.to_string(), nonce: self.unsigned.nonce.to_string(), - factory: self - .unsigned - .factory - .map(|addr| addr.to_string()) - .unwrap_or_default(), + factory: self.unsigned.factory.map(|addr| addr.to_string()), factory_data: hex::encode(&self.unsigned.factory_data, prefix), call_data: hex::encode(&self.unsigned.call_data, prefix), call_data_gas_limit: self.unsigned.call_data_gas_limit.to_string(), @@ -200,11 +196,7 @@ impl SignedTransaction for SignedUserOperationV0_7 { pre_verification_gas: self.unsigned.pre_verification_gas.to_string(), max_fee_per_gas: self.unsigned.max_fee_per_gas.to_string(), max_priority_fee_per_gas: self.unsigned.max_priority_fee_per_gas.to_string(), - paymaster: self - .unsigned - .paymaster - .map(|addr| addr.to_string()) - .unwrap_or_default(), + paymaster: self.unsigned.paymaster.map(|addr| addr.to_string()), paymaster_verification_gas_limit: self .unsigned .paymaster_verification_gas_limit @@ -229,7 +221,7 @@ impl SignedTransaction for SignedUserOperationV0_7 { struct SignedUserOperationV0_7Serde { sender: String, nonce: String, - factory: String, + factory: Option, factory_data: String, call_data: String, call_data_gas_limit: String, @@ -237,7 +229,7 @@ struct SignedUserOperationV0_7Serde { pre_verification_gas: String, max_fee_per_gas: String, max_priority_fee_per_gas: String, - paymaster: String, + paymaster: Option, paymaster_verification_gas_limit: String, paymaster_post_op_gas_limit: String, paymaster_data: String, From 2bf1de31953db78ca9cd01e370939c13c9ae12db Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Thu, 3 Apr 2025 20:20:27 +0700 Subject: [PATCH 17/72] feat(biz): Allow to call `Biz.execute` when EOA is delegated already (#4351) * fix(biz): Allow to call `Biz.execute` when EOA is delegated already * feature(biz): Adopt C++ tests * feat(biz): Add and fix android tests * feat(biz): Fix ios tests * chore(aa): Rename `Execute` and `Batch` to `AAExecute` and `AABatch` correspondingly * chore(scw): Rename `AABatch` and `AAExecute` to `SCWalletBatch` and `SCWalletExecute` --- .../core/app/blockchains/ethereum/TestBarz.kt | 72 +++++- rust/tw_evm/src/modules/tx_builder.rs | 243 ++++++++++++------ rust/tw_evm/tests/barz.rs | 231 ++++++++++++++--- src/proto/Ethereum.proto | 30 ++- swift/Tests/BarzTests.swift | 24 +- tests/chains/Ethereum/BarzTests.cpp | 6 +- 6 files changed, 456 insertions(+), 150 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt index c88bdc9b1f5..c1c0c80f905 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt @@ -117,8 +117,12 @@ class TestBarz { }.build() transaction = Ethereum.Transaction.newBuilder().apply { - transfer = Ethereum.Transaction.Transfer.newBuilder().apply { - amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { + transaction = Ethereum.Transaction.newBuilder().apply { + transfer = Ethereum.Transaction.Transfer.newBuilder().apply { + amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + }.build() + }.build() }.build() }.build() } @@ -159,8 +163,12 @@ class TestBarz { }.build() transaction = Ethereum.Transaction.newBuilder().apply { - transfer = Ethereum.Transaction.Transfer.newBuilder().apply { - amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { + transaction = Ethereum.Transaction.newBuilder().apply { + transfer = Ethereum.Transaction.Transfer.newBuilder().apply { + amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + }.build() + }.build() }.build() }.build() } @@ -204,14 +212,14 @@ class TestBarz { }.build() transaction = Ethereum.Transaction.newBuilder().apply { - batch = Ethereum.Transaction.Batch.newBuilder().apply { + scwBatch = Ethereum.Transaction.SCWalletBatch.newBuilder().apply { addAllCalls(listOf( - Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply { + Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266" amount = ByteString.copyFrom("0x00".toHexByteArray()) payload = ByteString.copyFrom(approveCall) }.build(), - Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply { + Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266" amount = ByteString.copyFrom("0x00".toHexByteArray()) payload = ByteString.copyFrom(transferCall) @@ -254,15 +262,16 @@ class TestBarz { maxInclusionFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) transaction = Ethereum.Transaction.newBuilder().apply { - batch = Ethereum.Transaction.Batch.newBuilder().apply { + scwBatch = Ethereum.Transaction.SCWalletBatch.newBuilder().apply { + walletType = Ethereum.SCWalletType.Biz addAllCalls(listOf( - Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply { + Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { // TWT address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" amount = ByteString.copyFrom("0x00".toHexByteArray()) payload = ByteString.copyFrom(transferPayload1) }.build(), - Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply { + Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { // TWT address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" amount = ByteString.copyFrom("0x00".toHexByteArray()) @@ -272,7 +281,6 @@ class TestBarz { }.build() }.build() - userOperationMode = Ethereum.SCAccountType.Biz eip7702Authority = Ethereum.Authority.newBuilder().apply { address = "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7" }.build() @@ -284,6 +292,42 @@ class TestBarz { assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x04f9030f3812843b9aca00843b9aca00830186a0945132829820b44dc3e8586cec926a16fca0a5608480b9024434fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000002ef648d7c03412b832726fd4683e2625dea047ba00000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000c0f85cf85a3894117bc8454756456a0f83dbd130bb94d793d3f3f71380a0073afc661c158a2dccf4183f87e1e4d62b4d406af418cfd69959368ec9bec2a6a064292fd61d4d16b840470a86fc4f7a89413f9126d897f2268eb76a1d887c6d7a01a0e8bcbd96323c9d3e67b74366b2f43299100996d9e8874a6fd87186ac8f580d4ca07c25b4f0619af77fb953e8f0e4372bfbee62616ad419697516108eeb9bcebb28") } + // https://bscscan.com/tx/0x6f8b2c8d50e8bb543d7124703b75d9e495832116a1a61afabf40b9b0ac43c980 + @Test + fun testSignEnvelopedBiz() { + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("0xe762e91cc4889a9fce79b2d2ffc079f86c48331f57b2cd16a33bee060fe448e1".toHexByteArray()).data()) + chainId = ByteString.copyFrom("0x38".toHexByteArray()) + nonce = ByteString.copyFrom("0x02".toHexByteArray()) + txMode = TransactionMode.Enveloped + + gasLimit = ByteString.copyFrom("0x186a0".toHexByteArray()) + maxFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + maxInclusionFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + + transaction = Ethereum.Transaction.newBuilder().apply { + scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { + walletType = Ethereum.SCWalletType.Biz + transaction = Ethereum.Transaction.newBuilder().apply { + erc20Transfer = Ethereum.Transaction.ERC20Transfer.newBuilder().apply { + to = "0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e" + amount = ByteString.copyFrom("0xb5e620f48000".toHexByteArray()) + }.build() + }.build() + }.build() + }.build() + + // TWT token. + toAddress = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x60260356568ae70838bd80085b971e1e4ebe42046688fd8511a268986e522121") + assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x02f901503802843b9aca00843b9aca00830186a0946e860086bba8fdeafb553815af0f09a854cc887a80b8e4b61d27f60000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000000b5e620f4800000000000000000000000000000000000000000000000000000000000c080a0fb45762a262f4c32090576e9de087482d25cd00b6ea2522eb7d5a40f435acdbaa0151dbd48a4f4bf06080313775fe32ececd68869d721518a92bf292e4a84322f9") + } + @Test fun testAuthorizationHash() { val chainId = "0x01".toHexByteArray() @@ -351,7 +395,11 @@ class TestBarz { toAddress = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9" transaction = Ethereum.Transaction.newBuilder().apply { - this.transfer = transfer + scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { + transaction = Ethereum.Transaction.newBuilder().apply { + this.transfer = transfer + }.build() + }.build() }.build() userOperationV07 = userOpV07 diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs index d246fc7d2b2..ae884b653a4 100644 --- a/rust/tw_evm/src/modules/tx_builder.rs +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -31,9 +31,31 @@ use tw_proto::Common::Proto::SigningError as CommonError; use tw_proto::Ethereum::Proto; use Proto::mod_SigningInput::OneOfuser_operation_oneof as UserOp; use Proto::mod_Transaction::OneOftransaction_oneof as Tx; -use Proto::SCAccountType; +use Proto::SCWalletType; use Proto::TransactionMode as TxMode; +pub struct TransactionParts { + pub eth_amount: U256, + pub data: Data, + pub to: Option
, +} + +impl TryFrom for ExecuteArgs { + type Error = SigningError; + + fn try_from(value: TransactionParts) -> Result { + let to = value + .to + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("Error creating a Smart Contract Wallet execute call - no destination address specified")?; + Ok(ExecuteArgs { + to, + value: value.eth_amount, + data: value.data, + }) + } +} + pub struct TxBuilder { _phantom: PhantomData, } @@ -47,16 +69,43 @@ impl TxBuilder { .context("No transaction specified"); }; - let (eth_amount, payload, to) = match transaction.transaction_oneof { + let TransactionParts { + eth_amount, + data, + to, + } = Self::handle_transaction_type(input, &transaction.transaction_oneof)?; + + let tx = match input.tx_mode { + TxMode::Legacy => { + Self::transaction_non_typed_from_proto(input, eth_amount, data, to)?.into_boxed() + }, + TxMode::Enveloped => { + Self::transaction_eip1559_from_proto(input, eth_amount, data, to)?.into_boxed() + }, + TxMode::UserOp => Self::user_operation_from_proto(input, data)?, + TxMode::SetCode => Self::transaction_eip7702_from_proto(input, eth_amount, data, to)?, + }; + Ok(tx) + } + + fn handle_transaction_type( + input: &Proto::SigningInput, + transaction: &Tx, + ) -> SigningResult { + match transaction { Tx::transfer(ref transfer) => { - let amount = U256::from_big_endian_slice(&transfer.amount) + let eth_amount = U256::from_big_endian_slice(&transfer.amount) .into_tw() .context("Invalid amount")?; let to_address = Self::parse_address(&input.to_address) .context("Invalid destination address")?; - (amount, transfer.data.to_vec(), Some(to_address)) + Ok(TransactionParts { + eth_amount, + data: transfer.data.to_vec(), + to: Some(to_address), + }) }, Tx::erc20_transfer(ref erc20_transfer) => { let token_to_address = Self::parse_address(&erc20_transfer.to) @@ -69,9 +118,14 @@ impl TxBuilder { let contract_address = Self::parse_address(&input.to_address).context("Invalid Contract address")?; - let payload = Erc20::transfer(token_to_address, token_amount) + let data = Erc20::transfer(token_to_address, token_amount) .map_err(abi_to_signing_error)?; - (U256::zero(), payload, Some(contract_address)) + + Ok(TransactionParts { + eth_amount: U256::zero(), + data, + to: Some(contract_address), + }) }, Tx::erc20_approve(ref erc20_approve) => { let spender = Self::parse_address(&erc20_approve.spender) @@ -84,9 +138,13 @@ impl TxBuilder { let contract_address = Self::parse_address(&input.to_address).context("Invalid Contract address")?; - let payload = - Erc20::approve(spender, token_amount).map_err(abi_to_signing_error)?; - (U256::zero(), payload, Some(contract_address)) + let data = Erc20::approve(spender, token_amount).map_err(abi_to_signing_error)?; + + Ok(TransactionParts { + eth_amount: U256::zero(), + data, + to: Some(contract_address), + }) }, Tx::erc721_transfer(ref erc721_transfer) => { let from = @@ -101,9 +159,14 @@ impl TxBuilder { let contract_address = Self::parse_address(&input.to_address).context("Invalid Contract address")?; - let payload = Erc721::encode_transfer_from(from, token_to_address, token_id) + let data = Erc721::encode_transfer_from(from, token_to_address, token_id) .map_err(abi_to_signing_error)?; - (U256::zero(), payload, Some(contract_address)) + + Ok(TransactionParts { + eth_amount: U256::zero(), + data, + to: Some(contract_address), + }) }, Tx::erc1155_transfer(ref erc1155_transfer) => { let from = Self::parse_address(&erc1155_transfer.from) @@ -124,83 +187,70 @@ impl TxBuilder { let contract_address = Self::parse_address(&input.to_address).context("Invalid Contract address")?; - let payload = Erc1155::encode_safe_transfer_from(from, to, token_id, value, data) + let data = Erc1155::encode_safe_transfer_from(from, to, token_id, value, data) .map_err(abi_to_signing_error)?; - (U256::zero(), payload, Some(contract_address)) + + Ok(TransactionParts { + eth_amount: U256::zero(), + data, + to: Some(contract_address), + }) }, Tx::contract_generic(ref contract_generic) => { - let amount = U256::from_big_endian_slice(&contract_generic.amount) + let eth_amount = U256::from_big_endian_slice(&contract_generic.amount) .into_tw() .context("Invalid amount")?; - let payload = contract_generic.data.to_vec(); + let data = contract_generic.data.to_vec(); // `to_address` can be omitted for the generic contract call. // For example, on creating a new smart contract. - let to_address = Self::parse_address_optional(&input.to_address) + let to = Self::parse_address_optional(&input.to_address) .context("Invalid destination address")?; - (amount, payload, to_address) + Ok(TransactionParts { + eth_amount, + data, + to, + }) }, - Tx::batch(ref batch) => { + Tx::scw_batch(ref batch) => { // Payload should match ERC4337 standard. let calls: Vec<_> = batch .calls .iter() .map(Self::erc4337_execute_call_from_proto) .collect::, _>>()?; - let execute_payload = Self::encode_execute_batch(input.user_operation_mode, calls)?; - - return match input.tx_mode { - TxMode::UserOp => Self::user_operation_from_proto(input, execute_payload), - TxMode::SetCode => { - Self::transaction_eip7702_from_proto(input, U256::zero(), execute_payload) - }, - _ => SigningError::err(SigningErrorType::Error_invalid_params).context( - "Transaction batch can be used in `UserOp` or `SetCode` modes only", - ), - }; - }, - Tx::None => { - return SigningError::err(SigningErrorType::Error_invalid_params) - .context("No transaction specified") - }, - }; + let execute_payload = Self::encode_execute_batch(batch.wallet_type, calls)?; + let to = Self::sc_tx_destination(input, batch.wallet_type)?; - let tx = match input.tx_mode { - TxMode::Legacy => { - Self::transaction_non_typed_from_proto(input, eth_amount, payload, to)?.into_boxed() - }, - TxMode::Enveloped => { - Self::transaction_eip1559_from_proto(input, eth_amount, payload, to)?.into_boxed() - }, - TxMode::UserOp => { - let to = to - .or_tw_err(SigningErrorType::Error_invalid_address) - .context("No contract/destination address specified")?; - let args = ExecuteArgs { + Ok(TransactionParts { + eth_amount: U256::zero(), + data: execute_payload, to, - value: eth_amount, - data: payload, - }; - - let user_op_payload = Self::encode_execute(input.user_operation_mode, args)?; - Self::user_operation_from_proto(input, user_op_payload)? + }) }, - TxMode::SetCode => { - let to = to - .or_tw_err(SigningErrorType::Error_invalid_address) - .context("No contract/destination address specified")?; - let args = ExecuteArgs { + Tx::scw_execute(ref execute) => { + let inner_transaction = execute + .transaction + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("`Execute.transaction` must be provided")?; + + let execute_args = + Self::handle_transaction_type(input, &inner_transaction.transaction_oneof)? + .try_into()?; + let execute_call_payload = Self::encode_execute(execute.wallet_type, execute_args)?; + + let to = Self::sc_tx_destination(input, execute.wallet_type)?; + Ok(TransactionParts { + eth_amount: U256::zero(), + data: execute_call_payload, to, - value: eth_amount, - data: payload, - }; - - let execute_payload = Self::encode_execute(input.user_operation_mode, args)?; - Self::transaction_eip7702_from_proto(input, eth_amount, execute_payload)? + }) }, - }; - Ok(tx) + Tx::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("No transaction specified"), + } } #[inline] @@ -224,7 +274,7 @@ impl TxBuilder { #[inline] fn erc4337_execute_call_from_proto( - call: &Proto::mod_Transaction::mod_Batch::BatchedCall, + call: &Proto::mod_Transaction::mod_SCWalletBatch::BatchedCall, ) -> SigningResult { let to = Self::parse_address(&call.address) .context("Invalid 'BatchedCall' destination address")?; @@ -311,11 +361,17 @@ impl TxBuilder { input: &Proto::SigningInput, eth_amount: U256, payload: Data, + to_address: Option
, ) -> SigningResult> { let signer_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref()) .into_tw() .context("Sender's private key must be provided to generate an EIP-7702 transaction")?; let signer = Address::with_secp256k1_pubkey(&signer_key.public()); + if to_address != Some(signer) { + return SigningError::err(SigningErrorType::Error_invalid_params).context( + "Unexpected 'accountAddress'. Expected to be the same as the signer address", + ); + } let nonce = U256::from_big_endian_slice(&input.nonce) .into_tw() @@ -517,25 +573,50 @@ impl TxBuilder { }) } + /// Returns a destination address of the Smart Contract Wallet transaction. + /// Returns: + /// - `Ok(Some(address))` when generating a transaction calling a function of the account itself through EIP-7702 authorized code. + /// - `Ok(None)` when generating a UserOperation. + /// - `Err(e)` when the account type is not supported for the given transaction mode. + #[inline] + fn sc_tx_destination( + input: &Proto::SigningInput, + wallet_type: SCWalletType, + ) -> SigningResult> { + match (input.tx_mode, wallet_type) { + // Destination address is not used when generating UserOperation. + (TxMode::UserOp, SCWalletType::SimpleAccount | SCWalletType::Biz4337) => Ok(None), + (TxMode::UserOp, _) => SigningError::err(SigningErrorType::Error_invalid_params) + .context("Biz account cannot be used in UserOperation flow"), + (TxMode::Legacy | TxMode::Enveloped | TxMode::SetCode, SCWalletType::Biz) => { + Self::signer_address(input).map(Some) + }, + (TxMode::Legacy | TxMode::Enveloped | TxMode::SetCode, _) => SigningError::err( + SigningErrorType::Error_invalid_params, + ) + .context("Biz account can only be used in Legacy/Enveloped/SetCode transactions flow"), + } + } + #[inline] - fn encode_execute(account_type: SCAccountType, args: ExecuteArgs) -> SigningResult { - match account_type { - SCAccountType::SimpleAccount => Erc4337SimpleAccount::encode_execute(args), - SCAccountType::Biz4337 => BizAccount::encode_execute_4337_op(args), - SCAccountType::Biz => BizAccount::encode_execute(args), + fn encode_execute(wallet_type: SCWalletType, args: ExecuteArgs) -> SigningResult { + match wallet_type { + SCWalletType::SimpleAccount => Erc4337SimpleAccount::encode_execute(args), + SCWalletType::Biz4337 => BizAccount::encode_execute_4337_op(args), + SCWalletType::Biz => BizAccount::encode_execute(args), } .map_err(abi_to_signing_error) } #[inline] fn encode_execute_batch( - account_type: SCAccountType, + wallet_type: SCWalletType, calls: Vec, ) -> SigningResult { - match account_type { - SCAccountType::SimpleAccount => Erc4337SimpleAccount::encode_execute_batch(calls), - SCAccountType::Biz4337 => BizAccount::encode_execute_4337_ops(calls), - SCAccountType::Biz => BizAccount::encode_execute_batch(calls), + match wallet_type { + SCWalletType::SimpleAccount => Erc4337SimpleAccount::encode_execute_batch(calls), + SCWalletType::Biz4337 => BizAccount::encode_execute_4337_ops(calls), + SCWalletType::Biz => BizAccount::encode_execute_batch(calls), } .map_err(abi_to_signing_error) } @@ -575,4 +656,12 @@ impl TxBuilder { } Ok(access) } + + fn signer_address(input: &Proto::SigningInput) -> SigningResult
{ + let signer_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref()) + .into_tw() + .context("Sender's private key must be provided to generate an EIP-7702 transaction")?; + let signer = Address::with_secp256k1_pubkey(&signer_key.public()); + Ok(signer) + } } diff --git a/rust/tw_evm/tests/barz.rs b/rust/tw_evm/tests/barz.rs index 4b8d1ee782d..e40cbe263a8 100644 --- a/rust/tw_evm/tests/barz.rs +++ b/rust/tw_evm/tests/barz.rs @@ -13,6 +13,20 @@ use tw_evm::modules::signer::Signer; use tw_misc::traits::ToBytesVec; use tw_number::U256; use tw_proto::Ethereum::Proto; +use tw_proto::Ethereum::Proto::mod_Transaction::OneOftransaction_oneof as TransactionType; + +fn execute(tx: TransactionType, wallet_type: Proto::SCWalletType) -> Proto::Transaction { + Proto::Transaction { + transaction_oneof: TransactionType::scw_execute(Box::new( + Proto::mod_Transaction::SCWalletExecute { + transaction: Some(Box::new(Proto::Transaction { + transaction_oneof: tx, + })), + wallet_type, + }, + )), + } +} // https://testnet.bscscan.com/tx/0x43fc13dfdf06bbb09da8ce070953753764f1e43782d0c8b621946d8b45749419 #[test] @@ -42,9 +56,10 @@ fn test_barz_transfer_account_deployed() { max_inclusion_fee_per_gas: U256::encode_be_compact(0x1_a339_c9e9), to_address: "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9".into(), private_key: private_key.into(), - transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), - }), + transaction: Some(execute( + TransactionType::transfer(transfer), + Proto::SCWalletType::SimpleAccount, + )), user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation( user_op, ), @@ -97,9 +112,10 @@ fn test_barz_transfer_account_not_deployed() { max_inclusion_fee_per_gas: U256::encode_be_compact(0x1_a339_c9e9), to_address: "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9".into(), private_key: private_key.into(), - transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), - }), + transaction: Some(execute( + TransactionType::transfer(transfer), + Proto::SCWalletType::SimpleAccount, + )), user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation( user_op, ), @@ -136,7 +152,7 @@ fn test_barz_batched_account_deployed() { let amount = U256::from(0x8AC7_2304_89E8_0000_u64); let payload = Erc20::approve(spender, amount).unwrap(); - calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + calls.push(Proto::mod_Transaction::mod_SCWalletBatch::BatchedCall { address: contract_address.into(), amount: Cow::default(), payload: payload.into(), @@ -149,7 +165,7 @@ fn test_barz_batched_account_deployed() { let amount = U256::from(0x8AC7_2304_89E8_0000_u64); let payload = Erc20::transfer(recipient, amount).unwrap(); - calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + calls.push(Proto::mod_Transaction::mod_SCWalletBatch::BatchedCall { address: contract_address.into(), amount: Cow::default(), payload: payload.into(), @@ -175,9 +191,10 @@ fn test_barz_batched_account_deployed() { to_address: contract_address.into(), private_key: private_key.into(), transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::batch( - Proto::mod_Transaction::Batch { calls }, - ), + transaction_oneof: TransactionType::scw_batch(Proto::mod_Transaction::SCWalletBatch { + calls, + wallet_type: Proto::SCWalletType::SimpleAccount, + }), }), user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation( user_op, @@ -230,9 +247,10 @@ fn test_barz_transfer_account_not_deployed_v0_7() { max_inclusion_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), to_address: "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9".into(), private_key: private_key.into(), - transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), - }), + transaction: Some(execute( + TransactionType::transfer(transfer), + Proto::SCWalletType::SimpleAccount, + )), user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation_v0_7(user_op), ..Proto::SigningInput::default() @@ -249,7 +267,7 @@ fn test_barz_transfer_account_not_deployed_v0_7() { } #[test] -fn test_barz_transfer_erc4337_eoa() { +fn test_biz4337_transfer() { let private_key = hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); @@ -281,14 +299,12 @@ fn test_barz_transfer_erc4337_eoa() { // USDT token. to_address: "0xdac17f958d2ee523a2206206994597c13d831ec7".into(), private_key: private_key.into(), - transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc20_approve( - approve, - ), - }), + transaction: Some(execute( + TransactionType::erc20_approve(approve), + Proto::SCWalletType::Biz4337, + )), user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation_v0_7(user_op), - user_operation_mode: Proto::SCAccountType::Biz4337, ..Proto::SigningInput::default() }; @@ -306,7 +322,7 @@ fn test_barz_transfer_erc4337_eoa() { } #[test] -fn test_barz_transfer_erc4337_eoa_batch() { +fn test_biz4337_transfer_batch() { let private_key = hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); @@ -333,7 +349,7 @@ fn test_barz_transfer_erc4337_eoa_batch() { let amount = U256::from(655_360_197_115_136_u64); let payload = Erc20::approve(recipient, amount).unwrap(); - calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + calls.push(Proto::mod_Transaction::mod_SCWalletBatch::BatchedCall { // USDT address: "0xdac17f958d2ee523a2206206994597c13d831ec7".into(), amount: Cow::default(), @@ -347,7 +363,7 @@ fn test_barz_transfer_erc4337_eoa_batch() { let amount = U256::from(0x8AC7_2304_89E8_0000_u64); let payload = Erc20::transfer(recipient, amount).unwrap(); - calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + calls.push(Proto::mod_Transaction::mod_SCWalletBatch::BatchedCall { address: "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266".into(), amount: Cow::default(), payload: payload.into(), @@ -365,13 +381,13 @@ fn test_barz_transfer_erc4337_eoa_batch() { to_address: "0xdac17f958d2ee523a2206206994597c13d831ec7".into(), private_key: private_key.into(), transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::batch( - Proto::mod_Transaction::Batch { calls }, - ), + transaction_oneof: TransactionType::scw_batch(Proto::mod_Transaction::SCWalletBatch { + calls, + wallet_type: Proto::SCWalletType::Biz4337, + }), }), user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation_v0_7(user_op), - user_operation_mode: Proto::SCAccountType::Biz4337, ..Proto::SigningInput::default() }; @@ -393,7 +409,7 @@ fn test_barz_transfer_erc4337_eoa_batch() { } #[test] -fn test_barz_transfer_erc7702_eoa() { +fn test_biz_eip7702_transfer() { let private_key = hex::decode("0xe148e40f06ee3ba316cdb2571f33486cf879c0ffd2b279ce9f9a88c41ce962e7").unwrap(); @@ -413,13 +429,12 @@ fn test_barz_transfer_erc7702_eoa() { .to_big_endian_compact() .into(), private_key: private_key.into(), - transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc20_transfer( - erc20_transfer, - ), - }), + transaction: Some(execute( + TransactionType::erc20_transfer(erc20_transfer), + Proto::SCWalletType::Biz, + )), + // TWT token. to_address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), - user_operation_mode: Proto::SCAccountType::Biz, eip7702_authority: Some(Proto::Authority { address: "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7".into(), }), @@ -447,7 +462,7 @@ fn test_barz_transfer_erc7702_eoa() { } #[test] -fn test_barz_transfer_erc7702_eoa_batch() { +fn test_biz_eip7702_transfer_batch() { let private_key = hex::decode("0xe148e40f06ee3ba316cdb2571f33486cf879c0ffd2b279ce9f9a88c41ce962e7").unwrap(); @@ -460,7 +475,7 @@ fn test_barz_transfer_erc7702_eoa_batch() { let amount = U256::from(100_000_000_000_000_u64); let payload = Erc20::transfer(recipient, amount).unwrap(); - calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + calls.push(Proto::mod_Transaction::mod_SCWalletBatch::BatchedCall { // TWT address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), amount: Cow::default(), @@ -475,7 +490,7 @@ fn test_barz_transfer_erc7702_eoa_batch() { let amount = U256::from(500_000_000_000_000_u64); let payload = Erc20::transfer(recipient, amount).unwrap(); - calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + calls.push(Proto::mod_Transaction::mod_SCWalletBatch::BatchedCall { // TWT address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), amount: Cow::default(), @@ -496,11 +511,11 @@ fn test_barz_transfer_erc7702_eoa_batch() { .into(), private_key: private_key.into(), transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::batch( - Proto::mod_Transaction::Batch { calls }, - ), + transaction_oneof: TransactionType::scw_batch(Proto::mod_Transaction::SCWalletBatch { + calls, + wallet_type: Proto::SCWalletType::Biz, + }), }), - user_operation_mode: Proto::SCAccountType::Biz, eip7702_authority: Some(Proto::Authority { address: "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7".into(), }), @@ -526,3 +541,135 @@ fn test_barz_transfer_erc7702_eoa_batch() { "04f9030f3812843b9aca00843b9aca00830186a0945132829820b44dc3e8586cec926a16fca0a5608480b9024434fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000002ef648d7c03412b832726fd4683e2625dea047ba00000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000c0f85cf85a3894117bc8454756456a0f83dbd130bb94d793d3f3f71380a0073afc661c158a2dccf4183f87e1e4d62b4d406af418cfd69959368ec9bec2a6a064292fd61d4d16b840470a86fc4f7a89413f9126d897f2268eb76a1d887c6d7a01a0e8bcbd96323c9d3e67b74366b2f43299100996d9e8874a6fd87186ac8f580d4ca07c25b4f0619af77fb953e8f0e4372bfbee62616ad419697516108eeb9bcebb28" ); } + +#[test] +fn test_biz_eip1559_transfer() { + // 0x6E860086BbA8fdEafB553815aF0F09a854cC887a + let private_key = + hex::decode("0xe762e91cc4889a9fce79b2d2ffc079f86c48331f57b2cd16a33bee060fe448e1").unwrap(); + + let erc20_transfer = Proto::mod_Transaction::ERC20Transfer { + to: "0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e".into(), + amount: U256::encode_be_compact(200_000_000_000_000_u64), + }; + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(56_u64), + nonce: U256::encode_be_compact(2_u64), + tx_mode: Proto::TransactionMode::Enveloped, + gas_limit: U256::from(100_000_u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + max_inclusion_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + private_key: private_key.into(), + transaction: Some(execute( + TransactionType::erc20_transfer(erc20_transfer), + Proto::SCWalletType::Biz, + )), + // TWT token. + to_address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!( + output.error, + SigningErrorType::OK, + "{}", + output.error_message + ); + + assert_eq!( + output.pre_hash.to_hex(), + "60260356568ae70838bd80085b971e1e4ebe42046688fd8511a268986e522121" + ); + // Successfully broadcasted transaction: + // https://bscscan.com/tx/0x6f8b2c8d50e8bb543d7124703b75d9e495832116a1a61afabf40b9b0ac43c980 + assert_eq!( + output.encoded.to_hex(), + "02f901503802843b9aca00843b9aca00830186a0946e860086bba8fdeafb553815af0f09a854cc887a80b8e4b61d27f60000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000000b5e620f4800000000000000000000000000000000000000000000000000000000000c080a0fb45762a262f4c32090576e9de087482d25cd00b6ea2522eb7d5a40f435acdbaa0151dbd48a4f4bf06080313775fe32ececd68869d721518a92bf292e4a84322f9" + ); +} + +#[test] +fn test_biz_eip1559_transfer_with_incorrect_wallet_type_error() { + // 0x6E860086BbA8fdEafB553815aF0F09a854cC887a + let private_key = + hex::decode("0xe762e91cc4889a9fce79b2d2ffc079f86c48331f57b2cd16a33bee060fe448e1").unwrap(); + + let erc20_transfer = Proto::mod_Transaction::ERC20Transfer { + to: "0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e".into(), + amount: U256::encode_be_compact(200_000_000_000_000_u64), + }; + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(56_u64), + nonce: U256::encode_be_compact(2_u64), + tx_mode: Proto::TransactionMode::Enveloped, + gas_limit: U256::from(100_000_u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + max_inclusion_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + private_key: private_key.into(), + transaction: Some(execute( + TransactionType::erc20_transfer(erc20_transfer), + // Biz4337 account cannot be used in Legacy/Enveloped/SetCode transaction flow. + Proto::SCWalletType::Biz4337, + )), + // TWT token. + to_address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::Error_invalid_params,); +} + +#[test] +fn test_user_operation_transfer_with_incorrect_wallet_type_error() { + let private_key = + hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(0x23_86f2_6fc1_0000), + data: Cow::default(), + }; + let user_op = Proto::UserOperationV0_7 { + entry_point: "0x0000000071727De22E5E9d8BAf0edAc6f37da032".into(), + sender: "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029".into(), + pre_verification_gas: U256::from(1000000u64).to_big_endian_compact().into(), + verification_gas_limit: U256::from(100000u128).to_big_endian_compact().into(), + factory: "0xf471789937856d80e589f5996cf8b0511ddd9de4".into(), + factory_data: "f471789937856d80e589f5996cf8b0511ddd9de4".decode_hex().unwrap().into(), + paymaster: "0xf62849f9a0b5bf2913b396098f7c7019b51a820a".into(), + paymaster_verification_gas_limit: U256::from(99999u128).to_big_endian_compact().into(), + paymaster_post_op_gas_limit: U256::from(88888u128).to_big_endian_compact().into(), + paymaster_data: "00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b".decode_hex().unwrap().into(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(31337u64), + nonce: U256::encode_be_compact(0u64), + tx_mode: Proto::TransactionMode::UserOp, + gas_limit: U256::from(100000u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), + max_inclusion_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), + to_address: "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9".into(), + private_key: private_key.into(), + transaction: Some(execute( + TransactionType::transfer(transfer), + // Biz account cannot be used in UserOperation flow. + Proto::SCWalletType::Biz, + )), + user_operation_oneof: + Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation_v0_7(user_op), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::Error_invalid_params); +} diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index f2e51c469a9..c1ce8b6fdb6 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -72,8 +72,8 @@ message Transaction { bytes data = 2; } - // Batched transaction for ERC-4337 wallets - message Batch { + // Batch transaction to a Smart Contract Wallet (ERC-4337 and ERC-7702). + message SCWalletBatch { message BatchedCall { // Recipient addresses. string address = 1; @@ -85,7 +85,19 @@ message Transaction { bytes payload = 3; } + // Batched calls to be executed on the smart contract wallet. repeated BatchedCall calls = 1; + // Smart contract wallet type. + SCWalletType wallet_type = 2; + } + + // Execute transaction to a Smart Contract Wallet (ERC-4337 and ERC-7702). + message SCWalletExecute { + // Transaction to be executed on the smart contract wallet. + // TODO currently, smart contract wallet address is specified in `SigningInput.toAddress`, but it will be refactored soon. + Transaction transaction = 1; + // Smart contract wallet type. + SCWalletType wallet_type = 2; } // Payload transfer @@ -96,7 +108,10 @@ message Transaction { ERC721Transfer erc721_transfer = 4; ERC1155Transfer erc1155_transfer = 5; ContractGeneric contract_generic = 6; - Batch batch = 7; + // Batch transaction to a Smart Contract Wallet (ERC-4337 and ERC-7702). + SCWalletBatch scw_batch = 7; + // Execute transaction to a Smart Contract Wallet (ERC-4337 and ERC-7702). + SCWalletExecute scw_execute = 8; } } @@ -185,8 +200,8 @@ message Authority { string address = 2; } -// Smart Contract account type. -enum SCAccountType { +// Smart Contract Wallet type. +enum SCWalletType { // ERC-4337 compatible smart contract wallet. // https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/accounts/SimpleAccount.sol SimpleAccount = 0; @@ -225,6 +240,7 @@ message SigningInput { bytes max_fee_per_gas = 7; // Recipient's address. + // TODO currently, will be moved to each `Transaction` oneof soon. string to_address = 8; // The secret private key used for signing (32 bytes). @@ -245,10 +261,8 @@ message SigningInput { // Used in `TransactionMode::Enveloped` only. repeated Access access_list = 12; - // Smart contract account type. Used in `TransactionMode::UserOp` only. - SCAccountType user_operation_mode = 14; - // A smart contract to which we’re delegating to. + // Used in `TransactionMode::SetOp` only. // Currently, we support delegation to only one authority at a time. Authority eip7702_authority = 15; } diff --git a/swift/Tests/BarzTests.swift b/swift/Tests/BarzTests.swift index c30d27e070c..53f03565314 100644 --- a/swift/Tests/BarzTests.swift +++ b/swift/Tests/BarzTests.swift @@ -87,8 +87,12 @@ class BarzTests: XCTestCase { } $0.transaction = EthereumTransaction.with { - $0.transfer = EthereumTransaction.Transfer.with { - $0.amount = Data(hexString: "2386f26fc10000")! + $0.scwExecute = EthereumTransaction.SCWalletExecute.with { + $0.transaction = EthereumTransaction.with { + $0.transfer = EthereumTransaction.Transfer.with { + $0.amount = Data(hexString: "2386f26fc10000")! + } + } } } } @@ -124,10 +128,14 @@ class BarzTests: XCTestCase { salt: 0 ) } - + $0.transaction = EthereumTransaction.with { - $0.transfer = EthereumTransaction.Transfer.with { - $0.amount = Data(hexString: "2386f26fc10000")! + $0.scwExecute = EthereumTransaction.SCWalletExecute.with { + $0.transaction = EthereumTransaction.with { + $0.transfer = EthereumTransaction.Transfer.with { + $0.amount = Data(hexString: "2386f26fc10000")! + } + } } } } @@ -165,14 +173,14 @@ class BarzTests: XCTestCase { } $0.transaction = EthereumTransaction.with { - $0.batch = EthereumTransaction.Batch.with { + $0.scwBatch = EthereumTransaction.SCWalletBatch.with { $0.calls = [ - EthereumTransaction.Batch.BatchedCall.with { + EthereumTransaction.SCWalletBatch.BatchedCall.with { $0.address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266" $0.amount = Data(hexString: "00")! $0.payload = approveCall }, - EthereumTransaction.Batch.BatchedCall.with { + EthereumTransaction.SCWalletBatch.BatchedCall.with { $0.address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266" $0.amount = Data(hexString: "00")! $0.payload = transferCall diff --git a/tests/chains/Ethereum/BarzTests.cpp b/tests/chains/Ethereum/BarzTests.cpp index bb3fb750c89..2d7106d16f3 100644 --- a/tests/chains/Ethereum/BarzTests.cpp +++ b/tests/chains/Ethereum/BarzTests.cpp @@ -171,7 +171,7 @@ TEST(Barz, SignK1TransferAccountDeployed) { user_operation.set_sender(sender); input.set_private_key(key.data(), key.size()); - auto& transfer = *input.mutable_transaction()->mutable_transfer(); + auto& transfer = *input.mutable_transaction()->mutable_scw_execute()->mutable_transaction()->mutable_transfer(); transfer.set_amount(amount.data(), amount.size()); std::string expected = "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"100000\",\"initCode\":\"0x\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"2\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0xb16Db98B365B1f89191996942612B14F1Da4Bd5f\",\"signature\":\"0x80e84992ebf8d5f71180231163ed150a7557ed0aa4b4bcee23d463a09847e4642d0fbf112df2e5fa067adf4b2fa17fc4a8ac172134ba5b78e3ec9c044e7f28d71c\",\"verificationGasLimit\":\"100000\"}"; @@ -224,7 +224,7 @@ TEST(Barz, SignR1TransferAccountNotDeployed) { user_operation.set_init_code(initCode.data(), initCode.size()); input.set_private_key(key.data(), key.size()); - auto& transfer = *input.mutable_transaction()->mutable_transfer(); + auto& transfer = *input.mutable_transaction()->mutable_scw_execute()->mutable_transaction()->mutable_transfer(); transfer.set_amount(amount.data(), amount.size()); std::string expected = "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"2500000\",\"initCode\":\"0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc51000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f00000000000000000000000000000000000000000000000000000000000000\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"0\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0x1392Ae041BfBdBAA0cFF9234a0C8F64df97B7218\",\"signature\":\"0xbf1b68323974e71ad9bd6dfdac07dc062599d150615419bb7876740d2bcf3c8909aa7e627bb0e08a2eab930e2e7313247c9b683c884236dd6ea0b6834fb2cb0a1b\",\"verificationGasLimit\":\"3000000\"}"; @@ -286,7 +286,7 @@ TEST(Barz, SignR1BatchedTransferAccountDeployed) { auto transferCall = data(TWDataBytes(transferCallEncoded.get()), TWDataSize(transferCallEncoded.get())); EXPECT_EQ(hex(transferCall), "a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000"); - auto *batch = input.mutable_transaction()->mutable_batch(); + auto *batch = input.mutable_transaction()->mutable_scw_batch(); auto *c1 = batch->add_calls(); c1->set_address(to); c1->set_amount(amount.data(), amount.size()); From 16f5cc53015044d06ea812bdebd971343c625f7e Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Fri, 4 Apr 2025 22:29:51 +0700 Subject: [PATCH 18/72] chore(uov7): Serialize UserOperation numbers as hex 0x prefixed (#4353) --- rust/tw_encoding/src/hex.rs | 25 +++++ rust/tw_evm/Cargo.toml | 2 +- rust/tw_evm/src/signature.rs | 11 ++- .../src/transaction/user_operation_v0_7.rs | 98 ++++++++++--------- rust/tw_evm/tests/barz.rs | 10 +- rust/tw_number/Cargo.toml | 1 + rust/tw_number/src/lib.rs | 1 + rust/tw_number/src/serde.rs | 15 +++ rust/tw_number/src/u256.rs | 12 +++ 9 files changed, 120 insertions(+), 55 deletions(-) create mode 100644 rust/tw_number/src/serde.rs diff --git a/rust/tw_encoding/src/hex.rs b/rust/tw_encoding/src/hex.rs index c7c5e5d6610..8eee855bf2d 100644 --- a/rust/tw_encoding/src/hex.rs +++ b/rust/tw_encoding/src/hex.rs @@ -135,6 +135,31 @@ pub mod as_hex { } } +pub mod as_hex_prefixed { + use crate::hex::encode; + use serde::{Deserializer, Serialize, Serializer}; + use std::fmt; + + /// Serializes the `value` as a `0x` prefixed hex. + pub fn serialize(value: &T, serializer: S) -> Result + where + T: AsRef<[u8]>, + S: Serializer, + { + encode(value, true).serialize(serializer) + } + + pub fn deserialize<'de, D, T, E>(deserializer: D) -> Result + where + D: Deserializer<'de>, + T: for<'a> TryFrom<&'a [u8], Error = E>, + E: fmt::Debug, + { + // `as_hex::deserialize` handles the prefix already. + super::as_hex::deserialize(deserializer) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/rust/tw_evm/Cargo.toml b/rust/tw_evm/Cargo.toml index 857a8f1bd5a..7e264f6ae05 100644 --- a/rust/tw_evm/Cargo.toml +++ b/rust/tw_evm/Cargo.toml @@ -15,7 +15,7 @@ tw_hash = { path = "../tw_hash" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } -tw_number = { path = "../tw_number" } +tw_number = { path = "../tw_number", features = ["serde"] } tw_proto = { path = "../tw_proto" } [dev-dependencies] diff --git a/rust/tw_evm/src/signature.rs b/rust/tw_evm/src/signature.rs index c3097c197a8..4b4cb2d64b3 100644 --- a/rust/tw_evm/src/signature.rs +++ b/rust/tw_evm/src/signature.rs @@ -3,7 +3,7 @@ // Copyright © 2017 Trust Wallet. use std::ops::BitXor; -use tw_number::{NumberResult, U256}; +use tw_number::{NumberError, NumberResult, U256}; /// EIP155 Eth encoding of V, of the form 27+v, or 35+chainID*2+v. /// cbindgin:ignore @@ -19,12 +19,19 @@ pub fn replay_protection(chain_id: U256, v: u8) -> NumberResult { } } -/// Embeds `chain_id` in `v` param, for replay protection, legacy. +/// Embeds legacy protection into `v` param. #[inline] pub fn legacy_replay_protection(v: u8) -> NumberResult { U256::from(v).checked_add(ETHEREUM_SIGNATURE_V_OFFSET) } +/// Embeds legacy protection into `v` param. +#[inline] +pub fn legacy_replay_protection_u8(v: u8) -> NumberResult { + v.checked_add(ETHEREUM_SIGNATURE_V_OFFSET) + .ok_or(NumberError::IntegerOverflow) +} + /// Embeds `chain_id` in `v` param, for replay protection, EIP155. #[inline] pub fn eip155_replay_protection(chain_id: U256, v: u8) -> NumberResult { diff --git a/rust/tw_evm/src/transaction/user_operation_v0_7.rs b/rust/tw_evm/src/transaction/user_operation_v0_7.rs index bfe9f1a0183..96e1f8624cd 100644 --- a/rust/tw_evm/src/transaction/user_operation_v0_7.rs +++ b/rust/tw_evm/src/transaction/user_operation_v0_7.rs @@ -6,14 +6,17 @@ use crate::abi::encode::encode_tokens; use crate::abi::non_empty_array::NonEmptyBytes; use crate::abi::token::Token; use crate::address::Address; +use crate::signature::legacy_replay_protection_u8; use crate::transaction::signature::Signature; use crate::transaction::{SignedTransaction, TransactionCommon, UnsignedTransaction}; -use serde::Serialize; +use serde::ser::Error as SerError; +use serde::{Serialize, Serializer}; use tw_coin_entry::error::prelude::*; -use tw_encoding::hex; +use tw_encoding::hex::{self, as_hex_prefixed}; use tw_hash::sha3::keccak256; use tw_hash::H256; use tw_memory::Data; +use tw_number::serde::as_u256_hex; use tw_number::U256; pub struct PackedUserOperation { @@ -115,22 +118,45 @@ impl PackedUserOperation { } } +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] pub struct UserOperationV0_7 { pub sender: Address, + #[serde(serialize_with = "U256::as_hex")] pub nonce: U256, + + #[serde(skip_serializing_if = "Option::is_none")] pub factory: Option
, + #[serde(skip_serializing_if = "Data::is_empty")] + #[serde(with = "as_hex_prefixed")] pub factory_data: Data, + + #[serde(with = "as_hex_prefixed")] pub call_data: Data, + + #[serde(serialize_with = "as_u256_hex")] pub call_data_gas_limit: u128, + #[serde(serialize_with = "as_u256_hex")] pub verification_gas_limit: u128, + #[serde(serialize_with = "U256::as_hex")] pub pre_verification_gas: U256, + + #[serde(serialize_with = "as_u256_hex")] pub max_fee_per_gas: u128, + #[serde(serialize_with = "as_u256_hex")] pub max_priority_fee_per_gas: u128, + + #[serde(skip_serializing_if = "Option::is_none")] pub paymaster: Option
, + #[serde(serialize_with = "as_u256_hex")] pub paymaster_verification_gas_limit: u128, + #[serde(serialize_with = "as_u256_hex")] pub paymaster_post_op_gas_limit: u128, + #[serde(skip_serializing_if = "Data::is_empty")] + #[serde(with = "as_hex_prefixed")] pub paymaster_data: Data, + #[serde(skip)] pub entry_point: Address, } @@ -165,8 +191,12 @@ impl UnsignedTransaction for UserOperationV0_7 { } } +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] pub struct SignedUserOperationV0_7 { + #[serde(flatten)] unsigned: UserOperationV0_7, + #[serde(serialize_with = "serialize_signature_with_legacy_replay_protect")] signature: Signature, } @@ -181,31 +211,7 @@ impl SignedTransaction for SignedUserOperationV0_7 { type Signature = Signature; fn encode(&self) -> Data { - let mut signature = self.signature.to_rsv_bytes(); - signature[64] += 27; - - let prefix = true; - let tx = SignedUserOperationV0_7Serde { - sender: self.unsigned.sender.to_string(), - nonce: self.unsigned.nonce.to_string(), - factory: self.unsigned.factory.map(|addr| addr.to_string()), - factory_data: hex::encode(&self.unsigned.factory_data, prefix), - call_data: hex::encode(&self.unsigned.call_data, prefix), - call_data_gas_limit: self.unsigned.call_data_gas_limit.to_string(), - verification_gas_limit: self.unsigned.verification_gas_limit.to_string(), - pre_verification_gas: self.unsigned.pre_verification_gas.to_string(), - max_fee_per_gas: self.unsigned.max_fee_per_gas.to_string(), - max_priority_fee_per_gas: self.unsigned.max_priority_fee_per_gas.to_string(), - paymaster: self.unsigned.paymaster.map(|addr| addr.to_string()), - paymaster_verification_gas_limit: self - .unsigned - .paymaster_verification_gas_limit - .to_string(), - paymaster_post_op_gas_limit: self.unsigned.paymaster_post_op_gas_limit.to_string(), - paymaster_data: hex::encode(&self.unsigned.paymaster_data, prefix), - signature: hex::encode(signature.as_slice(), prefix), - }; - serde_json::to_string(&tx) + serde_json::to_string(self) .expect("Simple structure should never fail on serialization") .into_bytes() } @@ -216,26 +222,6 @@ impl SignedTransaction for SignedUserOperationV0_7 { } } -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -struct SignedUserOperationV0_7Serde { - sender: String, - nonce: String, - factory: Option, - factory_data: String, - call_data: String, - call_data_gas_limit: String, - verification_gas_limit: String, - pre_verification_gas: String, - max_fee_per_gas: String, - max_priority_fee_per_gas: String, - paymaster: Option, - paymaster_verification_gas_limit: String, - paymaster_post_op_gas_limit: String, - paymaster_data: String, - signature: String, -} - fn concat_u128_be(a: u128, b: u128) -> [u8; 32] { let a = a.to_be_bytes(); let b = b.to_be_bytes(); @@ -248,6 +234,22 @@ fn concat_u128_be(a: u128, b: u128) -> [u8; 32] { }) } +pub fn serialize_signature_with_legacy_replay_protect( + signature: &Signature, + serializer: S, +) -> Result +where + S: Serializer, +{ + let prefix = true; + let mut rsv = signature.to_rsv_bytes(); + rsv[64] = + legacy_replay_protection_u8(rsv[64]).map_err(|e| SerError::custom(format!("{e:?}")))?; + + let hex_str = hex::encode(rsv, prefix); + serializer.serialize_str(&hex_str) +} + #[cfg(test)] mod tests { use super::*; @@ -310,7 +312,7 @@ mod tests { nonce: U256::from(0u64), init_code: "f471789937856d80e589f5996cf8b0511ddd9de4f471789937856d80e589f5996cf8b0511ddd9de4".decode_hex().unwrap(), call_data: "00".decode_hex().unwrap(), - account_gas_limits:concat_u128_be(100000u128, 100000u128).to_vec(), + account_gas_limits: concat_u128_be(100000u128, 100000u128).to_vec(), pre_verification_gas: U256::from(1000000u64), gas_fees: concat_u128_be(100000u128, 100000u128).to_vec(), paymaster_and_data: "f62849f9a0b5bf2913b396098f7c7019b51a820a0000000000000000000000000001869f00000000000000000000000000015b3800000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b".decode_hex().unwrap(), diff --git a/rust/tw_evm/tests/barz.rs b/rust/tw_evm/tests/barz.rs index e40cbe263a8..27006d1a13c 100644 --- a/rust/tw_evm/tests/barz.rs +++ b/rust/tw_evm/tests/barz.rs @@ -317,8 +317,9 @@ fn test_biz4337_transfer() { "68109b9caf49f7971b689307c9a77ceec46e4b8fa88421c4276dd846f782d92c" ); - let user_op: serde_json::Value = serde_json::from_slice(&output.encoded).unwrap(); - assert_eq!(user_op["callData"], "0x76276c82000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd0000000000000000000000000000000000000000000000000000000000"); + let expected = r#"{"sender":"0x2EF648D7C03412B832726fd4683E2625deA047Ba","nonce":"0x00","callData":"0x76276c82000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd0000000000000000000000000000000000000000000000000000000000","callDataGasLimit":"0x0186a0","verificationGasLimit":"0x0186a0","preVerificationGas":"0x0f4240","maxFeePerGas":"0x0186a0","maxPriorityFeePerGas":"0x0186a0","paymaster":"0xb0086171AC7b6BD4D046580bca6d6A4b0835c232","paymasterVerificationGasLimit":"0x01869f","paymasterPostOpGasLimit":"0x015b38","paymasterData":"0x00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b","signature":"0xf6b1f7ad22bcc68ca292bc10d15e82e0eab8c75c1a04f9750e7cff1418d38d9c6c115c510e3f47eb802103d62f88fa7d4a3b2e24e2ddbe7ee68153920ab3f6cc1b"}"#; + let actual = String::from_utf8(output.encoded.to_vec()).unwrap(); + assert_eq!(actual, expected); } #[test] @@ -404,8 +405,9 @@ fn test_biz4337_transfer_batch() { "f6340068891dc3eb78959993151c421dde23982b3a1407c0dbbd62c2c22c3cb8" ); - let user_op: serde_json::Value = serde_json::from_slice(&output.encoded).unwrap(); - assert_eq!(user_op["callData"], "0x26da7d880000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd000000000000000000000000000000000000000000000000000000000000000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b1266000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000"); + let expected = r#"{"sender":"0x2EF648D7C03412B832726fd4683E2625deA047Ba","nonce":"0x00","callData":"0x26da7d880000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd000000000000000000000000000000000000000000000000000000000000000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b1266000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000","callDataGasLimit":"0x0186a0","verificationGasLimit":"0x0186a0","preVerificationGas":"0x0f4240","maxFeePerGas":"0x0186a0","maxPriorityFeePerGas":"0x0186a0","paymaster":"0xb0086171AC7b6BD4D046580bca6d6A4b0835c232","paymasterVerificationGasLimit":"0x01869f","paymasterPostOpGasLimit":"0x015b38","paymasterData":"0x00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b","signature":"0x21ab0bdcd1441aef3e4046a922bab3636d0c74011c1b055c55ad9f39ae9b4dac59bcbf3bc1ff31b367a83360edfc8e9652f1a5c8b07eb76fe5a426835682d6721c"}"#; + let actual = String::from_utf8(output.encoded.to_vec()).unwrap(); + assert_eq!(actual, expected); } #[test] diff --git a/rust/tw_number/Cargo.toml b/rust/tw_number/Cargo.toml index 6f32da2d9b1..46f87781236 100644 --- a/rust/tw_number/Cargo.toml +++ b/rust/tw_number/Cargo.toml @@ -13,6 +13,7 @@ arbitrary = { version = "1", features = ["derive"], optional = true } lazy_static = "1.4.0" primitive-types = "0.10.1" serde = { version = "1.0", features = ["derive"], optional = true } +tw_encoding = { path = "../tw_encoding" } tw_hash = { path = "../tw_hash" } tw_memory = { path = "../tw_memory" } diff --git a/rust/tw_number/src/lib.rs b/rust/tw_number/src/lib.rs index 0c8189ba13d..5242eccabb2 100644 --- a/rust/tw_number/src/lib.rs +++ b/rust/tw_number/src/lib.rs @@ -3,6 +3,7 @@ // Copyright © 2017 Trust Wallet. mod i256; +pub mod serde; mod sign; mod u256; diff --git a/rust/tw_number/src/serde.rs b/rust/tw_number/src/serde.rs new file mode 100644 index 00000000000..59394df9736 --- /dev/null +++ b/rust/tw_number/src/serde.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::U256; +use serde::Serializer; + +pub fn as_u256_hex(num: &T, serializer: S) -> Result +where + S: Serializer, + T: Into + Copy, +{ + let num_u256: U256 = (*num).into(); + num_u256.as_hex(serializer) +} diff --git a/rust/tw_number/src/u256.rs b/rust/tw_number/src/u256.rs index e6136b8ce29..2e32094b274 100644 --- a/rust/tw_number/src/u256.rs +++ b/rust/tw_number/src/u256.rs @@ -236,6 +236,7 @@ mod impl_serde { use serde::de::Error as DeError; use serde::{Deserialize, Deserializer, Serializer}; use std::str::FromStr; + use tw_encoding::hex; impl U256 { pub fn as_decimal_str(&self, serializer: S) -> Result @@ -259,6 +260,17 @@ mod impl_serde { { crate::serde_common::from_num_or_decimal_str::<'de, U256, u64, D>(deserializer) } + + pub fn as_hex(&self, serializer: S) -> Result + where + S: Serializer, + { + let prefix = true; + let min_bytes_len = 1; + + let hex_str = hex::encode(self.to_big_endian_compact_min_len(min_bytes_len), prefix); + serializer.serialize_str(&hex_str) + } } } From 798ee9555656990bc2d5af3aa831fc70852cc23a Mon Sep 17 00:00:00 2001 From: gupnik Date: Tue, 8 Apr 2025 18:17:37 +0530 Subject: [PATCH 19/72] Upgrades protobuf to 3.20.3 (#4359) * Upgrades protobuf to 3.20.3 * Updates files * Use sorted list * Updates yml * Updates project.yml as well * Updates symlink --- WalletCore.podspec | 5 ++--- cmake/Protobuf.cmake | 12 +++++------- swift/common-xcframework.yml | 3 +-- swift/cpp.xcconfig.in | 2 +- swift/project.yml | 3 +-- swift/protobuf | 2 +- tools/dependencies-version | 2 +- 7 files changed, 12 insertions(+), 17 deletions(-) diff --git a/WalletCore.podspec b/WalletCore.podspec index 42fe7431537..ee960c3a1eb 100644 --- a/WalletCore.podspec +++ b/WalletCore.podspec @@ -29,7 +29,7 @@ Pod::Spec.new do |s| end s.subspec 'Core' do |ss| - protobuf_source_dir = 'build/local/src/protobuf/protobuf-3.19.2' + protobuf_source_dir = 'build/local/src/protobuf/protobuf-3.20.3' include_dir = 'build/local/include' ss.source_files = 'src/**/*.{c,cc,cpp,h}', @@ -45,6 +45,7 @@ Pod::Spec.new do |s| "#{protobuf_source_dir}/src/google/protobuf/api.pb.cc", "#{protobuf_source_dir}/src/google/protobuf/arena.cc", "#{protobuf_source_dir}/src/google/protobuf/arenastring.cc", + "#{protobuf_source_dir}/src/google/protobuf/arenaz_sampler.cc", "#{protobuf_source_dir}/src/google/protobuf/compiler/importer.cc", "#{protobuf_source_dir}/src/google/protobuf/compiler/parser.cc", "#{protobuf_source_dir}/src/google/protobuf/descriptor.cc", @@ -59,8 +60,6 @@ Pod::Spec.new do |s| "#{protobuf_source_dir}/src/google/protobuf/generated_enum_util.cc", "#{protobuf_source_dir}/src/google/protobuf/generated_message_bases.cc", "#{protobuf_source_dir}/src/google/protobuf/generated_message_reflection.cc", - "#{protobuf_source_dir}/src/google/protobuf/generated_message_table_driven.cc", - "#{protobuf_source_dir}/src/google/protobuf/generated_message_table_driven_lite.cc", "#{protobuf_source_dir}/src/google/protobuf/generated_message_tctable_full.cc", "#{protobuf_source_dir}/src/google/protobuf/generated_message_tctable_lite.cc", "#{protobuf_source_dir}/src/google/protobuf/generated_message_util.cc", diff --git a/cmake/Protobuf.cmake b/cmake/Protobuf.cmake index 8e0a93251d2..6228eed7bfc 100644 --- a/cmake/Protobuf.cmake +++ b/cmake/Protobuf.cmake @@ -2,8 +2,8 @@ # # Copyright © 2017 Trust Wallet. -set(protobuf_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../build/local/src/protobuf/protobuf-3.19.2) -set(protobuf_source_dir ${CMAKE_CURRENT_LIST_DIR}/../build/local/src/protobuf/protobuf-3.19.2) +set(protobuf_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../build/local/src/protobuf/protobuf-3.20.3) +set(protobuf_source_dir ${CMAKE_CURRENT_LIST_DIR}/../build/local/src/protobuf/protobuf-3.20.3) # sort + uniq -u # https://github.com/protocolbuffers/protobuf/blob/master/cmake/libprotobuf.cmake @@ -16,6 +16,7 @@ set(protobuf_SOURCE_FILES ${protobuf_source_dir}/src/google/protobuf/api.pb.cc ${protobuf_source_dir}/src/google/protobuf/arena.cc ${protobuf_source_dir}/src/google/protobuf/arenastring.cc + ${protobuf_source_dir}/src/google/protobuf/arenaz_sampler.cc ${protobuf_source_dir}/src/google/protobuf/compiler/importer.cc ${protobuf_source_dir}/src/google/protobuf/compiler/parser.cc ${protobuf_source_dir}/src/google/protobuf/descriptor.cc @@ -30,8 +31,6 @@ set(protobuf_SOURCE_FILES ${protobuf_source_dir}/src/google/protobuf/generated_enum_util.cc ${protobuf_source_dir}/src/google/protobuf/generated_message_bases.cc ${protobuf_source_dir}/src/google/protobuf/generated_message_reflection.cc - ${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven.cc - ${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven_lite.cc ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_full.cc ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_lite.cc ${protobuf_source_dir}/src/google/protobuf/generated_message_util.cc @@ -51,6 +50,7 @@ set(protobuf_SOURCE_FILES ${protobuf_source_dir}/src/google/protobuf/message.cc ${protobuf_source_dir}/src/google/protobuf/message_lite.cc ${protobuf_source_dir}/src/google/protobuf/parse_context.cc + ${protobuf_source_dir}/src/google/protobuf/reflection_internal.h ${protobuf_source_dir}/src/google/protobuf/reflection_ops.cc ${protobuf_source_dir}/src/google/protobuf/repeated_field.cc ${protobuf_source_dir}/src/google/protobuf/repeated_ptr_field.cc @@ -104,6 +104,7 @@ set(protobuf_HEADER_FILES ${protobuf_source_dir}/src/google/protobuf/arena.h ${protobuf_source_dir}/src/google/protobuf/arena_impl.h ${protobuf_source_dir}/src/google/protobuf/arenastring.h + ${protobuf_source_dir}/src/google/protobuf/arenaz_sampler.h ${protobuf_source_dir}/src/google/protobuf/compiler/importer.h ${protobuf_source_dir}/src/google/protobuf/compiler/parser.h ${protobuf_source_dir}/src/google/protobuf/descriptor.h @@ -121,11 +122,8 @@ set(protobuf_HEADER_FILES ${protobuf_source_dir}/src/google/protobuf/generated_enum_util.h ${protobuf_source_dir}/src/google/protobuf/generated_message_bases.h ${protobuf_source_dir}/src/google/protobuf/generated_message_reflection.h - ${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven.h - ${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven_lite.h ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_decl.h ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_impl.h - ${protobuf_source_dir}/src/google/protobuf/generated_message_tctable_impl.inc ${protobuf_source_dir}/src/google/protobuf/generated_message_util.h ${protobuf_source_dir}/src/google/protobuf/has_bits.h ${protobuf_source_dir}/src/google/protobuf/implicit_weak_message.h diff --git a/swift/common-xcframework.yml b/swift/common-xcframework.yml index dae0d4b55c7..962bbaf4239 100644 --- a/swift/common-xcframework.yml +++ b/swift/common-xcframework.yml @@ -132,6 +132,7 @@ targets: - protobuf/google/protobuf/api.pb.cc - protobuf/google/protobuf/arena.cc - protobuf/google/protobuf/arenastring.cc + - protobuf/google/protobuf/arenaz_sampler.cc - protobuf/google/protobuf/compiler/importer.cc - protobuf/google/protobuf/compiler/parser.cc - protobuf/google/protobuf/descriptor.cc @@ -146,8 +147,6 @@ targets: - protobuf/google/protobuf/generated_enum_util.cc - protobuf/google/protobuf/generated_message_bases.cc - protobuf/google/protobuf/generated_message_reflection.cc - - protobuf/google/protobuf/generated_message_table_driven.cc - - protobuf/google/protobuf/generated_message_table_driven_lite.cc - protobuf/google/protobuf/generated_message_tctable_full.cc - protobuf/google/protobuf/generated_message_tctable_lite.cc - protobuf/google/protobuf/generated_message_util.cc diff --git a/swift/cpp.xcconfig.in b/swift/cpp.xcconfig.in index 09e3c0f91af..e96e2db5138 100644 --- a/swift/cpp.xcconfig.in +++ b/swift/cpp.xcconfig.in @@ -3,4 +3,4 @@ // Copyright © 2017 Trust Wallet. HEADER_SEARCH_PATHS = $(SRCROOT)/../src @Boost_INCLUDE_DIRS@ -SYSTEM_HEADER_SEARCH_PATHS = $(SRCROOT)/../src/build/local/include $(SRCROOT)/../build/local/src/protobuf/protobuf-3.19.2/src +SYSTEM_HEADER_SEARCH_PATHS = $(SRCROOT)/../src/build/local/include $(SRCROOT)/../build/local/src/protobuf/protobuf-3.20.3/src diff --git a/swift/project.yml b/swift/project.yml index df46f6a29e5..9d68acab6cd 100644 --- a/swift/project.yml +++ b/swift/project.yml @@ -150,6 +150,7 @@ targets: - protobuf/google/protobuf/api.pb.cc - protobuf/google/protobuf/arena.cc - protobuf/google/protobuf/arenastring.cc + - protobuf/google/protobuf/arenaz_sampler.cc - protobuf/google/protobuf/compiler/importer.cc - protobuf/google/protobuf/compiler/parser.cc - protobuf/google/protobuf/descriptor.cc @@ -164,8 +165,6 @@ targets: - protobuf/google/protobuf/generated_enum_util.cc - protobuf/google/protobuf/generated_message_bases.cc - protobuf/google/protobuf/generated_message_reflection.cc - - protobuf/google/protobuf/generated_message_table_driven.cc - - protobuf/google/protobuf/generated_message_table_driven_lite.cc - protobuf/google/protobuf/generated_message_tctable_full.cc - protobuf/google/protobuf/generated_message_tctable_lite.cc - protobuf/google/protobuf/generated_message_util.cc diff --git a/swift/protobuf b/swift/protobuf index a96b2043a74..6e8a73eadea 120000 --- a/swift/protobuf +++ b/swift/protobuf @@ -1 +1 @@ -../build/local/src/protobuf/protobuf-3.19.2/src \ No newline at end of file +../build/local/src/protobuf/protobuf-3.20.3/src \ No newline at end of file diff --git a/tools/dependencies-version b/tools/dependencies-version index 66d095d4f1a..a18fbc3eaf3 100755 --- a/tools/dependencies-version +++ b/tools/dependencies-version @@ -3,5 +3,5 @@ export GTEST_VERSION=1.16.0 export CHECK_VERSION=0.15.2 export JSON_VERSION=3.11.3 -export PROTOBUF_VERSION=3.19.2 +export PROTOBUF_VERSION=3.20.3 export SWIFT_PROTOBUF_VERSION=1.18.0 From c5507f082ab354088bb0732ae1ee539a9174999e Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Tue, 8 Apr 2025 15:02:44 +0200 Subject: [PATCH 20/72] Chore: Fix typos in folder `rust` (#4328) Co-authored-by: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> --- rust/chains/tw_binance/src/transaction/message/mod.rs | 8 ++++---- .../{tranfer_out_order.rs => transfer_out_order.rs} | 0 rust/chains/tw_bitcoin/tests/babylon_staking.rs | 4 ++-- .../tests/data/babylon_staking_transactions.json | 8 ++++---- rust/chains/tw_bitcoincash/src/cash_address/checksum.rs | 2 +- rust/chains/tw_bitcoincash/src/cash_address/mod.rs | 2 +- rust/chains/tw_decred/src/modules/decred_sighash.rs | 4 ++-- rust/chains/tw_ripple/src/definitions.rs | 4 ++-- rust/chains/tw_solana/src/modules/tx_signer.rs | 2 +- rust/frameworks/tw_utxo/src/transaction/asset/brc20.rs | 2 +- rust/frameworks/tw_utxo/src/transaction/asset/ordinal.rs | 2 +- rust/tw_any_coin/src/ffi/tw_any_address.rs | 8 ++++---- rust/tw_base58_address/src/lib.rs | 2 +- rust/tw_coin_entry/src/prefix.rs | 2 +- rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs | 2 +- rust/tw_evm/src/abi/encode.rs | 2 +- rust/tw_evm/src/abi/param_type/writer.rs | 2 +- rust/tw_keypair/src/ffi/privkey.rs | 4 ++-- rust/tw_misc/src/serde.rs | 2 +- rust/tw_proto/build.rs | 2 +- rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2sh.rs | 2 +- rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs | 2 +- rust/wallet_core_rs/src/ffi/utils/bit_reader_ffi.rs | 6 +++--- 23 files changed, 37 insertions(+), 37 deletions(-) rename rust/chains/tw_binance/src/transaction/message/{tranfer_out_order.rs => transfer_out_order.rs} (100%) diff --git a/rust/chains/tw_binance/src/transaction/message/mod.rs b/rust/chains/tw_binance/src/transaction/message/mod.rs index 0879bcf5b3e..4917a852ca1 100644 --- a/rust/chains/tw_binance/src/transaction/message/mod.rs +++ b/rust/chains/tw_binance/src/transaction/message/mod.rs @@ -14,7 +14,7 @@ pub mod side_chain_delegate; pub mod time_lock_order; pub mod token_order; pub mod trade_order; -pub mod tranfer_out_order; +pub mod transfer_out_order; pub trait BinanceMessage { fn to_amino_protobuf(&self) -> SigningResult; @@ -29,7 +29,7 @@ pub trait TWBinanceProto: Sized { fn to_tw_proto(&self) -> Self::Proto<'static>; } -/// Please note that some of the fields are typped such as `SideDelegateOrder`. +/// Please note that some of the fields are typed such as `SideDelegateOrder`. #[derive(Deserialize, Serialize)] #[serde(untagged)] pub enum BinanceMessageEnum { @@ -52,7 +52,7 @@ pub enum BinanceMessageEnum { TokenBurnOrder(token_order::TokenBurnOrder), NewTradeOrder(trade_order::NewTradeOrder), CancelTradeOrder(trade_order::CancelTradeOrder), - TransferOutOrder(tranfer_out_order::TransferOutOrder), + TransferOutOrder(transfer_out_order::TransferOutOrder), } impl TWBinanceProto for BinanceMessageEnum { @@ -107,7 +107,7 @@ impl TWBinanceProto for BinanceMessageEnum { .map(BinanceMessageEnum::TokenBurnOrder) }, BinanceMessageProto::transfer_out_order(ref order) => { - tranfer_out_order::TransferOutOrder::from_tw_proto(coin, order) + transfer_out_order::TransferOutOrder::from_tw_proto(coin, order) .map(BinanceMessageEnum::TransferOutOrder) }, BinanceMessageProto::side_delegate_order(ref order) => { diff --git a/rust/chains/tw_binance/src/transaction/message/tranfer_out_order.rs b/rust/chains/tw_binance/src/transaction/message/transfer_out_order.rs similarity index 100% rename from rust/chains/tw_binance/src/transaction/message/tranfer_out_order.rs rename to rust/chains/tw_binance/src/transaction/message/transfer_out_order.rs diff --git a/rust/chains/tw_bitcoin/tests/babylon_staking.rs b/rust/chains/tw_bitcoin/tests/babylon_staking.rs index 801d7cfc4fe..5ff5a947ae6 100644 --- a/rust/chains/tw_bitcoin/tests/babylon_staking.rs +++ b/rust/chains/tw_bitcoin/tests/babylon_staking.rs @@ -35,12 +35,12 @@ fn test_babylon_scripts() { let expected = &test["expected"]; let covenant_public_keys = parse_pks(¶ms["covenant_public_keys"]); - let convenant_quorum = params["covenant_quorum"].as_u64().unwrap() as u32; + let covenant_quorum = params["covenant_quorum"].as_u64().unwrap() as u32; let finality_provider_public_keys = parse_pks(¶ms["finality_provider_public_keys"]); let staker_public_key = parse_pk(¶ms["staker_public_key"]); let staker_time = params["staking_time"].as_u64().unwrap() as u16; - let covenants = MultiSigOrderedKeys::new(covenant_public_keys, convenant_quorum).unwrap(); + let covenants = MultiSigOrderedKeys::new(covenant_public_keys, covenant_quorum).unwrap(); let finality_providers = MultiSigOrderedKeys::new(finality_provider_public_keys, 1).unwrap(); diff --git a/rust/chains/tw_bitcoin/tests/data/babylon_staking_transactions.json b/rust/chains/tw_bitcoin/tests/data/babylon_staking_transactions.json index eab9db9f383..79672e38438 100644 --- a/rust/chains/tw_bitcoin/tests/data/babylon_staking_transactions.json +++ b/rust/chains/tw_bitcoin/tests/data/babylon_staking_transactions.json @@ -33,7 +33,7 @@ } }, { - "name": "1 finality key, 3/5 covenant committe, 1 staker key with op_return", + "name": "1 finality key, 3/5 covenant committee, 1 staker key with op_return", "parameters": { "covenant_public_keys": [ "02cc5c77da065c490a320834fdcf2c3da70ecd442054c90f874a1edb4669607b83", @@ -70,7 +70,7 @@ } }, { - "name": "3 finality keys, 3/5 covenant committe, 1 staker key with no op_return", + "name": "3 finality keys, 3/5 covenant committee, 1 staker key with no op_return", "parameters": { "covenant_public_keys": [ "02042916c9cd52cfa146118c37b6b118f082bb50fb91da1c51b76dfc2100e66f00", @@ -109,7 +109,7 @@ } }, { - "name": "1 finality keys, 7/9 covenant committe, 1 staker key with op_return", + "name": "1 finality keys, 7/9 covenant committee, 1 staker key with op_return", "parameters": { "covenant_public_keys": [ "0287ed5bb2d036baf209eb49520327f6bd05285dabd30c97f239c3a69ff419950b", @@ -150,7 +150,7 @@ } }, { - "name": "10 finality keys, 18/20 covenant committe, 1 staker key with no op_return", + "name": "10 finality keys, 18/20 covenant committee, 1 staker key with no op_return", "parameters": { "covenant_public_keys": [ "02e3803a6ecff76daf35709c8484f382783d211970f22397d7a258f40ca3b46304", diff --git a/rust/chains/tw_bitcoincash/src/cash_address/checksum.rs b/rust/chains/tw_bitcoincash/src/cash_address/checksum.rs index dd9fbf6c13a..afc87af1ae1 100644 --- a/rust/chains/tw_bitcoincash/src/cash_address/checksum.rs +++ b/rust/chains/tw_bitcoincash/src/cash_address/checksum.rs @@ -19,7 +19,7 @@ pub fn calculate_checksum(prefix: &str, payload: &[u8]) -> u64 { poly_mod(&raw_data) } -pub fn cacl_and_append_checksum(prefix: &str, payload: &[u8]) -> Data { +pub fn calc_and_append_checksum(prefix: &str, payload: &[u8]) -> Data { // The checksum sits in the last eight bytes. // Append the phantom checksum to calculate an actual value. let mut payload_with_checksum: Vec<_> = diff --git a/rust/chains/tw_bitcoincash/src/cash_address/mod.rs b/rust/chains/tw_bitcoincash/src/cash_address/mod.rs index 96bc91e0815..d2211724bc3 100644 --- a/rust/chains/tw_bitcoincash/src/cash_address/mod.rs +++ b/rust/chains/tw_bitcoincash/src/cash_address/mod.rs @@ -100,7 +100,7 @@ impl CashAddress { bech32::convert_bits(&payload, from, to, pad).map_err(|_| AddressError::InvalidInput)? }; - let payload_with_checksum = checksum::cacl_and_append_checksum(hrp, &payload_u5); + let payload_with_checksum = checksum::calc_and_append_checksum(hrp, &payload_u5); let encoded_payload = cash_base32::encode(&payload_with_checksum).map_err(|_| AddressError::InvalidInput)?; Ok(format!("{hrp}:{encoded_payload}")) diff --git a/rust/chains/tw_decred/src/modules/decred_sighash.rs b/rust/chains/tw_decred/src/modules/decred_sighash.rs index 315bfbb7646..5682dfbc204 100644 --- a/rust/chains/tw_decred/src/modules/decred_sighash.rs +++ b/rust/chains/tw_decred/src/modules/decred_sighash.rs @@ -19,10 +19,10 @@ impl DecredSighash { let mut tx_preimage = tx.clone(); let inputs_to_preimage = LegacySighash::inputs_for_preimage(&tx_preimage, args)?; - let outpus_to_preimage = LegacySighash::outputs_for_preimage(&tx_preimage, args); + let outputs_to_preimage = LegacySighash::outputs_for_preimage(&tx_preimage, args); tx_preimage.replace_inputs(inputs_to_preimage); - tx_preimage.replace_outputs(outpus_to_preimage); + tx_preimage.replace_outputs(outputs_to_preimage); let prefix_hash = Self::tx_prefix_hash(&tx_preimage); let witness_hash = Self::tx_witness_hash(&tx_preimage); diff --git a/rust/chains/tw_ripple/src/definitions.rs b/rust/chains/tw_ripple/src/definitions.rs index 16b7cbfd594..eba1a3d9c50 100644 --- a/rust/chains/tw_ripple/src/definitions.rs +++ b/rust/chains/tw_ripple/src/definitions.rs @@ -5,7 +5,7 @@ use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use tw_misc::serde::hashmap_as_tupple_list; +use tw_misc::serde::hashmap_as_tuple_list; pub const DEFINITIONS_JSON: &str = include_str!("../definitions/definitions.json"); @@ -39,7 +39,7 @@ pub struct FieldInfo { #[serde(rename_all = "UPPERCASE")] pub struct Definitions { pub types: HashMap, - #[serde(with = "hashmap_as_tupple_list")] + #[serde(with = "hashmap_as_tuple_list")] pub fields: HashMap, pub transaction_types: HashMap, } diff --git a/rust/chains/tw_solana/src/modules/tx_signer.rs b/rust/chains/tw_solana/src/modules/tx_signer.rs index 4358161c0d5..2ca63f928a2 100644 --- a/rust/chains/tw_solana/src/modules/tx_signer.rs +++ b/rust/chains/tw_solana/src/modules/tx_signer.rs @@ -23,7 +23,7 @@ impl TxSigner { let message_encoded = Self::preimage_versioned(&unsigned_msg)?; - // Add external signatures first, so they can be overriden if corresponding private keys are specified. + // Add external signatures first, so they can be overridden if corresponding private keys are specified. key_signs.extend(external_signatures.clone()); // Sign the message with all given private keys. diff --git a/rust/frameworks/tw_utxo/src/transaction/asset/brc20.rs b/rust/frameworks/tw_utxo/src/transaction/asset/brc20.rs index e98371822f3..c33b9aaa757 100644 --- a/rust/frameworks/tw_utxo/src/transaction/asset/brc20.rs +++ b/rust/frameworks/tw_utxo/src/transaction/asset/brc20.rs @@ -71,7 +71,7 @@ mod tests { let ticker = Brc20Ticker::new("asdf".to_string()); assert!(ticker.is_ok()); - // Cover clone implemenation. + // Cover clone implementation. let ticker = ticker.unwrap(); let _cloned = ticker.clone(); diff --git a/rust/frameworks/tw_utxo/src/transaction/asset/ordinal.rs b/rust/frameworks/tw_utxo/src/transaction/asset/ordinal.rs index 8add262aa48..a25c790b9bb 100644 --- a/rust/frameworks/tw_utxo/src/transaction/asset/ordinal.rs +++ b/rust/frameworks/tw_utxo/src/transaction/asset/ordinal.rs @@ -58,7 +58,7 @@ fn create_envelope( .push_slice(b"ord") // Separator. .push_opcode(OP_PUSHBYTES_1) - // MIME types require this addtional push. It seems that the original + // MIME types require this additional push. It seems that the original // creator inadvertently used `push_slice(&[1])`, which leads to // `<1><1>`, which denotes a length prefix followed by the value. On the // other hand, for the data, `push_slice(&[])` is used, producing `<0>`. diff --git a/rust/tw_any_coin/src/ffi/tw_any_address.rs b/rust/tw_any_coin/src/ffi/tw_any_address.rs index 4d239208ae0..323336aff60 100644 --- a/rust/tw_any_coin/src/ffi/tw_any_address.rs +++ b/rust/tw_any_coin/src/ffi/tw_any_address.rs @@ -123,7 +123,7 @@ pub unsafe extern "C" fn tw_any_address_create_with_string( /// Creates an address from a public key and derivation option. /// -/// \param public_key derivates the address from the public key. +/// \param public_key derives the address from the public key. /// \param coin coin type of the address. /// \param derivation the custom derivation to use. /// \return `TWAnyAddress` pointer or nullptr if public key is invalid. @@ -145,7 +145,7 @@ pub unsafe extern "C" fn tw_any_address_create_with_public_key_derivation( /// Creates an bech32 address from a public key and a given hrp. /// -/// \param public_key derivates the address from the public key. +/// \param public_key derives the address from the public key. /// \param coin coin type of the address. /// \param hrp hrp of the address. /// \return TWAnyAddress pointer or nullptr if public key is invalid. @@ -174,7 +174,7 @@ pub unsafe extern "C" fn tw_any_address_create_bech32_with_public_key( /// Creates an Base58 Bitcoin address from a public key and a given hrp. /// -/// \param public_key derivates the address from the public key. +/// \param public_key derives the address from the public key. /// \param coin coin type of the address. /// \param p2pkh pay-to-public-key-hash address prefix. /// \param p2sh pay-to-script-hash address prefix. @@ -202,7 +202,7 @@ pub unsafe extern "C" fn tw_any_address_create_base58_with_public_key( /// Creates an SS58 Substrate address from a public key and a given ss58 prefix. /// -/// \param public_key derivates the address from the public key. +/// \param public_key derives the address from the public key. /// \param coin coin type of the address. /// \param ss58 SS58 address prefix. /// \return TWAnyAddress pointer or nullptr if public key is invalid. diff --git a/rust/tw_base58_address/src/lib.rs b/rust/tw_base58_address/src/lib.rs index 7a3949d0236..6f4d6a1a377 100644 --- a/rust/tw_base58_address/src/lib.rs +++ b/rust/tw_base58_address/src/lib.rs @@ -85,7 +85,7 @@ impl fmt::Display // } // /// Deserializes a `Base58Address` with Bitcoin alphabet. -// pub fn deserialize_with_bitcoin_alph<'de, const SIZE: usize, const CHECKSUM_SIZE: usize, D>( +// pub fn deserialize_with_bitcoin_alpha<'de, const SIZE: usize, const CHECKSUM_SIZE: usize, D>( // deserializer: D, // ) -> Result, D::Error> // where diff --git a/rust/tw_coin_entry/src/prefix.rs b/rust/tw_coin_entry/src/prefix.rs index 8b3a3618802..a1de1a62676 100644 --- a/rust/tw_coin_entry/src/prefix.rs +++ b/rust/tw_coin_entry/src/prefix.rs @@ -13,7 +13,7 @@ pub enum AddressPrefix { SubstrateNetwork(u16), } -/// A blockchain's address prefix should be convertable from an `AddressPrefix`. +/// A blockchain's address prefix should be convertible from an `AddressPrefix`. pub trait Prefix: TryFrom {} impl Prefix for T where T: TryFrom {} diff --git a/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs b/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs index 88fb612fb00..b346eeae5be 100644 --- a/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs +++ b/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs @@ -27,7 +27,7 @@ pub struct TestInput<'a> { pub struct TestCompileInput<'a> { pub coin: &'a dyn CoinContext, pub input: Proto::SigningInput<'a>, - /// Either a stringified JSON object or a hex-encoded serialzied `SignDoc`. + /// Either a stringified JSON object or a hex-encoded serialized `SignDoc`. pub tx_preimage: &'a str, /// Expected transaction preimage hash. pub tx_prehash: &'a str, diff --git a/rust/tw_evm/src/abi/encode.rs b/rust/tw_evm/src/abi/encode.rs index 5ab9acd7068..dbb3ee44025 100644 --- a/rust/tw_evm/src/abi/encode.rs +++ b/rust/tw_evm/src/abi/encode.rs @@ -719,7 +719,7 @@ mod tests { #[test] fn test_pad_u32() { - // this will fail if endianess is not supported + // this will fail if endianness is not supported assert_eq!(pad_u32(0x1)[31], 1); assert_eq!(pad_u32(0x100)[30], 1); } diff --git a/rust/tw_evm/src/abi/param_type/writer.rs b/rust/tw_evm/src/abi/param_type/writer.rs index d0ddcf64af4..ff22b84b484 100644 --- a/rust/tw_evm/src/abi/param_type/writer.rs +++ b/rust/tw_evm/src/abi/param_type/writer.rs @@ -8,7 +8,7 @@ use crate::abi::param_type::ParamType; pub struct Writer; impl Writer { - /// Returns string which is a formatted represenation of param. + /// Returns string which is a formatted representation of param. pub fn write(param: &ParamType) -> String { Writer::write_for_abi(param, true) } diff --git a/rust/tw_keypair/src/ffi/privkey.rs b/rust/tw_keypair/src/ffi/privkey.rs index 49436e1a2cb..6451e9c958e 100644 --- a/rust/tw_keypair/src/ffi/privkey.rs +++ b/rust/tw_keypair/src/ffi/privkey.rs @@ -76,7 +76,7 @@ pub unsafe extern "C" fn tw_private_key_size(data: *const TWPrivateKey) -> usize /// /// \param key *non-null* byte array. /// \param key_len the length of the `key` array. -/// \param curve Eliptic curve of the private key. +/// \param curve Elliptic curve of the private key. /// \return true if the private key is valid, false otherwise. #[no_mangle] pub unsafe extern "C" fn tw_private_key_is_valid( @@ -94,7 +94,7 @@ pub unsafe extern "C" fn tw_private_key_is_valid( /// \param key *non-null* pointer to a Private key /// \param message *non-null* byte array. /// \param message_len the length of the `input` array. -/// \param curve Eliptic curve. +/// \param curve Elliptic curve. /// \return Signature as a C-compatible result with a C-compatible byte array. #[no_mangle] pub unsafe extern "C" fn tw_private_key_sign( diff --git a/rust/tw_misc/src/serde.rs b/rust/tw_misc/src/serde.rs index 8df44c675f5..721e3396960 100644 --- a/rust/tw_misc/src/serde.rs +++ b/rust/tw_misc/src/serde.rs @@ -44,7 +44,7 @@ macro_rules! deserialize_from_str { }; } -pub mod hashmap_as_tupple_list { +pub mod hashmap_as_tuple_list { use serde::de::{SeqAccess, Visitor}; use serde::ser::SerializeSeq; use serde::{Deserialize, Deserializer, Serialize, Serializer}; diff --git a/rust/tw_proto/build.rs b/rust/tw_proto/build.rs index db64fb9f778..2b53c99b4de 100644 --- a/rust/tw_proto/build.rs +++ b/rust/tw_proto/build.rs @@ -70,7 +70,7 @@ fn main() { } /// Unfortunately, `pb-rs` does not provide a proper support of custom derives. -/// [`ConfigBuilder::custom_struct_derive`] adds the derive macroses for structs only, +/// [`ConfigBuilder::custom_struct_derive`] adds the derive macros for structs only, /// however they should be added for enums too. /// Issues: https://github.com/tafia/quick-protobuf/issues/195, https://github.com/tafia/quick-protobuf/issues/212 #[cfg(feature = "fuzz")] diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2sh.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2sh.rs index 06a4ae11b45..a491d9f03ae 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2sh.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2sh.rs @@ -162,7 +162,7 @@ fn test_bitcoin_sign_output_p2sh_with_address() { // // let sig = &signatures[0]; // -// // Construc the final redeem scrip with the necessary stack items (signature + pubkey). +// // Construct the final redeem scrip with the necessary stack items (signature + pubkey). // let mut sig_buf = PushBytesBuf::new(); // sig_buf.extend_from_slice(sig).unwrap(); // diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs index 80032341cfd..4b85dc2f5c6 100644 --- a/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs @@ -179,7 +179,7 @@ fn test_polymesh_encode_and_sign() { helper_encode_and_compile(CoinType::Polymesh, input, signature, public_key, true); assert_eq!(preimage, "050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f2050414444454420574954482053504143455320202020202020202025010400c20b0000020000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); - // This signed tranaction is different from the original C++ test, but matches the transaction on Polymesh. + // This signed transaction is different from the original C++ test, but matches the transaction on Polymesh. assert_eq!(signed, "bd0284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee000791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f25010400050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f20504144444544205749544820535041434553202020202020202020"); } diff --git a/rust/wallet_core_rs/src/ffi/utils/bit_reader_ffi.rs b/rust/wallet_core_rs/src/ffi/utils/bit_reader_ffi.rs index e9fcc925635..d4be35f0efb 100644 --- a/rust/wallet_core_rs/src/ffi/utils/bit_reader_ffi.rs +++ b/rust/wallet_core_rs/src/ffi/utils/bit_reader_ffi.rs @@ -21,7 +21,7 @@ pub enum CBitReaderCode { /// Requested more bits than the returned variable can hold, for example more than 8 bits when /// reading into a u8. TooManyBitsForType = 2, - InalidInput = 3, + InvalidInput = 3, } impl From for CBitReaderCode { @@ -126,7 +126,7 @@ pub unsafe extern "C" fn tw_bit_reader_read_u8( ) -> CUInt8Result { let tw_reader = try_or_else!( TWBitReader::from_ptr_as_mut(reader), - || CUInt8Result::error(CBitReaderCode::InalidInput) + || CUInt8Result::error(CBitReaderCode::InvalidInput) ); tw_reader.read_u8(bit_count).into() } @@ -143,7 +143,7 @@ pub unsafe extern "C" fn tw_bit_reader_read_u8_slice( byte_count: usize, ) -> CByteArrayResult { let tw_reader = try_or_else!(TWBitReader::from_ptr_as_mut(reader), || { - CByteArrayResult::error(CBitReaderCode::InalidInput) + CByteArrayResult::error(CBitReaderCode::InvalidInput) }); tw_reader .read_u8_slice(byte_count) From ad589931678fa61a10bc73a72ba8dc1b5c20f822 Mon Sep 17 00:00:00 2001 From: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Date: Fri, 11 Apr 2025 09:12:09 +0200 Subject: [PATCH 21/72] fix: typos in documentation files (#4281) * Update properties.rs * Update functions.rs --------- Co-authored-by: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> --- codegen-v2/src/codegen/swift/functions.rs | 2 +- codegen-v2/src/codegen/swift/properties.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen-v2/src/codegen/swift/functions.rs b/codegen-v2/src/codegen/swift/functions.rs index 1f7e4aa8d39..ec7888eca4f 100644 --- a/codegen-v2/src/codegen/swift/functions.rs +++ b/codegen-v2/src/codegen/swift/functions.rs @@ -28,7 +28,7 @@ pub(super) fn process_methods( let mut ops = vec![]; - // Initalize the 'self' type, which is then passed on to the underlying + // Initialize the 'self' type, which is then passed on to the underlying // C FFI function, assuming the function is not static. // // E.g: diff --git a/codegen-v2/src/codegen/swift/properties.rs b/codegen-v2/src/codegen/swift/properties.rs index 61d73e7171f..44ba77ee0a9 100644 --- a/codegen-v2/src/codegen/swift/properties.rs +++ b/codegen-v2/src/codegen/swift/properties.rs @@ -28,7 +28,7 @@ pub(super) fn process_properties( let mut ops = vec![]; - // Initalize the 'self' type, which is then passed on to the underlying + // Initialize the 'self' type, which is then passed on to the underlying // C FFI function. ops.push(match object { // E.g. `let obj = self.rawValue` From 4eaaa60c7ede2c658d18d0840b8356508939d2c0 Mon Sep 17 00:00:00 2001 From: gupnik Date: Fri, 11 Apr 2025 15:53:46 +0530 Subject: [PATCH 22/72] Renames Swift Package to WalletCoreSwiftProtobuf (#4352) * Renames Swift Package to WalletCoreSwiftProtobuf * Fixes build * Use SwiftProtobuf in podspec * Updates playground * Trigger Build * Updates podspec * Use WalletCoreSwiftProtobuf Podspec * Updates Podspec * Fixes deprecated API usage --------- Co-authored-by: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> --- Package.swift | 4 ++-- README.md | 2 +- WalletCoreSwiftProtobuf.podspec | 22 ++++++++++++++++++++++ swift/Playground.playground/Contents.swift | 2 +- swift/Podfile | 2 +- swift/Podfile.lock | 10 +++++----- swift/Sources/AnySigner.swift | 6 +++--- swift/fastlane/Fastfile | 2 +- tools/generate-files | 3 +++ tools/ios-release | 2 +- tools/ios-xcframework | 2 +- tools/ios-xcframework-release | 12 ++++++------ 12 files changed, 47 insertions(+), 22 deletions(-) create mode 100644 WalletCoreSwiftProtobuf.podspec diff --git a/Package.swift b/Package.swift index 16f845ca81f..3278a4e87dd 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ let package = Package( platforms: [.iOS(.v13)], products: [ .library(name: "WalletCore", targets: ["WalletCore"]), - .library(name: "SwiftProtobuf", targets: ["SwiftProtobuf"]) + .library(name: "WalletCoreSwiftProtobuf", targets: ["WalletCoreSwiftProtobuf"]) ], dependencies: [], targets: [ @@ -16,7 +16,7 @@ let package = Package( checksum: "651894a9418fdd33ae5374367a6a64a57fa92b6e6ffb2d6723c319da97472cb4" ), .binaryTarget( - name: "SwiftProtobuf", + name: "WalletCoreSwiftProtobuf", url: "https://github.com/trustwallet/wallet-core/releases/download/4.2.9/SwiftProtobuf.xcframework.zip", checksum: "946efd4b0132b92208335902e0b65e0aba2d11b9dd6f6d79cc8318e2530c9ae0" ) diff --git a/README.md b/README.md index 7456046acbd..06ce58f529f 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Then add libraries to target's `dependencies`: ```swift .product(name: "WalletCore", package: "WalletCore"), -.product(name: "SwiftProtobuf", package: "WalletCore"), +.product(name: "WalletCoreSwiftProtobuf", package: "WalletCore"), ``` ### CocoaPods diff --git a/WalletCoreSwiftProtobuf.podspec b/WalletCoreSwiftProtobuf.podspec new file mode 100644 index 00000000000..135ef9092be --- /dev/null +++ b/WalletCoreSwiftProtobuf.podspec @@ -0,0 +1,22 @@ +# Original podspec: +# https://github.com/apple/swift-protobuf/blob/main/SwiftProtobuf.podspec +Pod::Spec.new do |s| + s.name = 'WalletCoreSwiftProtobuf' + s.version = '1.29.0' + s.license = { :type => 'Apache 2.0', :file => 'LICENSE.txt' } + s.summary = 'Swift Protobuf Runtime Library' + s.homepage = 'https://github.com/apple/swift-protobuf' + s.author = 'Apple Inc.' + s.source = { :git => 'https://github.com/apple/swift-protobuf.git', :tag => s.version } + + s.requires_arc = true + s.ios.deployment_target = '11.0' + s.osx.deployment_target = '10.13' + + s.cocoapods_version = '>= 1.13.0' + + s.source_files = 'Sources/SwiftProtobuf/**/*.swift' + s.resource_bundle = {'WalletCoreSwiftProtobuf' => ['Sources/SwiftProtobuf/PrivacyInfo.xcprivacy']} + + s.swift_versions = ['5.0'] +end diff --git a/swift/Playground.playground/Contents.swift b/swift/Playground.playground/Contents.swift index 4c886c35bc6..ef8d1a6c62b 100644 --- a/swift/Playground.playground/Contents.swift +++ b/swift/Playground.playground/Contents.swift @@ -4,7 +4,7 @@ import UIKit import TrustWalletCore -import SwiftProtobuf +import WalletCoreSwiftProtobuf enum PlaygroundError: Error { case invalidHexString diff --git a/swift/Podfile b/swift/Podfile index ab704127b08..e1f840eb626 100644 --- a/swift/Podfile +++ b/swift/Podfile @@ -9,7 +9,7 @@ project 'TrustWalletCore.xcodeproj' target 'WalletCore' do use_frameworks! - pod 'SwiftProtobuf' + pod 'WalletCoreSwiftProtobuf' target 'WalletCoreTests' end diff --git a/swift/Podfile.lock b/swift/Podfile.lock index 88093ba1a0d..c8cb8c24b66 100644 --- a/swift/Podfile.lock +++ b/swift/Podfile.lock @@ -1,16 +1,16 @@ PODS: - - SwiftProtobuf (1.13.0) + - WalletCoreSwiftProtobuf (1.29.0) DEPENDENCIES: - - SwiftProtobuf + - WalletCoreSwiftProtobuf SPEC REPOS: trunk: - - SwiftProtobuf + - WalletCoreSwiftProtobuf SPEC CHECKSUMS: - SwiftProtobuf: fd4693388a96c8c2df35d3b063272b0e7c499d00 + WalletCoreSwiftProtobuf: a4798576a2d309511fc45f81843d348732ec571d -PODFILE CHECKSUM: aac2324ba35cdd5631cb37618cd483887bab9cfd +PODFILE CHECKSUM: 1112f54f83017d2c0c1d9d4bf5c21f65c5187d0e COCOAPODS: 1.16.2 diff --git a/swift/Sources/AnySigner.swift b/swift/Sources/AnySigner.swift index 5abf730b95d..d308aa06b34 100644 --- a/swift/Sources/AnySigner.swift +++ b/swift/Sources/AnySigner.swift @@ -5,7 +5,7 @@ // file LICENSE at the root of the source code distribution tree. import Foundation -import SwiftProtobuf +import WalletCoreSwiftProtobuf public typealias SigningInput = Message public typealias SigningOutput = Message @@ -22,7 +22,7 @@ public final class AnySigner { public static func sign(input: SigningInput, coin: CoinType) -> SigningOutput { do { let outputData = nativeSign(data: try input.serializedData(), coin: coin) - return try SigningOutput(serializedData: outputData) + return try SigningOutput(serializedBytes: outputData) } catch let error { fatalError(error.localizedDescription) } @@ -72,7 +72,7 @@ public final class AnySigner { public static func plan(input: SigningInput, coin: CoinType) -> TransactionPlan { do { let outputData = nativePlan(data: try input.serializedData(), coin: coin) - return try TransactionPlan(serializedData: outputData) + return try TransactionPlan(serializedBytes: outputData) } catch let error { fatalError(error.localizedDescription) } diff --git a/swift/fastlane/Fastfile b/swift/fastlane/Fastfile index 5fa18ea4945..b22a40e6cc9 100644 --- a/swift/fastlane/Fastfile +++ b/swift/fastlane/Fastfile @@ -23,7 +23,7 @@ platform :ios do lane :swift_protobuf_xcframework do create_xcframework( workspace: 'TrustWalletCore.xcworkspace', - scheme: 'SwiftProtobuf', + scheme: 'WalletCoreSwiftProtobuf', destinations: ['iOS'], xcframework_output_directory: 'build', enable_bitcode: false diff --git a/tools/generate-files b/tools/generate-files index a987d9c19f6..44577345a57 100755 --- a/tools/generate-files +++ b/tools/generate-files @@ -69,6 +69,9 @@ tools/doxygen_convert_comments if [ -x "$(command -v protoc-gen-swift)" ] && isTargetSpecified "ios"; then "$PROTOC" -I=$PREFIX/include -I=src/proto --cpp_out=src/proto --java_out=lite:jni/proto --swift_out=swift/Sources/Generated/Protobuf --swift_opt=Visibility=Public src/proto/*.proto + # Replace SwiftProtobuf with WalletCoreSwiftProtobuf + find swift/Sources/Generated/Protobuf -name "*.swift" -exec sed -i '' 's/import SwiftProtobuf/import WalletCoreSwiftProtobuf/g' {} \; + find swift/Sources/Generated/Protobuf -name "*.swift" -exec sed -i '' 's/SwiftProtobuf./WalletCoreSwiftProtobuf./g' {} \; else "$PROTOC" -I=$PREFIX/include -I=src/proto --cpp_out=src/proto --java_out=lite:jni/proto src/proto/*.proto fi diff --git a/tools/ios-release b/tools/ios-release index 2fce33e89ad..a8462222e82 100755 --- a/tools/ios-release +++ b/tools/ios-release @@ -64,7 +64,7 @@ Pod::Spec.new do |s| 'Sources/Types/*.swift', 'Sources/Generated/Enums/*.swift', 'Sources/Generated/Protobuf/*.swift' - ss.dependency 'SwiftProtobuf' + ss.dependency 'WalletCoreSwiftProtobuf' end s.subspec 'Core' do |ss| ss.vendored_frameworks = '*.xcframework' diff --git a/tools/ios-xcframework b/tools/ios-xcframework index d5d667f4b14..87acf8bc772 100755 --- a/tools/ios-xcframework +++ b/tools/ios-xcframework @@ -1,6 +1,6 @@ #!/bin/bash # -# This script builds dynamic WalletCore and SwiftProtobuf xcframework for SPM and CocoaPods +# This script builds dynamic WalletCore and WalletCoreSwiftProtobuf xcframework for SPM and CocoaPods # set -e diff --git a/tools/ios-xcframework-release b/tools/ios-xcframework-release index b00519f183f..3097364c046 100755 --- a/tools/ios-xcframework-release +++ b/tools/ios-xcframework-release @@ -16,8 +16,8 @@ pushd ${base_dir}/../swift/build # archive xcframeworks core_zip_filename="WalletCore.xcframework.zip" core_dsyms_filename="WalletCore.xcframework.dSYM.zip" -protobuf_zip_filename="SwiftProtobuf.xcframework.zip" -protobuf_dsyms_filename="SwiftProtobuf.xcframework.dSYM.zip" +protobuf_zip_filename="WalletCoreSwiftProtobuf.xcframework.zip" +protobuf_dsyms_filename="WalletCoreSwiftProtobuf.xcframework.dSYM.zip" rm -rf ${core_zip_filename} ${core_dsyms_filename} ${protobuf_zip_filename} ${protobuf_dsyms_filename} @@ -26,9 +26,9 @@ zip -r ${core_dsyms_filename} WalletCore.dSYMs zip -r ${core_zip_filename} WalletCore.xcframework core_hash=$(/usr/bin/shasum -a 256 ${core_zip_filename} | awk '{printf $1}') -zip -r ${protobuf_dsyms_filename} SwiftProtobuf.dSYMs +zip -r ${protobuf_dsyms_filename} WalletCoreSwiftProtobuf.dSYMs -zip -r ${protobuf_zip_filename} SwiftProtobuf.xcframework +zip -r ${protobuf_zip_filename} WalletCoreSwiftProtobuf.xcframework protobuf_hash=$(/usr/bin/shasum -a 256 ${protobuf_zip_filename} | awk '{printf $1}') # upload to release @@ -52,7 +52,7 @@ let package = Package( platforms: [.iOS(.v13)], products: [ .library(name: "WalletCore", targets: ["WalletCore"]), - .library(name: "SwiftProtobuf", targets: ["SwiftProtobuf"]) + .library(name: "WalletCoreSwiftProtobuf", targets: ["WalletCoreSwiftProtobuf"]) ], dependencies: [], targets: [ @@ -62,7 +62,7 @@ let package = Package( checksum: "${core_hash}" ), .binaryTarget( - name: "SwiftProtobuf", + name: "WalletCoreSwiftProtobuf", url: ${protobuf_download_url}, checksum: "${protobuf_hash}" ) From a75afa84e43d7c332be5687a9acf247f6ddf3621 Mon Sep 17 00:00:00 2001 From: b00f Date: Fri, 11 Apr 2025 18:24:26 +0800 Subject: [PATCH 23/72] feat(pactus): support testnet address derivation (#4330) * feat(pactus): support testnet address derivation * test(pactus): fix KT broken tests * test(pactus): fix KT broken tests * feat(pactus): support validator string for testnet * chore: derivation name in YAML file in camelCase --- .../core/app/blockchains/TestCoinType.kt | 28 +++++++- .../blockchains/pactus/TestPactusAddress.kt | 13 +++- .../core/app/utils/TestHDWallet.kt | 51 +++++++++++++-- codegen-v2/manifest/TWDerivation.yaml | 8 +++ include/TrustWalletCore/TWDerivation.h | 2 + registry.json | 5 ++ rust/chains/tw_pactus/src/entry.rs | 10 ++- rust/chains/tw_pactus/src/types/address.rs | 42 ++++++++---- rust/chains/tw_pactus/src/types/mod.rs | 1 + rust/chains/tw_pactus/src/types/network.rs | 64 +++++++++++++++++++ .../src/types/validator_public_key.rs | 10 ++- rust/tw_coin_registry/src/tw_derivation.rs | 4 ++ swift/Tests/CoinTypeTests.swift | 18 +++++- swift/Tests/HDWalletTests.swift | 54 ++++++++++++---- swift/Tests/Keystore/KeyStoreTests.swift | 14 +++- tests/chains/Pactus/WalletTests.cpp | 25 +++++++- tests/common/Keystore/DerivationPathTests.cpp | 6 ++ tests/common/Keystore/StoredKeyTests.cpp | 37 +++++++++-- tests/interface/TWAnyAddressTests.cpp | 22 ++++++- tests/interface/TWCoinTypeTests.cpp | 21 ++++++ wasm/tests/CoinType.test.ts | 22 +++++++ 21 files changed, 408 insertions(+), 49 deletions(-) create mode 100644 rust/chains/tw_pactus/src/types/network.rs diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt index abbc87b7c2f..98f46092a88 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt @@ -36,20 +36,23 @@ class TestCoinType { assertEquals(CoinType.TEZOS.value(), 1729) assertEquals(CoinType.QTUM.value(), 2301) assertEquals(CoinType.NEBULAS.value(), 2718) + assertEquals(CoinType.PACTUS.value(), 21888) } @Test fun testCoinPurpose() { assertEquals(Purpose.BIP84, CoinType.BITCOIN.purpose()) + assertEquals(Purpose.BIP44, CoinType.PACTUS.purpose()) } @Test fun testCoinCurve() { assertEquals(Curve.SECP256K1, CoinType.BITCOIN.curve()) + assertEquals(Curve.ED25519, CoinType.PACTUS.curve()) } @Test - fun testDerivationPath() { + fun testDerivationPathBitcoin() { var res = CoinType.createFromValue(CoinType.BITCOIN.value()).derivationPath().toString() assertEquals(res, "m/84'/0'/0'/0/0") res = CoinType.createFromValue(CoinType.BITCOIN.value()).derivationPathWithDerivation(Derivation.BITCOINLEGACY).toString() @@ -61,10 +64,31 @@ class TestCoinType { } @Test - fun testDeriveAddressFromPublicKeyAndDerivation() { + fun testDeriveAddressFromPublicKeyAndDerivationBitcoin() { val publicKey = PublicKey("0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798".toHexByteArray(), PublicKeyType.SECP256K1) val address = CoinType.BITCOIN.deriveAddressFromPublicKeyAndDerivation(publicKey, Derivation.BITCOINSEGWIT) assertEquals(address, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4") } + + @Test + fun testDerivationPathPactus() { + var res = CoinType.createFromValue(CoinType.PACTUS.value()).derivationPath().toString() + assertEquals(res, "m/44'/21888'/3'/0'") + res = CoinType.createFromValue(CoinType.PACTUS.value()).derivationPathWithDerivation(Derivation.PACTUSMAINNET).toString() + assertEquals(res, "m/44'/21888'/3'/0'") + res = CoinType.createFromValue(CoinType.PACTUS.value()).derivationPathWithDerivation(Derivation.PACTUSTESTNET).toString() + assertEquals(res, "m/44'/21777'/3'/0'") + } + + @Test + fun testDeriveAddressFromPublicKeyAndDerivationPactus() { + val publicKey = PublicKey("95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa".toHexByteArray(), PublicKeyType.ED25519) + + val mainnet_address = CoinType.PACTUS.deriveAddressFromPublicKeyAndDerivation(publicKey, Derivation.PACTUSMAINNET) + assertEquals(mainnet_address, "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr") + + val testnet_address = CoinType.PACTUS.deriveAddressFromPublicKeyAndDerivation(publicKey, Derivation.PACTUSTESTNET) + assertEquals(testnet_address, "tpc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymzqkcrg") + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt index b58b068b859..96e7217fc70 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt @@ -17,7 +17,7 @@ class TestPactusAddress { } @Test - fun testAddress() { + fun testMainnetAddress() { val key = PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6".toHexByteArray()) val pubkey = key.publicKeyEd25519 val address = AnyAddress(pubkey, CoinType.PACTUS) @@ -26,4 +26,15 @@ class TestPactusAddress { assertEquals(pubkey.data().toHex(), "0x95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa") assertEquals(address.description(), expected.description()) } + + @Test + fun testTestnetAddress() { + val key = PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6".toHexByteArray()) + val pubkey = key.publicKeyEd25519 + val address = AnyAddress(pubkey, CoinType.PACTUS, Derivation.PACTUSTESTNET) + val expected = AnyAddress("tpc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymzqkcrg", CoinType.PACTUS) + + assertEquals(pubkey.data().toHex(), "0x95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa") + assertEquals(address.description(), expected.description()) + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestHDWallet.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestHDWallet.kt index 9d165d95142..b4f3d147fb6 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestHDWallet.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestHDWallet.kt @@ -99,7 +99,7 @@ class TestHDWallet { } @Test - fun testGetKeyForCoin() { + fun testGetKeyForCoinBitcoin() { val coin = CoinType.BITCOIN val wallet = HDWallet(words, password) val key = wallet.getKeyForCoin(coin) @@ -109,7 +109,7 @@ class TestHDWallet { } @Test - fun testGetKeyDerivation() { + fun testGetKeyDerivationBitcoin() { val coin = CoinType.BITCOIN val wallet = HDWallet(words, password) @@ -127,7 +127,7 @@ class TestHDWallet { } @Test - fun testGetAddressForCoin() { + fun testGetAddressForCoinBitcoin() { val coin = CoinType.BITCOIN val wallet = HDWallet(words, password) @@ -136,7 +136,7 @@ class TestHDWallet { } @Test - fun testGetAddressDerivation() { + fun testGetAddressDerivationBitcoin() { val coin = CoinType.BITCOIN val wallet = HDWallet(words, password) @@ -153,6 +153,49 @@ class TestHDWallet { assertEquals(address4, "bc1pgqks0cynn93ymve4x0jq3u7hne77908nlysp289hc44yc4cmy0hslyckrz") } + @Test + fun testGetKeyForCoinPactus() { + val coin = CoinType.PACTUS + val wallet = HDWallet(words, password) + val key = wallet.getKeyForCoin(coin) + + val address = coin.deriveAddress(key) + assertEquals(address, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff") + } + + @Test + fun testGetKeyDerivationPactus() { + val coin = CoinType.PACTUS + val wallet = HDWallet(words, password) + + val key1 = wallet.getKeyDerivation(coin, Derivation.PACTUSMAINNET) + assertEquals(key1.data().toHex(), "0x153fefb8168f246f9f77c60ea10765c1c39828329e87284ddd316770717f3a5e") + + val key2 = wallet.getKeyDerivation(coin, Derivation.PACTUSTESTNET) + assertEquals(key2.data().toHex(), "0x54f3c54dd6af5794bea1f86de05b8b9f164215e8deee896f604919046399e54d") + } + + @Test + fun testGetAddressForCoinPactus() { + val coin = CoinType.PACTUS + val wallet = HDWallet(words, password) + + val address = wallet.getAddressForCoin(coin) + assertEquals(address, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff") + } + + @Test + fun testGetAddressDerivationPactus() { + val coin = CoinType.PACTUS + val wallet = HDWallet(words, password) + + val address1 = wallet.getAddressDerivation(coin, Derivation.PACTUSMAINNET) + assertEquals(address1, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff") + + val address2 = wallet.getAddressDerivation(coin, Derivation.PACTUSTESTNET) + assertEquals(address2, "tpc1rjtamyqp203j4367q4plkp4qt32d7sv34kfmj5e") + } + @Test fun testDerive() { val wallet = HDWallet(words, password) diff --git a/codegen-v2/manifest/TWDerivation.yaml b/codegen-v2/manifest/TWDerivation.yaml index c2143b80431..b8d1cb6f0b9 100644 --- a/codegen-v2/manifest/TWDerivation.yaml +++ b/codegen-v2/manifest/TWDerivation.yaml @@ -19,3 +19,11 @@ enums: value: 5 - name: solanaSolana value: 6 + - name: stratisSegwit + value: 7 + - name: bitcoinTaproot + value: 8 + - name: pactusMainnet + value: 9 + - name: pactusTestnet + value: 10 diff --git a/include/TrustWalletCore/TWDerivation.h b/include/TrustWalletCore/TWDerivation.h index 3437313e934..263f2710f96 100644 --- a/include/TrustWalletCore/TWDerivation.h +++ b/include/TrustWalletCore/TWDerivation.h @@ -25,6 +25,8 @@ enum TWDerivation { TWDerivationSolanaSolana = 6, TWDerivationStratisSegwit = 7, TWDerivationBitcoinTaproot = 8, + TWDerivationPactusMainnet = 9, + TWDerivationPactusTestnet = 10, // end_of_derivation_enum - USED TO GENERATE CODE }; diff --git a/registry.json b/registry.json index e53e559ab61..faca84e65ad 100644 --- a/registry.json +++ b/registry.json @@ -4827,7 +4827,12 @@ "blockchain": "Pactus", "derivation": [ { + "name": "mainnet", "path": "m/44'/21888'/3'/0'" + }, + { + "name": "testnet", + "path": "m/44'/21777'/3'/0'" } ], "curve": "ed25519", diff --git a/rust/chains/tw_pactus/src/entry.rs b/rust/chains/tw_pactus/src/entry.rs index e9d12f3ac8c..1e0344221a7 100644 --- a/rust/chains/tw_pactus/src/entry.rs +++ b/rust/chains/tw_pactus/src/entry.rs @@ -21,6 +21,7 @@ use tw_proto::TxCompiler::Proto as CompilerProto; use crate::compiler::PactusCompiler; use crate::modules::transaction_util::PactusTransactionUtil; use crate::signer::PactusSigner; +use crate::types::network::Network; use crate::types::Address; pub struct PactusEntry; @@ -60,13 +61,18 @@ impl CoinEntry for PactusEntry { &self, _coin: &dyn CoinContext, public_key: PublicKey, - _derivation: Derivation, + derivation: Derivation, _prefix: Option, ) -> AddressResult { let public_key = public_key .to_ed25519() .ok_or(AddressError::PublicKeyTypeMismatch)?; - Address::from_public_key(public_key) + + match derivation { + Derivation::Default => Address::from_public_key(public_key, Network::Mainnet), + Derivation::Testnet => Address::from_public_key(public_key, Network::Testnet), + _ => AddressResult::Err(AddressError::Unsupported), + } } #[inline] diff --git a/rust/chains/tw_pactus/src/types/address.rs b/rust/chains/tw_pactus/src/types/address.rs index 785e82c6639..aee9b1a6b7e 100644 --- a/rust/chains/tw_pactus/src/types/address.rs +++ b/rust/chains/tw_pactus/src/types/address.rs @@ -17,7 +17,8 @@ use tw_memory::Data; use crate::encoder::error::Error; use crate::encoder::{Decodable, Encodable}; -const ADDRESS_HRP: &str = "pc"; +use super::network::Network; + const TREASURY_ADDRESS_STRING: &str = "000000000000000000000000000000000000000000"; /// Enum for Pactus address types. @@ -66,18 +67,20 @@ impl Decodable for AddressType { /// The hash is computed as RIPEMD160(Blake2b(public key)). #[derive(Debug, Clone, PartialEq)] pub struct Address { + network: Network, addr_type: AddressType, pub_hash: H160, } impl Address { - pub fn from_public_key(public_key: &PublicKey) -> Result { + pub fn from_public_key(public_key: &PublicKey, network: Network) -> Result { let pud_data = public_key.to_bytes(); let pub_hash_data = ripemd_160(&blake2_b(pud_data.as_ref(), 32).map_err(|_| AddressError::Internal)?); let pub_hash = Address::vec_to_pub_hash(pub_hash_data)?; Ok(Address { + network, addr_type: AddressType::Ed25519Account, pub_hash, }) @@ -110,12 +113,12 @@ impl fmt::Display for Address { return f.write_str(TREASURY_ADDRESS_STRING); } + let hrp = self.network.address_hrp().map_err(|_| fmt::Error)?; let mut b32 = Vec::with_capacity(33); b32.push(bech32::u5::try_from_u8(self.addr_type.clone() as u8).map_err(|_| fmt::Error)?); b32.extend_from_slice(&self.pub_hash.to_vec().to_base32()); - bech32::encode_to_fmt(f, ADDRESS_HRP, &b32, bech32::Variant::Bech32m) - .map_err(|_| fmt::Error)? + bech32::encode_to_fmt(f, hrp, &b32, bech32::Variant::Bech32m).map_err(|_| fmt::Error)? } } @@ -146,6 +149,7 @@ impl Decodable for Address { let addr_type = AddressType::decode(r)?; if addr_type == AddressType::Treasury { return Ok(Address { + network: Network::Unknown, addr_type, pub_hash: H160::new(), }); @@ -153,6 +157,7 @@ impl Decodable for Address { let pub_hash = H160::decode(r)?; Ok(Address { + network: Network::Unknown, addr_type, pub_hash, }) @@ -165,16 +170,14 @@ impl FromStr for Address { fn from_str(s: &str) -> Result { if s == TREASURY_ADDRESS_STRING { return Ok(Address { + network: Network::Unknown, addr_type: AddressType::Treasury, pub_hash: H160::new(), }); } let (hrp, b32, _variant) = bech32::decode(s).map_err(|_| AddressError::FromBech32Error)?; - - if hrp != ADDRESS_HRP { - return Err(AddressError::InvalidHrp); - } + let network = Network::try_from_hrp(&hrp)?; if b32.len() != 33 { return Err(AddressError::InvalidInput); @@ -185,6 +188,7 @@ impl FromStr for Address { let pub_hash = Address::vec_to_pub_hash(b8)?; Ok(Address { + network, addr_type, pub_hash, }) @@ -241,12 +245,20 @@ mod test { .decode_hex() .unwrap(); - let addr = deserialize::
(&data).unwrap(); + let mut addr = deserialize::
(&data).unwrap(); assert!(!addr.is_treasury()); + + addr.network = Network::Mainnet; assert_eq!( addr.to_string(), "pc1rqqqsyqcyq5rqwzqfpg9scrgwpuqqzqsr36kkra" ); + + addr.network = Network::Testnet; + assert_eq!( + addr.to_string(), + "tpc1rqqqsyqcyq5rqwzqfpg9scrgwpuqqzqsrtuyllk" + ); } #[test] @@ -289,6 +301,7 @@ mod test { for case in test_cases { let pub_hash_data = case.pub_hash.decode_hex().unwrap(); let addr = Address { + network: Network::Mainnet, addr_type: case.addr_type, pub_hash: Address::vec_to_pub_hash(pub_hash_data).unwrap(), }; @@ -307,7 +320,7 @@ mod test { "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", ) .unwrap(); - let address = Address::from_public_key(&private_key.public()).unwrap(); + let address = Address::from_public_key(&private_key.public(), Network::Mainnet).unwrap(); let mut w = Vec::new(); address.encode(&mut w).unwrap(); @@ -323,13 +336,16 @@ mod test { .unwrap(); let private_key = PrivateKey::try_from(private_key_data.as_slice()).unwrap(); let public_key = private_key.public(); - let address = Address::from_public_key(&public_key).unwrap(); + let mainnet_address = Address::from_public_key(&public_key, Network::Mainnet).unwrap(); + let testnet_address = Address::from_public_key(&public_key, Network::Testnet).unwrap(); let expected_public_key = "95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa"; - let expected_address = "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr"; + let expected_mainnet_address = "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr"; + let expected_testnet_address = "tpc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymzqkcrg"; assert_eq!(public_key.to_bytes().to_hex(), expected_public_key); - assert_eq!(address.to_string(), expected_address); + assert_eq!(mainnet_address.to_string(), expected_mainnet_address); + assert_eq!(testnet_address.to_string(), expected_testnet_address); } } diff --git a/rust/chains/tw_pactus/src/types/mod.rs b/rust/chains/tw_pactus/src/types/mod.rs index ff66b91c4f4..d7ea9b78918 100644 --- a/rust/chains/tw_pactus/src/types/mod.rs +++ b/rust/chains/tw_pactus/src/types/mod.rs @@ -1,5 +1,6 @@ pub mod address; pub mod amount; +pub mod network; pub mod validator_public_key; pub use address::Address; diff --git a/rust/chains/tw_pactus/src/types/network.rs b/rust/chains/tw_pactus/src/types/network.rs new file mode 100644 index 00000000000..230d531efe7 --- /dev/null +++ b/rust/chains/tw_pactus/src/types/network.rs @@ -0,0 +1,64 @@ +use tw_coin_entry::error::prelude::*; + +/// Represents the type of network (e.g., Mainnet or Testnet). +/// +/// The `CoinType` for Mainnet is defined as `21888`, and for Testnet, it is `21777`. +/// +/// The network type does not affect the decoding or encoding of addresses or transactions. +/// Instead, it is primarily used to facilitate the conversion of an address or public key +/// into its string representation (using bech32m). +/// +/// Note: TrustWallet Core does not provide an API for converting a public key directly +/// to its string representation; it only converts it to a hex representation. +/// However, it provides the API to convert an address to its string representation. +#[derive(Debug, Clone, PartialEq)] +pub enum Network { + /// The network type is either unknown or not explicitly set. + /// + /// Address raw bytes do not inherently carry the network type, + /// but the address string carries the network using an HRP (Human-Readable Part). + /// - Mainnet addresses start with `pc1...`. + /// - Testnet addresses start with `tpc1...`. + /// + /// When deriving an address from a string, the network type is inferred from the HRP. + /// When decoding an address from a public key or raw data, the network type must be explicitly set. + Unknown = 0, + + /// Represents the Mainnet network. + Mainnet = 1, + + /// Represents the Testnet network. + Testnet = 2, +} + +const MAINNET_ADDRESS_HRP: &str = "pc"; +const TESTNET_ADDRESS_HRP: &str = "tpc"; + +pub const MAINNET_PUBLIC_KEY_HRP: &str = "public"; +pub const TESTNET_PUBLIC_KEY_HRP: &str = "tpublic"; + +impl Network { + pub fn try_from_hrp(hrp: &str) -> Result { + match hrp { + MAINNET_ADDRESS_HRP => Ok(Network::Mainnet), + TESTNET_ADDRESS_HRP => Ok(Network::Testnet), + _ => Err(AddressError::InvalidHrp), + } + } + + pub fn address_hrp(&self) -> Result<&'static str, AddressError> { + match &self { + Network::Mainnet => Ok(MAINNET_ADDRESS_HRP), + Network::Testnet => Ok(TESTNET_ADDRESS_HRP), + Network::Unknown => Err(AddressError::InvalidHrp), + } + } + + pub fn public_key_hrp(&self) -> Result<&'static str, AddressError> { + match &self { + Network::Mainnet => Ok(MAINNET_PUBLIC_KEY_HRP), + Network::Testnet => Ok(TESTNET_PUBLIC_KEY_HRP), + Network::Unknown => Err(AddressError::InvalidHrp), + } + } +} diff --git a/rust/chains/tw_pactus/src/types/validator_public_key.rs b/rust/chains/tw_pactus/src/types/validator_public_key.rs index 84425774143..5818d72d56d 100644 --- a/rust/chains/tw_pactus/src/types/validator_public_key.rs +++ b/rust/chains/tw_pactus/src/types/validator_public_key.rs @@ -5,8 +5,9 @@ use bech32::FromBase32; use std::str::FromStr; use tw_keypair::KeyPairError; +use super::network::{MAINNET_PUBLIC_KEY_HRP, TESTNET_PUBLIC_KEY_HRP}; + pub const BLS_PUBLIC_KEY_SIZE: usize = 96; -pub const PUBLIC_KEY_HRP: &str = "public"; #[derive(Debug)] pub struct ValidatorPublicKey(pub [u8; BLS_PUBLIC_KEY_SIZE]); @@ -34,7 +35,7 @@ impl FromStr for ValidatorPublicKey { fn from_str(s: &str) -> Result { let (hrp, b32, _variant) = bech32::decode(s).map_err(|_| KeyPairError::InvalidPublicKey)?; - if hrp != PUBLIC_KEY_HRP { + if hrp != MAINNET_PUBLIC_KEY_HRP && hrp != TESTNET_PUBLIC_KEY_HRP { return Err(KeyPairError::InvalidPublicKey); } @@ -83,6 +84,11 @@ mod test { pub_key_str: "public1p4u8hfytl2pj6l9rj0t54gxcdmna4hq52ncqkkqjf3arha5mlk3x4mzpyjkhmdl20jae7f65aamjrvqcvf4sudcapz52ctcwc8r9wz3z2gwxs38880cgvfy49ta5ssyjut05myd4zgmjqstggmetyuyg7v5jhx47a", pub_key_data: "af0f74917f5065af94727ae9541b0ddcfb5b828a9e016b02498f477ed37fb44d5d882495afb6fd4f9773e4ea9deee436030c4d61c6e3a1151585e1d838cae1444a438d089ce77e10c492a55f6908125c5be9b236a246e4082d08de564e111e65", }, + TestCase { + name: "OK", + pub_key_str: "tpublic1p4u8hfytl2pj6l9rj0t54gxcdmna4hq52ncqkkqjf3arha5mlk3x4mzpyjkhmdl20jae7f65aamjrvqcvf4sudcapz52ctcwc8r9wz3z2gwxs38880cgvfy49ta5ssyjut05myd4zgmjqstggmetyuyg7v5fmv7tx", + pub_key_data: "af0f74917f5065af94727ae9541b0ddcfb5b828a9e016b02498f477ed37fb44d5d882495afb6fd4f9773e4ea9deee436030c4d61c6e3a1151585e1d838cae1444a438d089ce77e10c492a55f6908125c5be9b236a246e4082d08de564e111e65", + }, ]; for case in test_cases { diff --git a/rust/tw_coin_registry/src/tw_derivation.rs b/rust/tw_coin_registry/src/tw_derivation.rs index 67436f91371..21307edfbc0 100644 --- a/rust/tw_coin_registry/src/tw_derivation.rs +++ b/rust/tw_coin_registry/src/tw_derivation.rs @@ -18,6 +18,8 @@ pub enum TWDerivation { SolanaSolana = 6, StratisSegwit = 7, BitcoinTaproot = 8, + PactusMainnet = 9, + PactusTestnet = 10, // end_of_derivation_enum - USED TO GENERATE CODE #[default] Default = 0, @@ -32,6 +34,8 @@ impl From for Derivation { TWDerivation::BitcoinTestnet => Derivation::Testnet, TWDerivation::SolanaSolana => Derivation::Default, TWDerivation::BitcoinTaproot => Derivation::Taproot, + TWDerivation::PactusMainnet => Derivation::Default, + TWDerivation::PactusTestnet => Derivation::Testnet, } } } diff --git a/swift/Tests/CoinTypeTests.swift b/swift/Tests/CoinTypeTests.swift index 46812077e59..9a6120fd503 100644 --- a/swift/Tests/CoinTypeTests.swift +++ b/swift/Tests/CoinTypeTests.swift @@ -27,20 +27,34 @@ class CoinTypeTests: XCTestCase { XCTAssertEqual(CoinType.nebulas.rawValue, 2718) XCTAssertEqual(CoinType.avalancheCChain.rawValue, 10009000) XCTAssertEqual(CoinType.xdai.rawValue, 10000100) + XCTAssertEqual(CoinType.pactus.rawValue, 21888) } - + func testCoinDerivation() { XCTAssertEqual(CoinType.bitcoin.derivationPath(), "m/84'/0'/0'/0/0") XCTAssertEqual(CoinType.bitcoin.derivationPathWithDerivation(derivation: Derivation.bitcoinLegacy), "m/44'/0'/0'/0/0") XCTAssertEqual(CoinType.bitcoin.derivationPathWithDerivation(derivation: Derivation.bitcoinTaproot), "m/86'/0'/0'/0/0") XCTAssertEqual(CoinType.solana.derivationPathWithDerivation(derivation: Derivation.solanaSolana), "m/44'/501'/0'/0'") + XCTAssertEqual(CoinType.pactus.derivationPathWithDerivation(derivation: Derivation.pactusMainnet), "m/44'/21888'/3'/0'") + XCTAssertEqual(CoinType.pactus.derivationPathWithDerivation(derivation: Derivation.pactusTestnet), "m/44'/21777'/3'/0'") } - func testDeriveAddressFromPublicKeyAndDerivation() { + func testDeriveAddressFromPublicKeyAndDerivationBitcoin() { let pkData = Data(hexString: "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798")! let publicKey = PublicKey(data: pkData, type: .secp256k1)! let address = CoinType.bitcoin.deriveAddressFromPublicKeyAndDerivation(publicKey: publicKey, derivation: Derivation.bitcoinSegwit) XCTAssertEqual(address, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4") } + + func testDeriveAddressFromPublicKeyAndDerivationPactus() { + let pkData = Data(hexString: "95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa")! + let publicKey = PublicKey(data: pkData, type: .ed25519)! + + let mainnet_address = CoinType.pactus.deriveAddressFromPublicKeyAndDerivation(publicKey: publicKey, derivation: Derivation.default) + XCTAssertEqual(mainnet_address, "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr") + + let testnet_address = CoinType.pactus.deriveAddressFromPublicKeyAndDerivation(publicKey: publicKey, derivation: Derivation.pactusTestnet) + XCTAssertEqual(testnet_address, "tpc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymzqkcrg") + } } diff --git a/swift/Tests/HDWalletTests.swift b/swift/Tests/HDWalletTests.swift index c36a17c62e3..c3e885a36bc 100644 --- a/swift/Tests/HDWalletTests.swift +++ b/swift/Tests/HDWalletTests.swift @@ -10,19 +10,19 @@ extension HDWallet { } class HDWalletTests: XCTestCase { - + func testFromMnemonicImmutableXMainnetFromSignature() { let wallet = HDWallet(mnemonic: "obscure opera favorite shuffle mail tip age debate dirt pact cement loyal", passphrase: "")! let starkDerivationPath = Ethereum.eip2645GetPath(ethAddress: "0xd0972E2312518Ca15A2304D56ff9cc0b7ea0Ea37", layer: "starkex", application: "immutablex", index: "1") XCTAssertEqual(starkDerivationPath, "m/2645'/579218131'/211006541'/2124474935'/1609799702'/1") - + // Retrieve eth private key let ethPrivateKey = wallet.getKeyForCoin(coin: CoinType.ethereum) XCTAssertEqual(ethPrivateKey.data.hexString, "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"); - + // StarkKey Derivation Path let derivationPath = DerivationPath(string: starkDerivationPath)! - + // Retrieve Stark Private key part let ethMsg = "Only sign this request if you’ve initiated an action with Immutable X." let ethSignature = EthereumMessageSigner.signMessageImmutableX(privateKey: ethPrivateKey, message: ethMsg) @@ -31,7 +31,7 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(starkPrivateKey.data.hexString, "04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de") let starkPublicKey = starkPrivateKey.getPublicKeyByType(pubkeyType: .starkex) XCTAssertEqual(starkPublicKey.data.hexString, "00e5b9b11f8372610ef35d647a1dcaba1a4010716588d591189b27bf3c2d5095") - + // Account Register let ethMsgToRegister = "Only sign this key linking request from Immutable X" let ethSignatureToRegister = EthereumMessageSigner.signMessageImmutableX(privateKey: ethPrivateKey, message: ethMsgToRegister) @@ -41,7 +41,7 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(starkSignature, "04cf5f21333dd189ada3c0f2a51430d733501a9b1d5e07905273c1938cfb261e05b6013d74adde403e8953743a338c8d414bb96bf69d2ca1a91a85ed2700a528") XCTAssertTrue(StarkExMessageSigner.verifyMessage(pubKey: starkPublicKey, message: starkMsg, signature: starkSignature)) } - + func testCreateFromMnemonic() { let wallet = HDWallet.test @@ -79,7 +79,7 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(masterKey.data.hexString, "e120fc1ef9d193a851926ebd937c3985dc2c4e642fb3d0832317884d5f18f3b3") } - func testGetKeyForCoin() { + func testGetKeyForCoinBitcoin() { let coin = CoinType.bitcoin let wallet = HDWallet.test let key = wallet.getKeyForCoin(coin: coin) @@ -88,7 +88,7 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85") } - func testGetKeyDerivation() { + func testGetKeyDerivationBitcoin() { let coin = CoinType.bitcoin let wallet = HDWallet.test @@ -100,12 +100,12 @@ class HDWalletTests: XCTestCase { let key3 = wallet.getKeyDerivation(coin: coin, derivation: .bitcoinTestnet) XCTAssertEqual(key3.data.hexString, "ca5845e1b43e3adf577b7f110b60596479425695005a594c88f9901c3afe864f") - + let key4 = wallet.getKeyDerivation(coin: coin, derivation: .bitcoinTaproot) XCTAssertEqual(key4.data.hexString, "a2c4d6df786f118f20330affd65d248ffdc0750ae9cbc729d27c640302afd030") } - func testGetAddressForCoin() { + func testGetAddressForCoinBitcoin() { let coin = CoinType.bitcoin let wallet = HDWallet.test @@ -113,7 +113,7 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85") } - func testGetAddressDerivation() { + func testGetAddressDerivationBitcoin() { let coin = CoinType.bitcoin let wallet = HDWallet.test @@ -125,11 +125,41 @@ class HDWalletTests: XCTestCase { let address3 = wallet.getAddressDerivation(coin: coin, derivation: .bitcoinTestnet) XCTAssertEqual(address3, "tb1qwgpxgwn33z3ke9s7q65l976pseh4edrzfmyvl0") - + let address4 = wallet.getAddressDerivation(coin: coin, derivation: .bitcoinTaproot) XCTAssertEqual(address4, "bc1pgqks0cynn93ymve4x0jq3u7hne77908nlysp289hc44yc4cmy0hslyckrz") } + func testGetKeyDerivationPactus() { + let coin = CoinType.pactus + let wallet = HDWallet.test + + let key1 = wallet.getKeyDerivation(coin: coin, derivation: .pactusMainnet) + XCTAssertEqual(key1.data.hexString, "153fefb8168f246f9f77c60ea10765c1c39828329e87284ddd316770717f3a5e") + + let key2 = wallet.getKeyDerivation(coin: coin, derivation: .pactusTestnet) + XCTAssertEqual(key2.data.hexString, "54f3c54dd6af5794bea1f86de05b8b9f164215e8deee896f604919046399e54d") + } + + func testGetAddressForCoinPactus() { + let coin = CoinType.pactus + let wallet = HDWallet.test + + let address = wallet.getAddressForCoin(coin: coin) + XCTAssertEqual(address, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff") + } + + func testGetAddressDerivationPactus() { + let coin = CoinType.pactus + let wallet = HDWallet.test + + let address1 = wallet.getAddressDerivation(coin: coin, derivation: .pactusMainnet) + XCTAssertEqual(address1, "pc1rjkzc23l7qkkenx6xwy04srwppzfk6m5t7q46ff") + + let address2 = wallet.getAddressDerivation(coin: coin, derivation: .pactusTestnet) + XCTAssertEqual(address2, "tpc1rjtamyqp203j4367q4plkp4qt32d7sv34kfmj5e") + } + func testDerive() { let wallet = HDWallet.test diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 99b2815b825..0b823afddf5 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -183,7 +183,7 @@ class KeyStoreTests: XCTestCase { XCTAssertNotNil(storedData) XCTAssertNotNil(PrivateKey(data: storedData!)) } - + func testImportPrivateKeyAES256() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let privateKeyData = Data(hexString: "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c")! @@ -222,7 +222,7 @@ class KeyStoreTests: XCTestCase { XCTAssertEqual(wallet.accounts.count, 1) XCTAssertNotNil(keyStore.hdWallet) } - + func testImportWalletAES256() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let wallet = try keyStore.import(mnemonic: mnemonic, name: "name", encryptPassword: "newPassword", coins: [.ethereum], encryption: .aes256Ctr) @@ -437,7 +437,7 @@ class KeyStoreTests: XCTestCase { let btc2 = try wallet.getAccount(password: password, coin: .bitcoin, derivation: .bitcoinLegacy) XCTAssertEqual(btc2.address, "1NyRyFewhZcWMa9XCj3bBxSXPXyoSg8dKz") XCTAssertEqual(btc2.extendedPublicKey, "xpub6CR52eaUuVb4kXAVyHC2i5ZuqJ37oWNPZFtjXaazFPXZD45DwWBYEBLdrF7fmCR9pgBuCA9Q57zZfyJjDUBDNtWkhWuGHNYKLgDHpqrHsxV") - + let btc3 = try wallet.getAccount(password: password, coin: .bitcoin, derivation: .bitcoinTaproot) XCTAssertEqual(btc3.address, "bc1pyqkqf20fmmwmcxf98tv6k63e2sgnjy4zne6d0r32vxwm3au0hnksq6ec57") XCTAssertEqual(btc3.extendedPublicKey, "zpub6qNRYbLLXquaD1GKxHZWDs3moUFQfP4iqXiDPCd8aD3oNHZkCAusAw5raKQEWV8BkXBTXhWBkgZTxzjjnQ5cRjWa6LNcjmrVVNdUKvbKTgm") @@ -449,6 +449,14 @@ class KeyStoreTests: XCTestCase { let solana2 = try wallet.getAccount(password: password, coin: .solana, derivation: .solanaSolana) XCTAssertEqual(solana2.address, "CgWJeEWkiYqosy1ba7a3wn9HAQuHyK48xs3LM4SSDc1C") XCTAssertEqual(solana2.derivationPath, "m/44'/501'/0'/0'") + + let pactus_mainnet = try wallet.getAccount(password: password, coin: .pactus, derivation: .pactusMainnet) + XCTAssertEqual(pactus_mainnet.address, "pc1rzuswvfwde5hleqfemvpz4swlh6uud6nkukumdu") + XCTAssertEqual(pactus_mainnet.derivationPath, "m/44'/21888'/3'/0'") + + let pactus_testnet = try wallet.getAccount(password: password, coin: .pactus, derivation: .pactusTestnet) + XCTAssertEqual(pactus_testnet.address, "tpc1rxs9tperv58gvfwpn0vj5na7vrcffml40j2v6r9") + XCTAssertEqual(pactus_testnet.derivationPath, "m/44'/21777'/3'/0'") } func createTempDirURL() throws -> URL { diff --git a/tests/chains/Pactus/WalletTests.cpp b/tests/chains/Pactus/WalletTests.cpp index bcd3076f398..0a2f553b8e6 100644 --- a/tests/chains/Pactus/WalletTests.cpp +++ b/tests/chains/Pactus/WalletTests.cpp @@ -18,7 +18,7 @@ TEST(PactusWallet, DerivationPath) { assertStringsEqual(WRAPS(derivationPath), "m/44'/21888'/3'/0'"); } -TEST(PactusWallet, HDWallet) { +TEST(PactusWallet, HDWallet_MainnetDerivation) { auto mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon cactus"; auto passphrase = ""; auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); @@ -41,4 +41,27 @@ TEST(PactusWallet, HDWallet) { TWStringDelete(derivationPath2); } +TEST(PactusWallet, HDWallet_TestnetDerivation) { + auto mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon cactus"; + auto passphrase = ""; + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); + + auto derivationPath1 = TWStringCreateWithUTF8Bytes("m/44'/21777'/3'/0'"); + auto privateKey1 = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypePactus, derivationPath1)); + auto publicKey1 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey1.get())); + auto address1 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(publicKey1.get(), TWCoinTypePactus, TWDerivationPactusTestnet)); + auto addressStr1 = WRAPS(TWAnyAddressDescription(address1.get())); + + auto derivationPath2 = TWStringCreateWithUTF8Bytes("m/44'/21777'/3'/1'"); + auto privateKey2 = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypePactus, derivationPath2)); + auto publicKey2 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey2.get())); + auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(publicKey2.get(), TWCoinTypePactus, TWDerivationPactusTestnet)); + auto addressStr2 = WRAPS(TWAnyAddressDescription(address2.get())); + + assertStringsEqual(addressStr1, "tpc1r35xwz99uw2qrhz9wmdanaqcsge2nzsfegvv555"); + assertStringsEqual(addressStr2, "tpc1r34xj32k004j8v35fx6uqw4yaka54g6jdr58tvk"); + TWStringDelete(derivationPath1); + TWStringDelete(derivationPath2); +} + } \ No newline at end of file diff --git a/tests/common/Keystore/DerivationPathTests.cpp b/tests/common/Keystore/DerivationPathTests.cpp index a38fc23903a..3bdb3734f4d 100644 --- a/tests/common/Keystore/DerivationPathTests.cpp +++ b/tests/common/Keystore/DerivationPathTests.cpp @@ -92,6 +92,12 @@ TEST(Derivation, alternativeDerivation) { EXPECT_EQ(TW::derivationPath(TWCoinTypeSolana, TWDerivationDefault).string(), "m/44'/501'/0'"); EXPECT_EQ(TW::derivationPath(TWCoinTypeSolana, TWDerivationSolanaSolana).string(), "m/44'/501'/0'/0'"); EXPECT_EQ(std::string(TW::derivationName(TWCoinTypeSolana, TWDerivationSolanaSolana)), "solana"); + + EXPECT_EQ(TW::derivationPath(TWCoinTypePactus).string(), "m/44'/21888'/3'/0'"); + EXPECT_EQ(TW::derivationPath(TWCoinTypePactus, TWDerivationPactusMainnet).string(), "m/44'/21888'/3'/0'"); + EXPECT_EQ(TW::derivationPath(TWCoinTypePactus, TWDerivationPactusTestnet).string(), "m/44'/21777'/3'/0'"); + EXPECT_EQ(std::string(TW::derivationName(TWCoinTypePactus, TWDerivationPactusMainnet)), "mainnet"); + EXPECT_EQ(std::string(TW::derivationName(TWCoinTypePactus, TWDerivationPactusTestnet)), "testnet"); } } // namespace TW diff --git a/tests/common/Keystore/StoredKeyTests.cpp b/tests/common/Keystore/StoredKeyTests.cpp index 02c9bed047b..12c7fc7a740 100644 --- a/tests/common/Keystore/StoredKeyTests.cpp +++ b/tests/common/Keystore/StoredKeyTests.cpp @@ -159,25 +159,25 @@ TEST(StoredKey, AccountGetCreate) { // not exists, wallet nonnull, create std::optional acc3 = key.account(coinTypeBc, &wallet); EXPECT_TRUE(acc3.has_value()); - EXPECT_EQ(acc3->coin, coinTypeBc); + EXPECT_EQ(acc3->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1ul); // exists std::optional acc4 = key.account(coinTypeBc); EXPECT_TRUE(acc4.has_value()); - EXPECT_EQ(acc4->coin, coinTypeBc); + EXPECT_EQ(acc4->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1ul); // exists, wallet nonnull, not create std::optional acc5 = key.account(coinTypeBc, &wallet); EXPECT_TRUE(acc5.has_value()); - EXPECT_EQ(acc5->coin, coinTypeBc); + EXPECT_EQ(acc5->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1ul); // exists, wallet null, not create std::optional acc6 = key.account(coinTypeBc, nullptr); EXPECT_TRUE(acc6.has_value()); - EXPECT_EQ(acc6->coin, coinTypeBc); + EXPECT_EQ(acc6->coin, coinTypeBc); EXPECT_EQ(key.accounts.size(), 1ul); } @@ -435,7 +435,7 @@ TEST(StoredKey, CreateAccounts) { string mnemonicPhrase = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; auto key = StoredKey::createWithMnemonic("name", gPassword, mnemonicPhrase, TWStoredKeyEncryptionLevelDefault); const auto wallet = key.wallet(gPassword); - + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->address, "0x494f60cb6Ac2c8F5E1393aD9FdBdF4Ad589507F7"); EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->publicKey, "04cc32a479080d83fdcf69966713f0aad1bc1dc3ecf873b034894e84259841bc1c9b122717803e68905220ff54952d3f5ea2ab2698ca31f843addf94ae73fae9fd"); EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->extendedPublicKey, ""); @@ -596,7 +596,7 @@ TEST(StoredKey, CreateEncryptionParametersRandomSalt) { EXPECT_NE(salt1, salt2) << "salt must be random on every StoredKey creation"; } -TEST(StoredKey, CreateMultiAccounts) { // Multiple accounts for the same coin +TEST(StoredKey, CreateMultiAccounts) { // Multiple accounts from the same wallet auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); const Data& mnemo2Data = key.payload.decrypt(gPassword); @@ -696,6 +696,31 @@ TEST(StoredKey, CreateMultiAccounts) { // Multiple accounts for the same coin EXPECT_EQ(key.getAccounts(coin)[2].address, expectedBtc3); EXPECT_EQ(key.getAccounts(coin)[2].derivationPath.string(), "m/44'/2'/0'/0/0"); } + + { // Create Pactus Accounts + const auto coin = TWCoinTypePactus; + + const auto pactusMainnet = key.account(coin, TWDerivationPactusMainnet, wallet); + const auto pactusTestnet = key.account(coin, TWDerivationPactusTestnet, wallet); + + const auto expectedMainnetAddr = "pc1rzuswvfwde5hleqfemvpz4swlh6uud6nkukumdu"; + const auto expectedTestnetAddr = "tpc1rxs9tperv58gvfwpn0vj5na7vrcffml40j2v6r9"; + + EXPECT_EQ(pactusMainnet.address, expectedMainnetAddr); + EXPECT_EQ(pactusTestnet.address, expectedTestnetAddr); + + EXPECT_EQ(pactusMainnet.derivationPath.string(), "m/44'/21888'/3'/0'"); + EXPECT_EQ(pactusTestnet.derivationPath.string(), "m/44'/21777'/3'/0'"); + + expectedAccounts += 2; + EXPECT_EQ(key.accounts.size(), expectedAccounts); + + EXPECT_EQ(key.account(coin)->address, expectedMainnetAddr); + EXPECT_EQ(key.account(coin, TWDerivationPactusMainnet, wallet).address, expectedMainnetAddr); + EXPECT_EQ(key.getAccounts(coin).size(), 2ul); + EXPECT_EQ(key.getAccounts(coin)[0].address, expectedMainnetAddr); + EXPECT_EQ(key.getAccounts(coin)[1].address, expectedTestnetAddr); + } } TEST(StoredKey, CreateWithMnemonicAlternativeDerivation) { diff --git a/tests/interface/TWAnyAddressTests.cpp b/tests/interface/TWAnyAddressTests.cpp index 1068efe40fb..212a1055c07 100644 --- a/tests/interface/TWAnyAddressTests.cpp +++ b/tests/interface/TWAnyAddressTests.cpp @@ -180,7 +180,7 @@ TEST(TWAnyAddress, createFromPubKey) { assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "bc1qcj2vfjec3c3luf9fx9vddnglhh9gawmncmgxhz"); } -TEST(TWAnyAddress, createFromPubKeyDerivation) { +TEST(TWAnyAddress, createFromPubKeyDerivationBitcoin) { constexpr auto pubkey = "02753f5c275e1847ba4d2fd3df36ad00af2e165650b35fe3991e9c9c46f68b12bc"; const auto pubkey_twstring = STRING(pubkey); const auto pubkey_data = WRAPD(TWDataCreateWithHexString(pubkey_twstring.get())); @@ -200,6 +200,26 @@ TEST(TWAnyAddress, createFromPubKeyDerivation) { } } +TEST(TWAnyAddress, createFromPubKeyDerivationPactus) { + constexpr auto pubkey = "95794161374b22c696dabb98e93f6ca9300b22f3b904921fbf560bb72145f4fa"; + const auto pubkey_twstring = STRING(pubkey); + const auto pubkey_data = WRAPD(TWDataCreateWithHexString(pubkey_twstring.get())); + const auto pubkey_obj = WRAP(TWPublicKey, TWPublicKeyCreateWithData(pubkey_data.get(), TWPublicKeyTypeED25519)); + + { + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(pubkey_obj.get(), TWCoinTypePactus, TWDerivationDefault)); + assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr"); + } + { + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(pubkey_obj.get(), TWCoinTypePactus, TWDerivationPactusMainnet)); + assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr"); + } + { + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(pubkey_obj.get(), TWCoinTypePactus, TWDerivationPactusTestnet)); + assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "tpc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymzqkcrg"); + } +} + TEST(TWAnyAddress, createFromPubKeyFilecoinAddressType) { constexpr auto pubkey = "0419bf99082cf2fcdaa812d6eba1eba9036ff3a3d84c1817c84954d4e8ae283fec5313e427a0f5f68dec3169b2eda876b1d9f97b1ede7f958baee6a2ce78f6e94a"; const auto pubkey_twstring = STRING(pubkey); diff --git a/tests/interface/TWCoinTypeTests.cpp b/tests/interface/TWCoinTypeTests.cpp index b7547ae34a9..8d19ec12f86 100644 --- a/tests/interface/TWCoinTypeTests.cpp +++ b/tests/interface/TWCoinTypeTests.cpp @@ -174,3 +174,24 @@ TEST(TWCoinType, TWCoinTypeDerivationPathWithDerivationSolana) { ASSERT_EQ(result, "m/44'/501'/0'/0'"); TWStringDelete(res); } + +TEST(TWCoinType, TWCoinTypeDerivationPathPactus) { + auto res = TWCoinTypeDerivationPath(TWCoinTypePactus); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/44'/21888'/3'/0'"); + TWStringDelete(res); +} + +TEST(TWCoinType, TWCoinTypeDerivationPathWithDerivationPactusMainnet) { + auto res = TWCoinTypeDerivationPathWithDerivation(TWCoinTypePactus, TWDerivationPactusMainnet); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/44'/21888'/3'/0'"); + TWStringDelete(res); +} + +TEST(TWCoinType, TWCoinTypeDerivationPathWithDerivationPactusTestnet) { + auto res = TWCoinTypeDerivationPathWithDerivation(TWCoinTypePactus, TWDerivationPactusTestnet); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/44'/21777'/3'/0'"); + TWStringDelete(res); +} diff --git a/wasm/tests/CoinType.test.ts b/wasm/tests/CoinType.test.ts index 75a0bed0408..84dc7da419d 100644 --- a/wasm/tests/CoinType.test.ts +++ b/wasm/tests/CoinType.test.ts @@ -16,6 +16,7 @@ describe("CoinType", () => { assert.equal(CoinType.binance.value, 714); assert.equal(CoinType.cosmos.value, 118); assert.equal(CoinType.solana.value, 501); + assert.equal(CoinType.pactus.value, 21888); }); it("test CoinTypeExt methods", () => { @@ -55,4 +56,25 @@ describe("CoinType", () => { const addr = CoinTypeExt.deriveAddressFromPublicKeyAndDerivation(CoinType.bitcoin, key, Derivation.bitcoinSegwit); assert.equal(addr, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"); }); + + it("test CoinTypeExt methods for Pactus", () => { + const { CoinType, CoinTypeExt, Blockchain, Purpose, Curve, Derivation } = globalThis.core; + + assert.equal(CoinTypeExt.blockchain(CoinType.pactus), Blockchain.pactus); + assert.equal(CoinTypeExt.purpose(CoinType.pactus), Purpose.bip44); + assert.equal(CoinTypeExt.curve(CoinType.pactus), Curve.ed25519); + assert.isTrue(CoinTypeExt.validate(CoinType.pactus, "pc1rnvlc4wa73lc0rydmgfswz4j5wad4un376vv2d7")) + assert.equal(CoinTypeExt.derivationPath(CoinType.pactus), "m/44'/21888'/3'/0'"); + assert.equal(CoinTypeExt.derivationPathWithDerivation(CoinType.pactus, Derivation.pactusMainnet), "m/44'/21888'/3'/0'"); + assert.equal(CoinTypeExt.derivationPathWithDerivation(CoinType.pactus, Derivation.pactusTestnet), "m/44'/21777'/3'/0'"); + }); + + it("test deriveAddress for Pactus", () => { + const { CoinType, CoinTypeExt, PrivateKey, HexCoding } = globalThis.core; + + const data = HexCoding.decode("8778cc93c6596387e751d2dc693bbd93e434bd233bc5b68a826c56131821cb63"); + const key = PrivateKey.createWithData(data); + const addr = CoinTypeExt.deriveAddress(CoinType.pactus, key); + assert.equal(addr, "pc1rnvlc4wa73lc0rydmgfswz4j5wad4un376vv2d7") + }); }); From 745437fadae6f94d37b63840d863afcae5bdc34a Mon Sep 17 00:00:00 2001 From: gupnik Date: Fri, 11 Apr 2025 18:38:02 +0530 Subject: [PATCH 24/72] Adds API to import encoded private keys (#4357) * Adds API to import encoded private keys * Adds solana base58 decoding as well * Supports 64 byte solana key import and moves Crc * Adds API to retrieve encoded key * Adds APIs and Tests based on the discussion * Addresses review comments * Addresses review comments * Updates Android test --- .../core/app/utils/TestKeyStore.kt | 32 +++++ include/TrustWalletCore/TWStoredKey.h | 37 ++++++ src/Coin.cpp | 6 + src/Coin.h | 3 + src/CoinEntry.cpp | 5 + src/CoinEntry.h | 2 + src/Crc.cpp | 17 +++ src/Crc.h | 38 ++++++ src/Keystore/StoredKey.cpp | 40 +++++- src/Keystore/StoredKey.h | 23 +++- src/Solana/Entry.cpp | 23 +++- src/Solana/Entry.h | 1 + src/Stellar/Entry.cpp | 53 ++++++++ src/Stellar/Entry.h | 1 + src/interface/TWStoredKey.cpp | 31 ++++- swift/Sources/KeyStore.swift | 55 ++++++++- swift/Tests/Keystore/KeyStoreTests.swift | 52 ++++++++ tests/interface/TWStoredKeyTests.cpp | 116 ++++++++++++++++++ wasm/src/keystore/default-impl.ts | 60 +++++++++ wasm/src/keystore/types.ts | 1 + wasm/tests/KeyStore.test.ts | 47 +++++++ 21 files changed, 637 insertions(+), 6 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt index 599d3369216..9b00f5adbfa 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt @@ -91,4 +91,36 @@ class TestKeyStore { val privateKey = newKeyStore.decryptPrivateKey("".toByteArray()) assertNull(privateKey) } + + @Test + fun testImportKeyEncodedEthereum() { + val privateKeyHex = "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c" + val password = "password".toByteArray() + val key = StoredKey.importPrivateKeyEncoded(privateKeyHex, "name", password, CoinType.ETHEREUM) + val json = key.exportJSON() + + val keyStore = StoredKey.importJSON(json) + val storedEncoded = keyStore.decryptPrivateKeyEncoded(password) + + assertTrue(keyStore.hasPrivateKeyEncoded()) + assertNotNull(keyStore) + assertNotNull(storedEncoded) + assertEquals(privateKeyHex, storedEncoded) + } + + @Test + fun testImportKeyEncodedSolana() { + val privateKeyBase58 = "A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr" + val password = "password".toByteArray() + val key = StoredKey.importPrivateKeyEncoded(privateKeyBase58, "name", password, CoinType.SOLANA) + val json = key.exportJSON() + + val keyStore = StoredKey.importJSON(json) + val storedEncoded = keyStore.decryptPrivateKeyEncoded(password) + + assertTrue(keyStore.hasPrivateKeyEncoded()) + assertNotNull(keyStore) + assertNotNull(storedEncoded) + assertEquals(privateKeyBase58, storedEncoded) + } } diff --git a/include/TrustWalletCore/TWStoredKey.h b/include/TrustWalletCore/TWStoredKey.h index 58a07e521c0..4d8901f8cc7 100644 --- a/include/TrustWalletCore/TWStoredKey.h +++ b/include/TrustWalletCore/TWStoredKey.h @@ -51,6 +51,28 @@ struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull priva TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyWithEncryption(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption); +/// Imports an encoded private key. +/// +/// \param privateKey Non-null encoded private key +/// \param password Non-null block of data, password of the stored key +/// \param coin the coin type +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise +TW_EXPORT_STATIC_METHOD +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncoded(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin); + +/// Imports an encoded private key. +/// +/// \param privateKey Non-null encoded private key +/// \param name The name of the stored key to import as a non-null string +/// \param password Non-null block of data, password of the stored key +/// \param coin the coin type +/// \param encryption cipher encryption mode +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise +TW_EXPORT_STATIC_METHOD +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncodedWithEncryption(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption); + /// Imports an HD wallet. /// /// \param mnemonic Non-null bip39 mnemonic @@ -253,6 +275,21 @@ bool TWStoredKeyStore(struct TWStoredKey* _Nonnull key, TWString* _Nonnull path) TW_EXPORT_METHOD TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); +/// Decrypts the encoded private key. +/// +/// \param key Non-null pointer to a stored key +/// \param password Non-null block of data, password of the stored key +/// \return Decrypted encoded private key as a string if success, null pointer otherwise +TW_EXPORT_METHOD +TWString* _Nullable TWStoredKeyDecryptPrivateKeyEncoded(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); + +/// Whether the private key is encoded. +/// +/// \param key Non-null pointer to a stored key +/// \return true if the private key is encoded, false otherwise +TW_EXPORT_PROPERTY +bool TWStoredKeyHasPrivateKeyEncoded(struct TWStoredKey* _Nonnull key); + /// Decrypts the mnemonic phrase. /// /// \param key Non-null pointer to a stored key diff --git a/src/Coin.cpp b/src/Coin.cpp index f0067766347..3aa2d14d4ac 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -300,6 +300,12 @@ std::string TW::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDer return dispatcher->deriveAddress(coin, publicKey, derivation, addressPrefix); } +PrivateKey TW::decodePrivateKey(TWCoinType coin, const std::string& privateKey) { + auto const* dispatcher = coinDispatcher(coin); + assert(dispatcher != nullptr); + return dispatcher->decodePrivateKey(coin, privateKey); +} + Data TW::addressToData(TWCoinType coin, const std::string& address) { const auto* dispatcher = coinDispatcher(coin); assert(dispatcher != nullptr); diff --git a/src/Coin.h b/src/Coin.h index 35ad5bc1d59..5bbc029ecf9 100644 --- a/src/Coin.h +++ b/src/Coin.h @@ -79,6 +79,9 @@ std::string deriveAddress(TWCoinType coin, const PrivateKey& privateKey, TWDeriv /// Derives the address for a particular coin from the public key, with given derivation and addressPrefix. std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation = TWDerivationDefault, const PrefixVariant& addressPrefix = std::monostate()); +/// Decodes a private key for a particular coin. +PrivateKey decodePrivateKey(TWCoinType coin, const std::string& privateKey); + /// Returns the binary representation of a string address Data addressToData(TWCoinType coin, const std::string& address); diff --git a/src/CoinEntry.cpp b/src/CoinEntry.cpp index c1748f7ab8d..31614fd41bf 100644 --- a/src/CoinEntry.cpp +++ b/src/CoinEntry.cpp @@ -30,4 +30,9 @@ byte getFromPrefixPkhOrDefault(const PrefixVariant &prefix, TWCoinType coin) { return TW::p2pkhPrefix(coin); } +PrivateKey CoinEntry::decodePrivateKey(TWCoinType coin, const std::string& privateKey) const { + auto data = parse_hex(privateKey); + return PrivateKey(data, TW::curve(coin)); +} + } // namespace TW diff --git a/src/CoinEntry.h b/src/CoinEntry.h index 569593dac84..c7f21184060 100644 --- a/src/CoinEntry.h +++ b/src/CoinEntry.h @@ -68,6 +68,8 @@ class CoinEntry { virtual Data preImageHashes([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& txInputData) const { return {}; } // Optional method for compiling a transaction with externally-supplied signatures & pubkeys. virtual void compile([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& txInputData, [[maybe_unused]] const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, [[maybe_unused]] Data& dataOut) const {} + // Optional method for decoding a private key. Could throw an exception if the encoded private key is invalid. + virtual PrivateKey decodePrivateKey([[maybe_unused]] TWCoinType coin, const std::string& privateKey) const; }; // In each coin's Entry.cpp the specific types of the coin are used, this template enforces the Signer implement: diff --git a/src/Crc.cpp b/src/Crc.cpp index 38fc2c84d9c..7024532f4ee 100644 --- a/src/Crc.cpp +++ b/src/Crc.cpp @@ -38,3 +38,20 @@ uint32_t Crc::crc32(const Data& data) { } return ~c; } + +Data Crc::crc16_xmodem(const Data& data) { + uint16_t crc16 = 0x0; + + for (size_t i = 0; i < data.size(); i++) { + uint8_t byte = data[i]; + uint8_t lookupIndex = (crc16 >> 8) ^ byte; + crc16 = static_cast((crc16 << 8) ^ crc16_xmodem_table[lookupIndex]); + crc16 &= 0xffff; + } + + Data checksum(2); + checksum[0] = crc16 & 0xff; + checksum[1] = (crc16 >> 8) & 0xff; + return checksum; +} + diff --git a/src/Crc.h b/src/Crc.h index 5268f186d13..173dc6ec2f0 100644 --- a/src/Crc.h +++ b/src/Crc.h @@ -15,6 +15,11 @@ uint16_t crc16(uint8_t* bytes, uint32_t length); uint32_t crc32(const TW::Data& data); +/// CRC16-XModem implementation compatible with the Stellar version +// Taken from: https://github.com/stellar/js-stellar-base/blob/087e2d651a59b5cbed01386b4b8c45862d358259/src/strkey.js#L353 +// Computes the CRC16-XModem checksum of `payload` in little-endian order +Data crc16_xmodem(const Data& data); + // Table taken from https://web.mit.edu/freebsd/head/sys/libkern/crc32.c (Public Domain code) // This table is used to speed up the crc calculation. static constexpr uint32_t crc32_table[] = { @@ -61,4 +66,37 @@ static constexpr uint32_t crc32_table[] = { 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d}; + +// CRC16-XModem lookup table +static const uint16_t crc16_xmodem_table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, + 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, + 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, + 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, + 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, + 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, + 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, + 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, + 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, + 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, + 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, + 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, + 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, + 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, + 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, + 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, + 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, + 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, + 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, + 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, + 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, + 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, + 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, + 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, + 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, + 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; } // namespace TW::Crc diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index 31ed44c0933..e5bbe4f04fe 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -67,11 +67,27 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na return key; } -StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption) +StoredKey StoredKey::createWithEncodedPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const std::string& encodedPrivateKey, TWStoredKeyEncryption encryption) { + const auto curve = TW::curve(coin); + const auto privateKey = TW::decodePrivateKey(coin, encodedPrivateKey); + StoredKey key = StoredKey(StoredKeyType::privateKey, name, password, privateKey.bytes, TWStoredKeyEncryptionLevelDefault, encryption, encodedPrivateKey); + const auto derivationPath = TW::derivationPath(coin); + const auto pubKeyType = TW::publicKeyType(coin); + const auto pubKey = privateKey.getPublicKey(pubKeyType); + const auto address = TW::deriveAddress(coin, privateKey); + key.accounts.emplace_back(address, coin, TWDerivationDefault, derivationPath, hex(pubKey.bytes), ""); + return key; +} + +StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption, const std::optional& encodedStr) : type(type), id(), name(std::move(name)), accounts() { const auto encryptionParams = EncryptionParameters::getPreset(encryptionLevel, encryption); payload = EncryptedPayload(password, data, encryptionParams); - + if (encodedStr) { + const auto bytes = reinterpret_cast(encodedStr->c_str()); + const auto encodedData = Data(bytes, bytes + encodedStr->size()); + encodedPayload = EncryptedPayload(password, encodedData, encryptionParams); + } const char* uuid_ptr = Rust::tw_uuid_random(); id = std::make_optional(uuid_ptr); Rust::free_string(uuid_ptr); @@ -310,6 +326,16 @@ bool StoredKey::updateAddress(TWCoinType coin) { return addressUpdated; } +const std::string StoredKey::decryptPrivateKeyEncoded(const Data& password) const { + if (encodedPayload) { + auto data = encodedPayload->decrypt(password); + return std::string(reinterpret_cast(data.data()), data.size()); + } else { + auto data = payload.decrypt(password); + return TW::hex(data); + } +} + // ----------------- // Encoding/Decoding // ----------------- @@ -327,6 +353,7 @@ static const auto type = "type"; static const auto name = "name"; static const auto id = "id"; static const auto crypto = "crypto"; +static const auto encodedCrypto = "encodedCrypto"; static const auto activeAccounts = "activeAccounts"; static const auto version = "version"; static const auto coin = "coin"; @@ -367,6 +394,12 @@ void StoredKey::loadJson(const nlohmann::json& json) { throw DecryptionError::invalidKeyFile; } + if (json.count(CodingKeys::SK::encodedCrypto) != 0) { + encodedPayload = EncryptedPayload(json[CodingKeys::SK::encodedCrypto]); + } else { + encodedPayload = std::nullopt; + } + if (json.count(CodingKeys::SK::activeAccounts) != 0 && json[CodingKeys::SK::activeAccounts].is_array()) { for (auto& accountJSON : json[CodingKeys::SK::activeAccounts]) { @@ -404,6 +437,9 @@ nlohmann::json StoredKey::json() const { j[CodingKeys::SK::name] = name; j[CodingKeys::SK::crypto] = payload.json(); + if (encodedPayload) { + j[CodingKeys::SK::encodedCrypto] = encodedPayload->json(); + } nlohmann::json accountsJSON = nlohmann::json::array(); for (const auto& account : accounts) { diff --git a/src/Keystore/StoredKey.h b/src/Keystore/StoredKey.h index 12eaf5038f4..f87cbe1b1bb 100644 --- a/src/Keystore/StoredKey.h +++ b/src/Keystore/StoredKey.h @@ -39,6 +39,9 @@ class StoredKey { /// Encrypted payload. EncryptedPayload payload; + /// Optional encoded payload. Used when an encoded private key is imported. + std::optional encodedPayload; + /// Active accounts. Address should be unique. std::vector accounts; @@ -62,6 +65,10 @@ class StoredKey { /// @throws std::invalid_argument if privateKeyData is not a valid private key static StoredKey createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); + /// Create a new StoredKey, with the given name and encoded private key, and also add the default address for the given coin.. + /// @throws std::invalid_argument if encodedPrivateKey is not a valid private key + static StoredKey createWithEncodedPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const std::string& encodedPrivateKey, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); + /// Create a StoredKey from a JSON object. static StoredKey createWithJson(const nlohmann::json& json); @@ -150,6 +157,12 @@ class StoredKey { /// In case of multiple accounts, all of them will be updated. bool updateAddress(TWCoinType coin); + /// Decrypts the encoded private key. + /// + /// \returns the decoded private key. + /// \throws DecryptionError + const std::string decryptPrivateKeyEncoded(const Data& password) const; + private: /// Default constructor, private StoredKey() : type(StoredKeyType::mnemonicPhrase) {} @@ -157,7 +170,15 @@ class StoredKey { /// Initializes a `StoredKey` with a type, an encryption password, and unencrypted data. /// This constructor will encrypt the provided data with default encryption /// parameters. - StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); + StoredKey( + StoredKeyType type, + std::string name, + const Data& password, + const Data& data, + TWStoredKeyEncryptionLevel encryptionLevel, + TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr, + const std::optional& encodedStr = std::nullopt + ); /// Find default account for coin, if exists. If multiple exist, default is returned. /// Optional wallet is needed to derive default address diff --git a/src/Solana/Entry.cpp b/src/Solana/Entry.cpp index 1fc18b03274..c913362d060 100644 --- a/src/Solana/Entry.cpp +++ b/src/Solana/Entry.cpp @@ -3,7 +3,9 @@ // Copyright © 2017 Trust Wallet. #include "Entry.h" - +#include "Base58.h" +#include "Coin.h" +#include "HexCoding.h" #include "proto/Solana.pb.h" using namespace TW; @@ -20,4 +22,23 @@ string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key ); } +PrivateKey Entry::decodePrivateKey(TWCoinType coin, const std::string& privateKey) const { + auto data = Base58::decode(privateKey); + if (data.size() == 64) { + const auto privateKeyData = subData(data, 0, 32); + const auto publicKeyData = subData(data, 32, 32); + auto privKey = PrivateKey(privateKeyData, TW::curve(coin)); + auto publicKey = privKey.getPublicKey(TWPublicKeyType::TWPublicKeyTypeED25519); + if (publicKey.bytes != publicKeyData) { + throw std::invalid_argument("Invalid private key"); + } + return privKey; + } else if (data.size() == 32) { + return PrivateKey(data, TW::curve(coin)); + } else { + auto hexData = parse_hex(privateKey); + return PrivateKey(hexData, TW::curve(coin)); + } +} + } // namespace TW::Solana diff --git a/src/Solana/Entry.h b/src/Solana/Entry.h index 90d02756e5f..226ab553ae0 100644 --- a/src/Solana/Entry.h +++ b/src/Solana/Entry.h @@ -15,6 +15,7 @@ class Entry final : public Rust::RustCoinEntryWithSignJSON { public: bool supportsJSONSigning() const override { return true; } std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const override; + PrivateKey decodePrivateKey(TWCoinType coin, const std::string& privateKey) const override; }; } // namespace TW::Solana diff --git a/src/Stellar/Entry.cpp b/src/Stellar/Entry.cpp index 56c20599a79..50bfd9c4463 100644 --- a/src/Stellar/Entry.cpp +++ b/src/Stellar/Entry.cpp @@ -7,9 +7,15 @@ #include "Address.h" #include "proto/TransactionCompiler.pb.h" #include "Signer.h" +#include "Base32.h" +#include "Coin.h" +#include "HexCoding.h" +#include "Crc.h" namespace TW::Stellar { +constexpr uint8_t ed25519SecretSeed = 18 << 3; + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { @@ -49,4 +55,51 @@ void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, c }); } +// Taken from: https://github.com/stellar/js-stellar-base/blob/087e2d651a59b5cbed01386b4b8c45862d358259/src/util/checksum.js#L1C1-L17C2 +bool verifyChecksum(const Data& expected, const Data& actual) { + if (expected.size() != actual.size()) { + return false; + } + + if (expected.empty()) { + return true; + } + + for (size_t i = 0; i < expected.size(); i++) { + if (expected[i] != actual[i]) { + return false; + } + } + + return true; +} + +// Taken from: https://github.com/stellar/js-stellar-base/blob/087e2d651a59b5cbed01386b4b8c45862d358259/src/strkey.js#L290 +PrivateKey Entry::decodePrivateKey(TWCoinType coin, const std::string& privateKey) const { + Data decoded; + Base32::decode(privateKey, decoded); + + if (decoded.size() != 35) { + auto hexData = parse_hex(privateKey); + return PrivateKey(hexData, TW::curve(coin)); + } + + uint8_t versionByte = decoded[0]; + if (versionByte != ed25519SecretSeed) { + throw std::invalid_argument("Invalid version byte. Expected " + std::to_string(ed25519SecretSeed) + + ", got " + std::to_string(versionByte)); + } + + Data payload(decoded.begin(), decoded.end() - 2); + Data data(payload.begin() + 1, payload.end()); + Data checksum(decoded.end() - 2, decoded.end()); + + const auto expectedChecksum = Crc::crc16_xmodem(payload); + if (!verifyChecksum(expectedChecksum, checksum)) { + throw std::invalid_argument("invalid checksum"); + } + + return PrivateKey(data, TW::curve(coin)); +} + } // namespace TW::Stellar diff --git a/src/Stellar/Entry.h b/src/Stellar/Entry.h index aef0e1b8221..a8966c5795e 100644 --- a/src/Stellar/Entry.h +++ b/src/Stellar/Entry.h @@ -18,6 +18,7 @@ class Entry final : public CoinEntry { Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; + PrivateKey decodePrivateKey(TWCoinType coin, const std::string& privateKey) const override; }; } // namespace TW::Stellar diff --git a/src/interface/TWStoredKey.cpp b/src/interface/TWStoredKey.cpp index f756ce5e6b3..d3463cef2cc 100644 --- a/src/interface/TWStoredKey.cpp +++ b/src/interface/TWStoredKey.cpp @@ -8,7 +8,7 @@ #include "Data.h" #include "../HDWallet.h" #include "../Keystore/StoredKey.h" - +#include "../HexCoding.h" #include #include @@ -56,6 +56,21 @@ struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyWithEncryption(TWData* } } +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncoded(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { + return TWStoredKeyImportPrivateKeyEncodedWithEncryption(privateKey, name, password, coin, TWStoredKeyEncryptionAes128Ctr); +} + +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncodedWithEncryption(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption) { + try { + const auto& privateKeyString = *reinterpret_cast(privateKey); + const auto& nameString = *reinterpret_cast(name); + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWStoredKey{ KeyStore::StoredKey::createWithEncodedPrivateKeyAddDefaultAddress(nameString, passwordData, coin, privateKeyString, encryption) }; + } catch (...) { + return nullptr; + } +} + struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { return TWStoredKeyImportHDWalletWithEncryption(mnemonic, name, password, coin, TWStoredKeyEncryptionAes128Ctr); } @@ -180,6 +195,20 @@ TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, } } +TWString* _Nullable TWStoredKeyDecryptPrivateKeyEncoded(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password) { + try { + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + const auto encodedStr = key->impl.decryptPrivateKeyEncoded(passwordData); + return TWStringCreateWithUTF8Bytes(encodedStr.c_str()); + } catch (...) { + return nullptr; + } +} + +bool TWStoredKeyHasPrivateKeyEncoded(struct TWStoredKey* _Nonnull key) { + return key->impl.encodedPayload.has_value(); +} + TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password) { try { const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); diff --git a/swift/Sources/KeyStore.swift b/swift/Sources/KeyStore.swift index 0b6c3368d77..dc4f55afd3c 100644 --- a/swift/Sources/KeyStore.swift +++ b/swift/Sources/KeyStore.swift @@ -141,7 +141,14 @@ public final class KeyStore { guard let privateKey = PrivateKey(data: data) else { throw Error.invalidKey } - return try self.import(privateKey: privateKey, name: name, password: newPassword, coin: coins.first ?? .ethereum) + if key.hasPrivateKeyEncoded { + guard let encodedPrivateKey = key.decryptPrivateKeyEncoded(password: Data(password.utf8)) else { + throw Error.invalidPassword + } + return try self.import(encodedPrivateKey: encodedPrivateKey, name: name, password: newPassword, coin: coins.first ?? .ethereum) + } else { + return try self.import(privateKey: privateKey, name: name, password: newPassword, coin: coins.first ?? .ethereum) + } } private func checkMnemonic(_ data: Data) -> String? { @@ -150,6 +157,13 @@ public final class KeyStore { } return mnemonic } + + private func checkEncoded(wallet: Wallet, password: String) -> String? { + guard wallet.key.hasPrivateKeyEncoded else { + return nil + } + return wallet.key.decryptPrivateKeyEncoded(password: Data(password.utf8)) + } /// Imports a private key. /// @@ -172,6 +186,27 @@ public final class KeyStore { return wallet } + /// Imports an encoded private key. + /// + /// - Parameters: + /// - privateKey: private key to import + /// - password: password to use for the imported private key + /// - coin: coin to use for this wallet + /// - Returns: new wallet + public func `import`(encodedPrivateKey: String, name: String, password: String, coin: CoinType, encryption: StoredKeyEncryption = .aes128Ctr) throws -> Wallet { + guard let newKey = StoredKey.importPrivateKeyEncodedWithEncryption(privateKey: encodedPrivateKey, name: name, password: Data(password.utf8), coin: coin, encryption: encryption) else { + throw Error.invalidKey + } + let url = makeAccountURL() + let wallet = Wallet(keyURL: url, key: newKey) + _ = try wallet.getAccount(password: password, coin: coin) + wallets.append(wallet) + + try save(wallet: wallet) + + return wallet + } + /// Imports a wallet. /// /// - Parameters: @@ -216,6 +251,11 @@ public final class KeyStore { throw Error.invalidKey } return json + } else if let privateKey = checkEncoded(wallet: wallet, password: password), let newKey = StoredKey.importPrivateKeyEncodedWithEncryption(privateKey: privateKey, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) { + guard let json = newKey.exportJSON() else { + throw Error.invalidKey + } + return json } else if let newKey = StoredKey.importPrivateKeyWithEncryption(privateKey: privateKeyData, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) { guard let json = newKey.exportJSON() else { throw Error.invalidKey @@ -238,6 +278,19 @@ public final class KeyStore { } return key } + + /// Exports a wallet as encoded private key data. + /// + /// - Parameters: + /// - wallet: wallet to export + /// - password: account password + /// - Returns: encoded private key data + public func exportPrivateKeyEncoded(wallet: Wallet, password: String) throws -> String { + guard let key = wallet.key.decryptPrivateKeyEncoded(password: Data(password.utf8)) else { + throw Error.invalidPassword + } + return key + } /// Exports a wallet as a mnemonic phrase. /// diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 0b823afddf5..23db9ab0a71 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -179,6 +179,7 @@ class KeyStoreTests: XCTestCase { let wallet = try keyStore.import(json: json, name: "name", password: "password", newPassword: "newPassword", coins: [.ethereum]) let storedData = wallet.key.decryptPrivateKey(password: Data("newPassword".utf8)) + XCTAssertFalse(wallet.key.hasPrivateKeyEncoded) XCTAssertNotNil(keyStore.keyWallet) XCTAssertNotNil(storedData) XCTAssertNotNil(PrivateKey(data: storedData!)) @@ -197,6 +198,57 @@ class KeyStoreTests: XCTestCase { XCTAssertNotNil(storedData) XCTAssertNotNil(PrivateKey(data: storedData!)) } + + func testImportKeyEncodedEthereum() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let privateKeyHex = "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c" + let key = StoredKey.importPrivateKeyEncoded(privateKey: privateKeyHex, name: "name", password: Data("password".utf8), coin: .ethereum)! + let json = key.exportJSON()! + + let wallet = try keyStore.import(json: json, name: "name", password: "password", newPassword: "newPassword", coins: [.ethereum]) + let storedEncoded = wallet.key.decryptPrivateKeyEncoded(password: Data("newPassword".utf8)) + + let exportedPrivateKey = try keyStore.exportPrivateKeyEncoded(wallet: wallet, password: "newPassword") + + XCTAssertTrue(wallet.key.hasPrivateKeyEncoded) + XCTAssertNotNil(keyStore.keyWallet) + XCTAssertNotNil(storedEncoded) + XCTAssertEqual(privateKeyHex, storedEncoded) + XCTAssertEqual(privateKeyHex, exportedPrivateKey) + } + + func testImportKeyEncodedSolana() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let privateKeyBase58 = "A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr" + let key = StoredKey.importPrivateKeyEncoded(privateKey: privateKeyBase58, name: "name", password: Data("password".utf8), coin: .solana)! + let json = key.exportJSON()! + + let wallet = try keyStore.import(json: json, name: "name", password: "password", newPassword: "newPassword", coins: [.solana]) + let storedEncoded = wallet.key.decryptPrivateKeyEncoded(password: Data("newPassword".utf8)) + + let exportedPrivateKey = try keyStore.exportPrivateKeyEncoded(wallet: wallet, password: "newPassword") + + XCTAssertTrue(wallet.key.hasPrivateKeyEncoded) + XCTAssertNotNil(keyStore.keyWallet) + XCTAssertNotNil(storedEncoded) + XCTAssertEqual(privateKeyBase58, storedEncoded) + XCTAssertEqual(privateKeyBase58, exportedPrivateKey) + } + + func testImportPrivateKeyAES256Stellar() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let privateKeyBase32 = "SAV76USXIJOBMEQXPANUOQM6F5LIOTLPDIDVRJBFFE2MDJXG24TAPUU7" + let key = StoredKey.importPrivateKeyEncodedWithEncryption(privateKey: privateKeyBase32, name: "name", password: Data("password".utf8), coin: .stellar, encryption: StoredKeyEncryption.aes256Ctr)! + let json = key.exportJSON()! + + let wallet = try keyStore.import(json: json, name: "name", password: "password", newPassword: "newPassword", coins: [.stellar]) + let storedEncoded = wallet.key.decryptPrivateKeyEncoded(password: Data("newPassword".utf8)) + + XCTAssertTrue(wallet.key.hasPrivateKeyEncoded) + XCTAssertNotNil(keyStore.keyWallet) + XCTAssertNotNil(storedEncoded) + XCTAssertEqual(privateKeyBase32, storedEncoded) + } func testImportPrivateKey() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) diff --git a/tests/interface/TWStoredKeyTests.cpp b/tests/interface/TWStoredKeyTests.cpp index ee758923b47..cc9ee253a9a 100644 --- a/tests/interface/TWStoredKeyTests.cpp +++ b/tests/interface/TWStoredKeyTests.cpp @@ -97,6 +97,122 @@ TEST(TWStoredKey, importPrivateKeyAes256) { TWPrivateKeyDelete(privateKey3); } +TEST(TWStoredKey, importPrivateKeyHexButDecryptEncoded) { + const auto privateKeyHex = "3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"; + const auto privateKey = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes(privateKeyHex)).get())); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto coin = TWCoinTypeBitcoin; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportPrivateKey(privateKey.get(), name.get(), password.get(), coin)); + const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(privateKey2.get()), TWDataSize(privateKey2.get()))), privateKeyHex); + EXPECT_FALSE(TWStoredKeyHasPrivateKeyEncoded(key.get())); + const auto privateKey2Encoded = WRAPS(TWStoredKeyDecryptPrivateKeyEncoded(key.get(), password.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(privateKey2Encoded.get())), privateKeyHex); + + const auto privateKey3 = TWStoredKeyPrivateKey(key.get(), coin, password.get()); + const auto pkData3 = WRAPD(TWPrivateKeyData(privateKey3)); + EXPECT_EQ(hex(data(TWDataBytes(pkData3.get()), TWDataSize(pkData3.get()))), privateKeyHex); + TWPrivateKeyDelete(privateKey3); +} + +TEST(TWStoredKey, importPrivateKeyEncodedHex) { + const auto privateKeyHex = "3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"; + const auto privateKey = WRAPS(TWStringCreateWithUTF8Bytes(privateKeyHex)); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto coin = TWCoinTypeBitcoin; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportPrivateKeyEncoded(privateKey.get(), name.get(), password.get(), coin)); + const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(privateKey2.get()), TWDataSize(privateKey2.get()))), privateKeyHex); + EXPECT_TRUE(TWStoredKeyHasPrivateKeyEncoded(key.get())); + const auto privateKey2Encoded = WRAPS(TWStoredKeyDecryptPrivateKeyEncoded(key.get(), password.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(privateKey2Encoded.get())), privateKeyHex); + + const auto privateKey3 = TWStoredKeyPrivateKey(key.get(), coin, password.get()); + const auto pkData3 = WRAPD(TWPrivateKeyData(privateKey3)); + EXPECT_EQ(hex(data(TWDataBytes(pkData3.get()), TWDataSize(pkData3.get()))), privateKeyHex); + TWPrivateKeyDelete(privateKey3); +} + +TEST(TWStoredKey, importPrivateKeyEncodedStellar) { + const auto privateKeyEncoded = "SAV76USXIJOBMEQXPANUOQM6F5LIOTLPDIDVRJBFFE2MDJXG24TAPUU7"; + const auto decodedPrivateKeyHex = "2bff5257425c161217781b47419e2f56874d6f1a0758a4252934c1a6e6d72607"; + const auto privateKey = WRAPS(TWStringCreateWithUTF8Bytes(privateKeyEncoded)); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto coin = TWCoinTypeStellar; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportPrivateKeyEncoded(privateKey.get(), name.get(), password.get(), coin)); + const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(privateKey2.get()), TWDataSize(privateKey2.get()))), decodedPrivateKeyHex); + EXPECT_TRUE(TWStoredKeyHasPrivateKeyEncoded(key.get())); + const auto privateKey2Encoded = WRAPS(TWStoredKeyDecryptPrivateKeyEncoded(key.get(), password.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(privateKey2Encoded.get())), privateKeyEncoded); + + const auto privateKey3 = TWStoredKeyPrivateKey(key.get(), coin, password.get()); + const auto pkData3 = WRAPD(TWPrivateKeyData(privateKey3)); + EXPECT_EQ(hex(data(TWDataBytes(pkData3.get()), TWDataSize(pkData3.get()))), decodedPrivateKeyHex); + TWPrivateKeyDelete(privateKey3); +} + +TEST(TWStoredKey, importPrivateKeyEncodedSolana) { + const auto solanaPrivateKey = "A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr"; + const auto decodedSolanaPrivateKeyHex = "8778cc93c6596387e751d2dc693bbd93e434bd233bc5b68a826c56131821cb63"; + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto solanaKey = WRAP(TWStoredKey, TWStoredKeyImportPrivateKeyEncoded(WRAPS(TWStringCreateWithUTF8Bytes(solanaPrivateKey)).get(), name.get(), password.get(), TWCoinTypeSolana)); + const auto solanaPrivateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(solanaKey.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(solanaPrivateKey2.get()), TWDataSize(solanaPrivateKey2.get()))), decodedSolanaPrivateKeyHex); + EXPECT_TRUE(TWStoredKeyHasPrivateKeyEncoded(solanaKey.get())); + const auto solanaPrivateKey2Encoded = WRAPS(TWStoredKeyDecryptPrivateKeyEncoded(solanaKey.get(), password.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(solanaPrivateKey2Encoded.get())), solanaPrivateKey); + + const auto solanaPrivateKey3 = TWStoredKeyPrivateKey(solanaKey.get(), TWCoinTypeSolana, password.get()); + const auto solanaPkData3 = WRAPD(TWPrivateKeyData(solanaPrivateKey3)); + EXPECT_EQ(hex(data(TWDataBytes(solanaPkData3.get()), TWDataSize(solanaPkData3.get()))), decodedSolanaPrivateKeyHex); + TWPrivateKeyDelete(solanaPrivateKey3); +} + +TEST(TWStoredKey, importPrivateKeyHexEncodedSolana) { + const auto solanaPrivateKey = "8778cc93c6596387e751d2dc693bbd93e434bd233bc5b68a826c56131821cb63"; + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto solanaKey = WRAP(TWStoredKey, TWStoredKeyImportPrivateKeyEncoded(WRAPS(TWStringCreateWithUTF8Bytes(solanaPrivateKey)).get(), name.get(), password.get(), TWCoinTypeSolana)); + const auto solanaPrivateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(solanaKey.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(solanaPrivateKey2.get()), TWDataSize(solanaPrivateKey2.get()))), solanaPrivateKey); + EXPECT_TRUE(TWStoredKeyHasPrivateKeyEncoded(solanaKey.get())); + const auto solanaPrivateKey2Encoded = WRAPS(TWStoredKeyDecryptPrivateKeyEncoded(solanaKey.get(), password.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(solanaPrivateKey2Encoded.get())), solanaPrivateKey); + + const auto solanaPrivateKey3 = TWStoredKeyPrivateKey(solanaKey.get(), TWCoinTypeSolana, password.get()); + const auto solanaPkData3 = WRAPD(TWPrivateKeyData(solanaPrivateKey3)); + EXPECT_EQ(hex(data(TWDataBytes(solanaPkData3.get()), TWDataSize(solanaPkData3.get()))), solanaPrivateKey); + TWPrivateKeyDelete(solanaPrivateKey3); +} + +TEST(TWStoredKey, importPrivateKeyEncodedAes256) { + const auto privateKeyEncoded = "SAV76USXIJOBMEQXPANUOQM6F5LIOTLPDIDVRJBFFE2MDJXG24TAPUU7"; + const auto decodedPrivateKeyHex = "2bff5257425c161217781b47419e2f56874d6f1a0758a4252934c1a6e6d72607"; + const auto privateKey = WRAPS(TWStringCreateWithUTF8Bytes(privateKeyEncoded)); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto coin = TWCoinTypeStellar; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportPrivateKeyEncodedWithEncryption(privateKey.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes256Ctr)); + const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(privateKey2.get()), TWDataSize(privateKey2.get()))), decodedPrivateKeyHex); + + const auto privateKey3 = TWStoredKeyPrivateKey(key.get(), coin, password.get()); + const auto pkData3 = WRAPD(TWPrivateKeyData(privateKey3)); + EXPECT_EQ(hex(data(TWDataBytes(pkData3.get()), TWDataSize(pkData3.get()))), decodedPrivateKeyHex); + TWPrivateKeyDelete(privateKey3); +} + TEST(TWStoredKey, importHDWallet) { const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes("team engine square letter hero song dizzy scrub tornado fabric divert saddle")); const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); diff --git a/wasm/src/keystore/default-impl.ts b/wasm/src/keystore/default-impl.ts index e7f9bcbf8fd..043fe3b1abe 100644 --- a/wasm/src/keystore/default-impl.ts +++ b/wasm/src/keystore/default-impl.ts @@ -104,6 +104,26 @@ export class Default implements Types.IKeyStore { }); } + importKeyEncoded( + key: string, + name: string, + password: string, + coin: CoinType, + encryption: StoredKeyEncryption + ): Promise { + return new Promise((resolve, reject) => { + const { StoredKey, PrivateKey, Curve, StoredKeyEncryption } = this.core; + + let pass = Buffer.from(password); + let storedKey = StoredKey.importPrivateKeyEncodedWithEncryption(key, name, pass, coin, encryption); + let wallet = this.mapWallet(storedKey); + storedKey.delete(); + this.importWallet(wallet) + .then(() => resolve(wallet)) + .catch((error) => reject(error)); + }); + } + addAccounts( id: string, password: string, @@ -164,4 +184,44 @@ export class Default implements Types.IKeyStore { return value; }); } + + getWalletType(id: string): Promise { + return this.load(id).then((wallet) => wallet.type); + } + + exportMnemonic(id: string, password: string): Promise { + return this.load(id).then((wallet) => { + if (wallet.type !== Types.WalletType.Mnemonic) { + throw Types.Error.UnsupportedWalletType; + } + let storedKey = this.mapStoredKey(wallet); + let value = storedKey.decryptMnemonic(Buffer.from(password)); + storedKey.delete(); + return value; + }); + } + + exportPrivateKey(id: string, password: string): Promise { + return this.load(id).then((wallet) => { + if (wallet.type !== Types.WalletType.PrivateKey) { + throw Types.Error.UnsupportedWalletType; + } + let storedKey = this.mapStoredKey(wallet); + let value = storedKey.decryptPrivateKey(Buffer.from(password)); + storedKey.delete(); + return value; + }); + } + + exportPrivateKeyEncoded(id: string, password: string): Promise { + return this.load(id).then((wallet) => { + if (wallet.type !== Types.WalletType.PrivateKey) { + throw Types.Error.UnsupportedWalletType; + } + let storedKey = this.mapStoredKey(wallet); + let value = storedKey.decryptPrivateKeyEncoded(Buffer.from(password)); + storedKey.delete(); + return value; + }); + } } diff --git a/wasm/src/keystore/types.ts b/wasm/src/keystore/types.ts index 63f9ba13961..888ca46e705 100644 --- a/wasm/src/keystore/types.ts +++ b/wasm/src/keystore/types.ts @@ -18,6 +18,7 @@ export enum Error { InvalidMnemonic = "invalid mnemonic", InvalidJSON = "invalid JSON", InvalidKey = "invalid key", + UnsupportedWalletType = "unsupported wallet type", } export interface ActiveAccount { diff --git a/wasm/tests/KeyStore.test.ts b/wasm/tests/KeyStore.test.ts index 27bbc3ea17f..a763dc99456 100644 --- a/wasm/tests/KeyStore.test.ts +++ b/wasm/tests/KeyStore.test.ts @@ -61,4 +61,51 @@ describe("KeyStore", () => { const exported2 = await keystore.export(wallet.id, password); assert.equal(HexCoding.encode(exported2), "0x9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c"); }).timeout(10000); + + it("test export mnemonic", async () => { + const { CoinType, StoredKeyEncryption, HexCoding } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + + const storage = new KeyStore.FileSystemStorage("/tmp"); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ], StoredKeyEncryption.aes128Ctr); + assert.equal(await keystore.getWalletType(wallet.id), "mnemonic"); + + const exported = await keystore.exportMnemonic(wallet.id, password); + assert.equal(exported, mnemonic); + }).timeout(10000); + + it("test export key", async () => { + const { CoinType, StoredKeyEncryption, HexCoding } = globalThis.core; + const password = globalThis.password as string; + + const storage = new KeyStore.FileSystemStorage("/tmp"); + const keystore = new KeyStore.Default(globalThis.core, storage); + + const inputPrivateKey = Buffer.from("9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c", "hex"); + const wallet = await keystore.importKey(inputPrivateKey, "Coolw", password, CoinType.solana, StoredKeyEncryption.aes128Ctr); + assert.equal(await keystore.getWalletType(wallet.id), "private-key"); + + const exported = await keystore.exportPrivateKey(wallet.id, password); + assert.equal(HexCoding.encode(exported), "0x9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c"); + }).timeout(10000); + + it("test export key encoded", async () => { + const { CoinType, StoredKeyEncryption } = globalThis.core; + const password = globalThis.password as string; + + const storage = new KeyStore.FileSystemStorage("/tmp"); + const keystore = new KeyStore.Default(globalThis.core, storage); + + const inputPrivateKeyBase58 = "A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr"; + const wallet = await keystore.importKeyEncoded(inputPrivateKeyBase58, "Coolw", password, CoinType.solana, StoredKeyEncryption.aes128Ctr); + assert.equal(await keystore.getWalletType(wallet.id), "private-key"); + + const exported = await keystore.exportPrivateKeyEncoded(wallet.id, password); + assert.equal(exported, inputPrivateKeyBase58); + }).timeout(10000); }); From 1211b11a67d9ed0b6ed7842d174b4750a356ec61 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Mon, 14 Apr 2025 15:21:01 +0700 Subject: [PATCH 25/72] chore(scw): Rename `callDataGasLimit` to `callGasLimit` (#4364) --- rust/tw_evm/src/modules/tx_builder.rs | 4 ++-- rust/tw_evm/src/transaction/user_operation_v0_7.rs | 6 +++--- rust/tw_evm/tests/barz.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs index ae884b653a4..c42bb79be10 100644 --- a/rust/tw_evm/src/modules/tx_builder.rs +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -498,7 +498,7 @@ impl TxBuilder { let factory = Self::parse_address_optional(user_op_v0_7.factory.as_ref()) .context("Invalid factory address")?; - let call_data_gas_limit = U256::from_big_endian_slice(&input.gas_limit) + let call_gas_limit = U256::from_big_endian_slice(&input.gas_limit) .into_tw() .context("Invalid gas limit")? .try_into() @@ -560,7 +560,7 @@ impl TxBuilder { factory, factory_data: user_op_v0_7.factory_data.to_vec(), call_data: erc4337_payload, - call_data_gas_limit, + call_gas_limit, verification_gas_limit, pre_verification_gas, max_fee_per_gas, diff --git a/rust/tw_evm/src/transaction/user_operation_v0_7.rs b/rust/tw_evm/src/transaction/user_operation_v0_7.rs index 96e1f8624cd..7272b6b0704 100644 --- a/rust/tw_evm/src/transaction/user_operation_v0_7.rs +++ b/rust/tw_evm/src/transaction/user_operation_v0_7.rs @@ -42,7 +42,7 @@ impl PackedUserOperation { }; let account_gas_limits = - concat_u128_be(user_op.verification_gas_limit, user_op.call_data_gas_limit); + concat_u128_be(user_op.verification_gas_limit, user_op.call_gas_limit); let gas_fees = concat_u128_be(user_op.max_fee_per_gas, user_op.max_priority_fee_per_gas); let paymaster_and_data = if let Some(paymaster) = user_op.paymaster { @@ -135,7 +135,7 @@ pub struct UserOperationV0_7 { pub call_data: Data, #[serde(serialize_with = "as_u256_hex")] - pub call_data_gas_limit: u128, + pub call_gas_limit: u128, #[serde(serialize_with = "as_u256_hex")] pub verification_gas_limit: u128, #[serde(serialize_with = "U256::as_hex")] @@ -294,7 +294,7 @@ mod tests { factory: Some(Address::from("0xf471789937856d80e589f5996cf8b0511ddd9de4")), factory_data: "f471789937856d80e589f5996cf8b0511ddd9de4".decode_hex().unwrap(), call_data: "00".decode_hex().unwrap(), - call_data_gas_limit: 100000u128, + call_gas_limit: 100000u128, verification_gas_limit: 100000u128, pre_verification_gas: U256::from(1000000u64), max_fee_per_gas: 100000u128, diff --git a/rust/tw_evm/tests/barz.rs b/rust/tw_evm/tests/barz.rs index 27006d1a13c..591f895fd14 100644 --- a/rust/tw_evm/tests/barz.rs +++ b/rust/tw_evm/tests/barz.rs @@ -317,7 +317,7 @@ fn test_biz4337_transfer() { "68109b9caf49f7971b689307c9a77ceec46e4b8fa88421c4276dd846f782d92c" ); - let expected = r#"{"sender":"0x2EF648D7C03412B832726fd4683E2625deA047Ba","nonce":"0x00","callData":"0x76276c82000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd0000000000000000000000000000000000000000000000000000000000","callDataGasLimit":"0x0186a0","verificationGasLimit":"0x0186a0","preVerificationGas":"0x0f4240","maxFeePerGas":"0x0186a0","maxPriorityFeePerGas":"0x0186a0","paymaster":"0xb0086171AC7b6BD4D046580bca6d6A4b0835c232","paymasterVerificationGasLimit":"0x01869f","paymasterPostOpGasLimit":"0x015b38","paymasterData":"0x00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b","signature":"0xf6b1f7ad22bcc68ca292bc10d15e82e0eab8c75c1a04f9750e7cff1418d38d9c6c115c510e3f47eb802103d62f88fa7d4a3b2e24e2ddbe7ee68153920ab3f6cc1b"}"#; + let expected = r#"{"sender":"0x2EF648D7C03412B832726fd4683E2625deA047Ba","nonce":"0x00","callData":"0x76276c82000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd0000000000000000000000000000000000000000000000000000000000","callGasLimit":"0x0186a0","verificationGasLimit":"0x0186a0","preVerificationGas":"0x0f4240","maxFeePerGas":"0x0186a0","maxPriorityFeePerGas":"0x0186a0","paymaster":"0xb0086171AC7b6BD4D046580bca6d6A4b0835c232","paymasterVerificationGasLimit":"0x01869f","paymasterPostOpGasLimit":"0x015b38","paymasterData":"0x00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b","signature":"0xf6b1f7ad22bcc68ca292bc10d15e82e0eab8c75c1a04f9750e7cff1418d38d9c6c115c510e3f47eb802103d62f88fa7d4a3b2e24e2ddbe7ee68153920ab3f6cc1b"}"#; let actual = String::from_utf8(output.encoded.to_vec()).unwrap(); assert_eq!(actual, expected); } @@ -405,7 +405,7 @@ fn test_biz4337_transfer_batch() { "f6340068891dc3eb78959993151c421dde23982b3a1407c0dbbd62c2c22c3cb8" ); - let expected = r#"{"sender":"0x2EF648D7C03412B832726fd4683E2625deA047Ba","nonce":"0x00","callData":"0x26da7d880000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd000000000000000000000000000000000000000000000000000000000000000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b1266000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000","callDataGasLimit":"0x0186a0","verificationGasLimit":"0x0186a0","preVerificationGas":"0x0f4240","maxFeePerGas":"0x0186a0","maxPriorityFeePerGas":"0x0186a0","paymaster":"0xb0086171AC7b6BD4D046580bca6d6A4b0835c232","paymasterVerificationGasLimit":"0x01869f","paymasterPostOpGasLimit":"0x015b38","paymasterData":"0x00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b","signature":"0x21ab0bdcd1441aef3e4046a922bab3636d0c74011c1b055c55ad9f39ae9b4dac59bcbf3bc1ff31b367a83360edfc8e9652f1a5c8b07eb76fe5a426835682d6721c"}"#; + let expected = r#"{"sender":"0x2EF648D7C03412B832726fd4683E2625deA047Ba","nonce":"0x00","callData":"0x26da7d880000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd000000000000000000000000000000000000000000000000000000000000000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b1266000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000","callGasLimit":"0x0186a0","verificationGasLimit":"0x0186a0","preVerificationGas":"0x0f4240","maxFeePerGas":"0x0186a0","maxPriorityFeePerGas":"0x0186a0","paymaster":"0xb0086171AC7b6BD4D046580bca6d6A4b0835c232","paymasterVerificationGasLimit":"0x01869f","paymasterPostOpGasLimit":"0x015b38","paymasterData":"0x00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b","signature":"0x21ab0bdcd1441aef3e4046a922bab3636d0c74011c1b055c55ad9f39ae9b4dac59bcbf3bc1ff31b367a83360edfc8e9652f1a5c8b07eb76fe5a426835682d6721c"}"#; let actual = String::from_utf8(output.encoded.to_vec()).unwrap(); assert_eq!(actual, expected); } From 83823464a621115cf7c06c86079a2597fe46e55b Mon Sep 17 00:00:00 2001 From: 10gic Date: Mon, 14 Apr 2025 19:40:16 +0800 Subject: [PATCH 26/72] Support signing EIP 7702 transactions with a sign server, e.g., AWS KMS. (#4363) * Support signing EIP 7702 transactions with a sign server, e.g., AWS KMS. * chore: rename MaybeOtherAuthFields to AuthorizationCustomSignature --- .../core/app/blockchains/ethereum/TestBarz.kt | 2 +- rust/tw_evm/src/modules/tx_builder.rs | 128 +++++++++++++----- .../src/transaction/transaction_eip7702.rs | 4 +- rust/tw_evm/tests/barz.rs | 6 +- .../tests/chains/ethereum/ethereum_compile.rs | 78 +++++++++++ src/proto/Ethereum.proto | 20 ++- 6 files changed, 193 insertions(+), 45 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt index c1c0c80f905..b793671d644 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt @@ -281,7 +281,7 @@ class TestBarz { }.build() }.build() - eip7702Authority = Ethereum.Authority.newBuilder().apply { + eip7702Authorization = Ethereum.Authorization.newBuilder().apply { address = "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7" }.build() } diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs index c42bb79be10..bc4f378fad1 100644 --- a/rust/tw_evm/src/modules/tx_builder.rs +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -13,7 +13,9 @@ use crate::address::{Address, EvmAddress}; use crate::evm_context::EvmContext; use crate::modules::authorization_signer::AuthorizationSigner; use crate::transaction::access_list::{Access, AccessList}; -use crate::transaction::authorization_list::{Authorization, AuthorizationList}; +use crate::transaction::authorization_list::{ + Authorization, AuthorizationList, SignedAuthorization, +}; use crate::transaction::transaction_eip1559::TransactionEip1559; use crate::transaction::transaction_eip7702::TransactionEip7702; use crate::transaction::transaction_non_typed::TransactionNonTyped; @@ -23,8 +25,11 @@ use crate::transaction::UnsignedTransactionBox; use std::marker::PhantomData; use std::str::FromStr; use tw_coin_entry::error::prelude::*; +use tw_encoding::hex::DecodeHex; use tw_hash::H256; use tw_keypair::ecdsa::secp256k1; +use tw_keypair::ecdsa::secp256k1::Signature; +use tw_keypair::KeyPairError; use tw_memory::Data; use tw_number::U256; use tw_proto::Common::Proto::SigningError as CommonError; @@ -363,16 +368,6 @@ impl TxBuilder { payload: Data, to_address: Option
, ) -> SigningResult> { - let signer_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref()) - .into_tw() - .context("Sender's private key must be provided to generate an EIP-7702 transaction")?; - let signer = Address::with_secp256k1_pubkey(&signer_key.public()); - if to_address != Some(signer) { - return SigningError::err(SigningErrorType::Error_invalid_params).context( - "Unexpected 'accountAddress'. Expected to be the same as the signer address", - ); - } - let nonce = U256::from_big_endian_slice(&input.nonce) .into_tw() .context("Invalid nonce")?; @@ -381,6 +376,10 @@ impl TxBuilder { .into_tw() .context("Invalid gas limit")?; + let to = to_address + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("'to' must be provided for `SetCode` transaction")?; + let max_inclusion_fee_per_gas = U256::from_big_endian_slice(&input.max_inclusion_fee_per_gas) .into_tw() @@ -393,37 +392,14 @@ impl TxBuilder { let access_list = Self::parse_access_list(&input.access_list).context("Invalid access list")?; - let authority: Address = input - .eip7702_authority - .as_ref() - .or_tw_err(SigningErrorType::Error_invalid_params) - .context("'eip7702Authority' must be provided for `SetCode` transaction")? - .address - // Parse `Address` - .parse() - .into_tw() - .context("Invalid authority address")?; - - let chain_id = U256::from_big_endian_slice(&input.chain_id) - .into_tw() - .context("Invalid chain ID")?; - - let authorization = Authorization { - chain_id, - address: authority, - // `authorization.nonce` must be incremented by 1 over `transaction.nonce`. - nonce: nonce + 1, - }; - let signed_authorization = AuthorizationSigner::sign(&signer_key, authorization)?; - let authorization_list = AuthorizationList::from(vec![signed_authorization]); + let authorization_list = Self::build_authorization_list(input, to)?; Ok(TransactionEip7702 { nonce, max_inclusion_fee_per_gas, max_fee_per_gas, gas_limit, - // EIP-7702 transaction calls a smart contract function of the authorized address. - to: Some(signer), + to, amount: eth_amount, payload, access_list, @@ -664,4 +640,84 @@ impl TxBuilder { let signer = Address::with_secp256k1_pubkey(&signer_key.public()); Ok(signer) } + + fn build_authorization_list( + input: &Proto::SigningInput, + destination: Address, // Field `destination` is only used for sanity check + ) -> SigningResult { + let eip7702_authorization = input + .eip7702_authorization + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("'eip7702Authorization' must be provided for `SetCode` transaction")?; + + let address = eip7702_authorization + .address + .parse() + .into_tw() + .context("Invalid authority address")?; + + let signed_authorization = + if let Some(other_auth_fields) = &eip7702_authorization.custom_signature { + // If field `custom_signature` is provided, it means that the authorization is already signed. + let chain_id = U256::from_big_endian_slice(&other_auth_fields.chain_id) + .into_tw() + .context("Invalid chain ID")?; + let nonce = U256::from_big_endian_slice(&other_auth_fields.nonce) + .into_tw() + .context("Invalid nonce")?; + let signature = Signature::try_from( + other_auth_fields + .signature + .decode_hex() + .map_err(|_| KeyPairError::InvalidSignature)? + .as_slice(), + ) + .tw_err(SigningErrorType::Error_invalid_params) + .context("Invalid signature")?; + + SignedAuthorization { + authorization: Authorization { + chain_id, + address, + nonce, + }, + y_parity: signature.v(), + r: U256::from_big_endian(signature.r()), + s: U256::from_big_endian(signature.s()), + } + } else { + // If field `custom_signature` is not provided, the authorization will be signed with the provided private key, nonce and chainId + let signer_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref()) + .into_tw() + .context( + "Sender's private key must be provided to generate an EIP-7702 transaction", + )?; + let signer = Address::with_secp256k1_pubkey(&signer_key.public()); + if destination != signer { + return SigningError::err(SigningErrorType::Error_invalid_params).context( + "Unexpected 'destination'. Expected to be the same as the signer address", + ); + } + + let chain_id = U256::from_big_endian_slice(&input.chain_id) + .into_tw() + .context("Invalid chain ID")?; + let nonce = U256::from_big_endian_slice(&input.nonce) + .into_tw() + .context("Invalid nonce")?; + + AuthorizationSigner::sign( + &signer_key, + Authorization { + chain_id, + address, + // `authorization.nonce` must be incremented by 1 over `transaction.nonce`. + nonce: nonce + 1, + }, + )? + }; + + Ok(AuthorizationList::from(vec![signed_authorization])) + } } diff --git a/rust/tw_evm/src/transaction/transaction_eip7702.rs b/rust/tw_evm/src/transaction/transaction_eip7702.rs index 9833bc056b2..6f6c3791f35 100644 --- a/rust/tw_evm/src/transaction/transaction_eip7702.rs +++ b/rust/tw_evm/src/transaction/transaction_eip7702.rs @@ -21,7 +21,7 @@ pub struct TransactionEip7702 { pub max_inclusion_fee_per_gas: U256, pub max_fee_per_gas: U256, pub gas_limit: U256, - pub to: Option
, + pub to: Address, pub amount: U256, pub payload: Data, pub access_list: AccessList, @@ -143,7 +143,7 @@ mod tests { max_inclusion_fee_per_gas: U256::from(2_u32), max_fee_per_gas: U256::from(3_u32), gas_limit: U256::from(4_u32), - to: Some(Address::from_str("0x0101010101010101010101010101010101010101").unwrap()), + to: Address::from_str("0x0101010101010101010101010101010101010101").unwrap(), amount: U256::from(5_u32), payload: "0x1234".decode_hex().unwrap(), access_list: AccessList::default(), diff --git a/rust/tw_evm/tests/barz.rs b/rust/tw_evm/tests/barz.rs index 591f895fd14..a7127fd7ca1 100644 --- a/rust/tw_evm/tests/barz.rs +++ b/rust/tw_evm/tests/barz.rs @@ -437,8 +437,9 @@ fn test_biz_eip7702_transfer() { )), // TWT token. to_address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), - eip7702_authority: Some(Proto::Authority { + eip7702_authorization: Some(Proto::Authorization { address: "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7".into(), + custom_signature: None, }), ..Proto::SigningInput::default() }; @@ -518,8 +519,9 @@ fn test_biz_eip7702_transfer_batch() { wallet_type: Proto::SCWalletType::Biz, }), }), - eip7702_authority: Some(Proto::Authority { + eip7702_authorization: Some(Proto::Authorization { address: "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7".into(), + custom_signature: None, }), ..Proto::SigningInput::default() }; diff --git a/rust/tw_tests/tests/chains/ethereum/ethereum_compile.rs b/rust/tw_tests/tests/chains/ethereum/ethereum_compile.rs index b83500c18e6..55fcae72b50 100644 --- a/rust/tw_tests/tests/chains/ethereum/ethereum_compile.rs +++ b/rust/tw_tests/tests/chains/ethereum/ethereum_compile.rs @@ -14,6 +14,7 @@ use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_memory::test_utils::tw_data_vector_helper::TWDataVectorHelper; use tw_number::U256; use tw_proto::Ethereum::Proto; +use tw_proto::Ethereum::Proto::{Authorization, AuthorizationCustomSignature, TransactionMode}; use tw_proto::TxCompiler::Proto as CompilerProto; use tw_proto::{deserialize, serialize}; @@ -83,6 +84,83 @@ fn test_transaction_compiler_eth() { assert_eq!(output.encoded.to_hex(), expected_encoded); } +#[test] +fn test_transaction_compiler_eip7702() { + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(0), + data: Cow::default(), + }; + let input = Proto::SigningInput { + tx_mode: TransactionMode::SetCode, + nonce: U256::encode_be_compact(0), + chain_id: U256::encode_be_compact(17000), // Holesky Testnet + max_inclusion_fee_per_gas: U256::encode_be_compact(20_000_000_000), + max_fee_per_gas: U256::encode_be_compact(20_000_000_000), + gas_limit: U256::encode_be_compact(50_000), + to_address: "0x18356de2Bc664e45dD22266A674906574087Cf54".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + eip7702_authorization: Some(Authorization { + address: "0x3535353535353535353535353535353535353535".into(), + custom_signature: Some(AuthorizationCustomSignature { + nonce: U256::encode_be_compact(1), + chain_id: U256::encode_be_compact(0), // chain id 0 means any chain + signature: "08b7bfc6bcaca1dfd7a295c3a6908fea545a62958cf2c048639224a8bede8d1f56dce327574529c56f7f3db308a34d44e2312a11c89db8af99371d4fe490e55f00".into(), + }), + }), + ..Proto::SigningInput::default() + }; + + // Step 2: Obtain preimage hash + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let preimage_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_pre_image_hashes(CoinType::Ethereum as u32, input_data.ptr()) + }) + .to_vec() + .expect("!tw_transaction_compiler_pre_image_hashes returned nullptr"); + + let preimage: CompilerProto::PreSigningOutput = + deserialize(&preimage_data).expect("Coin entry returned an invalid output"); + + assert_eq!(preimage.error, SigningErrorType::OK); + assert!(preimage.error_message.is_empty()); + assert_eq!( + preimage.data_hash.to_hex(), + "d5f3f94ff2c70686623c71ef9e367f341a3ef6691d02e9e9db671c9281d56432" + ); + + // Step 3: Compile transaction info + + // Simulate signature, normally obtained from signature server + let signature = "601148c0af0108fe9f051ca5e9bd5c0b9e7c4cc7385b1625eeae0ec4bbe5537960d8c76d98279329d1ef9d79002ccddd53e522c0e821003590e74eba51a1c95601".decode_hex().unwrap(); + let public_key = "04ec6632291fbfe6b47826a1c4b195f8b112a7e147e8a5a15fb0f7d7de022652e7f65b97a57011c09527688e23c07ae9c83a2cae2e49edba226e7c43f0baa7296d".decode_hex().unwrap(); + + let signatures = TWDataVectorHelper::create([signature]); + let public_keys = TWDataVectorHelper::create([public_key]); + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let output_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_compile( + CoinType::Ethereum as u32, + input_data.ptr(), + signatures.ptr(), + public_keys.ptr(), + ) + }) + .to_vec() + .expect("!tw_transaction_compiler_compile returned nullptr"); + + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + let expected_encoded = "04f8cc824268808504a817c8008504a817c80082c3509418356de2bc664e45dd22266a674906574087cf548080c0f85cf85a809435353535353535353535353535353535353535350180a008b7bfc6bcaca1dfd7a295c3a6908fea545a62958cf2c048639224a8bede8d1fa056dce327574529c56f7f3db308a34d44e2312a11c89db8af99371d4fe490e55f01a0601148c0af0108fe9f051ca5e9bd5c0b9e7c4cc7385b1625eeae0ec4bbe55379a060d8c76d98279329d1ef9d79002ccddd53e522c0e821003590e74eba51a1c956"; + // Successfully broadcasted: https://holesky.etherscan.io/tx/0x1c349e81dd135bfe104c8051fa9e668d6f8c0323abe852b1d1522b772932c0ec + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + #[test] fn test_transaction_compiler_plan_not_supported() { let transfer = Proto::mod_Transaction::Transfer { diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index c1ce8b6fdb6..dc1e354784a 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -194,10 +194,22 @@ message Access { repeated bytes stored_keys = 2; } -// [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) authority. -message Authority { +// [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) authorization. +message Authorization { // Address to be authorized, a smart contract address. string address = 2; + // If custom_signature isn't provided, the authorization will be signed with the provided private key, nonce and chainId + AuthorizationCustomSignature custom_signature = 3; +} + +// [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) authorization. +message AuthorizationCustomSignature { + // Chain id (uint256, serialized big endian). + bytes chain_id = 1; + // Nonce, the nonce of authority. + bytes nonce = 2; + // The signature, Hex-encoded. + string signature = 3; } // Smart Contract Wallet type. @@ -261,10 +273,10 @@ message SigningInput { // Used in `TransactionMode::Enveloped` only. repeated Access access_list = 12; - // A smart contract to which we’re delegating to. + // EIP7702 authorization. // Used in `TransactionMode::SetOp` only. // Currently, we support delegation to only one authority at a time. - Authority eip7702_authority = 15; + Authorization eip7702_authorization = 15; } // Result containing the signed and encoded transaction. From c80dd9a55ff5cc8c6b907d925ef60e026d05c29e Mon Sep 17 00:00:00 2001 From: gupnik Date: Tue, 22 Apr 2025 13:03:41 +0530 Subject: [PATCH 27/72] Fixes CI (#4375) --- .github/workflows/codegen-v2.yml | 2 +- .github/workflows/linux-ci-rust.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codegen-v2.yml b/.github/workflows/codegen-v2.yml index 8438d788d56..d7bde064e0d 100644 --- a/.github/workflows/codegen-v2.yml +++ b/.github/workflows/codegen-v2.yml @@ -21,7 +21,7 @@ jobs: tools/install-sys-dependencies-linux - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 + uses: mozilla-actions/sccache-action@v0.0.8 - name: Install Rust dependencies run: | diff --git a/.github/workflows/linux-ci-rust.yml b/.github/workflows/linux-ci-rust.yml index 377c60ee23d..d2940428273 100644 --- a/.github/workflows/linux-ci-rust.yml +++ b/.github/workflows/linux-ci-rust.yml @@ -29,7 +29,7 @@ jobs: tools/install-sys-dependencies-linux - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 + uses: mozilla-actions/sccache-action@v0.0.8 - name: Cache Rust uses: Swatinem/rust-cache@v2 @@ -74,7 +74,7 @@ jobs: tools/install-sys-dependencies-linux - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 + uses: mozilla-actions/sccache-action@v0.0.8 - name: Cache Rust uses: Swatinem/rust-cache@v2 @@ -105,7 +105,7 @@ jobs: tools/install-sys-dependencies-mac - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 + uses: mozilla-actions/sccache-action@v0.0.8 - name: Cache Rust uses: Swatinem/rust-cache@v2 From a75b1f1e159ca0300814c34a881872d8574c78ba Mon Sep 17 00:00:00 2001 From: 10gic Date: Tue, 22 Apr 2025 16:04:00 +0800 Subject: [PATCH 28/72] Fix incorrect user op v7 encode (#4369) Co-authored-by: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> --- .../src/transaction/user_operation_v0_7.rs | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/rust/tw_evm/src/transaction/user_operation_v0_7.rs b/rust/tw_evm/src/transaction/user_operation_v0_7.rs index 7272b6b0704..c9e9b1c6b81 100644 --- a/rust/tw_evm/src/transaction/user_operation_v0_7.rs +++ b/rust/tw_evm/src/transaction/user_operation_v0_7.rs @@ -43,7 +43,7 @@ impl PackedUserOperation { let account_gas_limits = concat_u128_be(user_op.verification_gas_limit, user_op.call_gas_limit); - let gas_fees = concat_u128_be(user_op.max_fee_per_gas, user_op.max_priority_fee_per_gas); + let gas_fees = concat_u128_be(user_op.max_priority_fee_per_gas, user_op.max_fee_per_gas); let paymaster_and_data = if let Some(paymaster) = user_op.paymaster { let mut paymaster_and_data = paymaster.bytes().into_vec(); @@ -262,7 +262,25 @@ mod tests { let chain_id = U256::from(11155111u64); let entry_point = Address::from("0x0000000071727De22E5E9d8BAf0edAc6f37da032"); - let user_op = PackedUserOperation { + let user_op = UserOperationV0_7 { + sender: Address::from("0xb292Cf4a8E1fF21Ac27C4f94071Cd02C022C414b"), + nonce: U256::from_str("0xF83D07238A7C8814A48535035602123AD6DBFA63000000000000000000000001").unwrap(), + factory: None, + factory_data: Vec::default(), + call_data: "e9ae5c530000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001d8b292cf4a8e1ff21ac27c4f94071cd02c022c414b00000000000000000000000000000000000000000000000000000000000000009517e29f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000ad6330089d9a1fe89f4020292e1afe9969a5a2fc00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018e2fbe8980000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000800000000000000000000000002372912728f93ab3daaaebea4f87e6e28476d987000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000".decode_hex().unwrap(), + call_gas_limit: 1231285u128, + verification_gas_limit: 70908u128, + pre_verification_gas: U256::from(48916u64), + max_fee_per_gas: 71308035098u128, + max_priority_fee_per_gas: 1380000000u128, + paymaster: None, + paymaster_verification_gas_limit: 0, + paymaster_post_op_gas_limit: 0, + paymaster_data: Vec::default(), + entry_point, + }; + + let packed_user_op = PackedUserOperation { sender: Address::from("0xb292Cf4a8E1fF21Ac27C4f94071Cd02C022C414b"), nonce: U256::from_str("0xF83D07238A7C8814A48535035602123AD6DBFA63000000000000000000000001").unwrap(), init_code: Vec::default(), @@ -273,14 +291,18 @@ mod tests { paymaster_and_data: Vec::default(), }; - let encoded = hex::encode(user_op.encode(), false); + let encoded_from_user_op = hex::encode(user_op.encode(chain_id), false); + let encoded_from_packed_user_op = hex::encode(packed_user_op.encode(), false); let expected = "000000000000000000000000b292cf4a8e1ff21ac27c4f94071cd02c022c414bf83d07238a7c8814a48535035602123ad6dbfa63000000000000000000000001c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470f1b8863cae5d3c89d78ce8e239e0c416de4c9224226a78fbb6c4af63ed0eebf7000000000000000000000000000114fc0000000000000000000000000012c9b5000000000000000000000000000000000000000000000000000000000000bf14000000000000000000000000524121000000000000000000000000109a4a441ac5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; - assert_eq!(encoded, expected); + assert_eq!(encoded_from_user_op, expected); + assert_eq!(encoded_from_packed_user_op, expected); - let pre_hash = user_op.pre_hash(chain_id, entry_point); + let pre_hash_from_user_op = user_op.pre_hash(chain_id); + let pre_hash_from_packed_user_op = packed_user_op.pre_hash(chain_id, entry_point); let expected_pre_hash = H256::from("e486401370d145766c3cf7ba089553214a1230d38662ae532c9b62eb6dadcf7e"); - assert_eq!(pre_hash, expected_pre_hash); + assert_eq!(pre_hash_from_user_op, expected_pre_hash); + assert_eq!(pre_hash_from_packed_user_op, expected_pre_hash); } #[test] From 655f89f0497e50fef55d99a1a3f24b153c1e579c Mon Sep 17 00:00:00 2001 From: 10gic Date: Thu, 24 Apr 2025 15:28:31 +0800 Subject: [PATCH 29/72] Support adding instruction in Solana transaction (#4371) Co-authored-by: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> --- rust/chains/tw_solana/src/instruction.rs | 7 +- .../src/modules/insert_instruction.rs | 155 ++++++++++++++++++ rust/chains/tw_solana/src/modules/utils.rs | 22 +++ .../chains/solana/solana_transaction_ffi.rs | 151 ++++++++++++++++- .../src/ffi/solana/transaction.rs | 23 +++ 5 files changed, 354 insertions(+), 4 deletions(-) diff --git a/rust/chains/tw_solana/src/instruction.rs b/rust/chains/tw_solana/src/instruction.rs index fee1928163b..ce3ff4e7b19 100644 --- a/rust/chains/tw_solana/src/instruction.rs +++ b/rust/chains/tw_solana/src/instruction.rs @@ -5,15 +5,19 @@ use crate::address::SolanaAddress; use borsh::BorshSerialize; use serde::{Deserialize, Serialize}; +use tw_encoding::base58::as_base58_bitcoin; use tw_memory::Data; +#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct Instruction { /// Pubkey of the program that executes this instruction. pub program_id: SolanaAddress, /// Metadata describing accounts that should be passed to the program. pub accounts: Vec, /// Opaque data passed to the program for its own interpretation. - pub data: Data, + #[serde(with = "as_base58_bitcoin")] + pub data: Data, // Rpc getTransaction uses base58 encoding, we use the same encoding for consistency } impl Instruction { @@ -72,6 +76,7 @@ impl Instruction { } #[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct AccountMeta { /// An account's public key. pub pubkey: SolanaAddress, diff --git a/rust/chains/tw_solana/src/modules/insert_instruction.rs b/rust/chains/tw_solana/src/modules/insert_instruction.rs index 36c1c583de3..9e05140e088 100644 --- a/rust/chains/tw_solana/src/modules/insert_instruction.rs +++ b/rust/chains/tw_solana/src/modules/insert_instruction.rs @@ -3,6 +3,7 @@ // Copyright © 2017 Trust Wallet. use crate::address::SolanaAddress; +use crate::instruction::AccountMeta; use crate::modules::compiled_keys::try_into_u8; use crate::transaction::v0::MessageAddressTableLookup; use crate::transaction::{CompiledInstruction, MessageHeader}; @@ -11,6 +12,59 @@ use tw_coin_entry::error::prelude::*; use tw_memory::Data; pub trait InsertInstruction { + /// Pushes an instruction + fn push_instruction( + &mut self, + program_id: SolanaAddress, + accounts: Vec, + data: Data, + ) -> SigningResult<()> { + let insert_at = self.instructions_mut().len(); + self.insert_instruction(insert_at, program_id, accounts, data) + } + + /// Inserts an instruction at the given `insert_at` index. + fn insert_instruction( + &mut self, + insert_at: usize, + program_id: SolanaAddress, + accounts: Vec, + data: Data, + ) -> SigningResult<()> { + if insert_at > self.instructions_mut().len() { + return SigningError::err(SigningErrorType::Error_internal) + .context(format!("Unable to add '{program_id}' instruction at the '{insert_at}' index. Number of existing instructions: {}", self.instructions_mut().len())); + } + + // Step 1 - add the `account` in the accounts list. + let accounts: Vec = accounts + .iter() + .map(|account_meta| self.push_account(account_meta)) + .collect::, _>>()?; + + // Step 2 - find or add the `program_id` in the accounts list. + let program_id_index = match self + .account_keys_mut() + .iter() + .position(|acc| *acc == program_id) + { + Some(pos) => try_into_u8(pos)?, + None => self.push_readonly_unsigned_account(program_id)?, + }; + + // Step 3 - Create a `CompiledInstruction` based on the `program_id` index and instruction `accounts` and `data`. + let new_compiled_ix = CompiledInstruction { + program_id_index, + accounts, + data, + }; + + // Step 4 - Insert the created instruction at the given `insert_at` index. + self.instructions_mut().insert(insert_at, new_compiled_ix); + + Ok(()) + } + /// Pushes a simple instruction that doesn't have accounts. fn push_simple_instruction( &mut self, @@ -56,6 +110,107 @@ pub trait InsertInstruction { Ok(()) } + /// Pushes an account to the message. + /// If the account already exists, it must match the `is_signer` and `is_writable` attributes + /// Returns the index of the account in the account list. + fn push_account(&mut self, account: &AccountMeta) -> SigningResult { + // The layout of the account keys is as follows: + // +-------------------------------------+ + // | Writable and required signature | \ + // +-------------------------------------+ |-> num_required_signatures + // | Readonly and required signature | --> num_readonly_signed_accounts / + // +-------------------------------------+ + // | Writable and not required signature | + // +-------------------------------------+ + // | Readonly and not required signature | --> num_readonly_unsigned_accounts + // +-------------------------------------+ + + // Check if the account already exists in `account_keys`, + // if it does, validate `is_signer` and `is_writable` match + if let Some(existing_index) = self + .account_keys_mut() + .iter() + .position(|key| *key == account.pubkey) + { + let is_signer = + existing_index < self.message_header_mut().num_required_signatures as usize; + + let is_writable = if is_signer { + existing_index + < (self.message_header_mut().num_required_signatures + - self.message_header_mut().num_readonly_signed_accounts) + as usize + } else { + existing_index + < (self.account_keys_mut().len() + - self.message_header_mut().num_readonly_unsigned_accounts as usize) + }; + + if account.is_signer != is_signer { + return SigningError::err(SigningErrorType::Error_internal).context( + "Account already exists but the `is_signer` attribute does not match", + ); + } + if account.is_writable != is_writable { + return SigningError::err(SigningErrorType::Error_internal).context( + "Account already exists but the `is_writable` attribute does not match", + ); + } + // Return the existing index if validation passes + return try_into_u8(existing_index); + } + + // Determine the insertion position based on is_signer and is_writable + let insert_at = match (account.is_signer, account.is_writable) { + (true, true) => { + self.message_header_mut().num_required_signatures += 1; + // The account is added at the end of the writable and signer accounts + (self.message_header_mut().num_required_signatures + - self.message_header_mut().num_readonly_signed_accounts) + as usize + - 1 + }, + (true, false) => { + self.message_header_mut().num_required_signatures += 1; + self.message_header_mut().num_readonly_signed_accounts += 1; + // The account is added at the end of the read-only and signer accounts + self.message_header_mut().num_required_signatures as usize - 1 + }, + (false, true) => { + // The account is added at the end of the writable and non-signer accounts + self.account_keys_mut().len() + - self.message_header_mut().num_readonly_unsigned_accounts as usize + }, + (false, false) => { + self.message_header_mut().num_readonly_unsigned_accounts += 1; + // The account is added at the end of the list + self.account_keys_mut().len() + }, + }; + + // Insert the account at the determined position + self.account_keys_mut().insert(insert_at, account.pubkey); + + let account_added_at = try_into_u8(insert_at)?; + + // Update program ID and account indexes if the new account was added before its position + let instructions = self.instructions_mut(); + instructions.iter_mut().for_each(|ix| { + // Update program ID index + if ix.program_id_index >= account_added_at { + ix.program_id_index += 1; + } + + // Update account indexes + ix.accounts + .iter_mut() + .filter(|ix_account_id| **ix_account_id >= account_added_at) + .for_each(|ix_account_id| *ix_account_id += 1); + }); + + Ok(account_added_at) + } + fn push_readonly_unsigned_account(&mut self, account: SolanaAddress) -> SigningResult { debug_assert!( !self.account_keys_mut().contains(&account), diff --git a/rust/chains/tw_solana/src/modules/utils.rs b/rust/chains/tw_solana/src/modules/utils.rs index 6ce3f5c77f6..cadf7229766 100644 --- a/rust/chains/tw_solana/src/modules/utils.rs +++ b/rust/chains/tw_solana/src/modules/utils.rs @@ -4,6 +4,7 @@ use crate::address::SolanaAddress; use crate::defined_addresses::{COMPUTE_BUDGET_ADDRESS, SYSTEM_PROGRAM_ID_ADDRESS}; +use crate::instruction::Instruction; use crate::modules::insert_instruction::InsertInstruction; use crate::modules::instruction_builder::compute_budget_instruction::{ ComputeBudgetInstruction, ComputeBudgetInstructionBuilder, UnitLimit, UnitPrice, @@ -173,6 +174,27 @@ impl SolanaTransaction { .to_base64() .tw_err(SigningErrorType::Error_internal) } + + pub fn add_instruction(encoded_tx: &str, instruction: &str) -> SigningResult { + let tx_bytes = base64::decode(encoded_tx, STANDARD)?; + let mut tx: VersionedTransaction = + bincode::deserialize(&tx_bytes).map_err(|_| SigningErrorType::Error_input_parse)?; + + let instruction: Instruction = + serde_json::from_str(instruction).map_err(|_| SigningErrorType::Error_input_parse)?; + + tx.message.push_instruction( + instruction.program_id, + instruction.accounts, + instruction.data, + )?; + + // Set the correct number of zero signatures + let unsigned_tx = VersionedTransaction::unsigned(tx.message); + unsigned_tx + .to_base64() + .tw_err(SigningErrorType::Error_internal) + } } fn try_instruction_as_compute_budget( diff --git a/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs b/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs index f0bd2c55622..10acc347432 100644 --- a/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs +++ b/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs @@ -8,16 +8,20 @@ use tw_coin_registry::coin_type::CoinType; use tw_encoding::base64::STANDARD; use tw_encoding::hex::{DecodeHex, ToHex}; use tw_encoding::{base58, base64}; +use tw_keypair::ed25519::sha512::KeyPair; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_memory::test_utils::tw_data_vector_helper::TWDataVectorHelper; use tw_memory::test_utils::tw_string_helper::TWStringHelper; +use tw_misc::traits::ToBytesVec; use tw_proto::Common::Proto::SigningError; use tw_proto::Solana::Proto::{self}; use tw_solana::SOLANA_ALPHABET; use wallet_core_rs::ffi::solana::transaction::{ - tw_solana_transaction_get_compute_unit_limit, tw_solana_transaction_get_compute_unit_price, - tw_solana_transaction_set_compute_unit_limit, tw_solana_transaction_set_compute_unit_price, - tw_solana_transaction_set_fee_payer, tw_solana_transaction_update_blockhash_and_sign, + tw_solana_transaction_add_instruction, tw_solana_transaction_get_compute_unit_limit, + tw_solana_transaction_get_compute_unit_price, tw_solana_transaction_set_compute_unit_limit, + tw_solana_transaction_set_compute_unit_price, tw_solana_transaction_set_fee_payer, + tw_solana_transaction_update_blockhash_and_sign, }; #[test] @@ -364,3 +368,144 @@ fn test_solana_transaction_set_fee_payer_already_exists() { // We expect tw_solana_transaction_set_fee_payer to return null. assert_eq!(updated_tx.to_string(), None); } + +#[test] +fn test_solana_transaction_add_instruction() { + // Step 1 - Prepare a transaction. + // The original transaction contains the following instruction: + // Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ transfer 0.001 SOL to B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V + let encoded_tx_str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3G7dwaciEKWYvJQnuUEK1pCl+Zbd0ANxw8XYKfUO2eQBAgIAAQwCAAAAQEIPAAAAAAA="; + let encoded_tx = TWStringHelper::create(encoded_tx_str); + + // Step 2 - Prepare a new instruction (sender and receiver are already in the transaction): + // YUz1AupPEy1vttBeDS7sXYZFhQJppcXMzjDiDx18Srf transfer 0.003 SOL to d8DiHEeHKdXkM2ZupT86mrvavhmJwUZjHPCzMiB5Lqb + let instruction_str = r#"{"programId":"11111111111111111111111111111111","accounts":[{"pubkey":"YUz1AupPEy1vttBeDS7sXYZFhQJppcXMzjDiDx18Srf","isSigner":true,"isWritable":true},{"pubkey":"d8DiHEeHKdXkM2ZupT86mrvavhmJwUZjHPCzMiB5Lqb","isSigner":false,"isWritable":true}],"data":"3Bxs4Z6oyhaczjLK"}"#; + let instruction = TWStringHelper::create(instruction_str); + + // Step 3 - Add the instruction to the transaction. + let updated_tx = TWStringHelper::wrap(unsafe { + tw_solana_transaction_add_instruction(encoded_tx.ptr(), instruction.ptr()) + }); + let updated_tx = updated_tx.to_string().unwrap(); + assert_eq!(updated_tx, "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgABBcsq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXCBClNVEnVmpLrtsxbBm36nAMo3+zrd60SK3Q+9MM/byUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWglBEmXIJbeoWIaTmY86w/B7o6y+gDQmzmrQfxH7VFEOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcbt3BpyIQpZi8lCe5QQrWkKX5lt3QA3HDxdgp9Q7Z5AIEAgACDAIAAABAQg8AAAAAAAQCAQMMAgAAAMDGLQAAAAAA"); + + // Step 4 - Obtain preimage hash. + let tx_data = base64::decode(&updated_tx, STANDARD).unwrap(); + let mut decoder = TransactionDecoderHelper::::default(); + let output = decoder.decode(CoinType::Solana, tx_data); + assert_eq!(output.error, SigningError::OK); + let decoded_tx = output.transaction.unwrap(); + let signing_input = Proto::SigningInput { + raw_message: Some(decoded_tx), + tx_encoding: Proto::Encoding::Base64, + ..Proto::SigningInput::default() + }; + + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Solana, &signing_input); + assert_eq!(preimage_output.error, SigningError::OK); + assert_eq!( + preimage_output.data.to_hex(), + "02000105cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b570810a5355127566a4baedb316c19b7ea700ca37fb3addeb448add0fbd30cfdbc94c3890fa8d4bc04ab2a676d2cafea5cdc899ecd95a9cbe593e9df258759685a09411265c825b7a8588693998f3ac3f07ba3acbe803426ce6ad07f11fb54510e0000000000000000000000000000000000000000000000000000000000000000dc6eddc1a72210a598bc9427b9410ad690a5f996ddd00371c3c5d829f50ed9e402040200020c0200000040420f0000000000040201030c02000000c0c62d0000000000" + ); + + // Step 5 - Get signatures. + let keypair1 = + KeyPair::try_from("4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7") + .unwrap(); + let signature1 = keypair1 + .sign(preimage_output.data.to_vec()) + .unwrap() + .to_vec(); + let public_key1 = keypair1.public().to_vec(); + + let keypair2 = + KeyPair::try_from("1c0774714429e780ca1f12151fb3a7e672bdbef0ce49d4ea9467ae8699af3451") + .unwrap(); + let signature2 = keypair2 + .sign(preimage_output.data.to_vec()) + .unwrap() + .to_vec(); + let public_key2 = keypair2.public().to_vec(); + + // Step 6 - Compile transaction info. + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile( + CoinType::Solana, + &signing_input, + vec![signature1, signature2], + vec![public_key1, public_key2], + ); + + assert_eq!(output.error, SigningError::OK); + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/3JqWkMtjepGLq9CKTjApNUfa5VGRwV3XPS2hHzAoafLXFdf9khxehhHQnvhYbszYdY9DZzCFgabceSxCsrAhZ4Yv?cluster=devnet + let expected = "AnNqW1ZasaRYorSc9KK2O/GxfEmKk3snmjOnqBNAeloK9BSUSONuXsxGSVT/UeDpiTmIkgupYjVeH6OlaXxnHQejdab2170qEk0Z3WgjGKqhqcl9RG1z5cUkK4pdqF1iM4qm6kbAM/f+mdKq4HwIyRODKRjHwm0OtV2zYnmkSM8FAgABBcsq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXCBClNVEnVmpLrtsxbBm36nAMo3+zrd60SK3Q+9MM/byUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWglBEmXIJbeoWIaTmY86w/B7o6y+gDQmzmrQfxH7VFEOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcbt3BpyIQpZi8lCe5QQrWkKX5lt3QA3HDxdgp9Q7Z5AIEAgACDAIAAABAQg8AAAAAAAQCAQMMAgAAAMDGLQAAAAAA"; + assert_eq!(output.encoded, expected); +} + +#[test] +fn test_solana_transaction_add_instruction_accounts_already_exist() { + // Step 1 - Prepare a transaction. + // The original transaction contains the following instruction: + // Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ transfer 0.001 SOL to B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V + let encoded_tx_str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGmdal3puKe8Ts38zAeBNhPflrnIsh+y0vOJUCYnOOIBAgIAAQwCAAAAQEIPAAAAAAA="; + let encoded_tx = TWStringHelper::create(encoded_tx_str); + + // Step 2 - Prepare a new instruction (sender and receiver are already in the transaction): + // Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ transfer 0.002 SOL to B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V + let instruction_str = r#"{"programId":"11111111111111111111111111111111","accounts":[{"pubkey":"Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ","isSigner":true,"isWritable":true},{"pubkey":"B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V","isSigner":false,"isWritable":true}],"data":"3Bxs4NMRjdEwjxAj"}"#; + let instruction = TWStringHelper::create(instruction_str); + + // Step 3 - Add the instruction to the transaction. + let updated_tx = TWStringHelper::wrap(unsafe { + tw_solana_transaction_add_instruction(encoded_tx.ptr(), instruction.ptr()) + }); + let updated_tx = updated_tx.to_string().unwrap(); + assert_eq!(updated_tx, "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGmdal3puKe8Ts38zAeBNhPflrnIsh+y0vOJUCYnOOICAgIAAQwCAAAAQEIPAAAAAAACAgABDAIAAACAhB4AAAAAAA=="); + + // Step 4 - Obtain preimage hash. + let tx_data = base64::decode(&updated_tx, STANDARD).unwrap(); + let mut decoder = TransactionDecoderHelper::::default(); + let output = decoder.decode(CoinType::Solana, tx_data); + assert_eq!(output.error, SigningError::OK); + let decoded_tx = output.transaction.unwrap(); + let signing_input = Proto::SigningInput { + raw_message: Some(decoded_tx), + tx_encoding: Proto::Encoding::Base64, + ..Proto::SigningInput::default() + }; + + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Solana, &signing_input); + assert_eq!(preimage_output.error, SigningError::OK); + assert_eq!( + preimage_output.data.to_hex(), + "01000103cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b5794c3890fa8d4bc04ab2a676d2cafea5cdc899ecd95a9cbe593e9df258759685a000000000000000000000000000000000000000000000000000000000000000028699d6a5de9b8a7bc4ecdfccc07813613df96b9c8b21fb2d2f38950262738e202020200010c0200000040420f0000000000020200010c0200000080841e0000000000" + ); + + // Step 5 - Get signature. + let keypair = + KeyPair::try_from("4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7") + .unwrap(); + let signature = keypair + .sign(preimage_output.data.to_vec()) + .unwrap() + .to_vec(); + let public_key = keypair.public().to_vec(); + + // Step 6 - Compile transaction info. + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile( + CoinType::Solana, + &signing_input, + vec![signature], + vec![public_key], + ); + + assert_eq!(output.error, SigningError::OK); + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/cBkG3BKyyWVCNbpW4GpUHPhZZyqaD9XUnEMaB7R4sNs51fhj8fsnUmpXdzhRmV483WQcgBwPCkKjrvqLmvP1Gug?cluster=devnet + let expected = "AR5Xqmoj8pkYuLazQMM6x79T9wp35xvnCua/a3YllOShi0pTPWLqZrHdYnZlBcX0wdV7Ef5DqKoQCrUxY2RyKwsBAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGmdal3puKe8Ts38zAeBNhPflrnIsh+y0vOJUCYnOOICAgIAAQwCAAAAQEIPAAAAAAACAgABDAIAAACAhB4AAAAAAA=="; + assert_eq!(output.encoded, expected); +} diff --git a/rust/wallet_core_rs/src/ffi/solana/transaction.rs b/rust/wallet_core_rs/src/ffi/solana/transaction.rs index 845cbe7322e..fd9a9f685d2 100644 --- a/rust/wallet_core_rs/src/ffi/solana/transaction.rs +++ b/rust/wallet_core_rs/src/ffi/solana/transaction.rs @@ -163,3 +163,26 @@ pub unsafe extern "C" fn tw_solana_transaction_set_fee_payer( _ => std::ptr::null_mut(), } } + +/// Adds an instruction to the given transaction, and returns the updated transaction. +/// +/// \param encoded_tx base64 encoded Solana transaction. +/// \param instruction json encoded instruction. Here is an example: {"programId":"11111111111111111111111111111111","accounts":[{"pubkey":"YUz1AupPEy1vttBeDS7sXYZFhQJppcXMzjDiDx18Srf","isSigner":true,"isWritable":true},{"pubkey":"d8DiHEeHKdXkM2ZupT86mrvavhmJwUZjHPCzMiB5Lqb","isSigner":false,"isWritable":true}],"data":"3Bxs4Z6oyhaczjLK"} +/// \return base64 encoded Solana transaction. Null if an error occurred. +#[tw_ffi(ty = static_function, class = TWSolanaTransaction, name = AddInstruction)] +#[no_mangle] +pub unsafe extern "C" fn tw_solana_transaction_add_instruction( + encoded_tx: Nonnull, + instruction: Nonnull, +) -> NullableMut { + let encoded_tx = try_or_else!(TWString::from_ptr_as_ref(encoded_tx), std::ptr::null_mut); + let encoded_tx = try_or_else!(encoded_tx.as_str(), std::ptr::null_mut); + + let instruction = try_or_else!(TWString::from_ptr_as_ref(instruction), std::ptr::null_mut); + let instruction = try_or_else!(instruction.as_str(), std::ptr::null_mut); + + match SolanaTransaction::add_instruction(encoded_tx, instruction) { + Ok(updated_tx) => TWString::from(updated_tx).into_ptr(), + _ => std::ptr::null_mut(), + } +} From 3823392a77a6735d134935709146187188a3b8ae Mon Sep 17 00:00:00 2001 From: 10gic Date: Thu, 24 Apr 2025 19:41:26 +0800 Subject: [PATCH 30/72] Remove deprecated code and fix build issue in newer compiler (#4380) See https://github.com/llvm/llvm-project/commit/e30a148b098d462d0267c479cd9e4783363c2761 --- src/interface/TWStoredKey.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interface/TWStoredKey.cpp b/src/interface/TWStoredKey.cpp index d3463cef2cc..5a501d667e2 100644 --- a/src/interface/TWStoredKey.cpp +++ b/src/interface/TWStoredKey.cpp @@ -90,8 +90,8 @@ struct TWStoredKey* _Nullable TWStoredKeyImportHDWalletWithEncryption(TWString* struct TWStoredKey* _Nullable TWStoredKeyImportJSON(TWData* _Nonnull json) { try { const auto& d = *reinterpret_cast(json); - const auto parsed = nlohmann::json::parse(d); - return new TWStoredKey{ KeyStore::StoredKey::createWithJson(nlohmann::json::parse(d)) }; + const auto parsed = nlohmann::json::parse(std::string(d.begin(), d.end())); + return new TWStoredKey{ KeyStore::StoredKey::createWithJson(parsed) }; } catch (...) { return nullptr; } From bb6c2860a6d4b0b6b7551a64833ccdb901bd2017 Mon Sep 17 00:00:00 2001 From: 10gic Date: Tue, 29 Apr 2025 21:17:00 +0800 Subject: [PATCH 31/72] Integrate the EIP 7702 authorization signature into the message signature framework (#4365) * Integrate the EIP 7702 authorization signature into the message signature framework * Adjust the code according to the comments --------- Co-authored-by: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> --- .../src/message/eip7702_authorization.rs | 86 ++++++++++++++++++ rust/tw_evm/src/message/mod.rs | 1 + .../src/modules/authorization_signer.rs | 91 ------------------- rust/tw_evm/src/modules/message_signer.rs | 16 +++- rust/tw_evm/src/modules/mod.rs | 1 - rust/tw_evm/src/modules/tx_builder.rs | 42 +++++---- .../src/transaction/authorization_list.rs | 5 + .../tests/data/eip7702_authorization.json | 5 + .../eip7702_authorization_decimal_string.json | 5 + rust/tw_evm/tests/message_signer.rs | 25 +++++ rust/tw_number/src/lib.rs | 12 +++ rust/tw_number/src/u256.rs | 2 +- src/proto/Ethereum.proto | 2 + 13 files changed, 181 insertions(+), 112 deletions(-) create mode 100644 rust/tw_evm/src/message/eip7702_authorization.rs delete mode 100644 rust/tw_evm/src/modules/authorization_signer.rs create mode 100644 rust/tw_evm/tests/data/eip7702_authorization.json create mode 100644 rust/tw_evm/tests/data/eip7702_authorization_decimal_string.json diff --git a/rust/tw_evm/src/message/eip7702_authorization.rs b/rust/tw_evm/src/message/eip7702_authorization.rs new file mode 100644 index 00000000000..5e802c30ce8 --- /dev/null +++ b/rust/tw_evm/src/message/eip7702_authorization.rs @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::message::{EthMessage, MessageSigningErrorKind, MessageSigningResult}; +use crate::rlp::list::RlpList; +use crate::transaction::authorization_list::Authorization; +use std::iter; +use std::str::FromStr; +use tw_hash::sha3::keccak256; +use tw_hash::H256; + +pub const EIP_7702_MAGIC_PREFIX: u8 = 0x05; + +impl FromStr for Authorization { + type Err = MessageSigningErrorKind; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s).map_err(|_| MessageSigningErrorKind::TypeValueMismatch) + } +} + +impl EthMessage for Authorization { + fn hash(&self) -> MessageSigningResult { + let mut list = RlpList::new(); + list.append(&self.chain_id) + .append(&self.address) + .append(&self.nonce); + + let to_hash: Vec<_> = iter::once(EIP_7702_MAGIC_PREFIX) + .chain(list.finish()) + .collect(); + + let hash_data = keccak256(&to_hash); + Ok(H256::try_from(hash_data.as_slice()).expect("Expected 32-byte hash")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::address::Address; + use std::str::FromStr; + use tw_encoding::hex::ToHex; + use tw_keypair::ecdsa::secp256k1; + use tw_keypair::traits::SigningKeyTrait; + use tw_number::U256; + + #[test] + fn test_pre_hash() { + let authorization = Authorization { + chain_id: U256::from(1_u32), + address: Address::from_str("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1").unwrap(), + nonce: U256::from(1_u32), + }; + assert_eq!( + Authorization::hash(&authorization).unwrap().to_hex(), + "3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9" + ); + } + + #[test] + fn test_sign_authorization() { + let authorization = Authorization { + chain_id: U256::from(1_u32), + address: Address::from_str("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1").unwrap(), + nonce: U256::from(1_u32), + }; + let private_key = secp256k1::PrivateKey::try_from( + "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8", + ) + .unwrap(); + let pre_hash = authorization.hash().unwrap(); + let signature = private_key.sign(pre_hash).unwrap(); + + assert_eq!( + signature.r().to_hex(), + "2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710" + ); + assert_eq!( + signature.s().to_hex(), + "5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c" + ); + assert_eq!(signature.v(), 1); + } +} diff --git a/rust/tw_evm/src/message/mod.rs b/rust/tw_evm/src/message/mod.rs index 44b27d428ab..e2272bf498b 100644 --- a/rust/tw_evm/src/message/mod.rs +++ b/rust/tw_evm/src/message/mod.rs @@ -7,6 +7,7 @@ use tw_hash::H256; pub mod eip191; pub mod eip712; +pub mod eip7702_authorization; pub mod signature; pub type EthMessageBoxed = Box; diff --git a/rust/tw_evm/src/modules/authorization_signer.rs b/rust/tw_evm/src/modules/authorization_signer.rs deleted file mode 100644 index 86d77fde04e..00000000000 --- a/rust/tw_evm/src/modules/authorization_signer.rs +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use crate::rlp::list::RlpList; -use crate::transaction::authorization_list::{Authorization, SignedAuthorization}; -use std::iter; -use tw_coin_entry::error::prelude::*; -use tw_hash::sha3::keccak256; -use tw_hash::H256; -use tw_keypair::ecdsa::secp256k1::PrivateKey; -use tw_keypair::traits::SigningKeyTrait; -use tw_number::U256; - -pub const EIP_7702_MAGIC_PREFIX: u8 = 0x05; - -pub struct AuthorizationSigner; - -impl AuthorizationSigner { - pub fn sign( - private_key: &PrivateKey, - authorization: Authorization, - ) -> SigningResult { - let hash = Self::get_authorization_hash(&authorization); - let signature = private_key.sign(hash)?; - - Ok(SignedAuthorization { - authorization, - y_parity: signature.v(), - r: U256::from_big_endian(signature.r()), - s: U256::from_big_endian(signature.s()), - }) - } - - fn get_authorization_hash(authorization: &Authorization) -> H256 { - let mut list = RlpList::new(); - list.append(&authorization.chain_id) - .append(&authorization.address) - .append(&authorization.nonce); - - let to_hash: Vec<_> = iter::once(EIP_7702_MAGIC_PREFIX) - .chain(list.finish()) - .collect(); - H256::try_from(keccak256(&to_hash).as_slice()) - .expect("keccak256 must return exactly 32 bytes") - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::address::Address; - use std::str::FromStr; - use tw_encoding::hex::ToHex; - - #[test] - fn test_get_authorization_hash() { - let authorization = Authorization { - chain_id: U256::from(1_u32), - address: Address::from_str("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1").unwrap(), - nonce: U256::from(1_u32), - }; - assert_eq!( - AuthorizationSigner::get_authorization_hash(&authorization).to_hex(), - "3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9" - ); - } - - #[test] - fn test_sign_authorization() { - let authorization = Authorization { - chain_id: U256::from(1_u32), - address: Address::from_str("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1").unwrap(), - nonce: U256::from(1_u32), - }; - let private_key = PrivateKey::try_from( - "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8", - ) - .unwrap(); - let signed_authorization = AuthorizationSigner::sign(&private_key, authorization).unwrap(); - assert_eq!( - signed_authorization.r.to_big_endian().to_hex(), - "2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710" - ); - assert_eq!( - signed_authorization.s.to_big_endian().to_hex(), - "5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c" - ); - assert_eq!(signed_authorization.y_parity, 1); - } -} diff --git a/rust/tw_evm/src/modules/message_signer.rs b/rust/tw_evm/src/modules/message_signer.rs index a67a60fb5ac..68830111795 100644 --- a/rust/tw_evm/src/modules/message_signer.rs +++ b/rust/tw_evm/src/modules/message_signer.rs @@ -6,6 +6,7 @@ use crate::message::eip191::Eip191Message; use crate::message::eip712::eip712_message::Eip712Message; use crate::message::signature::{MessageSignature, SignatureType}; use crate::message::{to_signing, EthMessage, EthMessageBoxed}; +use crate::transaction::authorization_list::Authorization; use std::borrow::Cow; use std::str::FromStr; use tw_coin_entry::coin_context::CoinContext; @@ -123,13 +124,23 @@ impl EthMessageSigner { .map_err(to_signing)? .into_boxed()), }, + Proto::MessageType::MessageType_eip7702_authorization => { + Ok(Authorization::from_str(&input.message) + .map_err(|e| to_signing(e.into()))? + .into_boxed()) + }, } } fn message_from_str(user_message: &str) -> SigningResult { match Eip712Message::new(user_message) { + // Try to parse as an EIP712 message Ok(typed_data) => Ok(typed_data.into_boxed()), - Err(_) => Ok(Eip191Message::new(user_message).into_boxed()), + Err(_) => match Authorization::from_str(user_message) { + // Try to parse as an EIP7702 authorization tuple + Ok(authorization) => Ok(authorization.into_boxed()), + Err(_) => Ok(Eip191Message::new(user_message).into_boxed()), + }, } } @@ -138,7 +149,8 @@ impl EthMessageSigner { maybe_chain_id: Option, ) -> SignatureType { match msg_type { - Proto::MessageType::MessageType_immutable_x => SignatureType::Standard, + Proto::MessageType::MessageType_immutable_x + | Proto::MessageType::MessageType_eip7702_authorization => SignatureType::Standard, Proto::MessageType::MessageType_legacy | Proto::MessageType::MessageType_typed => { SignatureType::Legacy }, diff --git a/rust/tw_evm/src/modules/mod.rs b/rust/tw_evm/src/modules/mod.rs index 6653db1f41f..fb246766ac1 100644 --- a/rust/tw_evm/src/modules/mod.rs +++ b/rust/tw_evm/src/modules/mod.rs @@ -3,7 +3,6 @@ // Copyright © 2017 Trust Wallet. pub mod abi_encoder; -pub mod authorization_signer; pub mod compiler; pub mod message_signer; pub mod rlp_encoder; diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs index bc4f378fad1..fa22e551977 100644 --- a/rust/tw_evm/src/modules/tx_builder.rs +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -11,7 +11,7 @@ use crate::abi::prebuild::erc721::Erc721; use crate::abi::prebuild::ExecuteArgs; use crate::address::{Address, EvmAddress}; use crate::evm_context::EvmContext; -use crate::modules::authorization_signer::AuthorizationSigner; +use crate::message::{to_signing, EthMessage}; use crate::transaction::access_list::{Access, AccessList}; use crate::transaction::authorization_list::{ Authorization, AuthorizationList, SignedAuthorization, @@ -29,6 +29,7 @@ use tw_encoding::hex::DecodeHex; use tw_hash::H256; use tw_keypair::ecdsa::secp256k1; use tw_keypair::ecdsa::secp256k1::Signature; +use tw_keypair::traits::SigningKeyTrait; use tw_keypair::KeyPairError; use tw_memory::Data; use tw_number::U256; @@ -657,7 +658,7 @@ impl TxBuilder { .into_tw() .context("Invalid authority address")?; - let signed_authorization = + let (authorization, signature) = if let Some(other_auth_fields) = &eip7702_authorization.custom_signature { // If field `custom_signature` is provided, it means that the authorization is already signed. let chain_id = U256::from_big_endian_slice(&other_auth_fields.chain_id) @@ -676,16 +677,14 @@ impl TxBuilder { .tw_err(SigningErrorType::Error_invalid_params) .context("Invalid signature")?; - SignedAuthorization { - authorization: Authorization { + ( + Authorization { chain_id, address, nonce, }, - y_parity: signature.v(), - r: U256::from_big_endian(signature.r()), - s: U256::from_big_endian(signature.s()), - } + signature, + ) } else { // If field `custom_signature` is not provided, the authorization will be signed with the provided private key, nonce and chainId let signer_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref()) @@ -707,17 +706,26 @@ impl TxBuilder { .into_tw() .context("Invalid nonce")?; - AuthorizationSigner::sign( - &signer_key, - Authorization { - chain_id, - address, - // `authorization.nonce` must be incremented by 1 over `transaction.nonce`. - nonce: nonce + 1, - }, - )? + let authorization = Authorization { + chain_id, + address, + // `authorization.nonce` must be incremented by 1 over `transaction.nonce`. + nonce: nonce + 1, + }; + + let pre_hash = authorization.hash().map_err(to_signing)?; + let signature = signer_key.sign(pre_hash)?; + + (authorization, signature) }; + let signed_authorization = SignedAuthorization { + authorization, + y_parity: signature.v(), + r: U256::from_big_endian(signature.r()), + s: U256::from_big_endian(signature.s()), + }; + Ok(AuthorizationList::from(vec![signed_authorization])) } } diff --git a/rust/tw_evm/src/transaction/authorization_list.rs b/rust/tw_evm/src/transaction/authorization_list.rs index ea652d8a716..7f7bd2fc430 100644 --- a/rust/tw_evm/src/transaction/authorization_list.rs +++ b/rust/tw_evm/src/transaction/authorization_list.rs @@ -5,15 +5,20 @@ use crate::address::Address; use crate::rlp::buffer::RlpBuffer; use crate::rlp::RlpEncode; +use serde::Deserialize; use tw_number::U256; /// Authorization for 7702 txn support. +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] pub struct Authorization { /// The chain ID of the authorization. + #[serde(deserialize_with = "U256::from_hex_or_decimal_str")] pub chain_id: U256, /// The address of the authorization. pub address: Address, /// The nonce for the authorization. + #[serde(deserialize_with = "U256::from_hex_or_decimal_str")] pub nonce: U256, } diff --git a/rust/tw_evm/tests/data/eip7702_authorization.json b/rust/tw_evm/tests/data/eip7702_authorization.json new file mode 100644 index 00000000000..79d5b88617f --- /dev/null +++ b/rust/tw_evm/tests/data/eip7702_authorization.json @@ -0,0 +1,5 @@ +{ + "chainId": "0x310c5", + "address": "0x3535353535353535353535353535353535353535", + "nonce": "0xabc" +} diff --git a/rust/tw_evm/tests/data/eip7702_authorization_decimal_string.json b/rust/tw_evm/tests/data/eip7702_authorization_decimal_string.json new file mode 100644 index 00000000000..5ea708abb66 --- /dev/null +++ b/rust/tw_evm/tests/data/eip7702_authorization_decimal_string.json @@ -0,0 +1,5 @@ +{ + "chainId": "200901", + "address": "0x3535353535353535353535353535353535353535", + "nonce": "2748" +} diff --git a/rust/tw_evm/tests/message_signer.rs b/rust/tw_evm/tests/message_signer.rs index 447e48df6db..d1cfa05b3a9 100644 --- a/rust/tw_evm/tests/message_signer.rs +++ b/rust/tw_evm/tests/message_signer.rs @@ -20,6 +20,9 @@ const EIP712_GREENFIELD: &str = include_str!("data/eip712_greenfield.json"); const EIP712_FIXED_BYTES: &str = include_str!("data/eip712_fixed_bytes.json"); const EIP712_LONG_BYTES: &str = include_str!("data/eip712_long_bytes.json"); const EIP712_DIFFERENT_BYTES: &str = include_str!("data/eip712_different_bytes.json"); +const EIP7702_AUTHORIZATION: &str = include_str!("data/eip7702_authorization.json"); +const EIP7702_AUTHORIZATION_DECIMAL_STRING: &str = + include_str!("data/eip7702_authorization_decimal_string.json"); struct SignVerifyTestInput { private_key: &'static str, @@ -312,3 +315,25 @@ fn test_message_signer_sign_verify_eip712_different_bytes() { signature: "48dc667cd8a53beb58ea6b1745f98c21b12e1a57587ce28bae07689dba3600d40cef2685dc8a68028d38f3e63289891868ecdf05e8affc275fee3001e51d6c581c", }); } + +#[test] +fn test_message_signer_sign_eip7702_authorization() { + test_message_signer_sign_verify(SignVerifyTestInput { + private_key: "6f96f3aa7e8052170f1864f72a9a53606ee9c0d185188266cab895512a4bcf84", + msg: EIP7702_AUTHORIZATION, + msg_type: Proto::MessageType::MessageType_eip7702_authorization, + chain_id: None, + signature: "a2c790e864ff524e703c6a476609e0b738cdcd3a96628be0ef74cb5610a29a473908f07f762f4da896e7f8d3cdaad567c58aa81412ac6dd864eb5d3956a1f04c00", + }); +} + +#[test] +fn test_message_signer_sign_eip7702_authorization_decimal_string() { + test_message_signer_sign_verify(SignVerifyTestInput { + private_key: "6f96f3aa7e8052170f1864f72a9a53606ee9c0d185188266cab895512a4bcf84", + msg: EIP7702_AUTHORIZATION_DECIMAL_STRING, + msg_type: Proto::MessageType::MessageType_eip7702_authorization, + chain_id: None, + signature: "a2c790e864ff524e703c6a476609e0b738cdcd3a96628be0ef74cb5610a29a473908f07f762f4da896e7f8d3cdaad567c58aa81412ac6dd864eb5d3956a1f04c00", + }); +} diff --git a/rust/tw_number/src/lib.rs b/rust/tw_number/src/lib.rs index 5242eccabb2..f70e9ee2e7f 100644 --- a/rust/tw_number/src/lib.rs +++ b/rust/tw_number/src/lib.rs @@ -9,6 +9,7 @@ mod u256; pub use i256::I256; pub use sign::Sign; +use std::fmt::{Display, Formatter}; pub use u256::U256; pub type NumberResult = Result; @@ -21,6 +22,17 @@ pub enum NumberError { Overflow, } +impl Display for NumberError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + NumberError::IntegerOverflow => write!(f, "Integer overflow"), + NumberError::InvalidBinaryRepresentation => write!(f, "Invalid binary representation"), + NumberError::InvalidStringRepresentation => write!(f, "Invalid string representation"), + NumberError::Overflow => write!(f, "Overflow"), + } + } +} + #[cfg(feature = "serde")] pub(crate) mod serde_common { use crate::NumberError; diff --git a/rust/tw_number/src/u256.rs b/rust/tw_number/src/u256.rs index 2e32094b274..a9abc046ee6 100644 --- a/rust/tw_number/src/u256.rs +++ b/rust/tw_number/src/u256.rs @@ -246,7 +246,7 @@ mod impl_serde { serializer.serialize_str(&self.to_string()) } - pub fn from_decimal_str<'de, D>(deserializer: D) -> Result + pub fn from_hex_or_decimal_str<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index dc1e354784a..0bb6e1168d9 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -314,6 +314,8 @@ enum MessageType { MessageType_typed_eip155 = 3; // Sign a message with Immutable X msg type. MessageType_immutable_x = 4; + // Sign a EIP-7702 authorization tuple. + MessageType_eip7702_authorization = 5; } message MaybeChainId { From 302938cf4c6d740e47ca5ed67c5107e2166a6cb5 Mon Sep 17 00:00:00 2001 From: Enrique Souza Date: Mon, 5 May 2025 07:48:29 -0300 Subject: [PATCH 32/72] Support for signing `wasm/MsgInstantiateContract` transactions (#4368) * my tracked commit * MsgInstantiateContract * revert formatting * test * Tests fixes and broadcast * cargo fmt * remove unused --------- Co-authored-by: gupnik --- .../Protobuf/cosmwasm_wasm_v1_tx.proto | 9 ++ rust/tw_cosmos_sdk/src/modules/tx_builder.rs | 47 ++++++++ .../src/transaction/message/mod.rs | 1 + .../wasm_message_instantiate_contract.rs | 103 ++++++++++++++++++ .../tw_cosmos_sdk/tests/sign_wasm_contract.rs | 50 ++++++++- src/proto/Cosmos.proto | 22 +++- 6 files changed, 225 insertions(+), 7 deletions(-) create mode 100644 rust/tw_cosmos_sdk/src/transaction/message/wasm_message_instantiate_contract.rs diff --git a/rust/tw_cosmos_sdk/Protobuf/cosmwasm_wasm_v1_tx.proto b/rust/tw_cosmos_sdk/Protobuf/cosmwasm_wasm_v1_tx.proto index 68f0995a016..a43db2d8ad5 100644 --- a/rust/tw_cosmos_sdk/Protobuf/cosmwasm_wasm_v1_tx.proto +++ b/rust/tw_cosmos_sdk/Protobuf/cosmwasm_wasm_v1_tx.proto @@ -17,3 +17,12 @@ message MsgExecuteContract { // Gap in field numbering is intentional! repeated cosmos.base.v1beta1.Coin funds = 5; } + +message MsgInstantiateContract { + string sender = 1; + string admin = 2; // optional + uint64 code_id = 3; + string label = 4; + bytes msg = 5; // JSON + repeated cosmos.base.v1beta1.Coin init_funds = 6; +} diff --git a/rust/tw_cosmos_sdk/src/modules/tx_builder.rs b/rust/tw_cosmos_sdk/src/modules/tx_builder.rs index bee953635c3..bc7b4853a17 100644 --- a/rust/tw_cosmos_sdk/src/modules/tx_builder.rs +++ b/rust/tw_cosmos_sdk/src/modules/tx_builder.rs @@ -237,6 +237,9 @@ where }, MessageEnum::None => SigningError::err(SigningErrorType::Error_invalid_params) .context("No TX message provided"), + MessageEnum::wasm_instantiate_contract_message(ref generic) => { + Self::wasm_instantiate_contract_generic_msg_from_proto(coin, generic) + }, } } @@ -601,6 +604,50 @@ where Ok(msg.into_boxed()) } + pub fn wasm_instantiate_contract_generic_msg_from_proto( + _coin: &dyn CoinContext, + generic: &Proto::mod_Message::WasmInstantiateContract<'_>, + ) -> SigningResult { + use crate::transaction::message::wasm_message_instantiate_contract::{ + InstantiateMsg, WasmInstantiateContractMessage, + }; + + let funds = generic + .init_funds + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + + let sender = Address::from_str(&generic.sender) + .into_tw() + .context("Invalid sender address")?; + + let admin = if generic.admin.is_empty() { + None + } else { + Some( + Address::from_str(&generic.admin) + .into_tw() + .context("Invalid admin address")?, + ) + }; + + let msg = WasmInstantiateContractMessage { + sender, + admin, + code_id: generic.code_id, + label: generic.label.to_string(), + msg: InstantiateMsg::Json( + serde_json::from_slice(&generic.msg) + .into_tw() + .context("Invalid JSON in msg")?, + ), + funds, + }; + + Ok(msg.into_boxed()) + } + pub fn thorchain_send_msg_from_proto( _coin: &dyn CoinContext, send: &Proto::mod_Message::THORChainSend<'_>, diff --git a/rust/tw_cosmos_sdk/src/transaction/message/mod.rs b/rust/tw_cosmos_sdk/src/transaction/message/mod.rs index ee56cb949dd..2208b00b849 100644 --- a/rust/tw_cosmos_sdk/src/transaction/message/mod.rs +++ b/rust/tw_cosmos_sdk/src/transaction/message/mod.rs @@ -18,6 +18,7 @@ pub mod stride_message; pub mod terra_wasm_message; pub mod thorchain_message; pub mod wasm_message; +pub mod wasm_message_instantiate_contract; pub type ProtobufMessage = google::protobuf::Any<'static>; pub type CosmosMessageBox = Box; diff --git a/rust/tw_cosmos_sdk/src/transaction/message/wasm_message_instantiate_contract.rs b/rust/tw_cosmos_sdk/src/transaction/message/wasm_message_instantiate_contract.rs new file mode 100644 index 00000000000..bfbe4792937 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/wasm_message_instantiate_contract.rs @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::CosmosAddress; +use crate::modules::serializer::protobuf_serializer::build_coin; +use crate::proto::cosmwasm; +use crate::transaction::message::{CosmosMessage, JsonMessage, ProtobufMessage}; +use crate::transaction::Coin; +use serde::Serialize; +use serde_json::{json, Value as Json}; +use tw_coin_entry::error::prelude::*; +use tw_memory::Data; +use tw_proto::to_any; + +const DEFAULT_INSTANTIATE_JSON_MSG_TYPE: &str = "wasm/MsgInstantiateContract"; + +#[derive(Clone, Serialize)] +#[serde(untagged)] +pub enum InstantiateMsg { + /// Either a regular string or a stringified JSON object. + String(String), + /// JSON object with a type. + Json(Json), +} + +impl InstantiateMsg { + /// Tries to convert [`InstantiateMsg::String`] to [`InstantiateMsg::Json`], otherwise returns the same object. + pub fn try_to_json(&self) -> InstantiateMsg { + if let InstantiateMsg::String(s) = self { + if let Ok(json) = serde_json::from_str(s) { + return InstantiateMsg::Json(json); + } + } + self.clone() + } + + /// Creates an [`InstantiateMsg`] from a serializable payload. + pub fn json(payload: Payload) -> SigningResult { + let payload = serde_json::to_value(payload) + .tw_err(SigningErrorType::Error_internal) + .context("Error serializing message payload to JSON")?; + Ok(InstantiateMsg::Json(payload)) + } + + /// Converts the message to bytes. + pub fn to_bytes(&self) -> Data { + match self { + InstantiateMsg::String(ref s) => s.as_bytes().to_vec(), + InstantiateMsg::Json(ref j) => j.to_string().as_bytes().to_vec(), + } + } +} + +/// This message is used for instantiating a new CosmWasm contract. +#[derive(Serialize)] +pub struct WasmInstantiateContractMessage { + pub sender: Address, + /// (Optional) The address with permission to perform migrations. + /// If no admin is provided, this field will be an empty string in the protobuf. + pub admin: Option
, + /// The Code ID referencing the stored contract code. + pub code_id: u64, + /// A human-readable label for the contract instance. + pub label: String, + /// A JSON-encoded initialization message. + pub msg: InstantiateMsg, + /// A list of coins to be sent along with the instantiation. + pub funds: Vec, +} + +impl CosmosMessage for WasmInstantiateContractMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmwasm::wasm::v1::MsgInstantiateContract { + sender: self.sender.to_string().into(), + admin: self + .admin + .as_ref() + .map_or("".into(), |admin| admin.to_string().into()), + code_id: self.code_id, + label: self.label.clone().into(), + msg: self.msg.to_bytes().into(), + // Use "init_funds" here, matching the protobuf definition. + init_funds: self.funds.iter().map(build_coin).collect(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + let value = json!({ + "sender": self.sender, + "admin": self.admin, + "code_id": self.code_id, + "label": self.label, + "msg": self.msg.try_to_json(), + "init_funds": self.funds, + }); + Ok(JsonMessage { + msg_type: DEFAULT_INSTANTIATE_JSON_MSG_TYPE.to_string(), + value, + }) + } +} diff --git a/rust/tw_cosmos_sdk/tests/sign_wasm_contract.rs b/rust/tw_cosmos_sdk/tests/sign_wasm_contract.rs index b80f2b8100c..af7afdc288f 100644 --- a/rust/tw_cosmos_sdk/tests/sign_wasm_contract.rs +++ b/rust/tw_cosmos_sdk/tests/sign_wasm_contract.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_cosmos_sdk::context::StandardCosmosContext; use tw_cosmos_sdk::modules::tx_builder::TxBuilder; -use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_fee_none, make_message}; use tw_cosmos_sdk::test_utils::sign_utils::{test_sign_json, test_sign_protobuf, TestInput}; use tw_encoding::base64::{self, STANDARD}; use tw_encoding::hex::DecodeHex; @@ -16,6 +16,13 @@ use tw_number::U256; use tw_proto::Cosmos::Proto; use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; +fn account_593_private_key() -> Cow<'static, [u8]> { + "7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e" + .decode_hex() + .unwrap() + .into() +} + fn account_336_private_key() -> Cow<'static, [u8]> { "37f0af5bc20adb6832d39368a15492cd1e9e0cc1556d4317a5f75f9ccdf525ee" .decode_hex() @@ -124,6 +131,47 @@ fn test_wasm_execute_generic_with_coins() { }); } +#[test] +fn test_wasm_instantiate_contract() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("thor"); + + let execute_msg = r#"{"foo":"bar"}"#; + + let contract = Proto::mod_Message::WasmInstantiateContract { + sender: "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r".into(), + admin: "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r".into(), + code_id: 1, + label: "Contract".into(), + msg: Cow::Borrowed(execute_msg.as_bytes()), + init_funds: vec![], + ..Default::default() + }; + + let input = Proto::SigningInput { + account_number: 593, + chain_id: "thorchain-1".into(), + sequence: 24, + fee: Some(make_fee_none(200_000)), + private_key: account_593_private_key(), + mode: Proto::BroadcastMode::SYNC, + messages: vec![make_message( + MessageEnum::wasm_instantiate_contract_message(contract), + )], + ..Default::default() + }; + + // https://thornode.ninerealms.com/cosmos/tx/v1beta1/txs/45CECF3858D1B4C2D49A48C83082D6F018A1E06CA68B1E76F7FF86D5CEC67255 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_SYNC","tx_bytes":"CqQBCqEBCigvY29zbXdhc20ud2FzbS52MS5Nc2dJbnN0YW50aWF0ZUNvbnRyYWN0EnUKK3Rob3IxejUzd3dlN21kNmNld3o5c3F3cXpuMGFhdnBhdW4wZ3cwZXhuMnISK3Rob3IxejUzd3dlN21kNmNld3o5c3F3cXpuMGFhdnBhdW4wZ3cwZXhuMnIYASIIQ29udHJhY3QqDXsiZm9vIjoiYmFyIn0SWApQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3EgQKAggBGBgSBBDAmgwaQBJNYdvq5b6xOY6tZDgJo7ueYCjs4XKPwg3Np10E6T6CLO6W7SsYKh2GiSrwHvzSA5ragedrQ+h9HwaTIGTLmOE="}"#, + signature: "124d61dbeae5beb1398ead643809a3bb9e6028ece1728fc20dcda75d04e93e822cee96ed2b182a1d86892af01efcd2039ada81e76b43e87d1f06932064cb98e1", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"Ek1h2+rlvrE5jq1kOAmju55gKOzhco/CDc2nXQTpPoIs7pbtKxgqHYaJKvAe/NIDmtqB52tD6H0fBpMgZMuY4Q=="}]"# + }); +} + /// TerraV2 Transfer #[test] fn test_wasm_execute_transfer() { diff --git a/src/proto/Cosmos.proto b/src/proto/Cosmos.proto index 0e5dbb7ffb0..be631f5a452 100644 --- a/src/proto/Cosmos.proto +++ b/src/proto/Cosmos.proto @@ -232,6 +232,16 @@ message Message { repeated Amount coins = 5; } + // MsgInstantiateContract defines a message for instantiating a new CosmWasm contract. + message WasmInstantiateContract { + string sender = 1; + string admin = 2; + uint64 code_id = 3; + string label = 4; + bytes msg = 5; + repeated Amount init_funds = 6; + } + message RawJSON { string type = 1; string value = 2; @@ -284,7 +294,6 @@ message Message { REDELEGATE = 3; } - // cosmos-sdk/MsgGrant message AuthGrant { string granter = 1; @@ -318,8 +327,8 @@ message Message { // cosmos-sdk/MsgVote defines a message to cast a vote. message MsgVote { - uint64 proposal_id = 1; - string voter = 2; + uint64 proposal_id = 1; + string voter = 2; VoteOption option = 3; } @@ -353,20 +362,21 @@ message Message { WasmExecuteContractSend wasm_execute_contract_send_message = 14; WasmExecuteContractGeneric wasm_execute_contract_generic = 15; SignDirect sign_direct_message = 16; - AuthGrant auth_grant = 17; + AuthGrant auth_grant = 17; AuthRevoke auth_revoke = 18; SetWithdrawAddress set_withdraw_address_message = 19; MsgVote msg_vote = 20; MsgStrideLiquidStakingStake msg_stride_liquid_staking_stake = 21; MsgStrideLiquidStakingRedeem msg_stride_liquid_staking_redeem = 22; THORChainDeposit thorchain_deposit_message = 23; + WasmInstantiateContract wasm_instantiate_contract_message = 24; } } // Options for transaction encoding: JSON (Amino, older) or Protobuf. enum SigningMode { - JSON = 0; // JSON format, Pre-Stargate - Protobuf = 1; // Protobuf-serialized (binary), Stargate + JSON = 0; // JSON format, Pre-Stargate + Protobuf = 1; // Protobuf-serialized (binary), Stargate } enum TxHasher { From e130b06ddbb437a2071ebcdd979d96d9df00bc0e Mon Sep 17 00:00:00 2001 From: 10gic Date: Tue, 20 May 2025 22:11:13 +0800 Subject: [PATCH 33/72] Support extra eip7702Auth parameter in eip4337 user operation (#4389) See https://eips.ethereum.org/EIPS/eip-4337#support-for-eip-7702-authorizations --- rust/tw_evm/src/modules/tx_builder.rs | 17 ++- .../src/transaction/authorization_list.rs | 4 +- .../src/transaction/user_operation_v0_7.rs | 22 +++ .../tests/chains/ethereum/ethereum_compile.rs | 128 +++++++++++++++++- src/proto/Ethereum.proto | 2 +- 5 files changed, 168 insertions(+), 5 deletions(-) diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs index fa22e551977..1a2846c4e32 100644 --- a/rust/tw_evm/src/modules/tx_builder.rs +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -20,7 +20,7 @@ use crate::transaction::transaction_eip1559::TransactionEip1559; use crate::transaction::transaction_eip7702::TransactionEip7702; use crate::transaction::transaction_non_typed::TransactionNonTyped; use crate::transaction::user_operation::UserOperation; -use crate::transaction::user_operation_v0_7::UserOperationV0_7; +use crate::transaction::user_operation_v0_7::{Eip7702Auth, UserOperationV0_7}; use crate::transaction::UnsignedTransactionBox; use std::marker::PhantomData; use std::str::FromStr; @@ -528,6 +528,20 @@ impl TxBuilder { .into_tw() .context("Paymaster post-op gas limit exceeds u128")?; + let eip7702_auth = + Self::build_authorization_list(input, sender) + .ok() + .and_then(|auth_list| { + auth_list.0.first().map(|signed_auth| Eip7702Auth { + chain_id: signed_auth.authorization.chain_id, + address: signed_auth.authorization.address, + nonce: signed_auth.authorization.nonce, + y_parity: U256::from(signed_auth.y_parity), + r: signed_auth.r, + s: signed_auth.s, + }) + }); + let entry_point = Self::parse_address(user_op_v0_7.entry_point.as_ref()) .context("Invalid entry point")?; @@ -546,6 +560,7 @@ impl TxBuilder { paymaster_verification_gas_limit, paymaster_post_op_gas_limit, paymaster_data: user_op_v0_7.paymaster_data.to_vec(), + eip7702_auth, entry_point, }) } diff --git a/rust/tw_evm/src/transaction/authorization_list.rs b/rust/tw_evm/src/transaction/authorization_list.rs index 7f7bd2fc430..7f0a7c3429d 100644 --- a/rust/tw_evm/src/transaction/authorization_list.rs +++ b/rust/tw_evm/src/transaction/authorization_list.rs @@ -48,9 +48,9 @@ impl RlpEncode for SignedAuthorization { } } -/// [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) access list. +/// [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) authorization list. #[derive(Default)] -pub struct AuthorizationList(Vec); +pub struct AuthorizationList(pub(crate) Vec); impl From> for AuthorizationList { fn from(value: Vec) -> Self { diff --git a/rust/tw_evm/src/transaction/user_operation_v0_7.rs b/rust/tw_evm/src/transaction/user_operation_v0_7.rs index c9e9b1c6b81..f239e5ff437 100644 --- a/rust/tw_evm/src/transaction/user_operation_v0_7.rs +++ b/rust/tw_evm/src/transaction/user_operation_v0_7.rs @@ -156,10 +156,30 @@ pub struct UserOperationV0_7 { #[serde(with = "as_hex_prefixed")] pub paymaster_data: Data, + #[serde(skip_serializing_if = "Option::is_none")] + pub eip7702_auth: Option, + #[serde(skip)] pub entry_point: Address, } +// See: https://eips.ethereum.org/EIPS/eip-4337#support-for-eip-7702-authorizations +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Eip7702Auth { + #[serde(serialize_with = "U256::as_hex")] + pub chain_id: U256, + pub address: Address, + #[serde(serialize_with = "U256::as_hex")] + pub nonce: U256, + #[serde(serialize_with = "U256::as_hex")] + pub y_parity: U256, + #[serde(serialize_with = "U256::as_hex")] + pub r: U256, + #[serde(serialize_with = "U256::as_hex")] + pub s: U256, +} + impl TransactionCommon for UserOperationV0_7 { #[inline] fn payload(&self) -> Data { @@ -277,6 +297,7 @@ mod tests { paymaster_verification_gas_limit: 0, paymaster_post_op_gas_limit: 0, paymaster_data: Vec::default(), + eip7702_auth: None, entry_point, }; @@ -325,6 +346,7 @@ mod tests { paymaster_verification_gas_limit: 99999u128, paymaster_post_op_gas_limit: 88888u128, paymaster_data: "00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b".decode_hex().unwrap(), + eip7702_auth: None, entry_point: Address::from("0x0000000071727De22E5E9d8BAf0edAc6f37da032"), }; let packed_user_op = PackedUserOperation::new(&user_op); diff --git a/rust/tw_tests/tests/chains/ethereum/ethereum_compile.rs b/rust/tw_tests/tests/chains/ethereum/ethereum_compile.rs index 55fcae72b50..d0aa3742dbe 100644 --- a/rust/tw_tests/tests/chains/ethereum/ethereum_compile.rs +++ b/rust/tw_tests/tests/chains/ethereum/ethereum_compile.rs @@ -9,12 +9,16 @@ use tw_any_coin::ffi::tw_transaction_compiler::{ }; use tw_coin_entry::error::prelude::*; use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex; use tw_encoding::hex::{DecodeHex, ToHex}; use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_memory::test_utils::tw_data_vector_helper::TWDataVectorHelper; +use tw_misc::assert_eq_json; use tw_number::U256; use tw_proto::Ethereum::Proto; -use tw_proto::Ethereum::Proto::{Authorization, AuthorizationCustomSignature, TransactionMode}; +use tw_proto::Ethereum::Proto::{ + Authorization, AuthorizationCustomSignature, TransactionMode, UserOperationV0_7, +}; use tw_proto::TxCompiler::Proto as CompilerProto; use tw_proto::{deserialize, serialize}; @@ -161,6 +165,128 @@ fn test_transaction_compiler_eip7702() { assert_eq!(output.encoded.to_hex(), expected_encoded); } +#[test] +fn test_transaction_compiler_user_op_v0_7_with_eip7702() { + // Step 1: Prepare the input + let contract_generic = Proto::mod_Transaction::ContractGeneric { + amount: U256::encode_be_compact(0), + // call function: execute(address target, uint256 value, bytes calldata data) + data: hex::decode("0xb61d27f60000000000000000000000003a4a63b8763749c4cd30909becfd0e68364870a400000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000").unwrap().into(), + }; + let input = Proto::SigningInput { + tx_mode: TransactionMode::UserOp, + nonce: U256::encode_be_compact(2), + chain_id: U256::encode_be_compact(11155111), + gas_limit: U256::from(600000u128).to_big_endian_compact().into(), + max_inclusion_fee_per_gas: U256::from(100000000u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(939734933u128).to_big_endian_compact().into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::contract_generic( + contract_generic, + ), + }), + user_operation_oneof: + Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation_v0_7( + UserOperationV0_7 { + entry_point: "0x0000000071727De22E5E9d8BAf0edAc6f37da032".into(), + factory: Default::default(), + factory_data: "".decode_hex().unwrap().into(), + sender: "0x18356de2Bc664e45dD22266A674906574087Cf54".into(), + pre_verification_gas: U256::from(400000u64).to_big_endian_compact().into(), + verification_gas_limit: U256::from(1000000u128).to_big_endian_compact().into(), + paymaster: Cow::from(""), + paymaster_verification_gas_limit: Vec::new().into(), + paymaster_post_op_gas_limit: Vec::new().into(), + paymaster_data: "".decode_hex().unwrap().into(), + }, + ), + eip7702_authorization: Some(Authorization { + address: "0xbA0db987220ecE42a60EE9f48FDE633E6c938482".into(), + custom_signature: Some(AuthorizationCustomSignature { + nonce: U256::encode_be_compact(19), + chain_id: U256::encode_be_compact(11155111), + signature: "7b2abdf6d874487751f3efd4d4eb8d39a50569c656f5863cf4de064caafa0e106954dfea7fa4e91e8d20c08e78b4cb2e21a7e56072b02d622fbb5637b5a0d9d701".into(), + }), + }), + ..Proto::SigningInput::default() + }; + + // Step 2: Obtain preimage hash + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let preimage_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_pre_image_hashes(CoinType::Ethereum as u32, input_data.ptr()) + }) + .to_vec() + .expect("!tw_transaction_compiler_pre_image_hashes returned nullptr"); + + let preimage: CompilerProto::PreSigningOutput = + deserialize(&preimage_data).expect("Coin entry returned an invalid output"); + assert_eq!(preimage.error, SigningErrorType::OK); + assert!(preimage.error_message.is_empty()); + assert_eq!( + preimage.data_hash.to_hex(), + "3bf2bf012750e35a16e3430b010f2eb2dda8309892309afce6714196d4912d56" // The AA user op hash + ); + + // Step 3: Compile transaction info + // Simulate signature, normally obtained from signature server + let signature = "4143bd278c97a4738846d9bc5756d0433bd062ea321cafb5ad3f7e3f68c68385277444f6fc851401ab319dad3756cd9e83c3ba9dfa502a69c43827fc2267f6d701".decode_hex().unwrap(); + let public_key = "04ec6632291fbfe6b47826a1c4b195f8b112a7e147e8a5a15fb0f7d7de022652e7f65b97a57011c09527688e23c07ae9c83a2cae2e49edba226e7c43f0baa7296d".decode_hex().unwrap(); + + let signatures = TWDataVectorHelper::create([signature]); + let public_keys = TWDataVectorHelper::create([public_key]); + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let output_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_compile( + CoinType::Ethereum as u32, + input_data.ptr(), + signatures.ptr(), + public_keys.ptr(), + ) + }) + .to_vec() + .expect("!tw_transaction_compiler_compile returned nullptr"); + + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected_encoded = r#" +{ + "sender": "0x18356de2Bc664e45dD22266A674906574087Cf54", + "nonce": "0x02", + "callData": "0xb61d27f60000000000000000000000003a4a63b8763749c4cd30909becfd0e68364870a400000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "callGasLimit": "0x0927c0", + "verificationGasLimit": "0x0f4240", + "preVerificationGas": "0x061a80", + "maxFeePerGas": "0x38033795", + "maxPriorityFeePerGas": "0x05f5e100", + "paymasterVerificationGasLimit": "0x00", + "paymasterPostOpGasLimit": "0x00", + "eip7702Auth": { + "chainId": "0xaa36a7", + "address": "0xbA0db987220ecE42a60EE9f48FDE633E6c938482", + "nonce": "0x13", + "yParity": "0x01", + "r": "0x7b2abdf6d874487751f3efd4d4eb8d39a50569c656f5863cf4de064caafa0e10", + "s": "0x6954dfea7fa4e91e8d20c08e78b4cb2e21a7e56072b02d622fbb5637b5a0d9d7" + }, + "signature": "0x4143bd278c97a4738846d9bc5756d0433bd062ea321cafb5ad3f7e3f68c68385277444f6fc851401ab319dad3756cd9e83c3ba9dfa502a69c43827fc2267f6d71c" +} + "#; + // Successfully broadcast by a bundler: + // https://sepolia.etherscan.io/tx/0x7b89079050a3f3f5afadaeda93750ed797132eb35e038962d15a18f9e862bb58 + // The AA Transaction: + // https://sepolia.etherscan.io/tx/0x3bf2bf012750e35a16e3430b010f2eb2dda8309892309afce6714196d4912d56 + assert_eq_json!( + std::str::from_utf8(output.encoded.iter().as_slice()).unwrap(), + expected_encoded + ); +} + #[test] fn test_transaction_compiler_plan_not_supported() { let transfer = Proto::mod_Transaction::Transfer { diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index 0bb6e1168d9..e25d8fc92f9 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -274,7 +274,7 @@ message SigningInput { repeated Access access_list = 12; // EIP7702 authorization. - // Used in `TransactionMode::SetOp` only. + // Used in `TransactionMode::SetOp` or `TransactionMode::UserOp`. // Currently, we support delegation to only one authority at a time. Authorization eip7702_authorization = 15; } From 3b6d7e8bc91172142e5671942ef2d520c202850c Mon Sep 17 00:00:00 2001 From: gupnik Date: Wed, 28 May 2025 12:53:07 +0530 Subject: [PATCH 34/72] Adds ability to specify derivation when importing a key (#4406) --- include/TrustWalletCore/TWStoredKey.h | 24 ++++++++++ src/Keystore/StoredKey.cpp | 30 ++++++++---- src/Keystore/StoredKey.h | 18 +++++++- src/interface/TWStoredKey.cpp | 36 +++++++++++++++ tests/interface/TWStoredKeyTests.cpp | 66 +++++++++++++++++++++++++++ wasm/src/keystore/default-impl.ts | 10 ++-- wasm/src/keystore/types.ts | 3 +- wasm/tests/KeyStore.test.ts | 54 ++++++++++++++++++---- 8 files changed, 217 insertions(+), 24 deletions(-) diff --git a/include/TrustWalletCore/TWStoredKey.h b/include/TrustWalletCore/TWStoredKey.h index 4d8901f8cc7..20615e776d4 100644 --- a/include/TrustWalletCore/TWStoredKey.h +++ b/include/TrustWalletCore/TWStoredKey.h @@ -51,6 +51,17 @@ struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull priva TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyWithEncryption(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption); +/// Imports a private key. +/// +/// \param privateKey Non-null Block of data private key +/// \param name The name of the stored key to import as a non-null string +/// \param password Non-null block of data, password of the stored key +/// \param coin the coin type +/// \param encryption cipher encryption mode +/// \param derivation derivation of the given coin type +TW_EXPORT_STATIC_METHOD +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyWithEncryptionAndDerivation(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption, enum TWDerivation derivation); + /// Imports an encoded private key. /// /// \param privateKey Non-null encoded private key @@ -73,6 +84,19 @@ struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncoded(TWString* _Nonn TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncodedWithEncryption(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption); +/// Imports an encoded private key. +/// +/// \param privateKey Non-null encoded private key +/// \param name The name of the stored key to import as a non-null string +/// \param password Non-null block of data, password of the stored key +/// \param coin the coin type +/// \param encryption cipher encryption mode +/// \param derivation derivation of the given coin type +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise +TW_EXPORT_STATIC_METHOD +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncodedWithEncryptionAndDerivation(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption, enum TWDerivation derivation); + /// Imports an HD wallet. /// /// \param mnemonic Non-null bip39 mnemonic diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index e5bbe4f04fe..83dfdc1aafb 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -52,30 +52,44 @@ StoredKey StoredKey::createWithPrivateKey(const std::string& name, const Data& p return StoredKey(StoredKeyType::privateKey, name, password, privateKeyData, TWStoredKeyEncryptionLevelDefault, encryption); } -StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData, TWStoredKeyEncryption encryption) { +StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress( + const std::string& name, + const Data& password, + TWCoinType coin, + const Data& privateKeyData, + TWStoredKeyEncryption encryption, + TWDerivation derivation +) { const auto curve = TW::curve(coin); if (!PrivateKey::isValid(privateKeyData, curve)) { throw std::invalid_argument("Invalid private key data"); } StoredKey key = createWithPrivateKey(name, password, privateKeyData, encryption); - const auto derivationPath = TW::derivationPath(coin); + const auto derivationPath = TW::derivationPath(coin, derivation); const auto pubKeyType = TW::publicKeyType(coin); const auto pubKey = PrivateKey(privateKeyData, TWCoinTypeCurve(coin)).getPublicKey(pubKeyType); - const auto address = TW::deriveAddress(coin, PrivateKey(privateKeyData)); - key.accounts.emplace_back(address, coin, TWDerivationDefault, derivationPath, hex(pubKey.bytes), ""); + const auto address = TW::deriveAddress(coin, PrivateKey(privateKeyData), derivation); + key.accounts.emplace_back(address, coin, derivation, derivationPath, hex(pubKey.bytes), ""); return key; } -StoredKey StoredKey::createWithEncodedPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const std::string& encodedPrivateKey, TWStoredKeyEncryption encryption) { +StoredKey StoredKey::createWithEncodedPrivateKeyAddDefaultAddress( + const std::string& name, + const Data& password, + TWCoinType coin, + const std::string& encodedPrivateKey, + TWStoredKeyEncryption encryption, + TWDerivation derivation +) { const auto curve = TW::curve(coin); const auto privateKey = TW::decodePrivateKey(coin, encodedPrivateKey); StoredKey key = StoredKey(StoredKeyType::privateKey, name, password, privateKey.bytes, TWStoredKeyEncryptionLevelDefault, encryption, encodedPrivateKey); - const auto derivationPath = TW::derivationPath(coin); + const auto derivationPath = TW::derivationPath(coin, derivation); const auto pubKeyType = TW::publicKeyType(coin); const auto pubKey = privateKey.getPublicKey(pubKeyType); - const auto address = TW::deriveAddress(coin, privateKey); - key.accounts.emplace_back(address, coin, TWDerivationDefault, derivationPath, hex(pubKey.bytes), ""); + const auto address = TW::deriveAddress(coin, privateKey, derivation); + key.accounts.emplace_back(address, coin, derivation, derivationPath, hex(pubKey.bytes), ""); return key; } diff --git a/src/Keystore/StoredKey.h b/src/Keystore/StoredKey.h index f87cbe1b1bb..6d9ec35cc8d 100644 --- a/src/Keystore/StoredKey.h +++ b/src/Keystore/StoredKey.h @@ -63,11 +63,25 @@ class StoredKey { /// Create a new StoredKey, with the given name and private key, and also add the default address for the given coin.. /// @throws std::invalid_argument if privateKeyData is not a valid private key - static StoredKey createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); + static StoredKey createWithPrivateKeyAddDefaultAddress( + const std::string& name, + const Data& password, + TWCoinType coin, + const Data& privateKeyData, + TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr, + TWDerivation derivation = TWDerivationDefault + ); /// Create a new StoredKey, with the given name and encoded private key, and also add the default address for the given coin.. /// @throws std::invalid_argument if encodedPrivateKey is not a valid private key - static StoredKey createWithEncodedPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const std::string& encodedPrivateKey, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); + static StoredKey createWithEncodedPrivateKeyAddDefaultAddress( + const std::string& name, + const Data& password, + TWCoinType coin, + const std::string& encodedPrivateKey, + TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr, + TWDerivation derivation = TWDerivationDefault + ); /// Create a StoredKey from a JSON object. static StoredKey createWithJson(const nlohmann::json& json); diff --git a/src/interface/TWStoredKey.cpp b/src/interface/TWStoredKey.cpp index 5a501d667e2..95cf199b060 100644 --- a/src/interface/TWStoredKey.cpp +++ b/src/interface/TWStoredKey.cpp @@ -56,6 +56,24 @@ struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyWithEncryption(TWData* } } +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyWithEncryptionAndDerivation( + TWData* _Nonnull privateKey, + TWString* _Nonnull name, + TWData* _Nonnull password, + enum TWCoinType coin, + enum TWStoredKeyEncryption encryption, + enum TWDerivation derivation +) { + try { + const auto& privateKeyData = *reinterpret_cast(privateKey); + const auto& nameString = *reinterpret_cast(name); + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWStoredKey{ KeyStore::StoredKey::createWithPrivateKeyAddDefaultAddress(nameString, passwordData, coin, privateKeyData, encryption, derivation) }; + } catch (...) { + return nullptr; + } +} + struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncoded(TWString* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { return TWStoredKeyImportPrivateKeyEncodedWithEncryption(privateKey, name, password, coin, TWStoredKeyEncryptionAes128Ctr); } @@ -71,6 +89,24 @@ struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncodedWithEncryption(T } } +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyEncodedWithEncryptionAndDerivation( + TWString* _Nonnull privateKey, + TWString* _Nonnull name, + TWData* _Nonnull password, + enum TWCoinType coin, + enum TWStoredKeyEncryption encryption, + enum TWDerivation derivation +) { + try { + const auto& privateKeyString = *reinterpret_cast(privateKey); + const auto& nameString = *reinterpret_cast(name); + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWStoredKey{ KeyStore::StoredKey::createWithEncodedPrivateKeyAddDefaultAddress(nameString, passwordData, coin, privateKeyString, encryption, derivation) }; + } catch (...) { + return nullptr; + } +} + struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { return TWStoredKeyImportHDWalletWithEncryption(mnemonic, name, password, coin, TWStoredKeyEncryptionAes128Ctr); } diff --git a/tests/interface/TWStoredKeyTests.cpp b/tests/interface/TWStoredKeyTests.cpp index cc9ee253a9a..587d3e652e4 100644 --- a/tests/interface/TWStoredKeyTests.cpp +++ b/tests/interface/TWStoredKeyTests.cpp @@ -97,6 +97,48 @@ TEST(TWStoredKey, importPrivateKeyAes256) { TWPrivateKeyDelete(privateKey3); } +TEST(TWStoredKey, importPrivateKeyAes256Legacy) { + const auto privateKeyHex = "28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4"; + const auto privateKey = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes(privateKeyHex)).get())); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto coin = TWCoinTypeBitcoin; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportPrivateKeyWithEncryptionAndDerivation(privateKey.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes256Ctr, TWDerivationBitcoinLegacy)); + const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(privateKey2.get()), TWDataSize(privateKey2.get()))), privateKeyHex); + + const auto privateKey3 = TWStoredKeyPrivateKey(key.get(), coin, password.get()); + const auto pkData3 = WRAPD(TWPrivateKeyData(privateKey3)); + EXPECT_EQ(hex(data(TWDataBytes(pkData3.get()), TWDataSize(pkData3.get()))), privateKeyHex); + TWPrivateKeyDelete(privateKey3); + + const auto accountCoin = WRAP(TWAccount, TWStoredKeyAccount(key.get(),0)); + const auto accountAddress = WRAPS(TWAccountAddress(accountCoin.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress.get())), "1PeUvjuxyf31aJKX6kCXuaqxhmG78ZUdL1"); +} + +TEST(TWStoredKey, importPrivateKeyAes256Taproot) { + const auto privateKeyHex = "28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4"; + const auto privateKey = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes(privateKeyHex)).get())); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto coin = TWCoinTypeBitcoin; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportPrivateKeyWithEncryptionAndDerivation(privateKey.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes256Ctr, TWDerivationBitcoinSegwit)); + const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(privateKey2.get()), TWDataSize(privateKey2.get()))), privateKeyHex); + + const auto privateKey3 = TWStoredKeyPrivateKey(key.get(), coin, password.get()); + const auto pkData3 = WRAPD(TWPrivateKeyData(privateKey3)); + EXPECT_EQ(hex(data(TWDataBytes(pkData3.get()), TWDataSize(pkData3.get()))), privateKeyHex); + TWPrivateKeyDelete(privateKey3); + + const auto accountCoin = WRAP(TWAccount, TWStoredKeyAccount(key.get(),0)); + const auto accountAddress = WRAPS(TWAccountAddress(accountCoin.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress.get())), "bc1qlp5hssx3qstf3m0mt7fd6tzlh90ssm32u2llf4"); +} + TEST(TWStoredKey, importPrivateKeyHexButDecryptEncoded) { const auto privateKeyHex = "3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"; const auto privateKey = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes(privateKeyHex)).get())); @@ -137,6 +179,30 @@ TEST(TWStoredKey, importPrivateKeyEncodedHex) { TWPrivateKeyDelete(privateKey3); } +TEST(TWStoredKey, importPrivateKeyEncodedHexLegacy) { + const auto privateKeyHex = "28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4"; + const auto privateKey = WRAPS(TWStringCreateWithUTF8Bytes(privateKeyHex)); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto coin = TWCoinTypeBitcoin; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportPrivateKeyEncodedWithEncryptionAndDerivation(privateKey.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes128Ctr, TWDerivationBitcoinLegacy)); + const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(privateKey2.get()), TWDataSize(privateKey2.get()))), privateKeyHex); + EXPECT_TRUE(TWStoredKeyHasPrivateKeyEncoded(key.get())); + const auto privateKey2Encoded = WRAPS(TWStoredKeyDecryptPrivateKeyEncoded(key.get(), password.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(privateKey2Encoded.get())), privateKeyHex); + + const auto privateKey3 = TWStoredKeyPrivateKey(key.get(), coin, password.get()); + const auto pkData3 = WRAPD(TWPrivateKeyData(privateKey3)); + EXPECT_EQ(hex(data(TWDataBytes(pkData3.get()), TWDataSize(pkData3.get()))), privateKeyHex); + TWPrivateKeyDelete(privateKey3); + + const auto accountCoin = WRAP(TWAccount, TWStoredKeyAccount(key.get(),0)); + const auto accountAddress = WRAPS(TWAccountAddress(accountCoin.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress.get())), "1PeUvjuxyf31aJKX6kCXuaqxhmG78ZUdL1"); +} + TEST(TWStoredKey, importPrivateKeyEncodedStellar) { const auto privateKeyEncoded = "SAV76USXIJOBMEQXPANUOQM6F5LIOTLPDIDVRJBFFE2MDJXG24TAPUU7"; const auto decodedPrivateKeyHex = "2bff5257425c161217781b47419e2f56874d6f1a0758a4252934c1a6e6d72607"; diff --git a/wasm/src/keystore/default-impl.ts b/wasm/src/keystore/default-impl.ts index 043fe3b1abe..c6368bc978d 100644 --- a/wasm/src/keystore/default-impl.ts +++ b/wasm/src/keystore/default-impl.ts @@ -82,7 +82,8 @@ export class Default implements Types.IKeyStore { name: string, password: string, coin: CoinType, - encryption: StoredKeyEncryption + encryption: StoredKeyEncryption, + derivation: Derivation ): Promise { return new Promise((resolve, reject) => { const { StoredKey, PrivateKey, Curve, StoredKeyEncryption } = this.core; @@ -95,7 +96,7 @@ export class Default implements Types.IKeyStore { throw Types.Error.InvalidKey; } let pass = Buffer.from(password); - let storedKey = StoredKey.importPrivateKeyWithEncryption(key, name, pass, coin, encryption); + let storedKey = StoredKey.importPrivateKeyWithEncryptionAndDerivation(key, name, pass, coin, encryption, derivation); let wallet = this.mapWallet(storedKey); storedKey.delete(); this.importWallet(wallet) @@ -109,13 +110,14 @@ export class Default implements Types.IKeyStore { name: string, password: string, coin: CoinType, - encryption: StoredKeyEncryption + encryption: StoredKeyEncryption, + derivation: Derivation ): Promise { return new Promise((resolve, reject) => { const { StoredKey, PrivateKey, Curve, StoredKeyEncryption } = this.core; let pass = Buffer.from(password); - let storedKey = StoredKey.importPrivateKeyEncodedWithEncryption(key, name, pass, coin, encryption); + let storedKey = StoredKey.importPrivateKeyEncodedWithEncryptionAndDerivation(key, name, pass, coin, encryption, derivation); let wallet = this.mapWallet(storedKey); storedKey.delete(); this.importWallet(wallet) diff --git a/wasm/src/keystore/types.ts b/wasm/src/keystore/types.ts index 888ca46e705..c5e94d2f8fd 100644 --- a/wasm/src/keystore/types.ts +++ b/wasm/src/keystore/types.ts @@ -68,7 +68,8 @@ export interface IKeyStore { name: string, password: string, coin: CoinType, - encryption: StoredKeyEncryption + encryption: StoredKeyEncryption, + derivation: Derivation ): Promise; // Import a Wallet object directly diff --git a/wasm/tests/KeyStore.test.ts b/wasm/tests/KeyStore.test.ts index a763dc99456..ffe9017dfe6 100644 --- a/wasm/tests/KeyStore.test.ts +++ b/wasm/tests/KeyStore.test.ts @@ -8,8 +8,42 @@ import { Buffer } from "buffer"; import { KeyStore } from "../dist"; describe("KeyStore", () => { - it("test get key", async () => { - const { CoinType, StoredKeyEncryption, HexCoding } = globalThis.core; + it("test get key default", async () => { + const { CoinType, StoredKeyEncryption, HexCoding, Derivation } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + + const storage = new KeyStore.FileSystemStorage("/tmp"); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ], StoredKeyEncryption.aes128Ctr); + + const account = wallet.activeAccounts[0]; + const key = await keystore.getKey(wallet.id, password, account); + + assert.equal( + HexCoding.encode(key.data()), + "0xd2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f" + ); + + const inputPrivateKey = Buffer.from("9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c", "hex"); + wallet = await keystore.importKey(inputPrivateKey, "Coolw", password, CoinType.bitcoin, StoredKeyEncryption.aes128Ctr, Derivation.default); + + const account2 = wallet.activeAccounts[0]; + assert.equal(account2.derivationPath, "m/84'/0'/0'/0/0"); + assert.equal(account2.address, "bc1qygled22ne2atsrdhhg92xaqflk67tpc4q69kjd"); + const key2 = await keystore.getKey(wallet.id, password, account2); + + assert.equal( + HexCoding.encode(key2.data()), + "0x9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c" + ); + }).timeout(10000); + + it("test get key legacy", async () => { + const { CoinType, StoredKeyEncryption, HexCoding, Derivation } = globalThis.core; const mnemonic = globalThis.mnemonic as string; const password = globalThis.password as string; @@ -29,9 +63,11 @@ describe("KeyStore", () => { ); const inputPrivateKey = Buffer.from("9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c", "hex"); - wallet = await keystore.importKey(inputPrivateKey, "Coolw", password, CoinType.bitcoin, StoredKeyEncryption.aes128Ctr); + wallet = await keystore.importKey(inputPrivateKey, "Coolw", password, CoinType.bitcoin, StoredKeyEncryption.aes128Ctr, Derivation.bitcoinLegacy); const account2 = wallet.activeAccounts[0]; + assert.equal(account2.derivationPath, "m/44'/0'/0'/0/0"); + assert.equal(account2.address, "14869jMDBsEra11WyervLDxCQD4GMBfjD7"); const key2 = await keystore.getKey(wallet.id, password, account2); assert.equal( @@ -41,7 +77,7 @@ describe("KeyStore", () => { }).timeout(10000); it("test export", async () => { - const { CoinType, StoredKeyEncryption, HexCoding } = globalThis.core; + const { CoinType, StoredKeyEncryption, HexCoding, Derivation } = globalThis.core; const mnemonic = globalThis.mnemonic as string; const password = globalThis.password as string; @@ -56,7 +92,7 @@ describe("KeyStore", () => { assert.equal(exported, mnemonic); const inputPrivateKey = Buffer.from("9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c", "hex"); - wallet = await keystore.importKey(inputPrivateKey, "Coolw", password, CoinType.bitcoin, StoredKeyEncryption.aes128Ctr); + wallet = await keystore.importKey(inputPrivateKey, "Coolw", password, CoinType.bitcoin, StoredKeyEncryption.aes128Ctr, Derivation.default); const exported2 = await keystore.export(wallet.id, password); assert.equal(HexCoding.encode(exported2), "0x9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c"); @@ -80,14 +116,14 @@ describe("KeyStore", () => { }).timeout(10000); it("test export key", async () => { - const { CoinType, StoredKeyEncryption, HexCoding } = globalThis.core; + const { CoinType, StoredKeyEncryption, HexCoding, Derivation } = globalThis.core; const password = globalThis.password as string; const storage = new KeyStore.FileSystemStorage("/tmp"); const keystore = new KeyStore.Default(globalThis.core, storage); const inputPrivateKey = Buffer.from("9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c", "hex"); - const wallet = await keystore.importKey(inputPrivateKey, "Coolw", password, CoinType.solana, StoredKeyEncryption.aes128Ctr); + const wallet = await keystore.importKey(inputPrivateKey, "Coolw", password, CoinType.solana, StoredKeyEncryption.aes128Ctr, Derivation.default); assert.equal(await keystore.getWalletType(wallet.id), "private-key"); const exported = await keystore.exportPrivateKey(wallet.id, password); @@ -95,14 +131,14 @@ describe("KeyStore", () => { }).timeout(10000); it("test export key encoded", async () => { - const { CoinType, StoredKeyEncryption } = globalThis.core; + const { CoinType, StoredKeyEncryption, Derivation } = globalThis.core; const password = globalThis.password as string; const storage = new KeyStore.FileSystemStorage("/tmp"); const keystore = new KeyStore.Default(globalThis.core, storage); const inputPrivateKeyBase58 = "A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr"; - const wallet = await keystore.importKeyEncoded(inputPrivateKeyBase58, "Coolw", password, CoinType.solana, StoredKeyEncryption.aes128Ctr); + const wallet = await keystore.importKeyEncoded(inputPrivateKeyBase58, "Coolw", password, CoinType.solana, StoredKeyEncryption.aes128Ctr, Derivation.default); assert.equal(await keystore.getWalletType(wallet.id), "private-key"); const exported = await keystore.exportPrivateKeyEncoded(wallet.id, password); From 2232874fb730702dddc3e8c03516724857de68f5 Mon Sep 17 00:00:00 2001 From: gupnik Date: Tue, 3 Jun 2025 19:13:20 +0530 Subject: [PATCH 35/72] Adds Safety and Codecov CI (#4408) * Adds Rust Memory Leak Checker * FMT * Fix memory leaks * Adds codecov ci as well * Addresses review comments * Minor * Use `uname -s` * Adds dependencies install * Minor change * Moves coverage to rust ci * Minor fix * Minor * Minor * No need to install llm cov again * comment the codecov step for now --- .../workflows/{linux-ci-rust.yml => rust.yml} | 126 ++++++++++++++++-- .gitignore | 1 + android/gradlew | 2 +- bootstrap.sh | 2 +- kotlin/gradlew | 2 +- rust/tw_encoding/tests/base32_ffi_tests.rs | 20 +-- rust/tw_encoding/tests/base64_ffi_tests.rs | 31 +++-- rust/tw_encoding/tests/hex_ffi_tests.rs | 20 ++- rust/tw_tests/tests/utils/bit_reader.rs | 6 +- rust/tw_tests/tests/utils/uuid.rs | 18 ++- rust/wallet_core_rs/src/ffi/utils/uuid_ffi.rs | 2 +- samples/android/gradlew | 4 +- samples/kmp/gradlew | 2 +- tools/android-build | 2 +- tools/android-release | 2 +- tools/android-test | 2 +- tools/coverage | 2 +- tools/install-dependencies | 2 +- tools/install-rust-dependencies | 2 +- tools/kotlin-build | 2 +- tools/kotlin-test | 2 +- tools/rust-bindgen | 4 +- tools/rust-coverage | 6 +- 23 files changed, 185 insertions(+), 77 deletions(-) rename .github/workflows/{linux-ci-rust.yml => rust.yml} (60%) diff --git a/.github/workflows/linux-ci-rust.yml b/.github/workflows/rust.yml similarity index 60% rename from .github/workflows/linux-ci-rust.yml rename to .github/workflows/rust.yml index d2940428273..cf0b7dfe462 100644 --- a/.github/workflows/linux-ci-rust.yml +++ b/.github/workflows/rust.yml @@ -1,4 +1,4 @@ -name: Linux CI Rust +name: Rust CI on: push: @@ -16,7 +16,7 @@ concurrency: jobs: # Check formatting, clippy warnings, run tests and check code coverage. - build-and-test: + rust-lints: permissions: contents: read checks: write @@ -51,18 +51,6 @@ jobs: cargo clippy -- -D warnings working-directory: rust - - name: Run tests - run: | - tools/rust-coverage - - - name: Gather and check Rust code coverage - run: | - tools/check-coverage rust/coverage.stats rust/coverage.info - - - name: Run Doc tests - run: | - tools/rust-test doc - # Run Rust tests in WASM. test-wasm: runs-on: ubuntu-24.04 @@ -163,3 +151,113 @@ jobs: comment-author: 'github-actions[bot]' edit-mode: replace body-path: 'report-diff.md' + + memory-profiler: + runs-on: ubuntu-24.04 + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.8 + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Install llvm + run: | + # to get the symbolizer for debug symbol resolution + sudo apt install llvm + + - name: Install nightly + uses: dtolnay/rust-toolchain@nightly + + - name: Enable debug symbols + run: | + cd rust + # to fix buggy leak analyzer: + # https://github.com/japaric/rust-san#unrealiable-leaksanitizer + # ensure there's a profile.dev section + if ! grep -qE '^[ \t]*[profile.dev]' Cargo.toml; then + echo >> Cargo.toml + echo '[profile.dev]' >> Cargo.toml + fi + # remove pre-existing opt-levels in profile.dev + sed -i '/^\s*\[profile.dev\]/,/^\s*\[/ {/^\s*opt-level/d}' Cargo.toml + # now set opt-level to 1 + sed -i '/^\s*\[profile.dev\]/a opt-level = 1' Cargo.toml + cat Cargo.toml + + - name: cargo test -Zsanitizer=address + # only --lib --tests b/c of https://github.com/rust-lang/rust/issues/53945 + run: | + cd rust + cargo test --lib --tests --all-features --target x86_64-unknown-linux-gnu + env: + ASAN_OPTIONS: "detect_odr_violation=0:detect_leaks=0" + RUSTFLAGS: "-Z sanitizer=address" + + - name: cargo test -Zsanitizer=leak + if: always() + run: | + cd rust + cargo test --all-features --target x86_64-unknown-linux-gnu + env: + RUSTFLAGS: "-Z sanitizer=leak" + + coverage: + runs-on: ubuntu-24.04 + if: github.event.pull_request.draft == false + + steps: + - uses: actions/checkout@v3 + - name: Install system dependencies + run: | + tools/install-sys-dependencies-linux + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.8 + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Install Rust dependencies + run: | + tools/install-rust-dependencies dev + + - name: cargo generate-lockfile + if: hashFiles('Cargo.lock') == '' + run: | + cd rust + cargo generate-lockfile + + - name: Run tests + run: | + tools/rust-coverage + + - name: Run Doc tests + run: | + tools/rust-test doc + + - name: Record Rust version + run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" + + # TODO: Uncomment this when we have a codecov token + # - name: Upload to codecov.io + # uses: codecov/codecov-action@v5 + # with: + # fail_ci_if_error: true + # token: ${{ secrets.CODECOV_TOKEN }} + # env_vars: OS,RUST + + - name: Gather and check Rust code coverage + run: | + tools/check-coverage rust/coverage.stats rust/lcov.info diff --git a/.gitignore b/.gitignore index e7e97f12830..327d4812e0d 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ emsdk/ wasm-build/ # Code coverage files +lcov.info coverage.info coverage/ swift/test_output/ diff --git a/android/gradlew b/android/gradlew index aeb74cbb43e..d0054d28acf 100755 --- a/android/gradlew +++ b/android/gradlew @@ -104,7 +104,7 @@ cygwin=false msys=false darwin=false nonstop=false -case "$( uname )" in #( +case "$( uname -s )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( diff --git a/bootstrap.sh b/bootstrap.sh index 1cc2636b987..10e5812aa06 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -15,7 +15,7 @@ if isHelp; then fi echo "#### Installing system dependencies ... ####" -if [[ $(uname) == "Darwin" ]]; then +if [[ $(uname -s) == "Darwin" ]]; then tools/install-sys-dependencies-mac else tools/install-sys-dependencies-linux diff --git a/kotlin/gradlew b/kotlin/gradlew index f5feea6d6b1..46695364f36 100755 --- a/kotlin/gradlew +++ b/kotlin/gradlew @@ -108,7 +108,7 @@ cygwin=false msys=false darwin=false nonstop=false -case "$( uname )" in #( +case "$( uname -s )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( diff --git a/rust/tw_encoding/tests/base32_ffi_tests.rs b/rust/tw_encoding/tests/base32_ffi_tests.rs index dd0906a596c..309534ba597 100644 --- a/rust/tw_encoding/tests/base32_ffi_tests.rs +++ b/rust/tw_encoding/tests/base32_ffi_tests.rs @@ -9,12 +9,14 @@ use tw_encoding::ffi::{decode_base32, encode_base32}; /// equals to `expected`. #[track_caller] fn test_base32_encode_helper(input: &[u8], expected: &str, alphabet: Option<&str>, padding: bool) { - let alphabet = alphabet - .map(|alphabet| CString::new(alphabet).unwrap().into_raw()) - .unwrap_or_else(std::ptr::null_mut); + let alphabet_cstring = alphabet.map(|alphabet| CString::new(alphabet).unwrap()); + let alphabet_ptr = alphabet_cstring + .as_ref() + .map(|s| s.as_ptr()) + .unwrap_or_else(std::ptr::null); let result_ptr = - unsafe { encode_base32(input.as_ptr(), input.len(), alphabet, padding) }.unwrap(); + unsafe { encode_base32(input.as_ptr(), input.len(), alphabet_ptr, padding) }.unwrap(); let result = unsafe { CString::from_raw(result_ptr) }; assert_eq!(result.to_str().unwrap(), expected); } @@ -24,12 +26,14 @@ fn test_base32_encode_helper(input: &[u8], expected: &str, alphabet: Option<&str #[track_caller] fn test_base32_decode_helper(input: &str, expected: &[u8], alphabet: Option<&str>, padding: bool) { let input = CString::new(input).unwrap(); - let alphabet = alphabet - .map(|alphabet| CString::new(alphabet).unwrap().into_raw()) - .unwrap_or_else(std::ptr::null_mut); + let alphabet_cstring = alphabet.map(|alphabet| CString::new(alphabet).unwrap()); + let alphabet_ptr = alphabet_cstring + .as_ref() + .map(|s| s.as_ptr()) + .unwrap_or_else(std::ptr::null); let decoded = unsafe { - decode_base32(input.as_ptr(), alphabet, padding) + decode_base32(input.as_ptr(), alphabet_ptr, padding) .unwrap() .into_vec() }; diff --git a/rust/tw_encoding/tests/base64_ffi_tests.rs b/rust/tw_encoding/tests/base64_ffi_tests.rs index 1677a929a32..a8c9869b67a 100644 --- a/rust/tw_encoding/tests/base64_ffi_tests.rs +++ b/rust/tw_encoding/tests/base64_ffi_tests.rs @@ -2,23 +2,25 @@ // // Copyright © 2017 Trust Wallet. -use std::ffi::{CStr, CString}; +use std::ffi::CString; use tw_encoding::ffi::{decode_base64, encode_base64}; #[test] fn test_encode_base64() { let data = b"hello world"; - let encoded = unsafe { CStr::from_ptr(encode_base64(data.as_ptr(), data.len(), false)) }; + let result_ptr = unsafe { encode_base64(data.as_ptr(), data.len(), false) }; + let result = unsafe { CString::from_raw(result_ptr) }; let expected = "aGVsbG8gd29ybGQ="; - assert_eq!(encoded.to_str().unwrap(), expected); + assert_eq!(result.to_str().unwrap(), expected); } #[test] fn test_encode_base64_url() { let data = b"+'?ab"; - let encoded = unsafe { CStr::from_ptr(encode_base64(data.as_ptr(), data.len(), true)) }; + let result_ptr = unsafe { encode_base64(data.as_ptr(), data.len(), true) }; + let result = unsafe { CString::from_raw(result_ptr) }; let expected = "Kyc_YWI="; - assert_eq!(encoded.to_str().unwrap(), expected); + assert_eq!(result.to_str().unwrap(), expected); } #[test] @@ -27,9 +29,11 @@ fn test_decode_base64_url() { let expected = b"+'?ab"; let encoded_c_str = CString::new(encoded).unwrap(); - let encoded_ptr = encoded_c_str.as_ptr(); - - let decoded = unsafe { decode_base64(encoded_ptr, true).unwrap().into_vec() }; + let decoded = unsafe { + decode_base64(encoded_c_str.as_ptr(), true) + .unwrap() + .into_vec() + }; assert_eq!(decoded, expected); } @@ -39,9 +43,11 @@ fn test_decode_base64() { let expected = b"hello world!"; let encoded_c_str = CString::new(encoded).unwrap(); - let encoded_ptr = encoded_c_str.as_ptr(); - - let decoded = unsafe { decode_base64(encoded_ptr, false).unwrap().into_vec() }; + let decoded = unsafe { + decode_base64(encoded_c_str.as_ptr(), false) + .unwrap() + .into_vec() + }; assert_eq!(decoded, expected); } @@ -49,7 +55,6 @@ fn test_decode_base64() { fn test_decode_base64_invalid() { let invalid_encoded = "_This_is_an_invalid_base64_"; let encoded_c_str = CString::new(invalid_encoded).unwrap(); - let encoded_ptr = encoded_c_str.as_ptr(); - let res = unsafe { decode_base64(encoded_ptr, false) }; + let res = unsafe { decode_base64(encoded_c_str.as_ptr(), false) }; assert!(res.is_err()); } diff --git a/rust/tw_encoding/tests/hex_ffi_tests.rs b/rust/tw_encoding/tests/hex_ffi_tests.rs index d0b259f94a9..6a39a184a80 100644 --- a/rust/tw_encoding/tests/hex_ffi_tests.rs +++ b/rust/tw_encoding/tests/hex_ffi_tests.rs @@ -2,23 +2,25 @@ // // Copyright © 2017 Trust Wallet. -use std::ffi::{CStr, CString}; +use std::ffi::CString; use tw_encoding::ffi::{decode_hex, encode_hex}; #[test] fn test_encode_hex_without_prefix() { let data = b"hello world"; - let encoded = unsafe { CStr::from_ptr(encode_hex(data.as_ptr(), data.len(), false)) }; + let result_ptr = unsafe { encode_hex(data.as_ptr(), data.len(), false) }; + let result = unsafe { CString::from_raw(result_ptr) }; let expected = "68656c6c6f20776f726c64"; - assert_eq!(encoded.to_str().unwrap(), expected); + assert_eq!(result.to_str().unwrap(), expected); } #[test] fn test_encode_hex_with_prefix() { let data = b"hello world"; - let encoded = unsafe { CStr::from_ptr(encode_hex(data.as_ptr(), data.len(), true)) }; + let result_ptr = unsafe { encode_hex(data.as_ptr(), data.len(), true) }; + let result = unsafe { CString::from_raw(result_ptr) }; let expected = "0x68656c6c6f20776f726c64"; - assert_eq!(encoded.to_str().unwrap(), expected); + assert_eq!(result.to_str().unwrap(), expected); } #[test] @@ -26,9 +28,7 @@ fn test_decode_hex() { let encoded = "68656c6c6f20776f726c64"; let encoded_c_str = CString::new(encoded).unwrap(); - let encoded_ptr = encoded_c_str.as_ptr(); - - let decoded: Vec<_> = unsafe { decode_hex(encoded_ptr).unwrap().into_vec() }; + let decoded: Vec<_> = unsafe { decode_hex(encoded_c_str.as_ptr()).unwrap().into_vec() }; assert_eq!(decoded, b"hello world"); } @@ -37,8 +37,6 @@ fn test_decode_hex_with_prefix() { let encoded = "0x68656c6c6f20776f726c64"; let encoded_c_str = CString::new(encoded).unwrap(); - let encoded_ptr = encoded_c_str.as_ptr(); - - let decoded: Vec<_> = unsafe { decode_hex(encoded_ptr).unwrap().into_vec() }; + let decoded: Vec<_> = unsafe { decode_hex(encoded_c_str.as_ptr()).unwrap().into_vec() }; assert_eq!(decoded, b"hello world"); } diff --git a/rust/tw_tests/tests/utils/bit_reader.rs b/rust/tw_tests/tests/utils/bit_reader.rs index ceb7d5fc5ba..1396cac6d85 100644 --- a/rust/tw_tests/tests/utils/bit_reader.rs +++ b/rust/tw_tests/tests/utils/bit_reader.rs @@ -5,7 +5,7 @@ use tw_encoding::hex::{DecodeHex, ToHex}; use tw_memory::test_utils::tw_data_helper::TWDataHelper; use wallet_core_rs::ffi::utils::bit_reader_ffi::{ - tw_bit_reader_create, tw_bit_reader_finished, tw_bit_reader_read_u8, + tw_bit_reader_create, tw_bit_reader_delete, tw_bit_reader_finished, tw_bit_reader_read_u8, tw_bit_reader_read_u8_slice, CBitReaderCode, }; @@ -37,6 +37,8 @@ fn test_tw_bit_reader_success() { ); assert!(unsafe { tw_bit_reader_finished(reader) }); + + unsafe { tw_bit_reader_delete(reader) }; } #[test] @@ -66,4 +68,6 @@ fn test_tw_bit_reader_error() { res.into_result().unwrap_err(), CBitReaderCode::NotEnoughData as i32 ); + + unsafe { tw_bit_reader_delete(reader) }; } diff --git a/rust/tw_tests/tests/utils/uuid.rs b/rust/tw_tests/tests/utils/uuid.rs index a5c9aad34e4..fc375017824 100644 --- a/rust/tw_tests/tests/utils/uuid.rs +++ b/rust/tw_tests/tests/utils/uuid.rs @@ -3,17 +3,16 @@ // Copyright © 2017 Trust Wallet. use std::collections::HashSet; -use std::ffi::CStr; +use std::ffi::CString; use wallet_core_rs::ffi::utils::uuid_ffi::tw_uuid_random; /// Example of the valid UUID: 3cbbcce1-db89-4ea2-be24-88a686be461c #[test] fn test_tw_uuid_random_is_valid() { - let uuid = unsafe { CStr::from_ptr(tw_uuid_random()) } - .to_str() - .unwrap(); + let uuid_ptr = unsafe { tw_uuid_random() }; + let uuid = unsafe { CString::from_raw(uuid_ptr) }; - let tokens: Vec<_> = uuid.split("-").collect(); + let tokens: Vec<_> = uuid.to_str().unwrap().split("-").collect(); assert_eq!(tokens.len(), 5); assert_eq!(tokens[0].len(), 8); assert_eq!(tokens[1].len(), 4); @@ -29,12 +28,11 @@ fn test_tw_uuid_random_do_not_repeat() { // Use `Vec` instead of `HashSet` here to make each iteration as fast as possible. let mut uuids = Vec::with_capacity(ITERATIONS); for _ in 0..ITERATIONS { - let uuid = unsafe { CStr::from_ptr(tw_uuid_random()) } - .to_str() - .unwrap(); - uuids.push(uuid); + let uuid_ptr = unsafe { tw_uuid_random() }; + let uuid = unsafe { CString::from_raw(uuid_ptr) }; + uuids.push(uuid.to_str().unwrap().to_string()); } - let unique_uuids: HashSet<&str> = uuids.into_iter().collect(); + let unique_uuids: HashSet = uuids.into_iter().collect(); assert_eq!(unique_uuids.len(), ITERATIONS); } diff --git a/rust/wallet_core_rs/src/ffi/utils/uuid_ffi.rs b/rust/wallet_core_rs/src/ffi/utils/uuid_ffi.rs index 159a4a079de..3fb5eddceda 100644 --- a/rust/wallet_core_rs/src/ffi/utils/uuid_ffi.rs +++ b/rust/wallet_core_rs/src/ffi/utils/uuid_ffi.rs @@ -10,7 +10,7 @@ use std::ffi::{c_char, CString}; /// This uses the [`getrandom`] crate to utilise the operating system's RNG /// as the source of random numbers. #[no_mangle] -pub unsafe extern "C" fn tw_uuid_random() -> *const c_char { +pub unsafe extern "C" fn tw_uuid_random() -> *mut c_char { let res = uuid::Uuid::new_v4(); CString::new(res.to_string()).unwrap().into_raw() } diff --git a/samples/android/gradlew b/samples/android/gradlew index cccdd3d517f..fd461ae0139 100755 --- a/samples/android/gradlew +++ b/samples/android/gradlew @@ -49,7 +49,7 @@ cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in +case "$( uname -s )" in CYGWIN* ) cygwin=true ;; @@ -165,7 +165,7 @@ APP_ARGS=$(save "$@") eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then +if [ "$(uname -s)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi diff --git a/samples/kmp/gradlew b/samples/kmp/gradlew index f5feea6d6b1..46695364f36 100755 --- a/samples/kmp/gradlew +++ b/samples/kmp/gradlew @@ -108,7 +108,7 @@ cygwin=false msys=false darwin=false nonstop=false -case "$( uname )" in #( +case "$( uname -s )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( diff --git a/tools/android-build b/tools/android-build index 8c4b1344af8..e20312e4384 100755 --- a/tools/android-build +++ b/tools/android-build @@ -7,7 +7,7 @@ set -e source $(dirname $0)/library version=$(wc_read_version $1) -if [[ `uname` == "Darwin" ]]; then +if [[ $(uname -s) == "Darwin" ]]; then export BOOST_ROOT=$(brew --prefix boost) fi diff --git a/tools/android-release b/tools/android-release index 54b1a97f799..f69b77a88c0 100755 --- a/tools/android-release +++ b/tools/android-release @@ -8,7 +8,7 @@ set -e source $(dirname $0)/library version=$(wc_read_version $1) -if [[ `uname` == "Darwin" ]]; then +if [[ $(uname -s) == "Darwin" ]]; then export BOOST_ROOT=$(brew --prefix boost) fi diff --git a/tools/android-test b/tools/android-test index 7185d860711..fd6a4a49a2d 100755 --- a/tools/android-test +++ b/tools/android-test @@ -11,7 +11,7 @@ ANDROID_CMDTOOLS=$(find_android_cmdline_tools) AVD_NAME="integration-tests" PORT=5556 -if [[ `uname` == "Darwin" ]]; then +if [[ $(uname -s) == "Darwin" ]]; then export BOOST_ROOT=$(brew --prefix boost) fi diff --git a/tools/coverage b/tools/coverage index 88b246fecd7..9d8d952b60c 100755 --- a/tools/coverage +++ b/tools/coverage @@ -28,7 +28,7 @@ EOF sudo chmod 755 /tmp/llvm-gcov.sh } -if [[ `uname` == "Darwin" ]]; then +if [[ $(uname -s) == "Darwin" ]]; then echo "gcov is llvm-cov on macOS" lcov --capture --directory . --output-file coverage.info else diff --git a/tools/install-dependencies b/tools/install-dependencies index acdcf8e56eb..e59287ba45a 100755 --- a/tools/install-dependencies +++ b/tools/install-dependencies @@ -68,7 +68,7 @@ function build_protobuf() { $CMAKE --install build --prefix $PREFIX $CMAKE --build build --target clean - if [[ -x "$(command -v swift)" && $(uname) == "Darwin" ]]; then + if [[ -x "$(command -v swift)" && $(uname -s) == "Darwin" ]]; then build_swift_plugin fi } diff --git a/tools/install-rust-dependencies b/tools/install-rust-dependencies index 17173643a7e..5249f333ddf 100755 --- a/tools/install-rust-dependencies +++ b/tools/install-rust-dependencies @@ -10,7 +10,7 @@ rustup toolchain install $NIGHTLY-x86_64-apple-darwin --force-non-host rustup toolchain install $NIGHTLY-aarch64-apple-darwin --force-non-host rustup component add rust-src --toolchain $NIGHTLY-aarch64-apple-darwin rustup component add rust-src --toolchain $NIGHTLY-x86_64-apple-darwin -if [[ `uname` == "Linux" ]]; then +if [[ $(uname -s) == "Linux" ]]; then rustup component add rust-src --toolchain $NIGHTLY-$(uname -m)-unknown-linux-gnu fi diff --git a/tools/kotlin-build b/tools/kotlin-build index 4681910d30a..1fb725438ad 100755 --- a/tools/kotlin-build +++ b/tools/kotlin-build @@ -2,7 +2,7 @@ set -e -if [[ `uname` == "Darwin" ]]; then +if [[ $(uname -s) == "Darwin" ]]; then export BOOST_ROOT=$(brew --prefix boost) fi diff --git a/tools/kotlin-test b/tools/kotlin-test index 2c97cef3d00..1efa9d64d4a 100755 --- a/tools/kotlin-test +++ b/tools/kotlin-test @@ -2,7 +2,7 @@ set -e -if [[ `uname` == "Darwin" ]]; then +if [[ $(uname -s) == "Darwin" ]]; then export BOOST_ROOT=$(brew --prefix boost) fi diff --git a/tools/rust-bindgen b/tools/rust-bindgen index 2ed44523398..1586f8fc356 100755 --- a/tools/rust-bindgen +++ b/tools/rust-bindgen @@ -55,7 +55,7 @@ if isTargetSpecified "android"; then cargo build -Z build-std=std,panic_abort --target aarch64-linux-android --target armv7-linux-androideabi --target x86_64-linux-android --target i686-linux-android --release --lib fi -if isTargetSpecified "ios" && [[ $(uname) == "Darwin" ]]; then +if isTargetSpecified "ios" && [[ $(uname -s) == "Darwin" ]]; then echo "Generating iOS targets" cargo build -Z build-std=std,panic_abort --target aarch64-apple-ios --target aarch64-apple-ios-sim --target x86_64-apple-ios --target aarch64-apple-darwin --target x86_64-apple-darwin --target aarch64-apple-ios-macabi --target x86_64-apple-ios-macabi --release --lib & wait @@ -75,7 +75,7 @@ cd - echo "Generating C++ files..." pushd codegen-v2 && cargo run -- cpp && popd -if isTargetSpecified "ios" && [[ $(uname) == "Darwin" ]]; then +if isTargetSpecified "ios" && [[ $(uname -s) == "Darwin" ]]; then cd rust cat > $TARGET_XCFRAMEWORK_NAME/Info.plist << EOF diff --git a/tools/rust-coverage b/tools/rust-coverage index 835b13997b3..e27775e761e 100755 --- a/tools/rust-coverage +++ b/tools/rust-coverage @@ -19,11 +19,11 @@ pushd rust # Generate HTML report if requested if [[ "$1" == "html" ]]; then cargo llvm-cov test --workspace --exclude wallet_core_bin --html - cargo llvm-cov report --lcov --output-path coverage.info + cargo llvm-cov report --lcov --output-path lcov.info else - cargo llvm-cov test --workspace --exclude wallet_core_bin --lcov --output-path coverage.info + cargo llvm-cov test --workspace --exclude wallet_core_bin --lcov --output-path lcov.info fi popd -tools/check-coverage rust/coverage.stats rust/coverage.info +tools/check-coverage rust/coverage.stats rust/lcov.info From 11bd2fbaae2115277d3783bf32ef0a690559fb5b Mon Sep 17 00:00:00 2001 From: gupnik Date: Mon, 9 Jun 2025 12:04:02 +0530 Subject: [PATCH 36/72] Adds flutter bindings (#4412) * Adds flutter bindings * Adds sample and CI * Adds tests * Add env * fix copy step --- .github/workflows/flutter-ci.yml | 70 +++ CMakeLists.txt | 5 + cmake/StandardSettings.cmake | 6 +- flutter/.gitignore | 5 + flutter/CHANGELOG.md | 3 + flutter/README.md | 24 + flutter/analysis_options.yaml | 30 ++ flutter/bin/flutter.dart | 139 ++++++ flutter/config.yaml | 7 + flutter/include | 1 + flutter/lib/wallet_core.dart | 31 ++ flutter/pubspec.lock | 469 ++++++++++++++++++ flutter/pubspec.yaml | 18 + .../test/coin_address_derivation_test.dart | 63 +++ flutter/test/tw_string_test.dart | 21 + tools/flutter-build | 31 ++ trezor-crypto/CMakeLists.txt | 2 +- 17 files changed, 922 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/flutter-ci.yml create mode 100644 flutter/.gitignore create mode 100644 flutter/CHANGELOG.md create mode 100644 flutter/README.md create mode 100644 flutter/analysis_options.yaml create mode 100644 flutter/bin/flutter.dart create mode 100644 flutter/config.yaml create mode 120000 flutter/include create mode 100644 flutter/lib/wallet_core.dart create mode 100644 flutter/pubspec.lock create mode 100644 flutter/pubspec.yaml create mode 100644 flutter/test/coin_address_derivation_test.dart create mode 100644 flutter/test/tw_string_test.dart create mode 100755 tools/flutter-build diff --git a/.github/workflows/flutter-ci.yml b/.github/workflows/flutter-ci.yml new file mode 100644 index 00000000000..e044e2d7b77 --- /dev/null +++ b/.github/workflows/flutter-ci.yml @@ -0,0 +1,70 @@ +name: Flutter CI + +on: + push: + branches: [ dev, master ] + pull_request: + branches: [ dev, master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-24.04 + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v3 + - name: Install system dependencies + run: | + tools/install-sys-dependencies-linux + tools/install-rust-dependencies + - name: Cache internal dependencies + id: internal_cache + uses: actions/cache@v3 + with: + path: build/local + key: ${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('tools/install-sys-dependencies-linux') }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} + - name: Install internal dependencies + run: | + tools/install-dependencies + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + if: steps.internal_cache.outputs.cache-hit != 'true' + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Code generation + run: | + tools/generate-files native + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + + - name: Setup Dart + uses: dart-lang/setup-dart@v1 + with: + sdk: '3.8.1' + cache: true + cache-key: dart-3.8.1 + cache-path: ${{ github.workspace }}/.pub-cache + + - name: Install Dart dependencies + run: | + cd flutter + dart pub get + dart pub upgrade + + - name: Flutter build + run: | + tools/flutter-build + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + diff --git a/CMakeLists.txt b/CMakeLists.txt index 6fec5513326..34a75490016 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,6 +91,11 @@ elseif (${TW_COMPILE_JAVA}) find_package(JNI REQUIRED) target_include_directories(TrustWalletCore PRIVATE ${JNI_INCLUDE_DIRS}) target_link_libraries(TrustWalletCore PUBLIC ${WALLET_CORE_BINDGEN} ${PROJECT_NAME}_INTERFACE PRIVATE TrezorCrypto protobuf Boost::boost) +elseif (${FLUTTER}) + message("Configuring for Flutter") + file(GLOB_RECURSE sources src/*.c src/*.cc src/*.cpp src/*.h) + add_library(TrustWalletCore SHARED ${sources} ${PROTO_SRCS} ${PROTO_HDRS}) + target_link_libraries(TrustWalletCore PUBLIC ${WALLET_CORE_BINDGEN} ${PROJECT_NAME}_INTERFACE PRIVATE TrezorCrypto protobuf Boost::boost) else () message("Configuring standalone") file(GLOB_RECURSE sources src/*.c src/*.cc src/*.cpp src/*.h) diff --git a/cmake/StandardSettings.cmake b/cmake/StandardSettings.cmake index bef5a651b52..14a781a120d 100644 --- a/cmake/StandardSettings.cmake +++ b/cmake/StandardSettings.cmake @@ -1,7 +1,9 @@ # # Default settings # -set(CMAKE_CXX_VISIBILITY_PRESET hidden) +if (NOT FLUTTER) + set(CMAKE_CXX_VISIBILITY_PRESET hidden) +endif () set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_EXPORT_COMPILE_COMMANDS 1) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version" FORCE) @@ -66,7 +68,7 @@ endif () option(TW_UNIT_TESTS "Enable the unit tests of the project" ON) option(TW_BUILD_EXAMPLES "Enable the examples builds of the project" ON) -if (ANDROID OR IOS_PLATFORM OR TW_COMPILE_WASM OR TW_COMPILE_JAVA) +if (ANDROID OR IOS_PLATFORM OR TW_COMPILE_WASM OR TW_COMPILE_JAVA OR FLUTTER) set(TW_UNIT_TESTS OFF) set(TW_BUILD_EXAMPLES OFF) endif() diff --git a/flutter/.gitignore b/flutter/.gitignore new file mode 100644 index 00000000000..c21255d8683 --- /dev/null +++ b/flutter/.gitignore @@ -0,0 +1,5 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ +*.dylib +wallet_core_bindings.dart diff --git a/flutter/CHANGELOG.md b/flutter/CHANGELOG.md new file mode 100644 index 00000000000..effe43c82c8 --- /dev/null +++ b/flutter/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/flutter/README.md b/flutter/README.md new file mode 100644 index 00000000000..60f642dfe32 --- /dev/null +++ b/flutter/README.md @@ -0,0 +1,24 @@ +Wallet Core Bindings for Flutter + +## Installation + +1. Install Dart SDK: + - Visit [Dart SDK installation page](https://dart.dev/get-dart) + - Follow the instructions for your operating system + +2. Install dependencies: + ```bash + dart pub get + ``` + +## Usage + +### Running the App +```bash +dart run +``` + +### Test +```bash +dart test +``` diff --git a/flutter/analysis_options.yaml b/flutter/analysis_options.yaml new file mode 100644 index 00000000000..dee8927aafe --- /dev/null +++ b/flutter/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/flutter/bin/flutter.dart b/flutter/bin/flutter.dart new file mode 100644 index 00000000000..deac69f6fe1 --- /dev/null +++ b/flutter/bin/flutter.dart @@ -0,0 +1,139 @@ +import 'package:flutter/wallet_core.dart'; +import 'package:flutter/wallet_core_bindings.dart'; + +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +void main(List arguments) { + final walletCore = load(); + + final passwordTwString = walletCore.TWStringCreateWithUTF8Bytes( + "".toNativeUtf8().cast(), + ); + + print("Creating a new HD wallet ... "); + final walletNew = walletCore.TWHDWalletCreate(128, passwordTwString); + print("done."); + print( + "Secret mnemonic for new wallet: '${walletCore.TWStringUTF8Bytes(walletCore.TWHDWalletMnemonic(walletNew)).cast().toDartString()}'.", + ); + walletCore.TWHDWalletDelete(walletNew); + + // Alternative: Import wallet with existing recovery phrase (mnemonic) + print("Importing an HD wallet from earlier ... "); + final secretMnemonic = walletCore.TWStringCreateWithUTF8Bytes( + "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal" + .toNativeUtf8() + .cast(), + ); + final walletImp = walletCore.TWHDWalletCreateWithMnemonic( + secretMnemonic, + walletCore.TWStringCreateWithUTF8Bytes("".toNativeUtf8().cast()), + ); + walletCore.TWStringDelete(secretMnemonic); + print("done."); + print( + "Secret mnemonic for imported wallet: '${walletCore.TWStringUTF8Bytes(walletCore.TWHDWalletMnemonic(walletImp)).cast().toDartString()}'.", + ); + + // coin type: we use Ethereum + const coinType = TWCoinType.TWCoinTypeEthereum; + print( + "Working with coin: ${walletCore.TWStringUTF8Bytes(walletCore.TWCoinTypeConfigurationGetName(coinType)).cast().toDartString()} ${walletCore.TWStringUTF8Bytes(walletCore.TWCoinTypeConfigurationGetSymbol(coinType)).cast().toDartString()}", + ); + + // Derive default address + print("Obtaining default address ... "); + final address = walletCore.TWStringUTF8Bytes( + walletCore.TWHDWalletGetAddressForCoin(walletImp, coinType), + ).cast().toDartString(); + print("done."); + print("Default address: '$address'"); + + // Alternative: Derive address using default derivation path + // Done in 2 steps: derive private key, then address from private key + // Note that private key is passed around between the two calls by the wallet -- be always cautious when handling secrets, avoid the risk of leaking secrets + print( + "Default derivation path: ${walletCore.TWStringUTF8Bytes(walletCore.TWCoinTypeDerivationPath(coinType)).cast().toDartString()}", + ); + final secretPrivateKeyDefault = walletCore.TWHDWalletGetKeyForCoin( + walletImp, + coinType, + ); + final addressDefault = walletCore.TWStringUTF8Bytes( + walletCore.TWCoinTypeDeriveAddress(coinType, secretPrivateKeyDefault), + ).cast().toDartString(); + print("Address from default key: '$addressDefault'"); + + // Alternative: Derive address using custom derivation path + final customDerivationPath = walletCore.TWStringCreateWithUTF8Bytes( + "m/44'/60'/1'/0/0".toNativeUtf8().cast(), + ); + final secretPrivateKeyCustom = walletCore.TWHDWalletGetKey( + walletImp, + coinType, + customDerivationPath, + ); + walletCore.TWStringDelete(customDerivationPath); + final addressCustom = walletCore.TWStringUTF8Bytes( + walletCore.TWCoinTypeDeriveAddress(coinType, secretPrivateKeyCustom), + ).cast().toDartString(); + print("Custom-derived address: '$addressCustom'"); + print(""); + + print( + "RECEIVE funds: Perform send from somewhere else to this address: $address", + ); + print(""); + + // Steps for sending: + // 1. put together a send message (contains sender and receiver address, amount, gas price, etc.) + // 2. sign this message + // 3. broadcast this message to the P2P network -- not done in this sample + print("SEND funds:"); + const dummyReceiverAddress = "0xC37054b3b48C3317082E7ba872d7753D13da4986"; + final secretPrivKey = walletCore.TWPrivateKeyData(secretPrivateKeyDefault); + + print("preparing transaction (using AnySigner) ... "); + const chainIdB64 = "AQ=="; // base64(parse_hex("01")) + const gasPriceB64 = + "1pOkAA=="; // base64(parse_hex("d693a4")) decimal 3600000000 + const gasLimitB64 = "Ugg="; // base64(parse_hex("5208")) decimal 21000 + const amountB64 = + "A0i8paFgAA=="; // base64(parse_hex("0348bca5a160")) 924400000000000 + final transaction = + "{" + "\"chainId\":\"$chainIdB64" + "\",\"gasPrice\":\"$gasPriceB64" + "\",\"gasLimit\":\"$gasLimitB64" + "\",\"toAddress\":\"$dummyReceiverAddress" + "\",\"transaction\":{\"transfer\":{\"amount\":\"$amountB64" + "\"}}}"; + print("transaction: $transaction"); + + print("signing transaction ... "); + final json = walletCore.TWStringCreateWithUTF8Bytes( + transaction.toNativeUtf8().cast(), + ); + final result = walletCore.TWAnySignerSignJSON( + json, + secretPrivKey, + TWCoinType.TWCoinTypeEthereum, + ); + final signedTransaction = walletCore.TWStringUTF8Bytes( + result, + ).cast().toDartString(); + print("done"); + print( + "Signed transaction data (to be broadcast to network): (len ${signedTransaction.length}) '$signedTransaction'", + ); + // see e.g. https://github.com/flightwallet/decode-eth-tx for checking binary output content + print(""); + walletCore.TWStringDelete(json); + walletCore.TWStringDelete(result); + + print("Bye!"); + walletCore.TWHDWalletDelete(walletImp); + + walletCore.TWStringDelete(passwordTwString); +} diff --git a/flutter/config.yaml b/flutter/config.yaml new file mode 100644 index 00000000000..49f89948405 --- /dev/null +++ b/flutter/config.yaml @@ -0,0 +1,7 @@ +output: 'lib/wallet_core_bindings.dart' +headers: + entry-points: + - 'include/**.h' +name: 'WalletCore' +description: 'Bindings to WalletCore' +silence-enum-warning: true diff --git a/flutter/include b/flutter/include new file mode 120000 index 00000000000..f5030fe8899 --- /dev/null +++ b/flutter/include @@ -0,0 +1 @@ +../include \ No newline at end of file diff --git a/flutter/lib/wallet_core.dart b/flutter/lib/wallet_core.dart new file mode 100644 index 00000000000..e4f25a6bbf3 --- /dev/null +++ b/flutter/lib/wallet_core.dart @@ -0,0 +1,31 @@ +import 'dart:ffi'; +import 'dart:io'; +import 'package:path/path.dart' as path; + +import 'package:flutter/wallet_core_bindings.dart'; + +WalletCore load() { + var libraryPath = path.join( + Directory.current.path, + 'lib', + 'libTrustWalletCore.so', + ); + + if (Platform.isMacOS) { + libraryPath = path.join( + Directory.current.path, + 'lib', + 'libTrustWalletCore.dylib', + ); + } + + if (Platform.isWindows) { + libraryPath = path.join( + Directory.current.path, + 'lib', + 'libTrustWalletCore.dll', + ); + } + + return WalletCore(DynamicLibrary.open(libraryPath)); +} diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock new file mode 100644 index 00000000000..6a3de8ad0dd --- /dev/null +++ b/flutter/pubspec.lock @@ -0,0 +1,469 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f + url: "https://pub.dev/" + source: hosted + version: "82.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" + url: "https://pub.dev/" + source: hosted + version: "7.4.5" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev/" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev/" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev/" + source: hosted + version: "2.1.2" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev/" + source: hosted + version: "2.0.3" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev/" + source: hosted + version: "0.2.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev/" + source: hosted + version: "0.4.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev/" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev/" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "4b8701e48a58f7712492c9b1f7ba0bb9d525644dd66d023b62e1fc8cdb560c8a" + url: "https://pub.dev/" + source: hosted + version: "1.14.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev/" + source: hosted + version: "3.0.6" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" + url: "https://pub.dev/" + source: hosted + version: "3.1.0" + ffi: + dependency: "direct main" + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev/" + source: hosted + version: "2.1.4" + ffigen: + dependency: "direct dev" + description: + name: ffigen + sha256: cb3edbfb68ac5283102a2deb7057913d3a1fb16552dacda0c07eb144497e4891 + url: "https://pub.dev/" + source: hosted + version: "19.0.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev/" + source: hosted + version: "7.0.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev/" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev/" + source: hosted + version: "2.1.3" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev/" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev/" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev/" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev/" + source: hosted + version: "0.7.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev/" + source: hosted + version: "4.9.0" + lints: + dependency: "direct dev" + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev/" + source: hosted + version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev/" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev/" + source: hosted + version: "0.12.17" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev/" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev/" + source: hosted + version: "2.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev/" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev/" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev/" + source: hosted + version: "1.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev/" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev/" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev/" + source: hosted + version: "1.5.0" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://pub.dev/" + source: hosted + version: "3.2.2" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev/" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev/" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev/" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev/" + source: hosted + version: "3.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev/" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev/" + source: hosted + version: "0.10.13" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev/" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev/" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev/" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev/" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev/" + source: hosted + version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + url: "https://pub.dev/" + source: hosted + version: "1.26.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.dev/" + source: hosted + version: "0.7.6" + test_core: + dependency: transitive + description: + name: test_core + sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + url: "https://pub.dev/" + source: hosted + version: "0.6.11" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev/" + source: hosted + version: "1.4.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "6f82e9ee8e7339f5d8b699317f6f3afc17c80a68ebef1bc0d6f52a678c14b1e6" + url: "https://pub.dev/" + source: hosted + version: "15.0.1" + watcher: + dependency: transitive + description: + name: watcher + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + url: "https://pub.dev/" + source: hosted + version: "1.1.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev/" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev/" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev/" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev/" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev/" + source: hosted + version: "3.1.3" + yaml_edit: + dependency: transitive + description: + name: yaml_edit + sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5 + url: "https://pub.dev/" + source: hosted + version: "2.2.2" +sdks: + dart: ">=3.8.1 <4.0.0" diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml new file mode 100644 index 00000000000..b0ca629c361 --- /dev/null +++ b/flutter/pubspec.yaml @@ -0,0 +1,18 @@ +name: flutter +description: A sample command-line application. +version: 1.0.0 +# repository: https://github.com/my_org/my_repo + +environment: + sdk: ^3.8.1 + +# Add regular dependencies here. +dependencies: + ffi: ^2.1.4 + path: ^1.9.1 + # path: ^1.8.0 + +dev_dependencies: + ffigen: ^19.0.0 + lints: ^5.0.0 + test: ^1.24.0 diff --git a/flutter/test/coin_address_derivation_test.dart b/flutter/test/coin_address_derivation_test.dart new file mode 100644 index 00000000000..88e5ac20d99 --- /dev/null +++ b/flutter/test/coin_address_derivation_test.dart @@ -0,0 +1,63 @@ +import 'package:flutter/wallet_core.dart'; +import 'package:flutter/wallet_core_bindings.dart'; +import 'package:test/test.dart'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +void main() { + test('derive addresses from phrase', () { + final walletCore = load(); + final mnemonic = + 'shoot island position soft burden budget tooth cruel issue economy destroy above'; + + final mnemonicTwString = walletCore.TWStringCreateWithUTF8Bytes( + mnemonic.toNativeUtf8().cast(), + ); + final passwordTwString = walletCore.TWStringCreateWithUTF8Bytes( + "".toNativeUtf8().cast(), + ); + + final wallet = walletCore.TWHDWalletCreateWithMnemonic( + mnemonicTwString, + passwordTwString, + ); + + // Test a few key coins + final coinsAndAddresses = [ + ( + TWCoinType.TWCoinTypeBitcoin, + 'bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d', + ), + ( + TWCoinType.TWCoinTypeEthereum, + '0x8f348F300873Fd5DA36950B2aC75a26584584feE', + ), + ( + TWCoinType.TWCoinTypeBinance, + 'bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw', + ), + ( + TWCoinType.TWCoinTypeCosmos, + 'cosmos142j9u5eaduzd7faumygud6ruhdwme98qsy2ekn', + ), + ]; + + for (final coinAndAddress in coinsAndAddresses) { + final coin = coinAndAddress.$1; + final expectedAddress = coinAndAddress.$2; + + final address = walletCore.TWHDWalletGetAddressForCoin(wallet, coin); + final addressStr = walletCore.TWStringUTF8Bytes( + address, + ).cast().toDartString(); + + expect(addressStr, expectedAddress); + + walletCore.TWStringDelete(address); + } + + walletCore.TWHDWalletDelete(wallet); + walletCore.TWStringDelete(mnemonicTwString); + walletCore.TWStringDelete(passwordTwString); + }); +} diff --git a/flutter/test/tw_string_test.dart b/flutter/test/tw_string_test.dart new file mode 100644 index 00000000000..79a84a2976a --- /dev/null +++ b/flutter/test/tw_string_test.dart @@ -0,0 +1,21 @@ +import 'package:flutter/wallet_core.dart'; +import 'package:test/test.dart'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +void main() { + test('twstring', () { + final walletCore = load(); + + final twString = walletCore.TWStringCreateWithUTF8Bytes( + 'Hello, World!'.toNativeUtf8().cast(), + ); + final bytes = walletCore.TWStringUTF8Bytes(twString); + expect(bytes.cast().toDartString(), 'Hello, World!'); + + final size = walletCore.TWStringSize(twString); + expect(size, 13); + + walletCore.TWStringDelete(twString); + }); +} diff --git a/tools/flutter-build b/tools/flutter-build new file mode 100755 index 00000000000..fee0babbb85 --- /dev/null +++ b/tools/flutter-build @@ -0,0 +1,31 @@ +#!/bin/bash + +set -e + +mkdir -p build + +pushd build +cmake .. -DFLUTTER=ON +make -j12 +popd + +if [ "$(uname -s)" == "Darwin" ]; then + cp build/libTrustWalletCore.dylib flutter/lib/ +elif [ "$(uname -s)" == "Linux" ]; then + cp build/libTrustWalletCore.so flutter/lib/ +elif [ "$(uname -s)" == "Windows" ]; then + cp build/libTrustWalletCore.dll flutter/lib/ +fi + +pushd flutter + +echo "Generating bindings..." +dart run ffigen --config config.yaml + + +echo "Verifying the build..." +dart run + +echo "Done" + +popd diff --git a/trezor-crypto/CMakeLists.txt b/trezor-crypto/CMakeLists.txt index a3ef46a38f2..414c6a618e2 100644 --- a/trezor-crypto/CMakeLists.txt +++ b/trezor-crypto/CMakeLists.txt @@ -65,7 +65,7 @@ if (EMSCRIPTEN) message(STATUS "Skip building trezor-crypto/tests") set(TW_WARNING_FLAGS ${TW_WARNING_FLAGS} -Wno-bitwise-instead-of-logical) else () - if(NOT ANDROID AND NOT IOS_PLATFORM AND NOT TW_COMPILE_JAVA) + if(NOT ANDROID AND NOT IOS_PLATFORM AND NOT TW_COMPILE_JAVA AND NOT FLUTTER) add_subdirectory(crypto/tests) endif() endif() From 9693d83de9f00aa70eb87b601a57d6e2dbf9944a Mon Sep 17 00:00:00 2001 From: gupnik Date: Mon, 9 Jun 2025 13:14:58 +0530 Subject: [PATCH 37/72] [TON]: Fixes issue with large transfers (#4413) * [TON]: Fixes issue with large transfers * Removes lock change --------- Co-authored-by: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> --- .../TestTheOpenNetworkSigner.kt | 10 ++--- .../tw_ton/src/signing_request/builder.rs | 18 +++++++-- rust/tw_tests/tests/chains/ton/ton_compile.rs | 5 ++- rust/tw_tests/tests/chains/ton/ton_sign.rs | 33 +++++++++-------- .../tests/chains/ton/ton_sign_wallet_v5r1.rs | 37 ++++++++++--------- src/proto/TheOpenNetwork.proto | 9 +++-- .../Blockchains/TheOpenNetworkTests.swift | 10 ++--- .../TheOpenNetwork/TWAnySignerTests.cpp | 11 +++++- wasm/tests/Blockchain/TheOpenNetwork.test.ts | 8 ++-- 9 files changed, 83 insertions(+), 58 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt index 69dd91abada..635d615734a 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt @@ -26,7 +26,7 @@ class TestTheOpenNetworkSigner { val transfer = TheOpenNetwork.Transfer.newBuilder() .setDest("EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") - .setAmount(10) + .setAmount(ByteString.copyFrom("0A".toHexByteArray())) // 10 .setMode(TheOpenNetwork.SendMode.PAY_FEES_SEPARATELY_VALUE or TheOpenNetwork.SendMode.IGNORE_ACTION_PHASE_ERRORS_VALUE) .setBounceable(true) .build() @@ -52,15 +52,15 @@ class TestTheOpenNetworkSigner { val privateKey = PrivateKey("c054900a527538c1b4325688a421c0469b171c29f23a62da216e90b0df2412ee".toHexByteArray()) val jettonTransfer = TheOpenNetwork.JettonTransfer.newBuilder() - .setJettonAmount(500 * 1000 * 1000) + .setJettonAmount(ByteString.copyFrom("1DCD6500".toHexByteArray())) // 500 * 1000 * 1000 .setToOwner("EQAFwMs5ha8OgZ9M4hQr80z9NkE7rGxUpE1hCFndiY6JnDx8") .setResponseAddress("EQBaKIMq5Am2p_rfR1IFTwsNWHxBkOpLTmwUain5Fj4llTXk") - .setForwardAmount(1) + .setForwardAmount(ByteString.copyFrom("01".toHexByteArray())) // 1 .build() val transfer = TheOpenNetwork.Transfer.newBuilder() .setDest("EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja") - .setAmount(100 * 1000 * 1000) + .setAmount(ByteString.copyFrom("05F5E100".toHexByteArray())) // 100 * 1000 * 1000 .setMode(TheOpenNetwork.SendMode.PAY_FEES_SEPARATELY_VALUE or TheOpenNetwork.SendMode.IGNORE_ACTION_PHASE_ERRORS_VALUE) .setComment("test comment") .setBounceable(true) @@ -100,7 +100,7 @@ class TestTheOpenNetworkSigner { val transfer = TheOpenNetwork.Transfer.newBuilder() .setDest(dogeChatbotDeployingAddress) // 0.069 TON - .setAmount(69_000_000) + .setAmount(ByteString.copyFrom("041CDB40".toHexByteArray())) // 69_000_000 .setMode(TheOpenNetwork.SendMode.PAY_FEES_SEPARATELY_VALUE or TheOpenNetwork.SendMode.IGNORE_ACTION_PHASE_ERRORS_VALUE) .setBounceable(false) .setStateInit(dogeChatbotStateInit) diff --git a/rust/chains/tw_ton/src/signing_request/builder.rs b/rust/chains/tw_ton/src/signing_request/builder.rs index 167fd290895..59acc26baf3 100644 --- a/rust/chains/tw_ton/src/signing_request/builder.rs +++ b/rust/chains/tw_ton/src/signing_request/builder.rs @@ -116,9 +116,13 @@ impl SigningRequestBuilder { PayloadType::None => None, }; + let ton_amount = U256::from_big_endian_slice(input.amount.as_ref()) + .tw_err(SigningErrorType::Error_invalid_params) + .context("Invalid 'amount'")?; + Ok(TransferRequest { dest, - ton_amount: U256::from(input.amount), + ton_amount, mode, comment, state_init, @@ -141,13 +145,21 @@ impl SigningRequestBuilder { Some(input.custom_payload.to_string()) }; + let jetton_amount = U256::from_big_endian_slice(input.jetton_amount.as_ref()) + .tw_err(SigningErrorType::Error_invalid_params) + .context("Invalid 'jetton_amount'")?; + + let forward_ton_amount = U256::from_big_endian_slice(input.forward_amount.as_ref()) + .tw_err(SigningErrorType::Error_invalid_params) + .context("Invalid 'forward_amount'")?; + let jetton_payload = JettonTransferRequest { query_id: input.query_id, - jetton_amount: U256::from(input.jetton_amount), + jetton_amount, dest, response_address, custom_payload, - forward_ton_amount: U256::from(input.forward_amount), + forward_ton_amount, }; Ok(TransferPayload::JettonTransfer(jetton_payload)) diff --git a/rust/tw_tests/tests/chains/ton/ton_compile.rs b/rust/tw_tests/tests/chains/ton/ton_compile.rs index b13d6fc472e..110edf7f86d 100644 --- a/rust/tw_tests/tests/chains/ton/ton_compile.rs +++ b/rust/tw_tests/tests/chains/ton/ton_compile.rs @@ -6,6 +6,7 @@ use crate::chains::ton::ton_sign::assert_eq_boc; use tw_any_coin::test_utils::sign_utils::{CompilerHelper, PreImageHelper}; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_number::U256; use tw_proto::Common::Proto::SigningError; use tw_proto::TheOpenNetwork::Proto; use tw_proto::TxCompiler::Proto as CompilerProto; @@ -16,7 +17,7 @@ fn test_ton_compile_wallet_v4r2_transfer_and_deploy() { let transfer = Proto::Transfer { dest: "EQDYW_1eScJVxtitoBRksvoV9cCYo4uKGWLVNIHB1JqRR3n0".into(), - amount: 10, + amount: U256::encode_be_compact(10), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -63,7 +64,7 @@ fn test_ton_compile_wallet_v5r1_transfer_and_deploy() { let transfer = Proto::Transfer { dest: "EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu".into(), - amount: 10, + amount: U256::encode_be_compact(10), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, diff --git a/rust/tw_tests/tests/chains/ton/ton_sign.rs b/rust/tw_tests/tests/chains/ton/ton_sign.rs index 7b5e3024755..00aafeef940 100644 --- a/rust/tw_tests/tests/chains/ton/ton_sign.rs +++ b/rust/tw_tests/tests/chains/ton/ton_sign.rs @@ -8,6 +8,7 @@ use crate::chains::ton::cell_example::{ use tw_any_coin::test_utils::sign_utils::AnySignerHelper; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_number::U256; use tw_proto::Common::Proto::SigningError; use tw_proto::TheOpenNetwork::Proto; use tw_proto::TheOpenNetwork::Proto::mod_Transfer::OneOfpayload as PayloadType; @@ -30,7 +31,7 @@ fn test_ton_sign_transfer_and_deploy() { let transfer = Proto::Transfer { dest: "EQDYW_1eScJVxtitoBRksvoV9cCYo4uKGWLVNIHB1JqRR3n0".into(), - amount: 10, + amount: U256::encode_be_compact(10), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -65,7 +66,7 @@ fn test_ton_sign_transfer_and_deploy_4b1d9f() { let transfer = Proto::Transfer { dest: "UQA6whN_oU5h9jPljnlDSWRYQNDPkLaUqqaEWULNB_Zoykuu".into(), // 0.0001 TON - amount: 100_000, + amount: U256::encode_be_compact(100_000), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -98,7 +99,7 @@ fn test_ton_sign_transfer_ordinary() { let transfer = Proto::Transfer { dest: "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q".into(), - amount: 10, + amount: U256::encode_be_compact(10), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -132,7 +133,7 @@ fn test_ton_sign_transfer_all_balance() { let transfer = Proto::Transfer { dest: "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q".into(), - amount: 0, + amount: U256::encode_be_compact(0), mode: Proto::SendMode::ATTACH_ALL_CONTRACT_BALANCE as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -166,7 +167,7 @@ fn test_ton_sign_transfer_all_balance_non_bounceable() { let transfer = Proto::Transfer { dest: "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV".into(), - amount: 0, + amount: U256::encode_be_compact(0), mode: Proto::SendMode::ATTACH_ALL_CONTRACT_BALANCE as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: false, @@ -200,7 +201,7 @@ fn test_ton_sign_transfer_with_ascii_comment() { let transfer = Proto::Transfer { dest: "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q".into(), - amount: 10, + amount: U256::encode_be_compact(10), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -235,7 +236,7 @@ fn test_ton_sign_transfer_with_utf8_comment() { let transfer = Proto::Transfer { dest: "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q".into(), - amount: 10, + amount: U256::encode_be_compact(10), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -270,7 +271,7 @@ fn test_ton_sign_transfer_invalid_wallet_version() { let transfer = Proto::Transfer { dest: "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q".into(), - amount: 10, + amount: U256::encode_be_compact(10), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -298,17 +299,17 @@ fn test_ton_sign_transfer_jettons() { let jetton_transfer = Proto::JettonTransfer { query_id: 69, // Transfer 1 testtwt (decimal precision is 9). - jetton_amount: 1000 * 1000 * 1000, + jetton_amount: U256::encode_be_compact(1000 * 1000 * 1000), to_owner: "EQAFwMs5ha8OgZ9M4hQr80z9NkE7rGxUpE1hCFndiY6JnDx8".into(), // Send unused toncoins back to sender. response_address: "EQBaKIMq5Am2p_rfR1IFTwsNWHxBkOpLTmwUain5Fj4llTXk".into(), - forward_amount: 1, + forward_amount: U256::encode_be_compact(1), ..Proto::JettonTransfer::default() }; let transfer = Proto::Transfer { dest: "EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja".into(), - amount: 100 * 1000 * 1000, + amount: U256::encode_be_compact(100 * 1000 * 1000), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -344,17 +345,17 @@ fn test_ton_sign_transfer_jettons_with_comment() { let jetton_transfer = Proto::JettonTransfer { query_id: 0, // Transfer 0.5 testtwt (decimal precision is 9). - jetton_amount: 500 * 1000 * 1000, + jetton_amount: U256::encode_be_compact(500 * 1000 * 1000), to_owner: "EQAFwMs5ha8OgZ9M4hQr80z9NkE7rGxUpE1hCFndiY6JnDx8".into(), // Send unused toncoins back to sender. response_address: "EQBaKIMq5Am2p_rfR1IFTwsNWHxBkOpLTmwUain5Fj4llTXk".into(), - forward_amount: 1, + forward_amount: U256::encode_be_compact(1), ..Proto::JettonTransfer::default() }; let transfer = Proto::Transfer { dest: "EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja".into(), - amount: 100 * 1000 * 1000, + amount: U256::encode_be_compact(100 * 1000 * 1000), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -392,7 +393,7 @@ fn test_ton_sign_transfer_custom_payload() { let transfer = Proto::Transfer { dest: "UQA6whN_oU5h9jPljnlDSWRYQNDPkLaUqqaEWULNB_Zoykuu".into(), // 0.00025 TON - amount: 250_000, + amount: U256::encode_be_compact(250_000), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -443,7 +444,7 @@ fn test_ton_sign_transfer_custom_payload_with_state_init() { let transfer = Proto::Transfer { dest: doge_contract_address.into(), // 0.069 TON - amount: 69_000_000, + amount: U256::encode_be_compact(69_000_000), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: false, diff --git a/rust/tw_tests/tests/chains/ton/ton_sign_wallet_v5r1.rs b/rust/tw_tests/tests/chains/ton/ton_sign_wallet_v5r1.rs index f6c8a5ec3eb..e45dec25ea6 100644 --- a/rust/tw_tests/tests/chains/ton/ton_sign_wallet_v5r1.rs +++ b/rust/tw_tests/tests/chains/ton/ton_sign_wallet_v5r1.rs @@ -9,6 +9,7 @@ use crate::chains::ton::ton_sign::assert_eq_boc; use tw_any_coin::test_utils::sign_utils::AnySignerHelper; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_number::U256; use tw_proto::Common::Proto::SigningError; use tw_proto::TheOpenNetwork::Proto; use tw_proto::TheOpenNetwork::Proto::mod_Transfer::OneOfpayload as PayloadType; @@ -19,7 +20,7 @@ fn test_ton_sign_wallet_v5r1_transfer_and_deploy() { let transfer = Proto::Transfer { dest: "EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu".into(), - amount: 10, + amount: U256::encode_be_compact(10), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -52,7 +53,7 @@ fn test_ton_sign_wallet_v5r1_transfer_ordinary() { let transfer = Proto::Transfer { dest: "EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu".into(), - amount: 10, + amount: U256::encode_be_compact(10), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -87,7 +88,7 @@ fn test_ton_sign_wallet_v5r1_transfer_all_balance() { let transfer = Proto::Transfer { dest: "UQAU3o5-Sp1MYRpw3U7b_wmARxqI49LxiFhEoVCxpUKjTYXk".into(), - amount: 0, + amount: U256::encode_be_compact(0), mode: Proto::SendMode::ATTACH_ALL_CONTRACT_BALANCE as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -121,7 +122,7 @@ fn test_ton_sign_wallet_v5r1_transfer_all_balance_non_bounceable() { let transfer = Proto::Transfer { dest: "UQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQa2r".into(), - amount: 0, + amount: U256::encode_be_compact(0), mode: Proto::SendMode::ATTACH_ALL_CONTRACT_BALANCE as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: false, @@ -155,7 +156,7 @@ fn test_ton_sign_wallet_v5r1_transfer_with_ascii_comment() { let transfer = Proto::Transfer { dest: "EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu".into(), - amount: 10, + amount: U256::encode_be_compact(10), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -190,7 +191,7 @@ fn test_ton_sign_wallet_v5r1_transfer_with_utf8_comment() { let transfer = Proto::Transfer { dest: "EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu".into(), - amount: 10, + amount: U256::encode_be_compact(10), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -226,17 +227,17 @@ fn test_ton_sign_wallet_v5r1_transfer_jettons() { let jetton_transfer = Proto::JettonTransfer { query_id: 69, // Transfer 0.12 USDT (decimal precision is 6). - jetton_amount: 120000, + jetton_amount: U256::encode_be_compact(120000), to_owner: "UQAU3o5-Sp1MYRpw3U7b_wmARxqI49LxiFhEoVCxpUKjTYXk".into(), // Send unused toncoins back to sender. response_address: "UQCh41gQP1A4I0lnAn6yAfitDAIYpXG6UFIXqeSz1TVxNOJ_".into(), - forward_amount: 1, + forward_amount: U256::encode_be_compact(1), ..Default::default() }; let transfer = Proto::Transfer { dest: "EQDg4AjfaxQBVsUFueenkKlHLhhYWrcBvCEzbEgfrT0nxuGC".into(), - amount: 100 * 1000 * 1000, + amount: U256::encode_be_compact(100 * 1000 * 1000), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -272,17 +273,17 @@ fn test_ton_sign_wallet_v5r1_transfer_jettons_with_comment() { let jetton_transfer = Proto::JettonTransfer { query_id: 0, // Transfer 0.11 USDT (decimal precision is 6). - jetton_amount: 110000, + jetton_amount: U256::encode_be_compact(110000), to_owner: "UQAU3o5-Sp1MYRpw3U7b_wmARxqI49LxiFhEoVCxpUKjTYXk".into(), // Send unused toncoins back to sender. response_address: "UQCh41gQP1A4I0lnAn6yAfitDAIYpXG6UFIXqeSz1TVxNOJ_".into(), - forward_amount: 1, + forward_amount: U256::encode_be_compact(1), ..Default::default() }; let transfer = Proto::Transfer { dest: "EQDg4AjfaxQBVsUFueenkKlHLhhYWrcBvCEzbEgfrT0nxuGC".into(), - amount: 100 * 1000 * 1000, + amount: U256::encode_be_compact(100 * 1000 * 1000), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -320,7 +321,7 @@ fn test_ton_sign_wallet_v5r1_transfer_custom_payload() { let transfer = Proto::Transfer { dest: "UQAU3o5-Sp1MYRpw3U7b_wmARxqI49LxiFhEoVCxpUKjTYXk".into(), // 0.00025 TON - amount: 250_000, + amount: U256::encode_be_compact(250_000), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, @@ -371,7 +372,7 @@ fn test_ton_sign_wallet_v5r1_transfer_custom_payload_with_state_init() { let transfer = Proto::Transfer { dest: doge_contract_address.into(), // 0.0069 TON - amount: 6_900_000, + amount: U256::encode_be_compact(6_900_000), mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: false, @@ -409,7 +410,7 @@ fn test_ton_sign_wallet_v5r1_missing_required_send_mode() { let transfer = Proto::Transfer { dest: "EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu".into(), - amount: 10, + amount: U256::encode_be_compact(10), // Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS is required for wallet v5r1 external messages. mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32, bounceable: true, @@ -438,17 +439,17 @@ fn test_ton_sign_wallet_v5r1_mintless_jetton() { let jetton_transfer = Proto::JettonTransfer { query_id: 1, // Transfer 0 mintless jetton to self. - jetton_amount: 0, + jetton_amount: U256::encode_be_compact(0), to_owner: "UQCh41gQP1A4I0lnAn6yAfitDAIYpXG6UFIXqeSz1TVxNOJ_".into(), // Send unused toncoins back to sender. response_address: "UQCh41gQP1A4I0lnAn6yAfitDAIYpXG6UFIXqeSz1TVxNOJ_".into(), - forward_amount: 1, + forward_amount: U256::encode_be_compact(1), custom_payload: "te6ccgECNQEABJMAAQgN9gLWAQlGA6+1FWXC4ss/wvDOFwMk2bVM97AUEWqaUhh63uWfQ26nAB4CIgWBcAIDBChIAQEZG2ZqtEYGAq27TvzHdGuGrhhKoICBU+Zg9Xq/qRMHGAAdIgEgBQYiASAHCChIAQEV0tdPcZG01smq0thhsmqf9ZzE0QqpP3c+ERvuHF1JDgAbKEgBAf3dO8qdKoPys7AWvavs1wMNWCOq5XashXaRopmksx/LABsiASAJCiIBIAsMKEgBAWP0xUs9JBrfQRl1FkF2tIfIDYpwLdf3fXqMi6BqxNtmABoiASANDihIAQFOErI5E7ld/nTAgHXdGI74UH8kxIaFyAkH42P54tEC9QAYIgEgDxAoSAEBrF16Czdlg18FB467CrR6Ucwxb8H+Z1e4qDeFWbkz1WEAFyIBIBESKEgBAXeWzg9xTFO6z0FP+axi8Njuxxp0zPrAUs4vnmt/dE3xABYoSAEBEZ7KazNpaWJoInmqO4II/AfncyhMNWxh6BE2qFU7/9wAFCIBIBMUKEgBAZleZTNXbgCF+8G08kiQeDPanQtNCVakzEU3g9GKB+K2ABQiASAVFihIAQFeCM83J7sm36g24qFeEDvStahHWn6SsEk+Wii49rzBiAASIgEgFxgoSAEBfV9jrgSeiAKVqeeLliXdoLrxFWe2HK0f4SG5h4kfb8YAESIBIBkaIgEgGxwoSAEBImHhXIbOHuOnOgE5f0KLqoXDB7/ZLQQGiHysuulUq2IAECIBIB0eKEgBAXT+qb5w1+qtvbJ1Fbn8y6IhO85YfxKIgKBga6ROO/yQAA8iASAfIChIAQGoJHXWXWRQGZdP9xIUrMowhvgnf+CwKTIIOBxlDiKgcAANKEgBAZ6tCuDr89HFRz3WwwK+wW4XmkE+O7Hf+NgUDI+uqnAJAAwiASAhIihIAQHtasTLBAw7MZHpRTsKyC47E1PZ/LAtF3n2Y2b5ThX0VgALIgEgIyQiASAlJihIAQGumGRf7UXrpK12Cuvj06565IC0Kbd4i2XoG6dnqC+uQAAJKEgBAXM19HUUkz6ns7o/2x45kQ2iLj8gl3zYhrAhISEUg0O1AAgiASAnKCIBICkqKEgBAa7kNA+lev+Z5T/xqKBbO648BvnLL6/hAp1auOiZTWRhAAcoSAEBxn19AKZGAUPYWs8pTpNQrCB4Ap0KfzyjOgB1Mc9PbIUABSIBICssKEgBAWarrCPqSS6+lq6NRcrWZ2/v6bN4b6Zd3GWAtN6j8a6BAAQiASAtLiIBIC8wKEgBAXYYqhLZ1tHg+HdKd8vLmTBsojkj61ZiafXB7pOt+hEFAAMiASAxMihIAQHt8p6qBiXtz+kKcgo13Udyh7Uo8irrdKlSSY2dOdALogAAIgFIMzQoSAEByacrlqsAKiFOlv4Rp4V1gNg2i4aVPkcHJq8Vug/89k4AAABduZA/UDgjSWcCfrIB+K0MAhilcbpQUhep5LPVNXE0Q7msoAAABm4N2AAABm9VrQgoSAEByIAktH0CNxT//QZ8Vgj68CApZON9XBKDfE0D2rY8Fx4AAA==".into() }; let transfer = Proto::Transfer { dest: "UQCn2lssMz09Gn60mBnv544DgiqIX3mK5cqGEPEPKxCNbE0E".into(), // jetton wallet address - amount: 90 * 1000 * 1000, // 0.09 TON as fee + amount: U256::encode_be_compact(90 * 1000 * 1000), // 0.09 TON as fee mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, bounceable: true, diff --git a/src/proto/TheOpenNetwork.proto b/src/proto/TheOpenNetwork.proto index 4ef1ca11ac4..afcd7154286 100644 --- a/src/proto/TheOpenNetwork.proto +++ b/src/proto/TheOpenNetwork.proto @@ -30,7 +30,8 @@ message Transfer { string dest = 1; // Amount to send in nanotons - uint64 amount = 2; + // uint128 / big endian byte order + bytes amount = 2; // Send mode (optional, 0 by default) // Learn more: https://ton.org/docs/develop/func/stdlib#send_raw_message @@ -61,7 +62,8 @@ message JettonTransfer { uint64 query_id = 1; // Amount of transferred jettons in elementary integer units. The real value transferred is jetton_amount multiplied by ten to the power of token decimal precision - uint64 jetton_amount = 2; + // uint128 / big endian byte order + bytes jetton_amount = 2; // Address of the new owner of the jettons. string to_owner = 3; @@ -70,7 +72,8 @@ message JettonTransfer { string response_address = 4; // Amount in nanotons to forward to recipient. Basically minimum amount - 1 nanoton should be used - uint64 forward_amount = 5; + // uint128 / big endian byte order + bytes forward_amount = 5; // Optional raw one-cell BoC encoded in Base64. // Can be used in the case of mintless jetton transfers. diff --git a/swift/Tests/Blockchains/TheOpenNetworkTests.swift b/swift/Tests/Blockchains/TheOpenNetworkTests.swift index 797ae8334e0..841d79e54dd 100644 --- a/swift/Tests/Blockchains/TheOpenNetworkTests.swift +++ b/swift/Tests/Blockchains/TheOpenNetworkTests.swift @@ -83,7 +83,7 @@ class TheOpenNetworkTests: XCTestCase { let transfer = TheOpenNetworkTransfer.with { $0.dest = "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q" - $0.amount = 10 + $0.amount = Data(hexString: "0A")! $0.mode = UInt32(TheOpenNetworkSendMode.payFeesSeparately.rawValue | TheOpenNetworkSendMode.ignoreActionPhaseErrors.rawValue) $0.bounceable = true } @@ -108,15 +108,15 @@ class TheOpenNetworkTests: XCTestCase { let privateKeyData = Data(hexString: "c054900a527538c1b4325688a421c0469b171c29f23a62da216e90b0df2412ee")! let jettonTransfer = TheOpenNetworkJettonTransfer.with { - $0.jettonAmount = 500 * 1000 * 1000 + $0.jettonAmount = Data(hexString: "1DCD6500")! //500 * 1000 * 1000 $0.toOwner = "EQAFwMs5ha8OgZ9M4hQr80z9NkE7rGxUpE1hCFndiY6JnDx8" $0.responseAddress = "EQBaKIMq5Am2p_rfR1IFTwsNWHxBkOpLTmwUain5Fj4llTXk" - $0.forwardAmount = 1 + $0.forwardAmount = Data(hexString: "01")!; } let transfer = TheOpenNetworkTransfer.with { $0.dest = "EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja" - $0.amount = 100 * 1000 * 1000 + $0.amount = Data(hexString: "05F5E100")! //100 * 1000 * 1000 $0.mode = UInt32(TheOpenNetworkSendMode.payFeesSeparately.rawValue | TheOpenNetworkSendMode.ignoreActionPhaseErrors.rawValue) $0.comment = "test comment" $0.bounceable = true @@ -156,7 +156,7 @@ class TheOpenNetworkTests: XCTestCase { let transfer = TheOpenNetworkTransfer.with { $0.dest = dogeChatbotDeployingAddress // 0.069 TON - $0.amount = 69_000_000 + $0.amount = Data(hexString: "041CDB40")! //69_000_000 $0.mode = UInt32(TheOpenNetworkSendMode.payFeesSeparately.rawValue | TheOpenNetworkSendMode.ignoreActionPhaseErrors.rawValue) $0.bounceable = false $0.stateInit = dogeChatbotStateInit diff --git a/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp b/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp index ffd5d276a78..76139adb715 100644 --- a/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp +++ b/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp @@ -8,6 +8,8 @@ #include #include "TestUtilities.h" +#include "uint256.h" + #include namespace TW::TheOpenNetwork::tests { @@ -17,7 +19,10 @@ TEST(TWAnySignerTheOpenNetwork, SignMessageToTransferAndDeployWalletV4R2) { auto& transfer = *input.add_messages(); transfer.set_dest("EQDYW_1eScJVxtitoBRksvoV9cCYo4uKGWLVNIHB1JqRR3n0"); - transfer.set_amount(10); + + Data amountData = store(uint256_t(10)); + std::string amountStr(amountData.begin(), amountData.end()); + transfer.set_amount(amountStr); transfer.set_mode(Proto::SendMode::PAY_FEES_SEPARATELY | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS); transfer.set_bounceable(true); @@ -42,7 +47,9 @@ TEST(TWAnySignerTheOpenNetwork, SignMessageToTransferAndDeployWalletV5R1) { auto& transfer = *input.add_messages(); transfer.set_dest("EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu"); - transfer.set_amount(10); + Data amountData = store(uint256_t(10)); + std::string amountStr(amountData.begin(), amountData.end()); + transfer.set_amount(amountStr); transfer.set_mode(Proto::SendMode::PAY_FEES_SEPARATELY | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS); transfer.set_bounceable(true); diff --git a/wasm/tests/Blockchain/TheOpenNetwork.test.ts b/wasm/tests/Blockchain/TheOpenNetwork.test.ts index b96cb551675..da55da7cfca 100644 --- a/wasm/tests/Blockchain/TheOpenNetwork.test.ts +++ b/wasm/tests/Blockchain/TheOpenNetwork.test.ts @@ -72,7 +72,7 @@ describe("TheOpenNetwork", () => { let transfer = TW.TheOpenNetwork.Proto.Transfer.create({ dest: "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q", - amount: new Long(10), + amount: Buffer.from("0A", "hex"), // 10 mode: (TW.TheOpenNetwork.Proto.SendMode.PAY_FEES_SEPARATELY | TW.TheOpenNetwork.Proto.SendMode.IGNORE_ACTION_PHASE_ERRORS), bounceable: true, }); @@ -101,15 +101,15 @@ describe("TheOpenNetwork", () => { let privateKeyData = HexCoding.decode("c054900a527538c1b4325688a421c0469b171c29f23a62da216e90b0df2412ee"); let jettonTransfer = TW.TheOpenNetwork.Proto.JettonTransfer.create({ - jettonAmount: new Long(500 * 1000 * 1000), + jettonAmount: Buffer.from("1DCD6500", "hex"), // 500 * 1000 * 1000, toOwner: "EQAFwMs5ha8OgZ9M4hQr80z9NkE7rGxUpE1hCFndiY6JnDx8", responseAddress: "EQBaKIMq5Am2p_rfR1IFTwsNWHxBkOpLTmwUain5Fj4llTXk", - forwardAmount: new Long(1) + forwardAmount: Buffer.from("01", "hex"), // 1 }); let transfer = TW.TheOpenNetwork.Proto.Transfer.create({ dest: "EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja", - amount: new Long(100 * 1000 * 1000), + amount: Buffer.from("05F5E100", "hex"), // 100 * 1000 * 1000, mode: (TW.TheOpenNetwork.Proto.SendMode.PAY_FEES_SEPARATELY | TW.TheOpenNetwork.Proto.SendMode.IGNORE_ACTION_PHASE_ERRORS), comment: "test comment", bounceable: true, From b5165068dfe86ead3793ccb9e7cc38f4179e7399 Mon Sep 17 00:00:00 2001 From: 10gic Date: Wed, 11 Jun 2025 14:34:49 +0800 Subject: [PATCH 38/72] [Solana]: Support adding instruction at specified index (#4415) * [Solana]: Support adding instruction at specified index * Rename tw_solana_transaction_add_instruction to tw_solana_transaction_insert_instruction --------- Co-authored-by: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> --- rust/chains/tw_solana/src/modules/utils.rs | 19 +- .../chains/solana/solana_transaction_ffi.rs | 243 +++++++++++------- .../src/ffi/solana/transaction.rs | 10 +- 3 files changed, 177 insertions(+), 95 deletions(-) diff --git a/rust/chains/tw_solana/src/modules/utils.rs b/rust/chains/tw_solana/src/modules/utils.rs index cadf7229766..a4bf539ab5f 100644 --- a/rust/chains/tw_solana/src/modules/utils.rs +++ b/rust/chains/tw_solana/src/modules/utils.rs @@ -175,7 +175,11 @@ impl SolanaTransaction { .tw_err(SigningErrorType::Error_internal) } - pub fn add_instruction(encoded_tx: &str, instruction: &str) -> SigningResult { + pub fn insert_instruction( + encoded_tx: &str, + insert_at: i32, + instruction: &str, + ) -> SigningResult { let tx_bytes = base64::decode(encoded_tx, STANDARD)?; let mut tx: VersionedTransaction = bincode::deserialize(&tx_bytes).map_err(|_| SigningErrorType::Error_input_parse)?; @@ -183,7 +187,18 @@ impl SolanaTransaction { let instruction: Instruction = serde_json::from_str(instruction).map_err(|_| SigningErrorType::Error_input_parse)?; - tx.message.push_instruction( + if insert_at >= 0 && insert_at as usize > tx.message.instructions().len() { + return Err(SigningError::from(SigningErrorType::Error_invalid_params)); + } + + let final_insert_at = if insert_at < 0 { + tx.message.instructions().len() // Append to the end if negative + } else { + insert_at as usize // Use the specified position + }; + + tx.message.insert_instruction( + final_insert_at, instruction.program_id, instruction.accounts, instruction.data, diff --git a/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs b/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs index 10acc347432..ceda3aec625 100644 --- a/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs +++ b/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs @@ -18,8 +18,8 @@ use tw_proto::Common::Proto::SigningError; use tw_proto::Solana::Proto::{self}; use tw_solana::SOLANA_ALPHABET; use wallet_core_rs::ffi::solana::transaction::{ - tw_solana_transaction_add_instruction, tw_solana_transaction_get_compute_unit_limit, - tw_solana_transaction_get_compute_unit_price, tw_solana_transaction_set_compute_unit_limit, + tw_solana_transaction_get_compute_unit_limit, tw_solana_transaction_get_compute_unit_price, + tw_solana_transaction_insert_instruction, tw_solana_transaction_set_compute_unit_limit, tw_solana_transaction_set_compute_unit_price, tw_solana_transaction_set_fee_payer, tw_solana_transaction_update_blockhash_and_sign, }; @@ -370,99 +370,170 @@ fn test_solana_transaction_set_fee_payer_already_exists() { } #[test] -fn test_solana_transaction_add_instruction() { - // Step 1 - Prepare a transaction. +fn test_solana_transaction_insert_instruction() { // The original transaction contains the following instruction: // Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ transfer 0.001 SOL to B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V let encoded_tx_str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3G7dwaciEKWYvJQnuUEK1pCl+Zbd0ANxw8XYKfUO2eQBAgIAAQwCAAAAQEIPAAAAAAA="; - let encoded_tx = TWStringHelper::create(encoded_tx_str); - // Step 2 - Prepare a new instruction (sender and receiver are already in the transaction): + // Add new instruction: // YUz1AupPEy1vttBeDS7sXYZFhQJppcXMzjDiDx18Srf transfer 0.003 SOL to d8DiHEeHKdXkM2ZupT86mrvavhmJwUZjHPCzMiB5Lqb let instruction_str = r#"{"programId":"11111111111111111111111111111111","accounts":[{"pubkey":"YUz1AupPEy1vttBeDS7sXYZFhQJppcXMzjDiDx18Srf","isSigner":true,"isWritable":true},{"pubkey":"d8DiHEeHKdXkM2ZupT86mrvavhmJwUZjHPCzMiB5Lqb","isSigner":false,"isWritable":true}],"data":"3Bxs4Z6oyhaczjLK"}"#; - let instruction = TWStringHelper::create(instruction_str); - // Step 3 - Add the instruction to the transaction. - let updated_tx = TWStringHelper::wrap(unsafe { - tw_solana_transaction_add_instruction(encoded_tx.ptr(), instruction.ptr()) - }); - let updated_tx = updated_tx.to_string().unwrap(); - assert_eq!(updated_tx, "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgABBcsq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXCBClNVEnVmpLrtsxbBm36nAMo3+zrd60SK3Q+9MM/byUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWglBEmXIJbeoWIaTmY86w/B7o6y+gDQmzmrQfxH7VFEOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcbt3BpyIQpZi8lCe5QQrWkKX5lt3QA3HDxdgp9Q7Z5AIEAgACDAIAAABAQg8AAAAAAAQCAQMMAgAAAMDGLQAAAAAA"); + // Test adding instruction, don't care about the position. + test_insert_instruction_helper( + encoded_tx_str, + -1, + instruction_str, + "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgABBcsq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXCBClNVEnVmpLrtsxbBm36nAMo3+zrd60SK3Q+9MM/byUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWglBEmXIJbeoWIaTmY86w/B7o6y+gDQmzmrQfxH7VFEOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcbt3BpyIQpZi8lCe5QQrWkKX5lt3QA3HDxdgp9Q7Z5AIEAgACDAIAAABAQg8AAAAAAAQCAQMMAgAAAMDGLQAAAAAA", + "02000105cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b570810a5355127566a4baedb316c19b7ea700ca37fb3addeb448add0fbd30cfdbc94c3890fa8d4bc04ab2a676d2cafea5cdc899ecd95a9cbe593e9df258759685a09411265c825b7a8588693998f3ac3f07ba3acbe803426ce6ad07f11fb54510e0000000000000000000000000000000000000000000000000000000000000000dc6eddc1a72210a598bc9427b9410ad690a5f996ddd00371c3c5d829f50ed9e402040200020c0200000040420f0000000000040201030c02000000c0c62d0000000000", + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/3JqWkMtjepGLq9CKTjApNUfa5VGRwV3XPS2hHzAoafLXFdf9khxehhHQnvhYbszYdY9DZzCFgabceSxCsrAhZ4Yv?cluster=devnet + "AnNqW1ZasaRYorSc9KK2O/GxfEmKk3snmjOnqBNAeloK9BSUSONuXsxGSVT/UeDpiTmIkgupYjVeH6OlaXxnHQejdab2170qEk0Z3WgjGKqhqcl9RG1z5cUkK4pdqF1iM4qm6kbAM/f+mdKq4HwIyRODKRjHwm0OtV2zYnmkSM8FAgABBcsq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXCBClNVEnVmpLrtsxbBm36nAMo3+zrd60SK3Q+9MM/byUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWglBEmXIJbeoWIaTmY86w/B7o6y+gDQmzmrQfxH7VFEOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcbt3BpyIQpZi8lCe5QQrWkKX5lt3QA3HDxdgp9Q7Z5AIEAgACDAIAAABAQg8AAAAAAAQCAQMMAgAAAMDGLQAAAAAA", + vec!["4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7", "1c0774714429e780ca1f12151fb3a7e672bdbef0ce49d4ea9467ae8699af3451"], + ); +} - // Step 4 - Obtain preimage hash. - let tx_data = base64::decode(&updated_tx, STANDARD).unwrap(); - let mut decoder = TransactionDecoderHelper::::default(); - let output = decoder.decode(CoinType::Solana, tx_data); - assert_eq!(output.error, SigningError::OK); - let decoded_tx = output.transaction.unwrap(); - let signing_input = Proto::SigningInput { - raw_message: Some(decoded_tx), - tx_encoding: Proto::Encoding::Base64, - ..Proto::SigningInput::default() - }; +#[test] +fn test_solana_transaction_insert_instruction_at_begin() { + // The original transaction contains the following instruction: + // Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ transfer 0.001 SOL to B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V + let encoded_tx_str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAm/KjJXypxOZEREGLk4gcEwWz73mw65GbMZT5F2vRJqkBAgIAAQwCAAAAQEIPAAAAAAA="; - let mut pre_imager = PreImageHelper::::default(); - let preimage_output = pre_imager.pre_image_hashes(CoinType::Solana, &signing_input); - assert_eq!(preimage_output.error, SigningError::OK); - assert_eq!( - preimage_output.data.to_hex(), - "02000105cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b570810a5355127566a4baedb316c19b7ea700ca37fb3addeb448add0fbd30cfdbc94c3890fa8d4bc04ab2a676d2cafea5cdc899ecd95a9cbe593e9df258759685a09411265c825b7a8588693998f3ac3f07ba3acbe803426ce6ad07f11fb54510e0000000000000000000000000000000000000000000000000000000000000000dc6eddc1a72210a598bc9427b9410ad690a5f996ddd00371c3c5d829f50ed9e402040200020c0200000040420f0000000000040201030c02000000c0c62d0000000000" + // Add new instruction: + // YUz1AupPEy1vttBeDS7sXYZFhQJppcXMzjDiDx18Srf transfer 0.003 SOL to d8DiHEeHKdXkM2ZupT86mrvavhmJwUZjHPCzMiB5Lqb + let instruction_str = r#"{"programId":"11111111111111111111111111111111","accounts":[{"pubkey":"YUz1AupPEy1vttBeDS7sXYZFhQJppcXMzjDiDx18Srf","isSigner":true,"isWritable":true},{"pubkey":"d8DiHEeHKdXkM2ZupT86mrvavhmJwUZjHPCzMiB5Lqb","isSigner":false,"isWritable":true}],"data":"3Bxs4Z6oyhaczjLK"}"#; + + // Test adding instruction at the beginning (insert_at = 0). + test_insert_instruction_helper( + encoded_tx_str, + 0, + instruction_str, + "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgABBcsq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXCBClNVEnVmpLrtsxbBm36nAMo3+zrd60SK3Q+9MM/byUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWglBEmXIJbeoWIaTmY86w/B7o6y+gDQmzmrQfxH7VFEOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACb8qMlfKnE5kREQYuTiBwTBbPvebDrkZsxlPkXa9EmqQIEAgEDDAIAAADAxi0AAAAAAAQCAAIMAgAAAEBCDwAAAAAA", + "02000105cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b570810a5355127566a4baedb316c19b7ea700ca37fb3addeb448add0fbd30cfdbc94c3890fa8d4bc04ab2a676d2cafea5cdc899ecd95a9cbe593e9df258759685a09411265c825b7a8588693998f3ac3f07ba3acbe803426ce6ad07f11fb54510e00000000000000000000000000000000000000000000000000000000000000009bf2a3257ca9c4e64444418b93881c1305b3ef79b0eb919b3194f9176bd126a902040201030c02000000c0c62d0000000000040200020c0200000040420f0000000000", + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/sQVHo7ckC1zrYzKDdQWtrEXijN5cAvfZ8gnBnfBr2T8cmtY1DKjNZrwUNRQYxpQLcKUCZaWCUk567Jjrx4HuKYx?cluster=devnet + "Ait3oqCXeuRbNPpnRmil5kQb8+VJEsWtSs4ncwygPat6YRvFf6cIgxd4QwaCWvswHqNgeBscrnEXG2yePvsolAXA1hv2lsQTzFzIalgVIfVTX78y4TXRnFQn19shjQ4N4xwQw9xG5/827ZjXoRwzWQxppC7fN4pg9CTE8WKlrjEDAgABBcsq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXCBClNVEnVmpLrtsxbBm36nAMo3+zrd60SK3Q+9MM/byUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWglBEmXIJbeoWIaTmY86w/B7o6y+gDQmzmrQfxH7VFEOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACb8qMlfKnE5kREQYuTiBwTBbPvebDrkZsxlPkXa9EmqQIEAgEDDAIAAADAxi0AAAAAAAQCAAIMAgAAAEBCDwAAAAAA", + vec!["4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7", "1c0774714429e780ca1f12151fb3a7e672bdbef0ce49d4ea9467ae8699af3451"], ); +} - // Step 5 - Get signatures. - let keypair1 = - KeyPair::try_from("4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7") - .unwrap(); - let signature1 = keypair1 - .sign(preimage_output.data.to_vec()) - .unwrap() - .to_vec(); - let public_key1 = keypair1.public().to_vec(); - - let keypair2 = - KeyPair::try_from("1c0774714429e780ca1f12151fb3a7e672bdbef0ce49d4ea9467ae8699af3451") - .unwrap(); - let signature2 = keypair2 - .sign(preimage_output.data.to_vec()) - .unwrap() - .to_vec(); - let public_key2 = keypair2.public().to_vec(); +#[test] +fn test_solana_transaction_insert_instruction_at_end() { + // The original transaction contains the following instruction: + // Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ transfer 0.001 SOL to B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V + let encoded_tx_str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3G7dwaciEKWYvJQnuUEK1pCl+Zbd0ANxw8XYKfUO2eQBAgIAAQwCAAAAQEIPAAAAAAA="; - // Step 6 - Compile transaction info. - let mut compiler = CompilerHelper::::default(); - let output = compiler.compile( - CoinType::Solana, - &signing_input, - vec![signature1, signature2], - vec![public_key1, public_key2], + // Add new instruction: + // YUz1AupPEy1vttBeDS7sXYZFhQJppcXMzjDiDx18Srf transfer 0.003 SOL to d8DiHEeHKdXkM2ZupT86mrvavhmJwUZjHPCzMiB5Lqb + let instruction_str = r#"{"programId":"11111111111111111111111111111111","accounts":[{"pubkey":"YUz1AupPEy1vttBeDS7sXYZFhQJppcXMzjDiDx18Srf","isSigner":true,"isWritable":true},{"pubkey":"d8DiHEeHKdXkM2ZupT86mrvavhmJwUZjHPCzMiB5Lqb","isSigner":false,"isWritable":true}],"data":"3Bxs4Z6oyhaczjLK"}"#; + + // Test adding instruction at the end (insert_at = 1, as only one instruction in the transaction). + test_insert_instruction_helper( + encoded_tx_str, + 1, + instruction_str, + "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgABBcsq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXCBClNVEnVmpLrtsxbBm36nAMo3+zrd60SK3Q+9MM/byUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWglBEmXIJbeoWIaTmY86w/B7o6y+gDQmzmrQfxH7VFEOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcbt3BpyIQpZi8lCe5QQrWkKX5lt3QA3HDxdgp9Q7Z5AIEAgACDAIAAABAQg8AAAAAAAQCAQMMAgAAAMDGLQAAAAAA", + "02000105cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b570810a5355127566a4baedb316c19b7ea700ca37fb3addeb448add0fbd30cfdbc94c3890fa8d4bc04ab2a676d2cafea5cdc899ecd95a9cbe593e9df258759685a09411265c825b7a8588693998f3ac3f07ba3acbe803426ce6ad07f11fb54510e0000000000000000000000000000000000000000000000000000000000000000dc6eddc1a72210a598bc9427b9410ad690a5f996ddd00371c3c5d829f50ed9e402040200020c0200000040420f0000000000040201030c02000000c0c62d0000000000", + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/3JqWkMtjepGLq9CKTjApNUfa5VGRwV3XPS2hHzAoafLXFdf9khxehhHQnvhYbszYdY9DZzCFgabceSxCsrAhZ4Yv?cluster=devnet + "AnNqW1ZasaRYorSc9KK2O/GxfEmKk3snmjOnqBNAeloK9BSUSONuXsxGSVT/UeDpiTmIkgupYjVeH6OlaXxnHQejdab2170qEk0Z3WgjGKqhqcl9RG1z5cUkK4pdqF1iM4qm6kbAM/f+mdKq4HwIyRODKRjHwm0OtV2zYnmkSM8FAgABBcsq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXCBClNVEnVmpLrtsxbBm36nAMo3+zrd60SK3Q+9MM/byUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWglBEmXIJbeoWIaTmY86w/B7o6y+gDQmzmrQfxH7VFEOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcbt3BpyIQpZi8lCe5QQrWkKX5lt3QA3HDxdgp9Q7Z5AIEAgACDAIAAABAQg8AAAAAAAQCAQMMAgAAAMDGLQAAAAAA", + vec!["4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7", "1c0774714429e780ca1f12151fb3a7e672bdbef0ce49d4ea9467ae8699af3451"], ); +} - assert_eq!(output.error, SigningError::OK); - // Successfully broadcasted tx: - // https://explorer.solana.com/tx/3JqWkMtjepGLq9CKTjApNUfa5VGRwV3XPS2hHzAoafLXFdf9khxehhHQnvhYbszYdY9DZzCFgabceSxCsrAhZ4Yv?cluster=devnet - let expected = "AnNqW1ZasaRYorSc9KK2O/GxfEmKk3snmjOnqBNAeloK9BSUSONuXsxGSVT/UeDpiTmIkgupYjVeH6OlaXxnHQejdab2170qEk0Z3WgjGKqhqcl9RG1z5cUkK4pdqF1iM4qm6kbAM/f+mdKq4HwIyRODKRjHwm0OtV2zYnmkSM8FAgABBcsq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXCBClNVEnVmpLrtsxbBm36nAMo3+zrd60SK3Q+9MM/byUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWglBEmXIJbeoWIaTmY86w/B7o6y+gDQmzmrQfxH7VFEOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcbt3BpyIQpZi8lCe5QQrWkKX5lt3QA3HDxdgp9Q7Z5AIEAgACDAIAAABAQg8AAAAAAAQCAQMMAgAAAMDGLQAAAAAA"; - assert_eq!(output.encoded, expected); +#[test] +fn test_solana_transaction_insert_instruction_accounts_already_exist() { + // The original transaction contains the following instruction: + // Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ transfer 0.001 SOL to B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V + let encoded_tx_str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGmdal3puKe8Ts38zAeBNhPflrnIsh+y0vOJUCYnOOIBAgIAAQwCAAAAQEIPAAAAAAA="; + + // Add new instruction (the sender and receiver are already in the transaction): + // Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ transfer 0.002 SOL to B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V + let instruction_str = r#"{"programId":"11111111111111111111111111111111","accounts":[{"pubkey":"Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ","isSigner":true,"isWritable":true},{"pubkey":"B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V","isSigner":false,"isWritable":true}],"data":"3Bxs4NMRjdEwjxAj"}"#; + + test_insert_instruction_helper( + encoded_tx_str, + -1, + instruction_str, + "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGmdal3puKe8Ts38zAeBNhPflrnIsh+y0vOJUCYnOOICAgIAAQwCAAAAQEIPAAAAAAACAgABDAIAAACAhB4AAAAAAA==", + "01000103cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b5794c3890fa8d4bc04ab2a676d2cafea5cdc899ecd95a9cbe593e9df258759685a000000000000000000000000000000000000000000000000000000000000000028699d6a5de9b8a7bc4ecdfccc07813613df96b9c8b21fb2d2f38950262738e202020200010c0200000040420f0000000000020200010c0200000080841e0000000000", + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/cBkG3BKyyWVCNbpW4GpUHPhZZyqaD9XUnEMaB7R4sNs51fhj8fsnUmpXdzhRmV483WQcgBwPCkKjrvqLmvP1Gug?cluster=devnet + "AR5Xqmoj8pkYuLazQMM6x79T9wp35xvnCua/a3YllOShi0pTPWLqZrHdYnZlBcX0wdV7Ef5DqKoQCrUxY2RyKwsBAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGmdal3puKe8Ts38zAeBNhPflrnIsh+y0vOJUCYnOOICAgIAAQwCAAAAQEIPAAAAAAACAgABDAIAAACAhB4AAAAAAA==", + vec!["4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7"], + ); } #[test] -fn test_solana_transaction_add_instruction_accounts_already_exist() { - // Step 1 - Prepare a transaction. +fn test_solana_transaction_insert_instruction_accounts_already_exist_at_begin() { + // The original transaction contains the following instruction: + // Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ transfer 0.001 SOL to B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V + let encoded_tx_str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAztQHuMytNoDl0ffcUALK8+HmkItxuRS720ZqYu9MBZMBAgIAAQwCAAAAQEIPAAAAAAA="; + + // Add new instruction (the sender and receiver are already in the transaction): + // Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ transfer 0.002 SOL to B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V + let instruction_str = r#"{"programId":"11111111111111111111111111111111","accounts":[{"pubkey":"Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ","isSigner":true,"isWritable":true},{"pubkey":"B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V","isSigner":false,"isWritable":true}],"data":"3Bxs4NMRjdEwjxAj"}"#; + + // Test adding instruction at the beginning (insert_at = 0). + test_insert_instruction_helper( + encoded_tx_str, + 0, + instruction_str, + "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAztQHuMytNoDl0ffcUALK8+HmkItxuRS720ZqYu9MBZMCAgIAAQwCAAAAgIQeAAAAAAACAgABDAIAAABAQg8AAAAAAA==", + "01000103cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b5794c3890fa8d4bc04ab2a676d2cafea5cdc899ecd95a9cbe593e9df258759685a0000000000000000000000000000000000000000000000000000000000000000ced407b8ccad3680e5d1f7dc5002caf3e1e6908b71b914bbdb466a62ef4c059302020200010c0200000080841e0000000000020200010c0200000040420f0000000000", + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/3yszAuvvzK8gSBCPX9Ep8u58Rb7B4MWdB1sKcgwLpkeyt24P1i3QCQDWMD2TwVWhbXadLXVCPFvybmU2RV4Z1gp6?cluster=devnet + "AZUVl3aRaL/JNqlW6yKTa3nz4V2m9izDFBUHcIbqiiCewCIjOGkolkqYpWdH2JJJQaQJk+/l7dFbysHTE6MjmAcBAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAztQHuMytNoDl0ffcUALK8+HmkItxuRS720ZqYu9MBZMCAgIAAQwCAAAAgIQeAAAAAAACAgABDAIAAABAQg8AAAAAAA==", + vec!["4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7"], + ); +} + +#[test] +fn test_solana_transaction_insert_instruction_accounts_already_exist_at_end() { // The original transaction contains the following instruction: // Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ transfer 0.001 SOL to B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V let encoded_tx_str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGmdal3puKe8Ts38zAeBNhPflrnIsh+y0vOJUCYnOOIBAgIAAQwCAAAAQEIPAAAAAAA="; - let encoded_tx = TWStringHelper::create(encoded_tx_str); - // Step 2 - Prepare a new instruction (sender and receiver are already in the transaction): + // Add new instruction (the sender and receiver are already in the transaction): // Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ transfer 0.002 SOL to B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V let instruction_str = r#"{"programId":"11111111111111111111111111111111","accounts":[{"pubkey":"Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ","isSigner":true,"isWritable":true},{"pubkey":"B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V","isSigner":false,"isWritable":true}],"data":"3Bxs4NMRjdEwjxAj"}"#; + + // Test adding instruction at the end (insert_at = 1, as only one instruction in the transaction). + test_insert_instruction_helper( + encoded_tx_str, + 1, + instruction_str, + "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGmdal3puKe8Ts38zAeBNhPflrnIsh+y0vOJUCYnOOICAgIAAQwCAAAAQEIPAAAAAAACAgABDAIAAACAhB4AAAAAAA==", + "01000103cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b5794c3890fa8d4bc04ab2a676d2cafea5cdc899ecd95a9cbe593e9df258759685a000000000000000000000000000000000000000000000000000000000000000028699d6a5de9b8a7bc4ecdfccc07813613df96b9c8b21fb2d2f38950262738e202020200010c0200000040420f0000000000020200010c0200000080841e0000000000", + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/cBkG3BKyyWVCNbpW4GpUHPhZZyqaD9XUnEMaB7R4sNs51fhj8fsnUmpXdzhRmV483WQcgBwPCkKjrvqLmvP1Gug?cluster=devnet + "AR5Xqmoj8pkYuLazQMM6x79T9wp35xvnCua/a3YllOShi0pTPWLqZrHdYnZlBcX0wdV7Ef5DqKoQCrUxY2RyKwsBAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGmdal3puKe8Ts38zAeBNhPflrnIsh+y0vOJUCYnOOICAgIAAQwCAAAAQEIPAAAAAAACAgABDAIAAACAhB4AAAAAAA==", + vec!["4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7"], + ); +} + +// Helper function to test adding instruction with different insert positions +fn test_insert_instruction_helper( + encoded_tx_str: &str, + insert_at: i32, + instruction_str: &str, + expected_updated_tx: &str, + expected_preimage_hash: &str, + expected_signed_tx: &str, + private_keys: Vec<&str>, +) { + // Step 1 - Prepare a transaction. + let encoded_tx = TWStringHelper::create(encoded_tx_str); + + // Step 2 - Prepare a new instruction. let instruction = TWStringHelper::create(instruction_str); // Step 3 - Add the instruction to the transaction. let updated_tx = TWStringHelper::wrap(unsafe { - tw_solana_transaction_add_instruction(encoded_tx.ptr(), instruction.ptr()) + tw_solana_transaction_insert_instruction(encoded_tx.ptr(), insert_at, instruction.ptr()) }); let updated_tx = updated_tx.to_string().unwrap(); - assert_eq!(updated_tx, "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGmdal3puKe8Ts38zAeBNhPflrnIsh+y0vOJUCYnOOICAgIAAQwCAAAAQEIPAAAAAAACAgABDAIAAACAhB4AAAAAAA=="); + assert_eq!(updated_tx, expected_updated_tx); // Step 4 - Obtain preimage hash. let tx_data = base64::decode(&updated_tx, STANDARD).unwrap(); @@ -479,33 +550,27 @@ fn test_solana_transaction_add_instruction_accounts_already_exist() { let mut pre_imager = PreImageHelper::::default(); let preimage_output = pre_imager.pre_image_hashes(CoinType::Solana, &signing_input); assert_eq!(preimage_output.error, SigningError::OK); - assert_eq!( - preimage_output.data.to_hex(), - "01000103cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b5794c3890fa8d4bc04ab2a676d2cafea5cdc899ecd95a9cbe593e9df258759685a000000000000000000000000000000000000000000000000000000000000000028699d6a5de9b8a7bc4ecdfccc07813613df96b9c8b21fb2d2f38950262738e202020200010c0200000040420f0000000000020200010c0200000080841e0000000000" - ); + assert_eq!(preimage_output.data.to_hex(), expected_preimage_hash); - // Step 5 - Get signature. - let keypair = - KeyPair::try_from("4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7") - .unwrap(); - let signature = keypair - .sign(preimage_output.data.to_vec()) - .unwrap() - .to_vec(); - let public_key = keypair.public().to_vec(); + // Step 5 - Get signatures. + let mut signatures = Vec::new(); + let mut public_keys = Vec::new(); + + for private_key in private_keys { + let keypair = KeyPair::try_from(private_key).unwrap(); + let signature = keypair + .sign(preimage_output.data.to_vec()) + .unwrap() + .to_vec(); + let public_key = keypair.public().to_vec(); + signatures.push(signature); + public_keys.push(public_key); + } // Step 6 - Compile transaction info. let mut compiler = CompilerHelper::::default(); - let output = compiler.compile( - CoinType::Solana, - &signing_input, - vec![signature], - vec![public_key], - ); + let output = compiler.compile(CoinType::Solana, &signing_input, signatures, public_keys); assert_eq!(output.error, SigningError::OK); - // Successfully broadcasted tx: - // https://explorer.solana.com/tx/cBkG3BKyyWVCNbpW4GpUHPhZZyqaD9XUnEMaB7R4sNs51fhj8fsnUmpXdzhRmV483WQcgBwPCkKjrvqLmvP1Gug?cluster=devnet - let expected = "AR5Xqmoj8pkYuLazQMM6x79T9wp35xvnCua/a3YllOShi0pTPWLqZrHdYnZlBcX0wdV7Ef5DqKoQCrUxY2RyKwsBAAEDyyrwibVqVXc3vBcY4MvyMs9bAuFO4Kp8ZnUjP19vm1eUw4kPqNS8BKsqZ20sr+pc3ImezZWpy+WT6d8lh1loWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGmdal3puKe8Ts38zAeBNhPflrnIsh+y0vOJUCYnOOICAgIAAQwCAAAAQEIPAAAAAAACAgABDAIAAACAhB4AAAAAAA=="; - assert_eq!(output.encoded, expected); + assert_eq!(output.encoded, expected_signed_tx); } diff --git a/rust/wallet_core_rs/src/ffi/solana/transaction.rs b/rust/wallet_core_rs/src/ffi/solana/transaction.rs index fd9a9f685d2..28ebe708d08 100644 --- a/rust/wallet_core_rs/src/ffi/solana/transaction.rs +++ b/rust/wallet_core_rs/src/ffi/solana/transaction.rs @@ -164,15 +164,17 @@ pub unsafe extern "C" fn tw_solana_transaction_set_fee_payer( } } -/// Adds an instruction to the given transaction, and returns the updated transaction. +/// Inserts an instruction to the given transaction at the specified position, returning the updated transaction. /// /// \param encoded_tx base64 encoded Solana transaction. +/// \param insert_at index where the instruction should be inserted. If you don't care about the position, use -1. /// \param instruction json encoded instruction. Here is an example: {"programId":"11111111111111111111111111111111","accounts":[{"pubkey":"YUz1AupPEy1vttBeDS7sXYZFhQJppcXMzjDiDx18Srf","isSigner":true,"isWritable":true},{"pubkey":"d8DiHEeHKdXkM2ZupT86mrvavhmJwUZjHPCzMiB5Lqb","isSigner":false,"isWritable":true}],"data":"3Bxs4Z6oyhaczjLK"} /// \return base64 encoded Solana transaction. Null if an error occurred. -#[tw_ffi(ty = static_function, class = TWSolanaTransaction, name = AddInstruction)] +#[tw_ffi(ty = static_function, class = TWSolanaTransaction, name = InsertInstruction)] #[no_mangle] -pub unsafe extern "C" fn tw_solana_transaction_add_instruction( +pub unsafe extern "C" fn tw_solana_transaction_insert_instruction( encoded_tx: Nonnull, + insert_at: i32, instruction: Nonnull, ) -> NullableMut { let encoded_tx = try_or_else!(TWString::from_ptr_as_ref(encoded_tx), std::ptr::null_mut); @@ -181,7 +183,7 @@ pub unsafe extern "C" fn tw_solana_transaction_add_instruction( let instruction = try_or_else!(TWString::from_ptr_as_ref(instruction), std::ptr::null_mut); let instruction = try_or_else!(instruction.as_str(), std::ptr::null_mut); - match SolanaTransaction::add_instruction(encoded_tx, instruction) { + match SolanaTransaction::insert_instruction(encoded_tx, insert_at, instruction) { Ok(updated_tx) => TWString::from(updated_tx).into_ptr(), _ => std::ptr::null_mut(), } From 0e19dd09882a453e59732eccc67fccd6c55e4f09 Mon Sep 17 00:00:00 2001 From: Tao Xu <360470+hewigovens@users.noreply.github.com> Date: Fri, 20 Jun 2025 15:57:59 +0900 Subject: [PATCH 39/72] add .hbs suffix to template Cargo.toml (#4424) --- codegen-v2/src/codegen/rust/coin_crate.rs | 3 ++- .../templates/blockchain_crate/{Cargo.toml => Cargo.toml.hbs} | 0 2 files changed, 2 insertions(+), 1 deletion(-) rename codegen-v2/src/codegen/rust/templates/blockchain_crate/{Cargo.toml => Cargo.toml.hbs} (100%) diff --git a/codegen-v2/src/codegen/rust/coin_crate.rs b/codegen-v2/src/codegen/rust/coin_crate.rs index 0e1b133f171..c546f4cbcf2 100644 --- a/codegen-v2/src/codegen/rust/coin_crate.rs +++ b/codegen-v2/src/codegen/rust/coin_crate.rs @@ -13,7 +13,8 @@ use std::path::PathBuf; const BLOCKCHAIN_ADDRESS_TEMPLATE: &str = include_str!("templates/blockchain_crate/address.rs"); const BLOCKCHAIN_COMPILER_TEMPLATE: &str = include_str!("templates/blockchain_crate/compiler.rs"); const BLOCKCHAIN_ENTRY_TEMPLATE: &str = include_str!("templates/blockchain_crate/entry.rs"); -const BLOCKCHAIN_MANIFEST_TEMPLATE: &str = include_str!("templates/blockchain_crate/Cargo.toml"); +const BLOCKCHAIN_MANIFEST_TEMPLATE: &str = + include_str!("templates/blockchain_crate/Cargo.toml.hbs"); const BLOCKCHAIN_LIB_TEMPLATE: &str = include_str!("templates/blockchain_crate/lib.rs"); const BLOCKCHAIN_SIGNER_TEMPLATE: &str = include_str!("templates/blockchain_crate/signer.rs"); diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml b/codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml.hbs similarity index 100% rename from codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml rename to codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml.hbs From 385185ddf9b896c32f5e55b1199795b0edf05223 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:33:40 +0700 Subject: [PATCH 40/72] feat(polkadot): Add `Staking.BondExtraAndNominate` transaction type (#4428) --- .../tw_polkadot/src/call_encoder/mod.rs | 30 +++++++++++- rust/tw_tests/tests/chains/polkadot/mod.rs | 9 +++- .../tests/chains/polkadot/polkadot_compile.rs | 48 ++++++++++++++++++- src/proto/Polkadot.proto | 19 ++++++++ 4 files changed, 102 insertions(+), 4 deletions(-) diff --git a/rust/chains/tw_polkadot/src/call_encoder/mod.rs b/rust/chains/tw_polkadot/src/call_encoder/mod.rs index 6b2548e3a38..afe4768152e 100644 --- a/rust/chains/tw_polkadot/src/call_encoder/mod.rs +++ b/rust/chains/tw_polkadot/src/call_encoder/mod.rs @@ -5,7 +5,7 @@ use tw_proto::Polkadot::Proto::{ mod_CallIndices::OneOfvariant as CallIndicesVariant, mod_SigningInput::OneOfmessage_oneof as SigningVariant, mod_Staking::{ - Bond, BondAndNominate, Chill, ChillAndUnbond, Nominate, + Bond, BondAndNominate, BondExtra, BondExtraAndNominate, Chill, ChillAndUnbond, Nominate, OneOfmessage_oneof as StakingVariant, Unbond, }, Balance, CallIndices, Staking, @@ -131,6 +131,30 @@ impl CallEncoder { self.encode_batch(vec![first, second], &ban.call_indices) } + fn encode_staking_bond_extra_and_nominate( + &self, + bean: &BondExtraAndNominate, + ) -> EncodeResult { + // Encode a bond call + let first = self.encode_call(&SigningVariant::staking_call(Proto::Staking { + message_oneof: StakingVariant::bond_extra(BondExtra { + value: bean.value.clone(), + call_indices: bean.bond_extra_call_indices.clone(), + }), + }))?; + + // Encode a nominate call + let second = self.encode_call(&SigningVariant::staking_call(Proto::Staking { + message_oneof: StakingVariant::nominate(Nominate { + nominators: bean.nominators.clone(), + call_indices: bean.nominate_call_indices.clone(), + }), + }))?; + + // Encode both calls as batched + self.encode_batch(vec![first, second], &bean.call_indices) + } + fn encode_staking_chill_and_unbond(&self, cau: &ChillAndUnbond) -> EncodeResult { let first = self.encode_call(&SigningVariant::staking_call(Proto::Staking { message_oneof: StakingVariant::chill(Chill { @@ -155,6 +179,10 @@ impl CallEncoder { let batch = self.encode_staking_bond_and_nominate(ban)?; Ok(Some(batch)) }, + StakingVariant::bond_extra_and_nominate(bean) => { + let batch = self.encode_staking_bond_extra_and_nominate(bean)?; + Ok(Some(batch)) + }, StakingVariant::chill_and_unbond(cau) => { let batch = self.encode_staking_chill_and_unbond(cau)?; Ok(Some(batch)) diff --git a/rust/tw_tests/tests/chains/polkadot/mod.rs b/rust/tw_tests/tests/chains/polkadot/mod.rs index 5b4b2509331..d4e8d84139b 100644 --- a/rust/tw_tests/tests/chains/polkadot/mod.rs +++ b/rust/tw_tests/tests/chains/polkadot/mod.rs @@ -68,7 +68,12 @@ pub fn helper_encode_and_compile( let mut pre_imager = PreImageHelper::::default(); let preimage_output = pre_imager.pre_image_hashes(coin, &input); - assert_eq!(preimage_output.error, SigningError::OK); + assert_eq!( + preimage_output.error, + SigningError::OK, + "{}", + preimage_output.error_message + ); let preimage = preimage_output.data.to_hex(); // Step 2: Compile transaction info @@ -87,7 +92,7 @@ pub fn helper_encode_and_compile( // Compile transaction info let mut compiler = CompilerHelper::::default(); let output = compiler.compile(coin, &input, vec![signature_bytes], vec![public_key]); - assert_eq!(output.error, SigningError::OK); + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); let signed = output.encoded.to_hex(); (preimage, signed) diff --git a/rust/tw_tests/tests/chains/polkadot/polkadot_compile.rs b/rust/tw_tests/tests/chains/polkadot/polkadot_compile.rs index 1f622f427c3..09904bf65ac 100644 --- a/rust/tw_tests/tests/chains/polkadot/polkadot_compile.rs +++ b/rust/tw_tests/tests/chains/polkadot/polkadot_compile.rs @@ -2,9 +2,13 @@ // // Copyright © 2017 Trust Wallet. -use crate::chains::polkadot::{balance_call, helper_encode_and_compile, GENESIS_HASH}; +use crate::chains::polkadot::{ + balance_call, helper_encode_and_compile, staking_call, GENESIS_HASH, +}; +use std::borrow::Cow; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::DecodeHex; +use tw_number::U256; use tw_proto::Polkadot::Proto::{self, mod_Balance::Transfer}; #[test] @@ -51,3 +55,45 @@ fn test_polkadot_compile_transfer() { "390284d84accbb64934815506288fafbfc7d275e64aa4e3cd9c5392db6e83b13256bf300fb43727477caaa12542b9060856816d42eedef6ebf2e98e4f8dff4355fe384751925833c4a26b2fed1707aebe655cb3317504a61ee59697c086f7baa6ca06a099dfe00000500be4c21aa92dcba057e9b719ce1de970f774f064c09b13a3ea3009affb8cb5ec707000cdc0f21" ); } + +// Successfully broadcasted transaction on Polkadot mainnet: +// https://polkadot.subscan.io/extrinsic/26528041-2 +#[test] +fn test_polkadot_compile_bond_extra_and_nominate() { + // Step 1: Prepare input. + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + let input = Proto::SigningInput { + network: 0, + nonce: 18, + block_hash: genesis_hash.clone().into(), + genesis_hash: genesis_hash.into(), + spec_version: 1005001, + transaction_version: 26, + message_oneof: staking_call( + Proto::mod_Staking::OneOfmessage_oneof::bond_extra_and_nominate( + Proto::mod_Staking::BondExtraAndNominate { + value: Cow::Owned(U256::from(50_000_000_000_u64).to_big_endian().to_vec()), + nominators: vec!["14Y4s6V1PWrwBLvxW47gcYgZCGTYekmmzvFsK1kiqNH2d84t".into()], + ..Default::default() + }, + ), + ), + ..Default::default() + }; + + // Simulate signature, normally obtained from signature server + let signature = "0xb959bd1506e2863cd3f92bd703d31b3f38847e6425f8abd94fa1aed863b5e27c45008abb8d260d8d306b0f129ebe5e2eb845dc3e6585a08d4be231e4b886cd0e"; + let public_key = "0x4883c6369ec0764c0cada1dcc55a4873d1c1c6a13400bf57c206e660302081ca"; + + let (preimage, signed) = + helper_encode_and_compile(CoinType::Polkadot, input, signature, public_key, true); + + assert_eq!( + preimage, + "1a020807010700743ba40b070504009c665073980c9bdbd5620ef9a860b9f1efbeda8f10e13ef7431f6970d765a25700480000c9550f001a00000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c391b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c300" + ); + assert_eq!( + signed, + "590284004883c6369ec0764c0cada1dcc55a4873d1c1c6a13400bf57c206e660302081ca00b959bd1506e2863cd3f92bd703d31b3f38847e6425f8abd94fa1aed863b5e27c45008abb8d260d8d306b0f129ebe5e2eb845dc3e6585a08d4be231e4b886cd0e004800001a020807010700743ba40b070504009c665073980c9bdbd5620ef9a860b9f1efbeda8f10e13ef7431f6970d765a257" + ); +} diff --git a/src/proto/Polkadot.proto b/src/proto/Polkadot.proto index 59f8aa00ba4..c95f4b22e97 100644 --- a/src/proto/Polkadot.proto +++ b/src/proto/Polkadot.proto @@ -139,6 +139,24 @@ message Staking { CallIndices nominate_call_indices = 7; } + // Bond extra, with nominators + message BondExtraAndNominate { + // amount (uint256, serialized big endian) + bytes value = 1; + + // list of nominators + repeated string nominators = 2; + + // Batch call indices + CallIndices call_indices = 3; + + // Staking.BondExtra call indices + CallIndices bond_extra_call_indices = 4; + + // Staking.Nominate call indices + CallIndices nominate_call_indices = 5; + } + // Bond extra amount message BondExtra { // amount (uint256, serialized big endian) @@ -215,6 +233,7 @@ message Staking { Chill chill = 7; ChillAndUnbond chill_and_unbond = 8; Rebond rebond = 9; + BondExtraAndNominate bond_extra_and_nominate = 10; } } From fdbfb61ae5cbb76092d8a96c876d25006af5eef8 Mon Sep 17 00:00:00 2001 From: Tao Xu <360470+hewigovens@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:29:18 +0900 Subject: [PATCH 41/72] remove CARGO_WORKSPACE_DIR from .env (#4429) * remove CARGO_WORKSPACE_DIR from .env * rustfmt tw_ffi.rs --- rust/.cargo/config.toml | 2 - rust/tw_macros/src/tw_ffi.rs | 102 +++++++++++++++++------------------ 2 files changed, 49 insertions(+), 55 deletions(-) delete mode 100644 rust/.cargo/config.toml diff --git a/rust/.cargo/config.toml b/rust/.cargo/config.toml deleted file mode 100644 index 47684146c24..00000000000 --- a/rust/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[env] -CARGO_WORKSPACE_DIR = { value = "", relative = true } diff --git a/rust/tw_macros/src/tw_ffi.rs b/rust/tw_macros/src/tw_ffi.rs index 983a3b3bee4..b0ecba0fdbc 100644 --- a/rust/tw_macros/src/tw_ffi.rs +++ b/rust/tw_macros/src/tw_ffi.rs @@ -160,66 +160,62 @@ pub fn tw_ffi(attr: TokenStream2, item: TokenStream2) -> Result { docs, }; - if let Ok(out_dir) = env::var("CARGO_WORKSPACE_DIR") { - let bindings_dir = Path::new(&out_dir).join("bindings"); - fs::create_dir_all(&bindings_dir).expect("Failed to create bindings directory"); - let yaml_file_path = bindings_dir.join(format!("{}.yaml", class)); - - let mut config = if yaml_file_path.exists() { - match fs::read_to_string(&yaml_file_path) { - Ok(contents) => match serde_yaml::from_str(&contents) { - Ok(config) => config, - Err(_) => { - let _ = fs::remove_file(&yaml_file_path); - TWConfig { - class, - ..Default::default() - } - }, - }, - Err(_) => TWConfig { - class, - ..Default::default() + let out_dir = env::var("CARGO_WORKSPACE_DIR").unwrap_or_default(); + let bindings_dir = Path::new(&out_dir).join("bindings"); + fs::create_dir_all(&bindings_dir).expect("Failed to create bindings directory"); + let yaml_file_path = bindings_dir.join(format!("{}.yaml", class)); + + let mut config = if yaml_file_path.exists() { + match fs::read_to_string(&yaml_file_path) { + Ok(contents) => match serde_yaml::from_str(&contents) { + Ok(config) => config, + Err(_) => { + let _ = fs::remove_file(&yaml_file_path); + TWConfig { + class, + ..Default::default() + } }, - } - } else { - TWConfig { + }, + Err(_) => TWConfig { class, ..Default::default() - } - }; - match args.ty { - Some(TWFFIType::StaticFunction) => { - if let Some(idx) = config - .static_functions - .iter() - .position(|f| f.name == function.name) - { - config.static_functions[idx] = function; - } else { - config.static_functions.push(function); - } }, - Some(TWFFIType::Constructor) => { - update_or_append_function(&mut config.constructors, function); - }, - Some(TWFFIType::Destructor) => { - config.destructor = Some(function); - }, - Some(TWFFIType::Method) => { - update_or_append_function(&mut config.methods, function); - }, - Some(TWFFIType::Property) => { - update_or_append_function(&mut config.properties, function); - }, - _ => panic!("Invalid FFI type"), } - let yaml_output: String = - serde_yaml::to_string(&config).expect("Failed to serialize to YAML"); - fs::write(&yaml_file_path, yaml_output).expect("Failed to write YAML file"); } else { - panic!("CARGO_WORKSPACE_DIR is not set"); + TWConfig { + class, + ..Default::default() + } + }; + match args.ty { + Some(TWFFIType::StaticFunction) => { + if let Some(idx) = config + .static_functions + .iter() + .position(|f| f.name == function.name) + { + config.static_functions[idx] = function; + } else { + config.static_functions.push(function); + } + }, + Some(TWFFIType::Constructor) => { + update_or_append_function(&mut config.constructors, function); + }, + Some(TWFFIType::Destructor) => { + config.destructor = Some(function); + }, + Some(TWFFIType::Method) => { + update_or_append_function(&mut config.methods, function); + }, + Some(TWFFIType::Property) => { + update_or_append_function(&mut config.properties, function); + }, + _ => panic!("Invalid FFI type"), } + let yaml_output: String = serde_yaml::to_string(&config).expect("Failed to serialize to YAML"); + fs::write(&yaml_file_path, yaml_output).expect("Failed to write YAML file"); Ok(item) } From 6e9567b5f9efc965e4fc1af00ecf485c4bf040a1 Mon Sep 17 00:00:00 2001 From: gupnik Date: Tue, 24 Jun 2025 15:08:31 +0530 Subject: [PATCH 42/72] Migrates Barz and Eth Address APIs to Rust (#4422) * Migrates Barz and Eth Address APIs to rust * Adds FFI tests and migrate C++ tests * FMT * Fixes memory leak * Fixes invalid ptr issue * Another try * Addresses review comments * Addresses review comments * Addresses review comment --------- Co-authored-by: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> --- .gitignore | 2 + codegen-v2/src/codegen/cpp/code_gen.rs | 18 +- include/TrustWalletCore/TWBarz.h | 109 ----- include/TrustWalletCore/TWEthereum.h | 26 -- rust/Cargo.lock | 2 + rust/tw_coin_entry/src/error/address_error.rs | 7 + rust/tw_encoding/src/hex.rs | 55 +++ rust/tw_evm/Cargo.toml | 3 + rust/tw_evm/fuzz/Cargo.toml | 6 + .../fuzz/fuzz_targets/diamond_cut_code.rs | 13 + rust/tw_evm/src/abi/param.rs | 2 - rust/tw_evm/src/abi/prebuild/erc1967.rs | 5 + rust/tw_evm/src/abi/prebuild/mod.rs | 1 + rust/tw_evm/src/address.rs | 234 ++++++++++- rust/tw_evm/src/ffi/barz.rs | 296 +++++++++++++ rust/tw_evm/src/ffi/ethereum_address.rs | 110 +++++ rust/tw_evm/src/ffi/mod.rs | 6 + rust/tw_evm/src/lib.rs | 1 + rust/tw_evm/src/modules/barz/core.rs | 393 ++++++++++++++++++ rust/tw_evm/src/modules/barz/error.rs | 73 ++++ rust/tw_evm/src/modules/barz/mod.rs | 6 + rust/tw_evm/src/modules/mod.rs | 1 + rust/tw_evm/src/modules/tx_builder.rs | 18 +- .../src/transaction/authorization_list.rs | 16 +- .../src/transaction/user_operation_v0_7.rs | 21 +- rust/tw_evm/tests/barz.rs | 258 ++++++++++++ rust/tw_evm/tests/barz_ffi.rs | 338 +++++++++++++++ rust/tw_keypair/src/ecdsa/der.rs | 12 +- rust/tw_keypair/src/ecdsa/signature.rs | 7 +- rust/tw_proto/src/lib.rs | 4 +- rust/wallet_core_rs/Cargo.toml | 3 + rust/wallet_core_rs/cbindgen.toml | 2 + rust/wallet_core_rs/src/lib.rs | 2 + src/Ethereum/Barz.cpp | 337 --------------- src/Ethereum/Barz.h | 36 -- src/Ethereum/EIP1014.cpp | 27 -- src/Ethereum/EIP1014.h | 13 - src/Ethereum/EIP1967.cpp | 30 -- src/Ethereum/EIP1967.h | 13 - src/Ethereum/EIP2645.cpp | 36 -- src/Ethereum/EIP2645.h | 13 - src/interface/TWBarz.cpp | 112 ----- src/interface/TWEthereum.cpp | 18 - swift/Tests/BarzTests.swift | 10 +- swift/Tests/HDWalletTests.swift | 2 +- tests/chains/Ethereum/BarzTests.cpp | 168 ++++---- tests/chains/Ethereum/EIP1014Tests.cpp | 96 +++-- tests/chains/Ethereum/EIP1967Tests.cpp | 7 +- tests/chains/ImmutableX/StarkKeyTests.cpp | 13 +- tests/common/HDWallet/HDWalletTests.cpp | 16 +- 50 files changed, 2017 insertions(+), 980 deletions(-) delete mode 100644 include/TrustWalletCore/TWBarz.h delete mode 100644 include/TrustWalletCore/TWEthereum.h create mode 100644 rust/tw_evm/fuzz/fuzz_targets/diamond_cut_code.rs create mode 100644 rust/tw_evm/src/abi/prebuild/erc1967.rs create mode 100644 rust/tw_evm/src/ffi/barz.rs create mode 100644 rust/tw_evm/src/ffi/ethereum_address.rs create mode 100644 rust/tw_evm/src/ffi/mod.rs create mode 100644 rust/tw_evm/src/modules/barz/core.rs create mode 100644 rust/tw_evm/src/modules/barz/error.rs create mode 100644 rust/tw_evm/src/modules/barz/mod.rs create mode 100644 rust/tw_evm/tests/barz_ffi.rs delete mode 100644 src/Ethereum/Barz.cpp delete mode 100644 src/Ethereum/Barz.h delete mode 100644 src/Ethereum/EIP1014.cpp delete mode 100644 src/Ethereum/EIP1014.h delete mode 100644 src/Ethereum/EIP1967.cpp delete mode 100644 src/Ethereum/EIP1967.h delete mode 100644 src/Ethereum/EIP2645.cpp delete mode 100644 src/Ethereum/EIP2645.h delete mode 100644 src/interface/TWBarz.cpp delete mode 100644 src/interface/TWEthereum.cpp diff --git a/.gitignore b/.gitignore index 327d4812e0d..b5eaf7dd735 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,8 @@ include/TrustWalletCore/TWWalletConnectRequest.h include/TrustWalletCore/TWSolanaTransaction.h include/TrustWalletCore/TWCryptoBoxPublicKey.h include/TrustWalletCore/TWCryptoBoxSecretKey.h +include/TrustWalletCore/TWEthereum.h +include/TrustWalletCore/TWBarz.h # Wasm emsdk/ diff --git a/codegen-v2/src/codegen/cpp/code_gen.rs b/codegen-v2/src/codegen/cpp/code_gen.rs index e83f3af603e..96af8f7c525 100644 --- a/codegen-v2/src/codegen/cpp/code_gen.rs +++ b/codegen-v2/src/codegen/cpp/code_gen.rs @@ -235,7 +235,15 @@ fn generate_return_type(func: &TWFunction, converted_args: &Vec) -> Resu .map_err(|e| BadFormat(e.to_string()))?; } (TWPointerType::NonnullMut, "TWString") | (TWPointerType::Nonnull, "TWString") => { - panic!("Nonnull TWString is not supported"); + write!( + &mut return_string, + "\tconst Rust::TWStringWrapper result = Rust::{}{}\n\ + \tconst auto resultString = result.toStringOrDefault();\n\ + \treturn TWStringCreateWithUTF8Bytes(resultString.c_str());\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; } (TWPointerType::NullableMut, "TWData") | (TWPointerType::Nullable, "TWData") => { write!( @@ -390,7 +398,7 @@ fn generate_conversion_code_with_var_name(tw_type: TWType, name: &str) -> Result .map_err(|e| BadFormat(e.to_string()))?; Ok((conversion_code, format!("{}RustData.get()", name))) } - (TWPointerType::Nonnull, "TWPrivateKey") => { + (TWPointerType::Nonnull, "TWPrivateKey") | (TWPointerType::NonnullMut, "TWPrivateKey") => { let mut conversion_code = String::new(); writeln!( &mut conversion_code, @@ -401,7 +409,7 @@ fn generate_conversion_code_with_var_name(tw_type: TWType, name: &str) -> Result .map_err(|e| BadFormat(e.to_string()))?; Ok((conversion_code, format!("{}RustPrivateKey.get()", name))) } - (TWPointerType::Nullable, "TWPrivateKey") => { + (TWPointerType::Nullable, "TWPrivateKey") | (TWPointerType::NullableMut, "TWPrivateKey") => { let mut conversion_code = String::new(); writeln!( &mut conversion_code, @@ -415,7 +423,7 @@ fn generate_conversion_code_with_var_name(tw_type: TWType, name: &str) -> Result .map_err(|e| BadFormat(e.to_string()))?; Ok((conversion_code, format!("{}RustPrivateKey.get()", name))) } - (TWPointerType::Nonnull, "TWPublicKey") => { + (TWPointerType::Nonnull, "TWPublicKey") | (TWPointerType::NonnullMut, "TWPublicKey") => { let mut conversion_code = String::new(); writeln!( &mut conversion_code, @@ -427,7 +435,7 @@ fn generate_conversion_code_with_var_name(tw_type: TWType, name: &str) -> Result .map_err(|e| BadFormat(e.to_string()))?; Ok((conversion_code, format!("{}RustPublicKey.get()", name))) } - (TWPointerType::Nullable, "TWPublicKey") => { + (TWPointerType::Nullable, "TWPublicKey") | (TWPointerType::NullableMut, "TWPublicKey") => { let mut conversion_code = String::new(); writeln!( &mut conversion_code, diff --git a/include/TrustWalletCore/TWBarz.h b/include/TrustWalletCore/TWBarz.h deleted file mode 100644 index cda18a1a478..00000000000 --- a/include/TrustWalletCore/TWBarz.h +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. -#pragma once - -#include "TWBase.h" -#include "TWData.h" -#include "TWString.h" -#include "TWPublicKey.h" - -TW_EXTERN_C_BEGIN - -/// Barz functions -TW_EXPORT_STRUCT -struct TWBarz; - -/// Calculate a counterfactual address for the smart contract wallet -/// -/// \param input The serialized data of ContractAddressInput. -/// \return The address. -TW_EXPORT_STATIC_METHOD -TWString *_Nonnull TWBarzGetCounterfactualAddress(TWData *_Nonnull input); - -/// Returns the init code parameter of ERC-4337 User Operation -/// -/// \param factory Wallet factory address (BarzFactory) -/// \param publicKey Public key for the verification facet -/// \param verificationFacet Verification facet address -/// \return The address. -TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWBarzGetInitCode(TWString* _Nonnull factory, struct TWPublicKey* _Nonnull publicKey, TWString* _Nonnull verificationFacet, uint32_t salt); - -/// Converts the original ASN-encoded signature from webauthn to the format accepted by Barz -/// -/// \param signature Original signature -/// \param challenge The original challenge that was signed -/// \param authenticatorData Returned from Webauthn API -/// \param clientDataJSON Returned from Webauthn API -/// \return Bytes of the formatted signature -TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWBarzGetFormattedSignature(TWData* _Nonnull signature, TWData* _Nonnull challenge, TWData* _Nonnull authenticatorData, TWString* _Nonnull clientDataJSON); - -/// Returns the final hash to be signed by Barz for signing messages & typed data -/// -/// \param msgHash Original msgHash -/// \param barzAddress The address of Barz wallet signing the message -/// \param chainId The chainId of the network the verification will happen -/// \return The final hash to be signed -TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWBarzGetPrefixedMsgHash(TWData* _Nonnull msgHash, TWString* _Nonnull barzAddress, uint32_t chainId); - -/// Returns the encoded diamondCut function call for Barz contract upgrades -/// -/// \param input The serialized data of DiamondCutInput -/// \return The encoded bytes of diamondCut function call -TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWBarzGetDiamondCutCode(TWData *_Nonnull input); - -/// Computes an Authorization hash in [EIP-7702 format](https://eips.ethereum.org/EIPS/eip-7702) -/// `keccak256('0x05' || rlp([chain_id, address, nonce]))`. -/// -/// \param chainId The chainId of the network -/// \param contractAddress The address of the contract to be authorized -/// \param nonce The nonce of the transaction -/// \return The authorization hash -TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWBarzGetAuthorizationHash(TWData* _Nonnull chainId, TWString* _Nonnull contractAddress, TWData* _Nonnull nonce); - -/// Returns the signed authorization hash -/// -/// \param chainId The chainId of the network -/// \param contractAddress The address of the contract to be authorized -/// \param nonce The nonce of the transaction -/// \param privateKey The private key -/// \return A json string of the signed authorization -TW_EXPORT_STATIC_METHOD -TWString *_Nonnull TWBarzSignAuthorization(TWData* _Nonnull chainId, TWString* _Nonnull contractAddress, TWData* _Nonnull nonce, TWString* _Nonnull privateKey); - -/// Returns the encoded hash of the user operation -/// -/// \param chainId The chainId of the network. -/// \param codeAddress The address of the Biz Smart Contract. -/// \param codeName The name of the Biz Smart Contract. -/// \param codeVersion The version of the Biz Smart Contract. -/// \param typeHash The type hash of the transaction. -/// \param domainSeparatorHash The domain separator hash of the wallet. -/// \param sender The address of the UserOperation sender. -/// \param userOpHash The hash of the user operation. -/// \return The encoded hash of the user operation -TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWBarzGetEncodedHash( - TWData* _Nonnull chainId, - TWString* _Nonnull codeAddress, - TWString* _Nonnull codeName, - TWString* _Nonnull codeVersion, - TWString* _Nonnull typeHash, - TWString* _Nonnull domainSeparatorHash, - TWString* _Nonnull sender, - TWString* _Nonnull userOpHash); - -/// Signs a message using the private key -/// -/// \param hash The hash to sign -/// \param privateKey The private key -/// \return The signature -TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWBarzGetSignedHash(TWString* _Nonnull hash, TWString* _Nonnull privateKey); - -TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereum.h b/include/TrustWalletCore/TWEthereum.h deleted file mode 100644 index 9ff49f208b2..00000000000 --- a/include/TrustWalletCore/TWEthereum.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -#include "TWBase.h" -#include "TWString.h" - -TW_EXTERN_C_BEGIN - -TW_EXPORT_STRUCT -struct TWEthereum; - -/// Generate a layer 2 eip2645 derivation path from eth address, layer, application and given index. -/// -/// \param wallet non-null TWHDWallet -/// \param ethAddress non-null Ethereum address -/// \param layer non-null layer 2 name (E.G starkex) -/// \param application non-null layer 2 application (E.G immutablex) -/// \param index non-null layer 2 index (E.G 1) -/// \return a valid eip2645 layer 2 derivation path as a string -TW_EXPORT_STATIC_METHOD -TWString* _Nonnull TWEthereumEip2645GetPath(TWString* _Nonnull ethAddress, TWString* _Nonnull layer, TWString* _Nonnull application, TWString* _Nonnull index); - -TW_EXTERN_C_END diff --git a/rust/Cargo.lock b/rust/Cargo.lock index b60b8a02478..d1e1d9c7423 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1991,6 +1991,7 @@ dependencies = [ "tw_encoding", "tw_hash", "tw_keypair", + "tw_macros", "tw_memory", "tw_misc", "tw_number", @@ -2517,6 +2518,7 @@ dependencies = [ "tw_coin_registry", "tw_encoding", "tw_ethereum", + "tw_evm", "tw_hash", "tw_keypair", "tw_macros", diff --git a/rust/tw_coin_entry/src/error/address_error.rs b/rust/tw_coin_entry/src/error/address_error.rs index 8ad12331858..520eb41075a 100644 --- a/rust/tw_coin_entry/src/error/address_error.rs +++ b/rust/tw_coin_entry/src/error/address_error.rs @@ -3,6 +3,7 @@ // Copyright © 2017 Trust Wallet. use strum_macros::Display; +use tw_encoding::hex::FromHexError; pub type AddressResult = Result; @@ -25,3 +26,9 @@ pub enum AddressError { InvalidWitnessProgram, Internal, } + +impl From for AddressError { + fn from(_: FromHexError) -> Self { + AddressError::FromHexError + } +} diff --git a/rust/tw_encoding/src/hex.rs b/rust/tw_encoding/src/hex.rs index 8eee855bf2d..9a0d9d838fb 100644 --- a/rust/tw_encoding/src/hex.rs +++ b/rust/tw_encoding/src/hex.rs @@ -67,6 +67,28 @@ pub fn encode>(data: T, prefixed: bool) -> String { encoded } +pub fn hex_to_bits(str: &str) -> FromHexResult { + let hex_str = str.trim_start_matches("0x"); + let mut bits = String::new(); + for (index, c) in hex_str.chars().enumerate() { + let val = c + .to_digit(16) + .ok_or(FromHexError::InvalidHexCharacter { c, index })?; + bits.push_str(&format!("{:04b}", val)); + } + Ok(bits) +} + +pub fn data_to_bits(data: &[u8]) -> FromHexResult { + let hex_str = encode(data, false); + hex_to_bits(&hex_str) +} + +pub fn bits_to_u32(bits: &str, from: usize, to: usize) -> FromHexResult { + u32::from_str_radix(&bits[from..to], 2) + .map_err(|_| FromHexError::InvalidHexCharacter { c: '0', index: 0 }) +} + pub mod as_hex { use super::*; use serde::de::Error; @@ -160,6 +182,31 @@ pub mod as_hex_prefixed { } } +pub mod u8_as_hex { + use super::*; + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(value: &u8, serializer: S) -> Result + where + S: Serializer, + { + let hex_str = encode([*value], true); + serializer.serialize_str(&hex_str) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let hex_str = String::deserialize(deserializer)?; + let bytes = decode(&hex_str).map_err(serde::de::Error::custom)?; + bytes + .first() + .copied() + .ok_or_else(|| serde::de::Error::custom("Expected single byte but got empty bytes")) + } +} + #[cfg(test)] mod tests { use super::*; @@ -187,4 +234,12 @@ mod tests { Ok(expected) ); } + + #[test] + fn test_encode_bits() { + assert_eq!( + hex_to_bits("0x2db500ac919cdde351ac36e3711d832c6db97669").unwrap(), + "0010110110110101000000001010110010010001100111001101110111100011010100011010110000110110111000110111000100011101100000110010110001101101101110010111011001101001" + ); + } } diff --git a/rust/tw_evm/Cargo.toml b/rust/tw_evm/Cargo.toml index 7e264f6ae05..80e3302bdbe 100644 --- a/rust/tw_evm/Cargo.toml +++ b/rust/tw_evm/Cargo.toml @@ -13,6 +13,7 @@ tw_coin_entry = { path = "../tw_coin_entry" } tw_encoding = { path = "../tw_encoding" } tw_hash = { path = "../tw_hash" } tw_keypair = { path = "../tw_keypair" } +tw_macros = { path = "../tw_macros" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } tw_number = { path = "../tw_number", features = ["serde"] } @@ -20,3 +21,5 @@ tw_proto = { path = "../tw_proto" } [dev-dependencies] tw_coin_entry = { path = "../tw_coin_entry", features = ["test-utils"] } +tw_memory = { path = "../tw_memory", features = ["test-utils"] } +tw_keypair = { path = "../tw_keypair", features = ["test-utils"] } diff --git a/rust/tw_evm/fuzz/Cargo.toml b/rust/tw_evm/fuzz/Cargo.toml index e008ae78164..6ea327d3045 100644 --- a/rust/tw_evm/fuzz/Cargo.toml +++ b/rust/tw_evm/fuzz/Cargo.toml @@ -52,3 +52,9 @@ name = "sign" path = "fuzz_targets/sign.rs" test = false doc = false + +[[bin]] +name = "diamond_cut_code" +path = "fuzz_targets/diamond_cut_code.rs" +test = false +doc = false diff --git a/rust/tw_evm/fuzz/fuzz_targets/diamond_cut_code.rs b/rust/tw_evm/fuzz/fuzz_targets/diamond_cut_code.rs new file mode 100644 index 00000000000..67e4aebb870 --- /dev/null +++ b/rust/tw_evm/fuzz/fuzz_targets/diamond_cut_code.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_evm::modules::barz::core; +use tw_proto::Barz::Proto; + +fuzz_target!(|input: Proto::DiamondCutInput<'_>| { + let _ = core::get_diamond_cut_code(&input); +}); diff --git a/rust/tw_evm/src/abi/param.rs b/rust/tw_evm/src/abi/param.rs index ba58d999f5b..966e7619e5f 100644 --- a/rust/tw_evm/src/abi/param.rs +++ b/rust/tw_evm/src/abi/param.rs @@ -18,8 +18,6 @@ pub struct Param { } impl Param { - /// Should be used in tests only. - #[cfg(test)] pub(crate) fn with_type(kind: ParamType) -> Param { Param { name: None, diff --git a/rust/tw_evm/src/abi/prebuild/erc1967.rs b/rust/tw_evm/src/abi/prebuild/erc1967.rs new file mode 100644 index 00000000000..990984a7e74 --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/erc1967.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub const EIP_1967_PROXY_BYTE_CODE_HEX: &str = "0x608060405260405162000c5138038062000c51833981810160405281019062000029919062000580565b6200003d828260006200004560201b60201c565b5050620007d7565b62000056836200008860201b60201c565b600082511180620000645750805b156200008357620000818383620000df60201b620000371760201c565b505b505050565b62000099816200011560201b60201c565b8073ffffffffffffffffffffffffffffffffffffffff167fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b60405160405180910390a250565b60606200010d838360405180606001604052806027815260200162000c2a60279139620001eb60201b60201c565b905092915050565b6200012b816200027d60201b620000641760201c565b6200016d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000164906200066d565b60405180910390fd5b80620001a77f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b620002a060201b620000871760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60606000808573ffffffffffffffffffffffffffffffffffffffff1685604051620002179190620006dc565b600060405180830381855af49150503d806000811462000254576040519150601f19603f3d011682016040523d82523d6000602084013e62000259565b606091505b50915091506200027286838387620002aa60201b60201c565b925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b606083156200031a5760008351036200031157620002ce856200027d60201b60201c565b62000310576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620003079062000745565b60405180910390fd5b5b8290506200032d565b6200032c83836200033560201b60201c565b5b949350505050565b600082511115620003495781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200037f9190620007b3565b60405180910390fd5b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620003c9826200039c565b9050919050565b620003db81620003bc565b8114620003e757600080fd5b50565b600081519050620003fb81620003d0565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b62000456826200040b565b810181811067ffffffffffffffff821117156200047857620004776200041c565b5b80604052505050565b60006200048d62000388565b90506200049b82826200044b565b919050565b600067ffffffffffffffff821115620004be57620004bd6200041c565b5b620004c9826200040b565b9050602081019050919050565b60005b83811015620004f6578082015181840152602081019050620004d9565b60008484015250505050565b6000620005196200051384620004a0565b62000481565b90508281526020810184848401111562000538576200053762000406565b5b62000545848285620004d6565b509392505050565b600082601f83011262000565576200056462000401565b5b81516200057784826020860162000502565b91505092915050565b600080604083850312156200059a576200059962000392565b5b6000620005aa85828601620003ea565b925050602083015167ffffffffffffffff811115620005ce57620005cd62000397565b5b620005dc858286016200054d565b9150509250929050565b600082825260208201905092915050565b7f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60008201527f6f74206120636f6e747261637400000000000000000000000000000000000000602082015250565b600062000655602d83620005e6565b91506200066282620005f7565b604082019050919050565b60006020820190508181036000830152620006888162000646565b9050919050565b600081519050919050565b600081905092915050565b6000620006b2826200068f565b620006be81856200069a565b9350620006d0818560208601620004d6565b80840191505092915050565b6000620006ea8284620006a5565b915081905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b60006200072d601d83620005e6565b91506200073a82620006f5565b602082019050919050565b6000602082019050818103600083015262000760816200071e565b9050919050565b600081519050919050565b60006200077f8262000767565b6200078b8185620005e6565b93506200079d818560208601620004d6565b620007a8816200040b565b840191505092915050565b60006020820190508181036000830152620007cf818462000772565b905092915050565b61044380620007e76000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b610025610091565b610035610030610093565b6100a2565b565b606061005c83836040518060600160405280602781526020016103e7602791396100c8565b905092915050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b565b600061009d61014e565b905090565b3660008037600080366000845af43d6000803e80600081146100c3573d6000f35b3d6000fd5b60606000808573ffffffffffffffffffffffffffffffffffffffff16856040516100f291906102db565b600060405180830381855af49150503d806000811461012d576040519150601f19603f3d011682016040523d82523d6000602084013e610132565b606091505b5091509150610143868383876101a5565b925050509392505050565b600061017c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b610087565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606083156102075760008351036101ff576101bf85610064565b6101fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101f59061034f565b60405180910390fd5b5b829050610212565b610211838361021a565b5b949350505050565b60008251111561022d5781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161026191906103c4565b60405180910390fd5b600081519050919050565b600081905092915050565b60005b8381101561029e578082015181840152602081019050610283565b60008484015250505050565b60006102b58261026a565b6102bf8185610275565b93506102cf818560208601610280565b80840191505092915050565b60006102e782846102aa565b915081905092915050565b600082825260208201905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b6000610339601d836102f2565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600081519050919050565b6000601f19601f8301169050919050565b60006103968261036f565b6103a081856102f2565b93506103b0818560208601610280565b6103b98161037a565b840191505092915050565b600060208201905081810360008301526103de818461038b565b90509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e57dd3eafc9985be746025b6d82d4f011b9a7bb3db56f9a1eb7eadfddd376b6064736f6c63430008110033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564"; diff --git a/rust/tw_evm/src/abi/prebuild/mod.rs b/rust/tw_evm/src/abi/prebuild/mod.rs index 5eeb9ab85a1..cc9012e5833 100644 --- a/rust/tw_evm/src/abi/prebuild/mod.rs +++ b/rust/tw_evm/src/abi/prebuild/mod.rs @@ -8,6 +8,7 @@ use tw_number::U256; pub mod biz; pub mod erc1155; +pub mod erc1967; pub mod erc20; pub mod erc4337; pub mod erc721; diff --git a/rust/tw_evm/src/address.rs b/rust/tw_evm/src/address.rs index 8cff9fb514d..41d97a77be6 100644 --- a/rust/tw_evm/src/address.rs +++ b/rust/tw_evm/src/address.rs @@ -10,10 +10,15 @@ use std::str::FromStr; use tw_coin_entry::coin_entry::CoinAddress; use tw_coin_entry::error::prelude::*; use tw_encoding::hex; +use tw_hash::sha2::sha256; use tw_hash::{sha3::keccak256, H160, H256}; use tw_keypair::ecdsa::secp256k1; use tw_memory::Data; +use crate::abi::encode; +use crate::abi::prebuild::erc1967::EIP_1967_PROXY_BYTE_CODE_HEX; +use crate::abi::token::Token; + pub trait EvmAddress: FromStr + Into
{ /// Tries to parse an address from the string representation. /// Returns `Ok(None)` if the given `s` string is empty. @@ -59,7 +64,7 @@ impl Address { /// Displays the address in mixed-case checksum form /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md - fn into_checksum_address(self) -> String { + pub fn into_checksum_address(self) -> String { const UPPER_RANGE_1: RangeInclusive = '8'..='9'; const UPPER_RANGE_2: RangeInclusive = 'a'..='f'; @@ -80,6 +85,60 @@ impl Address { "0x".chars().chain(payload_chars).collect() } + /// Returns the account path from the address according to EIP-2645 + /// https://docs.starkware.co/starkex/key-derivation.html + pub fn account_path( + &self, + layer: &str, + application: &str, + index: &str, + ) -> Result { + let layer_bits = hex::data_to_bits(sha256(layer.as_bytes()).as_slice())?; + let layer_hash = hex::bits_to_u32(&layer_bits, 256 - 31, 256)?; + let application_bits = hex::data_to_bits(sha256(application.as_bytes()).as_slice())?; + let application_hash = hex::bits_to_u32(&application_bits, 256 - 31, 256)?; + let addr_hex = hex::encode(self.bytes, false); + + let addr_bits = hex::hex_to_bits(&addr_hex)?; + let addr1 = u32::from_str_radix(&addr_bits[addr_bits.len() - 31..], 2) + .map_err(|_| AddressError::InvalidInput)?; + let addr2 = u32::from_str_radix(&addr_bits[addr_bits.len() - 62..addr_bits.len() - 31], 2) + .map_err(|_| AddressError::InvalidInput)?; + + Ok(format!( + "m/2645'/{layer_hash}'/{application_hash}'/{addr1}'/{addr2}'/{index}" + )) + } + + pub fn eip1014_create2_address( + from: &str, + salt: &H256, + init_code_hash: &H256, + ) -> Result { + let from = Address::from_str(from)?; + let mut address = vec![255]; + address.extend_from_slice(from.as_slice()); + address.extend_from_slice(salt.as_slice()); + address.extend_from_slice(init_code_hash.as_slice()); + let address = keccak256(&address); + let mut address_bytes = [0u8; 20]; + address_bytes.copy_from_slice(&address[address.len() - 20..]); + Ok(Address::from_bytes(address_bytes.into())) + } + + pub fn eip_1967_proxy_init_code( + logic_address: &str, + data: &[u8], + ) -> Result, AddressError> { + let logic_address = Address::from_str(logic_address)?; + + let tokens = [Token::Address(logic_address), Token::Bytes(data.to_vec())]; + + let mut encoded = hex::decode(EIP_1967_PROXY_BYTE_CODE_HEX).expect("Expected valid hex"); + encoded.extend_from_slice(&encode::encode_tokens(&tokens)); + Ok(encoded) + } + /// Returns bytes of the address. #[inline] pub fn bytes(&self) -> H160 { @@ -227,4 +286,177 @@ mod tests { "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309" ); } + + #[test] + fn test_account_path() { + // https://github.com/immutable/imx-core-sdk-swift/blob/main/Tests/ImmutableXCoreTests/Crypto/Stark/StarkKeyTests.swift#L30 + let addr: Address = "0xa76e3eeb2f7143165618ab8feaabcd395b6fac7f" + .parse() + .unwrap(); + let path = addr.account_path("starkex", "immutablex", "1").unwrap(); + assert_eq!( + path, + "m/2645'/579218131'/211006541'/1534045311'/1431804530'/1" + ); + } + + #[test] + fn test_eip1014_create2_address_example0() { + let from = "0x0000000000000000000000000000000000000000"; + let salt = H256::try_from( + hex::decode("0x0000000000000000000000000000000000000000000000000000000000000000") + .unwrap() + .as_slice(), + ) + .unwrap(); + let init_code_hash = + H256::try_from(keccak256(hex::decode("0x00").unwrap().as_slice()).as_slice()).unwrap(); + let address = Address::eip1014_create2_address(from, &salt, &init_code_hash).unwrap(); + assert_eq!( + address.into_checksum_address(), + "0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38" + ); + } + + #[test] + fn test_eip1014_create2_address_example1() { + let from = "0xdeadbeef00000000000000000000000000000000"; + let salt = H256::try_from( + hex::decode("0x0000000000000000000000000000000000000000000000000000000000000000") + .unwrap() + .as_slice(), + ) + .unwrap(); + let init_code_hash = + H256::try_from(keccak256(hex::decode("0x00").unwrap().as_slice()).as_slice()).unwrap(); + let address = Address::eip1014_create2_address(from, &salt, &init_code_hash).unwrap(); + assert_eq!( + address.into_checksum_address(), + "0xB928f69Bb1D91Cd65274e3c79d8986362984fDA3" + ); + } + + #[test] + fn test_eip1014_create2_address_example2() { + let from = "0xdeadbeef00000000000000000000000000000000"; + let salt = H256::try_from( + hex::decode("0x000000000000000000000000feed000000000000000000000000000000000000") + .unwrap() + .as_slice(), + ) + .unwrap(); + let mut init_code = hex::decode("0x00").unwrap(); + init_code.resize(32, 0); + let init_code_hash = H256::try_from(init_code.as_slice()).unwrap(); + let address = Address::eip1014_create2_address(from, &salt, &init_code_hash).unwrap(); + assert_eq!( + address.into_checksum_address(), + "0x2DB27D1d6BE32C9abfA484BA3d591101881D4B9f" + ); + } + + #[test] + fn test_eip1014_create2_address_example3() { + let from = "0x0000000000000000000000000000000000000000"; + let salt = H256::try_from( + hex::decode("0x0000000000000000000000000000000000000000000000000000000000000000") + .unwrap() + .as_slice(), + ) + .unwrap(); + let mut init_code = hex::decode("0xdeadbeef").unwrap(); + init_code.resize(32, 0); + let init_code_hash = H256::try_from(init_code.as_slice()).unwrap(); + let address = Address::eip1014_create2_address(from, &salt, &init_code_hash).unwrap(); + assert_eq!( + address.into_checksum_address(), + "0x219438aC82230Cb9A9C13Cd99D324fA1d66CF018" + ); + } + + #[test] + fn test_eip1014_create2_address_example4() { + let from = "0x00000000000000000000000000000000deadbeef"; + let salt = H256::try_from( + hex::decode("0x00000000000000000000000000000000000000000000000000000000cafebabe") + .unwrap() + .as_slice(), + ) + .unwrap(); + let init_code_hash = + H256::try_from(keccak256(hex::decode("0xdeadbeef").unwrap().as_slice()).as_slice()) + .unwrap(); + let address = Address::eip1014_create2_address(from, &salt, &init_code_hash).unwrap(); + assert_eq!( + address.into_checksum_address(), + "0x60f3f640a8508fC6a86d45DF051962668E1e8AC7" + ); + } + + #[test] + fn test_eip1014_create2_address_example5() { + let from = "0x00000000000000000000000000000000deadbeef"; + let salt = H256::try_from( + hex::decode("0x00000000000000000000000000000000000000000000000000000000cafebabe") + .unwrap() + .as_slice(), + ) + .unwrap(); + let init_code_hash = H256::try_from(keccak256(hex::decode("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef").unwrap().as_slice()).as_slice()).unwrap(); + let address = Address::eip1014_create2_address(from, &salt, &init_code_hash).unwrap(); + assert_eq!( + address.into_checksum_address(), + "0x1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C" + ); + } + + #[test] + fn test_eip1014_create2_address_example6() { + let from = "0x0000000000000000000000000000000000000000"; + let salt = H256::try_from( + hex::decode("0x0000000000000000000000000000000000000000000000000000000000000000") + .unwrap() + .as_slice(), + ) + .unwrap(); + let init_code_hash = + H256::try_from(keccak256(hex::decode("0x").unwrap().as_slice()).as_slice()).unwrap(); + let address = Address::eip1014_create2_address(from, &salt, &init_code_hash).unwrap(); + assert_eq!( + address.into_checksum_address(), + "0xE33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0" + ); + } + + #[test] + fn test_eip1014_create2_address_example7() { + let from = "0x7EF2e0048f5bAeDe046f6BF797943daF4ED8CB47"; + let salt = H256::try_from( + hex::decode("0x0000000000000000000000000000000000000000000000000000000000000000") + .unwrap() + .as_slice(), + ) + .unwrap(); + let init_code_hash = keccak256(hex::decode("0x608060405260405162000c5138038062000c51833981810160405281019062000029919062000580565b6200003d828260006200004560201b60201c565b5050620007d7565b62000056836200008860201b60201c565b600082511180620000645750805b156200008357620000818383620000df60201b620000371760201c565b505b505050565b62000099816200011560201b60201c565b8073ffffffffffffffffffffffffffffffffffffffff167fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b60405160405180910390a250565b60606200010d838360405180606001604052806027815260200162000c2a60279139620001eb60201b60201c565b905092915050565b6200012b816200027d60201b620000641760201c565b6200016d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000164906200066d565b60405180910390fd5b80620001a77f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b620002a060201b620000871760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60606000808573ffffffffffffffffffffffffffffffffffffffff1685604051620002179190620006dc565b600060405180830381855af49150503d806000811462000254576040519150601f19603f3d011682016040523d82523d6000602084013e62000259565b606091505b50915091506200027286838387620002aa60201b60201c565b925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b606083156200031a5760008351036200031157620002ce856200027d60201b60201c565b62000310576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620003079062000745565b60405180910390fd5b5b8290506200032d565b6200032c83836200033560201b60201c565b5b949350505050565b600082511115620003495781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200037f9190620007b3565b60405180910390fd5b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620003c9826200039c565b9050919050565b620003db81620003bc565b8114620003e757600080fd5b50565b600081519050620003fb81620003d0565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b62000456826200040b565b810181811067ffffffffffffffff821117156200047857620004776200041c565b5b80604052505050565b60006200048d62000388565b90506200049b82826200044b565b919050565b600067ffffffffffffffff821115620004be57620004bd6200041c565b5b620004c9826200040b565b9050602081019050919050565b60005b83811015620004f6578082015181840152602081019050620004d9565b60008484015250505050565b6000620005196200051384620004a0565b62000481565b90508281526020810184848401111562000538576200053762000406565b5b62000545848285620004d6565b509392505050565b600082601f83011262000565576200056462000401565b5b81516200057784826020860162000502565b91505092915050565b600080604083850312156200059a576200059962000392565b5b6000620005aa85828601620003ea565b925050602083015167ffffffffffffffff811115620005ce57620005cd62000397565b5b620005dc858286016200054d565b9150509250929050565b600082825260208201905092915050565b7f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60008201527f6f74206120636f6e747261637400000000000000000000000000000000000000602082015250565b600062000655602d83620005e6565b91506200066282620005f7565b604082019050919050565b60006020820190508181036000830152620006888162000646565b9050919050565b600081519050919050565b600081905092915050565b6000620006b2826200068f565b620006be81856200069a565b9350620006d0818560208601620004d6565b80840191505092915050565b6000620006ea8284620006a5565b915081905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b60006200072d601d83620005e6565b91506200073a82620006f5565b602082019050919050565b6000602082019050818103600083015262000760816200071e565b9050919050565b600081519050919050565b60006200077f8262000767565b6200078b8185620005e6565b93506200079d818560208601620004d6565b620007a8816200040b565b840191505092915050565b60006020820190508181036000830152620007cf818462000772565b905092915050565b61044380620007e76000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b610025610091565b610035610030610093565b6100a2565b565b606061005c83836040518060600160405280602781526020016103e7602791396100c8565b905092915050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b565b600061009d61014e565b905090565b3660008037600080366000845af43d6000803e80600081146100c3573d6000f35b3d6000fd5b60606000808573ffffffffffffffffffffffffffffffffffffffff16856040516100f291906102db565b600060405180830381855af49150503d806000811461012d576040519150601f19603f3d011682016040523d82523d6000602084013e610132565b606091505b5091509150610143868383876101a5565b925050509392505050565b600061017c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b610087565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606083156102075760008351036101ff576101bf85610064565b6101fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101f59061034f565b60405180910390fd5b5b829050610212565b610211838361021a565b5b949350505050565b60008251111561022d5781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161026191906103c4565b60405180910390fd5b600081519050919050565b600081905092915050565b60005b8381101561029e578082015181840152602081019050610283565b60008484015250505050565b60006102b58261026a565b6102bf8185610275565b93506102cf818560208601610280565b80840191505092915050565b60006102e782846102aa565b915081905092915050565b600082825260208201905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b6000610339601d836102f2565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600081519050919050565b6000601f19601f8301169050919050565b60006103968261036f565b6103a081856102f2565b93506103b0818560208601610280565b6103b98161037a565b840191505092915050565b600060208201905081810360008301526103de818461038b565b90509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e57dd3eafc9985be746025b6d82d4f011b9a7bb3db56f9a1eb7eadfddd376b6064736f6c63430008110033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564000000000000000000000000d9ec9e840bb5df076dbbb488d01485058f421e5800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000be8fa0112dcb7d21dc63645b633073651e19934800000000000000000000000000000000000000000000000000000000").unwrap().as_slice()); + let init_code_hash = H256::try_from(init_code_hash.as_slice()).unwrap(); + let address = Address::eip1014_create2_address(from, &salt, &init_code_hash).unwrap(); + assert_eq!( + address.into_checksum_address(), + "0x4455e5f0038795939c001aa4d296A45956C460AA" + ); + } + + #[test] + fn test_eip1967_example0() { + let login_address = "0x5C9eb5D6a6C2c1B3EFc52255C0b356f116f6f66D"; + let data = hex::decode( + "0xc4d66de8000000000000000000000000a5a1dddef094095afb7b6e322de72961df2e1988", + ) + .unwrap(); + let init_code = Address::eip_1967_proxy_init_code(login_address, &data).unwrap(); + assert_eq!( + hex::encode(&init_code, true), + "0x608060405260405162000c5138038062000c51833981810160405281019062000029919062000580565b6200003d828260006200004560201b60201c565b5050620007d7565b62000056836200008860201b60201c565b600082511180620000645750805b156200008357620000818383620000df60201b620000371760201c565b505b505050565b62000099816200011560201b60201c565b8073ffffffffffffffffffffffffffffffffffffffff167fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b60405160405180910390a250565b60606200010d838360405180606001604052806027815260200162000c2a60279139620001eb60201b60201c565b905092915050565b6200012b816200027d60201b620000641760201c565b6200016d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000164906200066d565b60405180910390fd5b80620001a77f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b620002a060201b620000871760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60606000808573ffffffffffffffffffffffffffffffffffffffff1685604051620002179190620006dc565b600060405180830381855af49150503d806000811462000254576040519150601f19603f3d011682016040523d82523d6000602084013e62000259565b606091505b50915091506200027286838387620002aa60201b60201c565b925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b606083156200031a5760008351036200031157620002ce856200027d60201b60201c565b62000310576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620003079062000745565b60405180910390fd5b5b8290506200032d565b6200032c83836200033560201b60201c565b5b949350505050565b600082511115620003495781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200037f9190620007b3565b60405180910390fd5b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620003c9826200039c565b9050919050565b620003db81620003bc565b8114620003e757600080fd5b50565b600081519050620003fb81620003d0565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b62000456826200040b565b810181811067ffffffffffffffff821117156200047857620004776200041c565b5b80604052505050565b60006200048d62000388565b90506200049b82826200044b565b919050565b600067ffffffffffffffff821115620004be57620004bd6200041c565b5b620004c9826200040b565b9050602081019050919050565b60005b83811015620004f6578082015181840152602081019050620004d9565b60008484015250505050565b6000620005196200051384620004a0565b62000481565b90508281526020810184848401111562000538576200053762000406565b5b62000545848285620004d6565b509392505050565b600082601f83011262000565576200056462000401565b5b81516200057784826020860162000502565b91505092915050565b600080604083850312156200059a576200059962000392565b5b6000620005aa85828601620003ea565b925050602083015167ffffffffffffffff811115620005ce57620005cd62000397565b5b620005dc858286016200054d565b9150509250929050565b600082825260208201905092915050565b7f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60008201527f6f74206120636f6e747261637400000000000000000000000000000000000000602082015250565b600062000655602d83620005e6565b91506200066282620005f7565b604082019050919050565b60006020820190508181036000830152620006888162000646565b9050919050565b600081519050919050565b600081905092915050565b6000620006b2826200068f565b620006be81856200069a565b9350620006d0818560208601620004d6565b80840191505092915050565b6000620006ea8284620006a5565b915081905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b60006200072d601d83620005e6565b91506200073a82620006f5565b602082019050919050565b6000602082019050818103600083015262000760816200071e565b9050919050565b600081519050919050565b60006200077f8262000767565b6200078b8185620005e6565b93506200079d818560208601620004d6565b620007a8816200040b565b840191505092915050565b60006020820190508181036000830152620007cf818462000772565b905092915050565b61044380620007e76000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b610025610091565b610035610030610093565b6100a2565b565b606061005c83836040518060600160405280602781526020016103e7602791396100c8565b905092915050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b565b600061009d61014e565b905090565b3660008037600080366000845af43d6000803e80600081146100c3573d6000f35b3d6000fd5b60606000808573ffffffffffffffffffffffffffffffffffffffff16856040516100f291906102db565b600060405180830381855af49150503d806000811461012d576040519150601f19603f3d011682016040523d82523d6000602084013e610132565b606091505b5091509150610143868383876101a5565b925050509392505050565b600061017c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b610087565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606083156102075760008351036101ff576101bf85610064565b6101fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101f59061034f565b60405180910390fd5b5b829050610212565b610211838361021a565b5b949350505050565b60008251111561022d5781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161026191906103c4565b60405180910390fd5b600081519050919050565b600081905092915050565b60005b8381101561029e578082015181840152602081019050610283565b60008484015250505050565b60006102b58261026a565b6102bf8185610275565b93506102cf818560208601610280565b80840191505092915050565b60006102e782846102aa565b915081905092915050565b600082825260208201905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b6000610339601d836102f2565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600081519050919050565b6000601f19601f8301169050919050565b60006103968261036f565b6103a081856102f2565b93506103b0818560208601610280565b6103b98161037a565b840191505092915050565b600060208201905081810360008301526103de818461038b565b90509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e57dd3eafc9985be746025b6d82d4f011b9a7bb3db56f9a1eb7eadfddd376b6064736f6c63430008110033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c65640000000000000000000000005c9eb5d6a6c2c1b3efc52255c0b356f116f6f66d00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000a5a1dddef094095afb7b6e322de72961df2e198800000000000000000000000000000000000000000000000000000000" + ); + } } diff --git a/rust/tw_evm/src/ffi/barz.rs b/rust/tw_evm/src/ffi/barz.rs new file mode 100644 index 00000000000..5cbca2be829 --- /dev/null +++ b/rust/tw_evm/src/ffi/barz.rs @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#![allow(clippy::missing_safety_doc)] + +use crate::modules::barz::core::{ + get_authorization_hash, get_counterfactual_address, get_diamond_cut_code, get_encoded_hash, + get_formatted_signature, get_init_code, get_prefixed_msg_hash, sign_authorization, + sign_user_op_hash, +}; +use tw_keypair::ffi::pubkey::{tw_public_key_data, TWPublicKey}; +use tw_macros::tw_ffi; +use tw_memory::ffi::{ + tw_data::TWData, tw_string::TWString, Nonnull, NonnullMut, NullableMut, RawPtrTrait, +}; +use tw_misc::try_or_else; +use tw_proto::{ + Barz::Proto::{ContractAddressInput, DiamondCutInput}, + BytesReader, MessageRead, +}; + +/// Calculate a counterfactual address for the smart contract wallet +/// +/// \param input The serialized data of ContractAddressInput. +/// \return The address. +#[tw_ffi(ty = static_function, class = TWBarz, name = GetCounterfactualAddress)] +#[no_mangle] +pub unsafe extern "C" fn tw_barz_get_counterfactual_address( + input: Nonnull, +) -> NullableMut { + let input = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let mut reader = BytesReader::from_bytes(input.as_slice()); + let input = try_or_else!( + ContractAddressInput::from_reader(&mut reader, input.as_slice()), + std::ptr::null_mut + ); + let address = try_or_else!(get_counterfactual_address(&input), std::ptr::null_mut); + TWString::from(address).into_ptr() +} + +/// Returns the init code parameter of ERC-4337 User Operation +/// +/// \param factory The address of the factory contract. +/// \param public_key Public key for the verification facet +/// \param verification_facet The address of the verification facet. +/// \param salt The salt of the init code. +/// \return The init code. +#[tw_ffi(ty = static_function, class = TWBarz, name = GetInitCode)] +#[no_mangle] +pub unsafe extern "C" fn tw_barz_get_init_code( + factory: Nonnull, + public_key: NonnullMut, + verification_facet: Nonnull, + salt: u32, +) -> NullableMut { + let factory_address = try_or_else!(TWString::from_ptr_as_ref(factory), std::ptr::null_mut); + let factory_address = try_or_else!(factory_address.as_str(), std::ptr::null_mut); + let public_key = try_or_else!(TWPublicKey::from_ptr_as_mut(public_key), std::ptr::null_mut); + let public_key_data = tw_public_key_data(public_key); + let verification_facet = try_or_else!( + TWString::from_ptr_as_ref(verification_facet), + std::ptr::null_mut + ); + let verification_facet = try_or_else!(verification_facet.as_str(), std::ptr::null_mut); + let init_code = try_or_else!( + get_init_code( + factory_address, + public_key_data.as_slice(), + verification_facet, + salt + ), + std::ptr::null_mut + ); + TWData::from(init_code).into_ptr() +} + +/// Converts the original ASN-encoded signature from webauthn to the format accepted by Barz +/// +/// \param signature Original signature +/// \param challenge The original challenge that was signed +/// \param authenticator_data Returned from Webauthn API +/// \param client_data_json Returned from Webauthn API +/// \return Bytes of the formatted signature +#[tw_ffi(ty = static_function, class = TWBarz, name = GetFormattedSignature)] +#[no_mangle] +pub unsafe extern "C" fn tw_barz_get_formatted_signature( + signature: Nonnull, + challenge: Nonnull, + authenticator_data: Nonnull, + client_data_json: Nonnull, +) -> NullableMut { + let signature = try_or_else!(TWData::from_ptr_as_ref(signature), std::ptr::null_mut); + let challenge = try_or_else!(TWData::from_ptr_as_ref(challenge), std::ptr::null_mut); + let authenticator_data = try_or_else!( + TWData::from_ptr_as_ref(authenticator_data), + std::ptr::null_mut + ); + let client_data_json = try_or_else!( + TWString::from_ptr_as_ref(client_data_json), + std::ptr::null_mut + ); + let client_data_json = try_or_else!(client_data_json.as_str(), std::ptr::null_mut); + let formatted_signature = try_or_else!( + get_formatted_signature( + signature.as_slice(), + challenge.as_slice(), + authenticator_data.as_slice(), + client_data_json + ), + std::ptr::null_mut + ); + TWData::from(formatted_signature).into_ptr() +} + +/// Returns the final hash to be signed by Barz for signing messages & typed data +/// +/// \param msg_hash Original msgHash +/// \param barzAddress The address of Barz wallet signing the message +/// \param chainId The chainId of the network the verification will happen +/// \return The final hash to be signed. +#[tw_ffi(ty = static_function, class = TWBarz, name = GetPrefixedMsgHash)] +#[no_mangle] +pub unsafe extern "C" fn tw_barz_get_prefixed_msg_hash( + msg_hash: Nonnull, + barz_address: Nonnull, + chain_id: u32, +) -> NullableMut { + let msg_hash = try_or_else!(TWData::from_ptr_as_ref(msg_hash), std::ptr::null_mut); + let barz_address = try_or_else!(TWString::from_ptr_as_ref(barz_address), std::ptr::null_mut); + let barz_address = try_or_else!(barz_address.as_str(), std::ptr::null_mut); + let prefixed_msg_hash = try_or_else!( + get_prefixed_msg_hash(msg_hash.as_slice(), barz_address, chain_id), + std::ptr::null_mut + ); + TWData::from(prefixed_msg_hash).into_ptr() +} + +/// Returns the encoded diamondCut function call for Barz contract upgrades +/// +/// \param input The serialized data of DiamondCutInput. +/// \return The diamond cut code. +#[tw_ffi(ty = static_function, class = TWBarz, name = GetDiamondCutCode)] +#[no_mangle] +pub unsafe extern "C" fn tw_barz_get_diamond_cut_code( + input: Nonnull, +) -> NullableMut { + let input = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let mut reader = BytesReader::from_bytes(input.as_slice()); + let input = try_or_else!( + DiamondCutInput::from_reader(&mut reader, input.as_slice()), + std::ptr::null_mut + ); + let diamond_cut_code = try_or_else!(get_diamond_cut_code(&input), std::ptr::null_mut); + TWData::from(diamond_cut_code).into_ptr() +} + +/// Computes an Authorization hash in [EIP-7702 format](https://eips.ethereum.org/EIPS/eip-7702) +/// `keccak256('0x05' || rlp([chain_id, address, nonce]))`. +/// +/// \param chain_id The chain ID of the user. +/// \param contract_address The address of the smart contract wallet. +/// \param nonce The nonce of the user. +/// \return The authorization hash. +#[tw_ffi(ty = static_function, class = TWBarz, name = GetAuthorizationHash)] +#[no_mangle] +pub unsafe extern "C" fn tw_barz_get_authorization_hash( + chain_id: Nonnull, + contract_address: Nonnull, + nonce: Nonnull, +) -> NullableMut { + let chain_id = try_or_else!(TWData::from_ptr_as_ref(chain_id), std::ptr::null_mut); + let contract_address = try_or_else!( + TWString::from_ptr_as_ref(contract_address), + std::ptr::null_mut + ); + let contract_address = try_or_else!(contract_address.as_str(), std::ptr::null_mut); + let nonce = try_or_else!(TWData::from_ptr_as_ref(nonce), std::ptr::null_mut); + let authorization_hash = try_or_else!( + get_authorization_hash(chain_id.as_slice(), contract_address, nonce.as_slice()), + std::ptr::null_mut + ); + TWData::from(authorization_hash).into_ptr() +} + +/// Returns the signed authorization hash +/// +/// \param chain_id The chain ID of the user. +/// \param contract_address The address of the smart contract wallet. +/// \param nonce The nonce of the user. +/// \param private_key The private key of the user. +/// \return The signed authorization. +#[tw_ffi(ty = static_function, class = TWBarz, name = SignAuthorization)] +#[no_mangle] +pub unsafe extern "C" fn tw_barz_sign_authorization( + chain_id: Nonnull, + contract_address: Nonnull, + nonce: Nonnull, + private_key: Nonnull, +) -> NullableMut { + let chain_id = try_or_else!(TWData::from_ptr_as_ref(chain_id), std::ptr::null_mut); + let contract_address = try_or_else!( + TWString::from_ptr_as_ref(contract_address), + std::ptr::null_mut + ); + let contract_address = try_or_else!(contract_address.as_str(), std::ptr::null_mut); + let nonce = try_or_else!(TWData::from_ptr_as_ref(nonce), std::ptr::null_mut); + let private_key = try_or_else!(TWString::from_ptr_as_ref(private_key), std::ptr::null_mut); + let private_key = try_or_else!(private_key.as_str(), std::ptr::null_mut); + let signed_authorization = try_or_else!( + sign_authorization( + chain_id.as_slice(), + contract_address, + nonce.as_slice(), + private_key + ), + std::ptr::null_mut + ); + TWString::from(signed_authorization).into_ptr() +} + +/// Returns the encoded hash of the user operation +/// +/// \param chain_id The chain ID of the user. +/// \param code_address The address of the smart contract wallet. +/// \param code_name The name of the smart contract wallet. +/// \param code_version The version of the smart contract wallet. +/// \param type_hash The type hash of the smart contract wallet. +/// \param domain_separator_hash The domain separator hash of the smart contract wallet. +/// \param sender The sender of the smart contract wallet. +/// \param user_op_hash The user operation hash of the smart contract wallet. +/// \return The encoded hash. +#[tw_ffi(ty = static_function, class = TWBarz, name = GetEncodedHash)] +#[no_mangle] +pub unsafe extern "C" fn tw_barz_get_encoded_hash( + chain_id: Nonnull, + code_address: Nonnull, + code_name: Nonnull, + code_version: Nonnull, + type_hash: Nonnull, + domain_separator_hash: Nonnull, + sender: Nonnull, + user_op_hash: Nonnull, +) -> NullableMut { + let chain_id = try_or_else!(TWData::from_ptr_as_ref(chain_id), std::ptr::null_mut); + let code_address = try_or_else!(TWString::from_ptr_as_ref(code_address), std::ptr::null_mut); + let code_address = try_or_else!(code_address.as_str(), std::ptr::null_mut); + let code_name = try_or_else!(TWString::from_ptr_as_ref(code_name), std::ptr::null_mut); + let code_name = try_or_else!(code_name.as_str(), std::ptr::null_mut); + let code_version = try_or_else!(TWString::from_ptr_as_ref(code_version), std::ptr::null_mut); + let code_version = try_or_else!(code_version.as_str(), std::ptr::null_mut); + let type_hash = try_or_else!(TWString::from_ptr_as_ref(type_hash), std::ptr::null_mut); + let type_hash = try_or_else!(type_hash.as_str(), std::ptr::null_mut); + let domain_separator_hash = try_or_else!( + TWString::from_ptr_as_ref(domain_separator_hash), + std::ptr::null_mut + ); + let domain_separator_hash = try_or_else!(domain_separator_hash.as_str(), std::ptr::null_mut); + let sender = try_or_else!(TWString::from_ptr_as_ref(sender), std::ptr::null_mut); + let sender = try_or_else!(sender.as_str(), std::ptr::null_mut); + let user_op_hash = try_or_else!(TWString::from_ptr_as_ref(user_op_hash), std::ptr::null_mut); + let user_op_hash = try_or_else!(user_op_hash.as_str(), std::ptr::null_mut); + let encoded_hash = try_or_else!( + get_encoded_hash( + chain_id.as_slice(), + code_address, + code_name, + code_version, + type_hash, + domain_separator_hash, + sender, + user_op_hash + ), + std::ptr::null_mut + ); + TWData::from(encoded_hash).into_ptr() +} + +/// Signs a message using the private key +/// +/// \param hash The hash of the user. +/// \param private_key The private key of the user. +/// \return The signed hash. +#[tw_ffi(ty = static_function, class = TWBarz, name = GetSignedHash)] +#[no_mangle] +pub unsafe extern "C" fn tw_barz_get_signed_hash( + hash: Nonnull, + private_key: Nonnull, +) -> NullableMut { + let hash = try_or_else!(TWString::from_ptr_as_ref(hash), std::ptr::null_mut); + let hash = try_or_else!(hash.as_str(), std::ptr::null_mut); + let private_key = try_or_else!(TWString::from_ptr_as_ref(private_key), std::ptr::null_mut); + let private_key = try_or_else!(private_key.as_str(), std::ptr::null_mut); + let signed_hash = try_or_else!(sign_user_op_hash(hash, private_key), std::ptr::null_mut); + TWData::from(signed_hash).into_ptr() +} diff --git a/rust/tw_evm/src/ffi/ethereum_address.rs b/rust/tw_evm/src/ffi/ethereum_address.rs new file mode 100644 index 00000000000..e93ab92b3d2 --- /dev/null +++ b/rust/tw_evm/src/ffi/ethereum_address.rs @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#![allow(clippy::missing_safety_doc)] + +use std::str::FromStr; + +use crate::address::Address; +use tw_hash::H256; +use tw_macros::tw_ffi; +use tw_memory::ffi::{tw_data::TWData, tw_string::TWString, Nonnull, NullableMut, RawPtrTrait}; +use tw_misc::try_or_else; + +/// Returns the checksummed address. +/// +/// \param address *non-null* string. +/// \return the checksummed address. +#[tw_ffi(ty = static_function, class = TWEthereum, name = AddressChecksummed)] +#[no_mangle] +pub unsafe extern "C" fn tw_ethereum_address_checksummed( + address: Nonnull, +) -> NullableMut { + let address = try_or_else!(TWString::from_ptr_as_ref(address), std::ptr::null_mut); + let address = try_or_else!(address.as_str(), std::ptr::null_mut); + let address = try_or_else!(Address::from_str(address), std::ptr::null_mut); + let checksummed = address.into_checksum_address(); + TWString::from(checksummed).into_ptr() +} + +/// Returns the account path from address. +/// +/// \param eth_address *non-null* string. +/// \param layer *non-null* string. +/// \param application *non-null* string. +/// \param index *non-null* string. +/// \return the account path. +#[tw_ffi(ty = static_function, class = TWEthereum, name = Eip2645GetPath)] +#[no_mangle] +pub unsafe extern "C" fn tw_ethereum_eip2645_get_path( + eth_address: Nonnull, + layer: Nonnull, + application: Nonnull, + index: Nonnull, +) -> NullableMut { + let address = try_or_else!(TWString::from_ptr_as_ref(eth_address), std::ptr::null_mut); + let address = try_or_else!(address.as_str(), std::ptr::null_mut); + let address = try_or_else!(Address::from_str(address), std::ptr::null_mut); + let layer = try_or_else!(TWString::from_ptr_as_ref(layer), std::ptr::null_mut); + let layer = try_or_else!(layer.as_str(), std::ptr::null_mut); + let application = try_or_else!(TWString::from_ptr_as_ref(application), std::ptr::null_mut); + let application = try_or_else!(application.as_str(), std::ptr::null_mut); + let index = try_or_else!(TWString::from_ptr_as_ref(index), std::ptr::null_mut); + let index = try_or_else!(index.as_str(), std::ptr::null_mut); + let path = try_or_else!( + address.account_path(layer, application, index), + std::ptr::null_mut + ); + TWString::from(path).into_ptr() +} + +/// Returns EIP-1014 Create2 address +/// +/// \param from *non-null* string. +/// \param salt *non-null* data. +/// \param init_code_hash *non-null* data. +/// \return the EIP-1014 Create2 address. +#[tw_ffi(ty = static_function, class = TWEthereum, name = Eip1014Create2Address)] +#[no_mangle] +pub unsafe extern "C" fn tw_ethereum_eip1014_create2_address( + from: Nonnull, + salt: Nonnull, + init_code_hash: Nonnull, +) -> NullableMut { + let from = try_or_else!(TWString::from_ptr_as_ref(from), std::ptr::null_mut); + let from = try_or_else!(from.as_str(), std::ptr::null_mut); + let salt = try_or_else!(TWData::from_ptr_as_ref(salt), std::ptr::null_mut); + let salt = try_or_else!(H256::try_from(salt.as_slice()), std::ptr::null_mut); + let init_code_hash = try_or_else!(TWData::from_ptr_as_ref(init_code_hash), std::ptr::null_mut); + let init_code_hash = try_or_else!( + H256::try_from(init_code_hash.as_slice()), + std::ptr::null_mut + ); + let address = try_or_else!( + Address::eip1014_create2_address(from, &salt, &init_code_hash), + std::ptr::null_mut + ); + TWString::from(address.into_checksum_address()).into_ptr() +} + +/// Returns EIP-1967 proxy init code +/// +/// \param logic_address *non-null* string. +/// \param data *non-null* data. +/// \return the EIP-1967 proxy init code. +#[tw_ffi(ty = static_function, class = TWEthereum, name = Eip1967ProxyInitCode)] +#[no_mangle] +pub unsafe extern "C" fn tw_ethereum_eip1967_proxy_init_code( + logic_address: Nonnull, + data: Nonnull, +) -> NullableMut { + let logic_address = try_or_else!(TWString::from_ptr_as_ref(logic_address), std::ptr::null_mut); + let logic_address = try_or_else!(logic_address.as_str(), std::ptr::null_mut); + let data = try_or_else!(TWData::from_ptr_as_ref(data), std::ptr::null_mut); + let init_code = try_or_else!( + Address::eip_1967_proxy_init_code(logic_address, data.as_slice()), + std::ptr::null_mut + ); + TWData::from(init_code).into_ptr() +} diff --git a/rust/tw_evm/src/ffi/mod.rs b/rust/tw_evm/src/ffi/mod.rs new file mode 100644 index 00000000000..9271ddea0d7 --- /dev/null +++ b/rust/tw_evm/src/ffi/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod barz; +pub mod ethereum_address; diff --git a/rust/tw_evm/src/lib.rs b/rust/tw_evm/src/lib.rs index 42425782979..4d0b07c0a43 100644 --- a/rust/tw_evm/src/lib.rs +++ b/rust/tw_evm/src/lib.rs @@ -6,6 +6,7 @@ pub mod abi; pub mod address; pub mod evm_context; pub mod evm_entry; +pub mod ffi; pub mod message; pub mod modules; pub mod rlp; diff --git a/rust/tw_evm/src/modules/barz/core.rs b/rust/tw_evm/src/modules/barz/core.rs new file mode 100644 index 00000000000..ea1b84be951 --- /dev/null +++ b/rust/tw_evm/src/modules/barz/core.rs @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::abi::function::Function; +use crate::abi::non_empty_array::NonEmptyBytes; +use crate::abi::param::Param; +use crate::abi::param_type::ParamType; +use crate::abi::uint::UintBits; +use crate::abi::{encode, token::Token}; +use crate::address::Address; +use crate::message::EthMessage; +use crate::transaction::authorization_list::{Authorization, SignedAuthorization}; +use std::str::FromStr; +use tw_encoding::{base64, hex}; +use tw_hash::sha3::keccak256; +use tw_hash::H256; +use tw_keypair::ecdsa::der; +use tw_keypair::ecdsa::secp256k1::PrivateKey; +use tw_keypair::traits::SigningKeyTrait; +use tw_misc::traits::ToBytesVec; +use tw_number::U256; +use tw_proto::Barz::Proto::{ContractAddressInput, DiamondCutInput}; + +use super::error::BarzResult; + +const BARZ_DOMAIN_SEPARATOR_TYPE_HASH: &str = + "47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218"; +const BARZ_MSG_HASH_DATA: &str = "b1bcb804a4a3a1af3ee7920d949bdfd417ea1b736c3552c8d6563a229a619100"; +const BARZ_SIGNED_DATA_PREFIX: &str = "1901"; + +const BARZ_DIAMOND_CUT_SELECTOR: &str = "1f931c1c"; +const BARZ_DATA_LOCATION_CHUNK: &str = "60"; + +pub fn get_counterfactual_address(input: &ContractAddressInput) -> BarzResult { + let encoded_data = encode::encode_tokens(&[ + Token::Address(Address::from_str(&input.account_facet)?), + Token::Address(Address::from_str(&input.verification_facet)?), + Token::Address(Address::from_str(&input.entry_point)?), + Token::Address(Address::from_str(&input.facet_registry)?), + Token::Address(Address::from_str(&input.default_fallback)?), + Token::Bytes(hex::decode(&input.public_key)?), + ]); + + let mut init_code = hex::decode(&input.bytecode)?; + init_code.extend_from_slice(&encoded_data); + let init_code_hash = keccak256(&init_code); + let init_code_hash = H256::try_from(init_code_hash.as_slice())?; + + let salt = input.salt.to_be_bytes(); + let mut salt_bytes = [0u8; 32]; + salt_bytes[32 - salt.len()..].copy_from_slice(&salt); + let salt_bytes = H256::try_from(salt_bytes.as_slice())?; + + let address = Address::eip1014_create2_address(&input.factory, &salt_bytes, &init_code_hash)?; + Ok(address.into_checksum_address()) +} + +pub fn get_init_code( + factory_address: &str, + public_key: &[u8], + verification_facet: &str, + salt: u32, +) -> BarzResult> { + let tokens = [ + Token::Address(Address::from_str(verification_facet)?), + Token::Bytes(public_key.to_vec()), + Token::Uint { + uint: salt.into(), + bits: UintBits::default(), + }, + ]; + + let function = Function { + name: "createAccount".to_string(), + inputs: vec![ + Param::with_type(ParamType::Address), + Param::with_type(ParamType::Bytes), + Param::with_type(ParamType::Uint { + bits: UintBits::default(), + }), + ], + outputs: vec![], + }; + let encoded = function.encode_input(&tokens)?; + + let mut envelope = Vec::new(); + envelope.extend_from_slice(&hex::decode(factory_address)?); + envelope.extend_from_slice(&encoded); + Ok(envelope) +} + +pub fn get_formatted_signature( + signature: &[u8], + challenge: &[u8], + authenticator_data: &[u8], + client_data_json: &str, +) -> BarzResult> { + let challenge_base64 = base64::encode(challenge, base64::URL_SAFE); + + let challenge_base64 = challenge_base64.trim_end_matches('='); + + let Some(challenge_pos) = client_data_json.find(challenge_base64) else { + return Ok(Vec::new()); + }; + + let client_data_json_pre = &client_data_json[..challenge_pos]; + let client_data_json_post = &client_data_json[challenge_pos + challenge_base64.len()..]; + + let Ok(signature) = der::Signature::from_bytes(signature) else { + return Ok(Vec::new()); + }; + + Ok(encode::encode_tokens(&[ + Token::Uint { + uint: U256::from_big_endian(*signature.r()), + bits: UintBits::default(), + }, + Token::Uint { + uint: U256::from_big_endian(*signature.s()), + bits: UintBits::default(), + }, + Token::Bytes(authenticator_data.to_vec()), + Token::String(client_data_json_pre.to_string()), + Token::String(client_data_json_post.to_string()), + ])) +} + +pub fn get_prefixed_msg_hash( + msg_hash: &[u8], + barz_address: &str, + chain_id: u32, +) -> BarzResult> { + // keccak256("EIP712Domain(uint256 chainId,address verifyingContract)") + let domain_separator_type_hash = hex::decode(BARZ_DOMAIN_SEPARATOR_TYPE_HASH)?; + // keccak256("BarzMessage(bytes message)") + let barz_msg_hash_data = hex::decode(BARZ_MSG_HASH_DATA)?; + let signed_data_prefix = hex::decode(BARZ_SIGNED_DATA_PREFIX)?; + + let domain_separator_tokens = vec![ + Token::FixedBytes(NonEmptyBytes::new(domain_separator_type_hash)?), + Token::Uint { + uint: U256::from(chain_id), + bits: UintBits::default(), + }, + Token::Address(Address::from_str(barz_address)?), + ]; + + let domain_separator = encode::encode_tokens(&domain_separator_tokens); + let domain_separator_hash = keccak256(&domain_separator); + + let raw_message_tokens = vec![ + Token::FixedBytes(NonEmptyBytes::new(barz_msg_hash_data)?), + Token::FixedBytes(NonEmptyBytes::new(keccak256(msg_hash).to_vec())?), + ]; + + let raw_message_data = encode::encode_tokens(&raw_message_tokens); + + let encoded_msg_tokens = vec![ + Token::FixedBytes(NonEmptyBytes::new(domain_separator_hash.to_vec())?), + Token::FixedBytes(NonEmptyBytes::new(keccak256(&raw_message_data).to_vec())?), + ]; + + let encoded_msg = encode::encode_tokens(&encoded_msg_tokens); + + let mut final_encoded_msg = signed_data_prefix; + final_encoded_msg.extend_from_slice(&encoded_msg); + + let encoded_msg_hash = keccak256(&final_encoded_msg); + Ok(encoded_msg_hash.to_vec()) +} + +// Function to encode the diamondCut function call using protobuf message as input +pub fn get_diamond_cut_code(input: &DiamondCutInput) -> BarzResult> { + let diamond_cut_selector = hex::decode(BARZ_DIAMOND_CUT_SELECTOR)?; + let data_location_chunk = hex::decode(BARZ_DATA_LOCATION_CHUNK)?; + let mut encoded = Vec::new(); + + // diamondCut() function selector + encoded.extend_from_slice(&diamond_cut_selector); + + // First argument Data Location `diamondCut` + let mut data_location = data_location_chunk.clone(); + while data_location.len() < 32 { + data_location.insert(0, 0); + } + encoded.extend_from_slice(&data_location); + + // Encode second Parameter `init` + let mut init_address = hex::decode(&input.init_address)?; + while init_address.len() < 32 { + init_address.insert(0, 0); + } + encoded.extend_from_slice(&init_address); + + // Third Argument Data location `_calldata` + let call_data_data_location = encoded.len(); + + // Encode number of facet cuts + let facet_cuts_size = U256::from(input.facet_cuts.len()); + let facet_cuts_size_bytes = facet_cuts_size.to_big_endian(); + encoded.extend_from_slice(&facet_cuts_size_bytes[..]); + + let encoding_chunk = 32; + let bytes_chunk_line = 5; + let mut total_instruct_chunk = 0; + let mut chunk_location = 0; + let mut prev_data_position = 0; + let mut instruct_chunk = 0; + + // Encode each FacetCut + for facet_cut in input.facet_cuts.iter() { + if instruct_chunk == 0 { + prev_data_position = input.facet_cuts.len() * encoding_chunk; + let position_bytes = U256::from(prev_data_position).to_big_endian(); + encoded.extend_from_slice(&position_bytes[..]); + chunk_location = encoded.len(); + } else { + prev_data_position += instruct_chunk * encoding_chunk; + let position_bytes = U256::from(prev_data_position).to_big_endian(); + instruct_chunk = 0; + + // encoded.extend_from_slice(&position_bytes[..]); + encoded.splice(chunk_location..chunk_location, position_bytes.to_vec()); + instruct_chunk += 1; + } + + let mut tokens = vec![]; + + // Encode facet address + let token = Token::Address(Address::from_str(&facet_cut.facet_address)?); + tokens.push(token); + instruct_chunk += 1; + + // Encode FacetAction enum + let token = Token::Uint { + uint: U256::from(facet_cut.action as u32), + bits: UintBits::default(), + }; + tokens.push(token); + instruct_chunk += 1; + // Add data storage position + let token = Token::FixedBytes(NonEmptyBytes::new(data_location.clone())?); + tokens.push(token); + instruct_chunk += 1; + + // Encode number of function selectors + let token = Token::Uint { + uint: U256::from(facet_cut.function_selectors.len()), + bits: UintBits::default(), + }; + tokens.push(token); + instruct_chunk += 1; + + // Encode function selectors + for selector in &facet_cut.function_selectors { + let token = Token::FixedBytes(NonEmptyBytes::new(selector.to_vec())?); + tokens.push(token); + instruct_chunk += 1; + } + let encoded_token = encode::encode_tokens(&tokens); + encoded.extend_from_slice(&encoded_token); + total_instruct_chunk += instruct_chunk; + } + + // Insert calldata length + let calldata_length = + (total_instruct_chunk * encoding_chunk) + (bytes_chunk_line * encoding_chunk); + let calldata_length_bytes = U256::from(calldata_length).to_big_endian(); + encoded.splice( + call_data_data_location..call_data_data_location, + calldata_length_bytes.to_vec(), + ); + + // Handle init data + let init_data_length = input.init_data.len(); + if init_data_length == 0 { + return Ok(Vec::new()); + } + let init_data_length_bytes = U256::from(init_data_length).to_big_endian(); + encoded.extend_from_slice(&init_data_length_bytes[..]); + encoded.extend_from_slice(&input.init_data); + + // Add padding + let padding_length = (encoding_chunk) - ((init_data_length) % (encoding_chunk * 2)); + let padding = vec![0u8; padding_length]; + encoded.extend_from_slice(&padding); + Ok(encoded) +} + +#[allow(clippy::too_many_arguments)] +pub fn get_encoded_hash( + chain_id: &[u8], + code_address: &str, + code_name: &str, + code_version: &str, + type_hash: &str, + domain_separator_hash: &str, + sender: &str, + user_op_hash: &str, +) -> BarzResult> { + // Create code_address_bytes32 by padding with zeros + let mut code_address_bytes32 = vec![0u8; 12]; + code_address_bytes32.extend_from_slice(&hex::decode(code_address.trim_start_matches("0x"))?); + + // Create domain separator + let tokens = vec![ + Token::FixedBytes(NonEmptyBytes::new(hex::decode( + domain_separator_hash.trim_start_matches("0x"), + )?)?), + Token::FixedBytes(NonEmptyBytes::new( + keccak256(code_name.as_bytes()).to_vec(), + )?), + Token::FixedBytes(NonEmptyBytes::new( + keccak256(code_version.as_bytes()).to_vec(), + )?), + Token::Uint { + uint: U256::from_big_endian_slice(chain_id)?, + bits: UintBits::default(), + }, + Token::Address(Address::from_str(sender)?), + Token::FixedBytes(NonEmptyBytes::new(code_address_bytes32)?), + ]; + + let domain_separator = encode::encode_tokens(&tokens); + let domain_separator_encoded_hash = keccak256(&domain_separator); + + // Create message hash + let mut message_to_hash = Vec::new(); + message_to_hash.extend_from_slice(&hex::decode(type_hash)?); + message_to_hash.extend_from_slice(&hex::decode(user_op_hash)?); + let message_hash = keccak256(&message_to_hash); + + // Create final encoded hash + let mut encoded = Vec::new(); + encoded.extend_from_slice(&hex::decode(BARZ_SIGNED_DATA_PREFIX)?); + encoded.extend_from_slice(&domain_separator_encoded_hash); + encoded.extend_from_slice(&message_hash); + + Ok(keccak256(&encoded)) +} + +pub fn sign_user_op_hash(hash: &str, private_key: &str) -> BarzResult> { + let private_key = PrivateKey::try_from(private_key)?; + let message = H256::from_str(hash)?; + let signature = private_key.sign(message)?; + + let mut result = signature.to_vec(); + // v value (last byte, should be 0 or 1, add 27 to make it 27 or 28) + let v_value = result[64] + 27; + result[64] = v_value; + + Ok(result) +} + +pub fn get_authorization_hash( + chain_id: &[u8], + contract_address: &str, + nonce: &[u8], +) -> BarzResult> { + let authorization = Authorization { + chain_id: U256::from_big_endian_slice(chain_id)?, + address: Address::from_str(contract_address)?, + nonce: U256::from_big_endian_slice(nonce)?, + }; + + Ok(authorization.hash()?.to_vec()) +} + +pub fn sign_authorization( + chain_id: &[u8], + contract_address: &str, + nonce: &[u8], + private_key: &str, +) -> BarzResult { + let authorization = Authorization { + chain_id: U256::from_big_endian_slice(chain_id)?, + address: Address::from_str(contract_address)?, + nonce: U256::from_big_endian_slice(nonce)?, + }; + let authorization_hash = authorization.hash()?; + let private_key = PrivateKey::try_from(private_key)?; + let signature = private_key.sign(authorization_hash)?; + + let signed_authorization = SignedAuthorization { + authorization, + y_parity: signature.v(), + r: U256::from_big_endian(signature.r()), + s: U256::from_big_endian(signature.s()), + }; + + Ok(serde_json::to_string(&signed_authorization)?) +} diff --git a/rust/tw_evm/src/modules/barz/error.rs b/rust/tw_evm/src/modules/barz/error.rs new file mode 100644 index 00000000000..2e6424f6d32 --- /dev/null +++ b/rust/tw_evm/src/modules/barz/error.rs @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::error::prelude::AddressError; +use tw_encoding::hex::FromHexError; +use tw_keypair::KeyPairError; +use tw_number::NumberError; + +use crate::{abi::AbiError, message::MessageSigningError}; + +pub type BarzResult = Result; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum BarzError { + InvalidInput, + AddressError, + FromHexError, + AbiError, + NumberError, + KeypairError, + HashError, + MessageSigningError, + SerializationError, +} + +impl From for BarzError { + fn from(_: AddressError) -> Self { + BarzError::AddressError + } +} + +impl From for BarzError { + fn from(_: FromHexError) -> Self { + BarzError::FromHexError + } +} + +impl From for BarzError { + fn from(_: AbiError) -> Self { + BarzError::AbiError + } +} + +impl From for BarzError { + fn from(_: NumberError) -> Self { + BarzError::NumberError + } +} + +impl From for BarzError { + fn from(_: KeyPairError) -> Self { + BarzError::KeypairError + } +} + +impl From for BarzError { + fn from(_: tw_hash::Error) -> Self { + BarzError::HashError + } +} + +impl From for BarzError { + fn from(_: MessageSigningError) -> Self { + BarzError::MessageSigningError + } +} + +impl From for BarzError { + fn from(_: serde_json::Error) -> Self { + BarzError::SerializationError + } +} diff --git a/rust/tw_evm/src/modules/barz/mod.rs b/rust/tw_evm/src/modules/barz/mod.rs new file mode 100644 index 00000000000..b82c5619e33 --- /dev/null +++ b/rust/tw_evm/src/modules/barz/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod core; +pub mod error; diff --git a/rust/tw_evm/src/modules/mod.rs b/rust/tw_evm/src/modules/mod.rs index fb246766ac1..49a56aa1a12 100644 --- a/rust/tw_evm/src/modules/mod.rs +++ b/rust/tw_evm/src/modules/mod.rs @@ -3,6 +3,7 @@ // Copyright © 2017 Trust Wallet. pub mod abi_encoder; +pub mod barz; pub mod compiler; pub mod message_signer; pub mod rlp_encoder; diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs index 1a2846c4e32..33a32e37d39 100644 --- a/rust/tw_evm/src/modules/tx_builder.rs +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -20,7 +20,7 @@ use crate::transaction::transaction_eip1559::TransactionEip1559; use crate::transaction::transaction_eip7702::TransactionEip7702; use crate::transaction::transaction_non_typed::TransactionNonTyped; use crate::transaction::user_operation::UserOperation; -use crate::transaction::user_operation_v0_7::{Eip7702Auth, UserOperationV0_7}; +use crate::transaction::user_operation_v0_7::UserOperationV0_7; use crate::transaction::UnsignedTransactionBox; use std::marker::PhantomData; use std::str::FromStr; @@ -528,19 +528,9 @@ impl TxBuilder { .into_tw() .context("Paymaster post-op gas limit exceeds u128")?; - let eip7702_auth = - Self::build_authorization_list(input, sender) - .ok() - .and_then(|auth_list| { - auth_list.0.first().map(|signed_auth| Eip7702Auth { - chain_id: signed_auth.authorization.chain_id, - address: signed_auth.authorization.address, - nonce: signed_auth.authorization.nonce, - y_parity: U256::from(signed_auth.y_parity), - r: signed_auth.r, - s: signed_auth.s, - }) - }); + let eip7702_auth = Self::build_authorization_list(input, sender) + .ok() + .and_then(|auth_list| auth_list.0.first().cloned()); let entry_point = Self::parse_address(user_op_v0_7.entry_point.as_ref()) .context("Invalid entry point")?; diff --git a/rust/tw_evm/src/transaction/authorization_list.rs b/rust/tw_evm/src/transaction/authorization_list.rs index 7f0a7c3429d..ef88e812b0e 100644 --- a/rust/tw_evm/src/transaction/authorization_list.rs +++ b/rust/tw_evm/src/transaction/authorization_list.rs @@ -5,31 +5,43 @@ use crate::address::Address; use crate::rlp::buffer::RlpBuffer; use crate::rlp::RlpEncode; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; +use tw_encoding::hex::u8_as_hex; use tw_number::U256; /// Authorization for 7702 txn support. -#[derive(Deserialize)] +#[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Authorization { /// The chain ID of the authorization. #[serde(deserialize_with = "U256::from_hex_or_decimal_str")] + #[serde(serialize_with = "U256::as_hex")] pub chain_id: U256, /// The address of the authorization. pub address: Address, /// The nonce for the authorization. #[serde(deserialize_with = "U256::from_hex_or_decimal_str")] + #[serde(serialize_with = "U256::as_hex")] pub nonce: U256, } /// Signed authorization for 7702 txn support. +/// See: https://eips.ethereum.org/EIPS/eip-4337#support-for-eip-7702-authorizations +#[derive(Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] pub struct SignedAuthorization { + #[serde(flatten)] pub authorization: Authorization, /// y-parity of the signature. + #[serde(with = "u8_as_hex")] pub y_parity: u8, /// r part of the signature. + #[serde(deserialize_with = "U256::from_hex_or_decimal_str")] + #[serde(serialize_with = "U256::as_hex")] pub r: U256, /// s part of the signature. + #[serde(deserialize_with = "U256::from_hex_or_decimal_str")] + #[serde(serialize_with = "U256::as_hex")] pub s: U256, } diff --git a/rust/tw_evm/src/transaction/user_operation_v0_7.rs b/rust/tw_evm/src/transaction/user_operation_v0_7.rs index f239e5ff437..546154b0bb5 100644 --- a/rust/tw_evm/src/transaction/user_operation_v0_7.rs +++ b/rust/tw_evm/src/transaction/user_operation_v0_7.rs @@ -19,6 +19,8 @@ use tw_memory::Data; use tw_number::serde::as_u256_hex; use tw_number::U256; +use super::authorization_list::SignedAuthorization; + pub struct PackedUserOperation { pub sender: Address, pub nonce: U256, @@ -157,29 +159,12 @@ pub struct UserOperationV0_7 { pub paymaster_data: Data, #[serde(skip_serializing_if = "Option::is_none")] - pub eip7702_auth: Option, + pub eip7702_auth: Option, #[serde(skip)] pub entry_point: Address, } -// See: https://eips.ethereum.org/EIPS/eip-4337#support-for-eip-7702-authorizations -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Eip7702Auth { - #[serde(serialize_with = "U256::as_hex")] - pub chain_id: U256, - pub address: Address, - #[serde(serialize_with = "U256::as_hex")] - pub nonce: U256, - #[serde(serialize_with = "U256::as_hex")] - pub y_parity: U256, - #[serde(serialize_with = "U256::as_hex")] - pub r: U256, - #[serde(serialize_with = "U256::as_hex")] - pub s: U256, -} - impl TransactionCommon for UserOperationV0_7 { #[inline] fn payload(&self) -> Data { diff --git a/rust/tw_evm/tests/barz.rs b/rust/tw_evm/tests/barz.rs index a7127fd7ca1..c24927365ef 100644 --- a/rust/tw_evm/tests/barz.rs +++ b/rust/tw_evm/tests/barz.rs @@ -9,6 +9,7 @@ use tw_encoding::hex::{DecodeHex, ToHex}; use tw_evm::abi::prebuild::erc20::Erc20; use tw_evm::address::Address; use tw_evm::evm_context::StandardEvmContext; +use tw_evm::modules::barz::core::*; use tw_evm::modules::signer::Signer; use tw_misc::traits::ToBytesVec; use tw_number::U256; @@ -677,3 +678,260 @@ fn test_user_operation_transfer_with_incorrect_wallet_type_error() { let output = Signer::::sign_proto(input); assert_eq!(output.error, SigningErrorType::Error_invalid_params); } + +use tw_proto::Barz::Proto::{ContractAddressInput, DiamondCutInput, FacetCut, FacetCutAction}; + +#[test] +fn test_get_counterfactual_address() { + let input = ContractAddressInput { + account_facet: "0x3322C04EAe11B9b14c6c289f2668b6f07071b591".into(), + verification_facet: "0x90A6fE0A938B0d4188e9013C99A0d7D9ca6bFB63".into(), + entry_point: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789".into(), + facet_registry: "0xFd1A8170c12747060324D9079a386BD4290e6f93".into(), + default_fallback: "0x22eB0720d9Fc4bC90BB812B309e939880B71c20d".into(), + public_key: "0xB5547FBdC56DCE45e1B8ef75569916D438e09c46".into(), + bytecode: "0x608060405260405162003cc638038062003cc68339818101604052810190620000299190620019ad565b60008673ffffffffffffffffffffffffffffffffffffffff16633253960f6040518163ffffffff1660e01b81526004016020604051808303816000875af115801562000079573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200009f919062001aec565b9050600060e01b817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191603620000fe576040517f5a5b4d3900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600267ffffffffffffffff8111156200011e576200011d6200196b565b5b6040519080825280602002602001820160405280156200015b57816020015b62000147620018ff565b8152602001906001900390816200013d5790505b5090506000600167ffffffffffffffff8111156200017e576200017d6200196b565b5b604051908082528060200260200182016040528015620001ad5781602001602082028036833780820191505090505b5090506000600167ffffffffffffffff811115620001d057620001cf6200196b565b5b604051908082528060200260200182016040528015620001ff5781602001602082028036833780820191505090505b509050631f931c1c60e01b8260008151811062000221576200022062001b21565b5b60200260200101907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152505060405180606001604052808d73ffffffffffffffffffffffffffffffffffffffff16815260200160006002811115620002ab57620002aa62001b37565b5b81526020018381525083600081518110620002cb57620002ca62001b21565b5b60200260200101819052508381600081518110620002ee57620002ed62001b21565b5b60200260200101907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152505060405180606001604052808b73ffffffffffffffffffffffffffffffffffffffff1681526020016000600281111562000378576200037762001b37565b5b8152602001828152508360018151811062000398576200039762001b21565b5b6020026020010181905250620003b9846200047e60201b620001671760201c565b6200046c838c8b8e8e8d8d8d8d604051602401620003de979695949392919062001b8c565b6040516020818303038152906040527f95a21aec000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050620004b060201b620001911760201c565b5050505050505050505050506200211f565b806200048f6200073460201b60201c565b60010160016101000a81548163ffffffff021916908360e01c021790555050565b60005b8351811015620006df576000848281518110620004d557620004d462001b21565b5b602002602001015160200151905060006002811115620004fa57620004f962001b37565b5b81600281111562000510576200050f62001b37565b5b0362000570576200056a85838151811062000530576200052f62001b21565b5b60200260200101516000015186848151811062000552576200055162001b21565b5b6020026020010151604001516200073960201b60201c565b620006c8565b6001600281111562000587576200058662001b37565b5b8160028111156200059d576200059c62001b37565b5b03620005fd57620005f7858381518110620005bd57620005bc62001b21565b5b602002602001015160000151868481518110620005df57620005de62001b21565b5b602002602001015160400151620009db60201b60201c565b620006c7565b60028081111562000613576200061262001b37565b5b81600281111562000629576200062862001b37565b5b0362000689576200068385838151811062000649576200064862001b21565b5b6020026020010151600001518684815181106200066b576200066a62001b21565b5b60200260200101516040015162000c8f60201b60201c565b620006c6565b6040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620006bd9062001be7565b60405180910390fd5b5b5b508080620006d69062001c61565b915050620004b3565b507f8faa70878671ccd212d20771b795c50af8fd3ff6cf27f4bde57e5d4de0aeb673838383604051620007159392919062001c82565b60405180910390a16200072f828262000e3760201b60201c565b505050565b600090565b600081511162000780576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620007779062001d97565b60405180910390fd5b60006200079262000f6b60201b60201c565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160362000806576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620007fd9062001dfb565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff16036200087c576200087b828562000f9860201b60201c565b5b60005b8351811015620009d4576000848281518110620008a157620008a062001b21565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161462000998576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200098f9062001e5f565b60405180910390fd5b620009ac8583868a6200107c60201b60201c565b8380620009b99062001ec3565b94505050508080620009cb9062001c61565b9150506200087f565b5050505050565b600081511162000a22576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000a199062001d97565b60405180910390fd5b600062000a3462000f6b60201b60201c565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160362000aa8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000a9f9062001dfb565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff160362000b1e5762000b1d828562000f9860201b60201c565b5b60005b835181101562000c8857600084828151811062000b435762000b4262001b21565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508673ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160362000c39576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000c309062001eef565b60405180910390fd5b62000c4c8582846200122960201b60201c565b62000c608583868a6200107c60201b60201c565b838062000c6d9062001ec3565b9450505050808062000c7f9062001c61565b91505062000b21565b5050505050565b600081511162000cd6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000ccd9062001d97565b60405180910390fd5b600062000ce862000f6b60201b60201c565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161462000d5c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000d539062001f53565b60405180910390fd5b60005b825181101562000e3157600083828151811062000d815762000d8062001b21565b5b602002602001015190506000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905062000e198482846200122960201b60201c565b5050808062000e289062001c61565b91505062000d5f565b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16031562000f675762000e988260405180606001604052806028815260200162003c7a60289139620018aa60201b60201c565b6000808373ffffffffffffffffffffffffffffffffffffffff168360405162000ec2919062001fb7565b600060405180830381855af49150503d806000811462000eff576040519150601f19603f3d011682016040523d82523d6000602084013e62000f04565b606091505b50915091508162000f645760008151111562000f235780518082602001fd5b83836040517f192105d700000000000000000000000000000000000000000000000000000000815260040162000f5b92919062001fd7565b60405180910390fd5b50505b5050565b6000807f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f90508091505090565b62000fc38160405180606001604052806024815260200162003ca260249139620018aa60201b60201c565b81600201805490508260010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001018190555081600201819080600181540180825580915050600190039060005260206000200160009091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b81846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508360010160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018390806001815401808255809150506001900390600052602060002090600891828204019190066004029091909190916101000a81548163ffffffff021916908360e01c021790555080846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036200129b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620012929062002003565b60405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036200130c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620013039062002067565b60405180910390fd5b6000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160149054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff169050600060018560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000180549050620013e59190620020cb565b9050808214620015805760008560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000182815481106200144a576200144962001b21565b5b90600052602060002090600891828204019190066004029054906101000a900460e01b9050808660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018481548110620014c957620014c862001b21565b5b90600052602060002090600891828204019190066004026101000a81548163ffffffff021916908360e01c021790555082866000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550505b8460010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001805480620015d757620015d6620020ec565b5b60019003818190600052602060002090600891828204019190066004026101000a81549063ffffffff02191690559055846000016000847bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001908152602001600020600080820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556000820160146101000a8154906bffffffffffffffffffffffff0219169055505060008103620018a357600060018660020180549050620016c49190620020cb565b905060008660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001015490508181146200180c57600087600201838154811062001732576200173162001b21565b5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508088600201838154811062001779576200177862001b21565b5b9060005260206000200160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550818860010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010181905550505b86600201805480620018235762001822620020ec565b5b6001900381819060005260206000200160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905590558660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001016000905550505b5050505050565b6000823b9050600081118290620018f9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620018f0919062002102565b60405180910390fd5b50505050565b6040518060600160405280600073ffffffffffffffffffffffffffffffffffffffff168152602001600060028111156200193e576200193d62001b37565b5b8152602001606081525090565b60008151905060018060a01b03811681146200196657600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b83811015620019a157808201518184015260208101905062001984565b50600083830152505050565b600080600080600080600080610100898b031215620019cb57600080fd5b620019d6896200194b565b9750620019e660208a016200194b565b9650620019f660408a016200194b565b955062001a0660608a016200194b565b945062001a1660808a016200194b565b935062001a2660a08a016200194b565b925062001a3660c08a016200194b565b915060e089015160018060401b038082111562001a5257600080fd5b818b0191508b601f83011262001a6757600080fd5b81518181111562001a7d5762001a7c6200196b565b5b601f1960405181603f83601f860116011681019150808210848311171562001aaa5762001aa96200196b565b5b816040528281528e602084870101111562001ac457600080fd5b62001ad783602083016020880162001981565b80955050505050509295985092959890939650565b60006020828403121562001aff57600080fd5b815163ffffffff60e01b8116811462001b1757600080fd5b8091505092915050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052602160045260246000fd5b60018060a01b03811682525050565b6000815180845262001b7681602086016020860162001981565b6020601f19601f83011685010191505092915050565b600060018060a01b03808a168352808916602084015280881660408401528087166060840152808616608084015280851660a08401525060e060c083015262001bd960e083018462001b5c565b905098975050505050505050565b60208152602760208201527f4c69624469616d6f6e644375743a20496e636f7272656374204661636574437560408201527f74416374696f6e0000000000000000000000000000000000000000000000000060608201526000608082019050919050565b634e487b7160e01b600052601160045260246000fd5b60008019820362001c775762001c7662001c4b565b5b600182019050919050565b60006060808301818452808751808352608092508286019150828160051b8701016020808b0160005b8481101562001d6357607f198a8503018652815188850160018060a01b038251168652848201516003811062001cf157634e487b7160e01b600052602160045260246000fd5b80868801525060408083015192508a81880152508082518083528a880191508684019350600092505b8083101562001d465763ffffffff60e01b84511682528682019150868401935060018301925062001d1a565b508096505050508282019150828601955060018101905062001cab565b505062001d738189018b62001b4d565b50868103604088015262001d88818962001b5c565b95505050505050949350505050565b60208152602b60208201527f4c69624469616d6f6e644375743a204e6f2073656c6563746f727320696e206660408201527f6163657420746f2063757400000000000000000000000000000000000000000060608201526000608082019050919050565b60208152602c60208201527f4c69624469616d6f6e644375743a204164642066616365742063616e2774206260408201527f652061646472657373283029000000000000000000000000000000000000000060608201526000608082019050919050565b60208152603560208201527f4c69624469616d6f6e644375743a2043616e2774206164642066756e6374696f60408201527f6e207468617420616c726561647920657869737473000000000000000000000060608201526000608082019050919050565b600060018060601b0380831681810362001ee25762001ee162001c4b565b5b6001810192505050919050565b60208152603860208201527f4c69624469616d6f6e644375743a2043616e2774207265706c6163652066756e60408201527f6374696f6e20776974682073616d652066756e6374696f6e000000000000000060608201526000608082019050919050565b60208152603660208201527f4c69624469616d6f6e644375743a2052656d6f7665206661636574206164647260408201527f657373206d75737420626520616464726573732830290000000000000000000060608201526000608082019050919050565b6000825162001fcb81846020870162001981565b80830191505092915050565b60018060a01b038316815260406020820152600062001ffa604083018462001b5c565b90509392505050565b60208152603760208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f76652066756e6360408201527f74696f6e207468617420646f65736e277420657869737400000000000000000060608201526000608082019050919050565b60208152602e60208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f766520696d6d7560408201527f7461626c652066756e6374696f6e00000000000000000000000000000000000060608201526000608082019050919050565b6000828203905081811115620020e657620020e562001c4b565b5b92915050565b634e487b7160e01b600052603160045260246000fd5b60208152600062002117602083018462001b5c565b905092915050565b611b4b806200212f6000396000f3fe60806040523661000b57005b6000807f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f9050809150600082600001600080357fffffffff00000000000000000000000000000000000000000000000000000000167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610141576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610138906114d3565b60405180910390fd5b3660008037600080366000845af43d6000803e8060008114610162573d6000f35b3d6000fd5b806101706103c0565b60010160016101000a81548163ffffffff021916908360e01c021790555050565b60005b83518110156103755760008482815181106101b2576101b1611510565b5b6020026020010151602001519050600060028111156101d4576101d3611526565b5b8160028111156101e7576101e6611526565b5b036102375761023285838151811061020257610201611510565b5b60200260200101516000015186848151811061022157610220611510565b5b6020026020010151604001516103c5565b610361565b6001600281111561024b5761024a611526565b5b81600281111561025e5761025d611526565b5b036102ae576102a985838151811061027957610278611510565b5b60200260200101516000015186848151811061029857610297611510565b5b60200260200101516040015161063c565b610360565b6002808111156102c1576102c0611526565b5b8160028111156102d4576102d3611526565b5b036103245761031f8583815181106102ef576102ee611510565b5b60200260200101516000015186848151811061030e5761030d611510565b5b6020026020010151604001516108bd565b61035f565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103569061153c565b60405180910390fd5b5b5b50808061036d906115b6565b915050610194565b507f8faa70878671ccd212d20771b795c50af8fd3ff6cf27f4bde57e5d4de0aeb6738383836040516103a99392919061163b565b60405180910390a16103bb8282610a48565b505050565b600090565b6000815111610409576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161040090611747565b60405180910390fd5b6000610413610b6a565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610484576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161047b906117ab565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff16036104f1576104f08285610b97565b5b60005b835181101561063557600084828151811061051257610511611510565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610606576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105fd9061180f565b60405180910390fd5b6106128583868a610c72565b838061061d90611873565b9450505050808061062d906115b6565b9150506104f4565b5050505050565b6000815111610680576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067790611747565b60405180910390fd5b600061068a610b6a565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036106fb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106f2906117ab565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff1603610768576107678285610b97565b5b60005b83518110156108b657600084828151811061078957610788611510565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508673ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361087c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610873906118a2565b60405180910390fd5b610887858284610e1f565b6108938583868a610c72565b838061089e90611873565b945050505080806108ae906115b6565b91505061076b565b5050505050565b6000815111610901576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108f890611747565b60405180910390fd5b600061090b610b6a565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161461097c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161097390611906565b60405180910390fd5b60005b8251811015610a4257600083828151811061099d5761099c611510565b5b602002602001015190506000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050610a2d848284610e1f565b50508080610a3a906115b6565b91505061097f565b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160315610b6657610a9f82604051806060016040528060288152602001611aca60289139611481565b6000808373ffffffffffffffffffffffffffffffffffffffff1683604051610ac7919061196a565b600060405180830381855af49150503d8060008114610b02576040519150601f19603f3d011682016040523d82523d6000602084013e610b07565b606091505b509150915081610b6357600081511115610b245780518082602001fd5b83836040517f192105d7000000000000000000000000000000000000000000000000000000008152600401610b5a929190611988565b60405180910390fd5b50505b5050565b6000807f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f90508091505090565b610bb981604051806060016040528060248152602001611af260249139611481565b81600201805490508260010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001018190555081600201819080600181540180825580915050600190039060005260206000200160009091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b81846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508360010160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018390806001815401808255809150506001900390600052602060002090600891828204019190066004029091909190916101000a81548163ffffffff021916908360e01c021790555080846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610e8e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e85906119b2565b60405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610efc576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ef390611a16565b60405180910390fd5b6000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160149054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff169050600060018560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000180549050610fd39190611a7a565b90508082146111675760008560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001828154811061103457611033611510565b5b90600052602060002090600891828204019190066004029054906101000a900460e01b9050808660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000184815481106110b0576110af611510565b5b90600052602060002090600891828204019190066004026101000a81548163ffffffff021916908360e01c021790555082866000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550505b8460010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054806111bb576111ba611a98565b5b60019003818190600052602060002090600891828204019190066004026101000a81549063ffffffff02191690559055846000016000847bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001908152602001600020600080820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556000820160146101000a8154906bffffffffffffffffffffffff021916905550506000810361147a576000600186600201805490506112a59190611a7a565b905060008660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001015490508181146113e657600087600201838154811061130f5761130e611510565b5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508088600201838154811061135357611352611510565b5b9060005260206000200160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550818860010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010181905550505b866002018054806113fa576113f9611a98565b5b6001900381819060005260206000200160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905590558660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001016000905550505b5050505050565b6000823b90506000811182906114cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114c49190611aae565b60405180910390fd5b50505050565b602081526020808201527f4469616d6f6e643a2046756e6374696f6e20646f6573206e6f7420657869737460408201526000606082019050919050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052602160045260246000fd5b60208152602760208201527f4c69624469616d6f6e644375743a20496e636f7272656374204661636574437560408201527f74416374696f6e0000000000000000000000000000000000000000000000000060608201526000608082019050919050565b634e487b7160e01b600052601160045260246000fd5b6000801982036115c9576115c86115a0565b5b600182019050919050565b60018060a01b03811682525050565b60005b838110156116015780820151818401526020810190506115e6565b50600083830152505050565b600081518084526116258160208601602086016115e3565b6020601f19601f83011685010191505092915050565b60006060808301818452808751808352608092508286019150828160051b8701016020808b0160005b8481101561171757607f198a8503018652815188850160018060a01b03825116865284820151600381106116a857634e487b7160e01b600052602160045260246000fd5b80868801525060408083015192508a81880152508082518083528a880191508684019350600092505b808310156116fb5763ffffffff60e01b8451168252868201915086840193506001830192506116d1565b5080965050505082820191508286019550600181019050611664565b50506117258189018b6115d4565b508681036040880152611738818961160d565b95505050505050949350505050565b60208152602b60208201527f4c69624469616d6f6e644375743a204e6f2073656c6563746f727320696e206660408201527f6163657420746f2063757400000000000000000000000000000000000000000060608201526000608082019050919050565b60208152602c60208201527f4c69624469616d6f6e644375743a204164642066616365742063616e2774206260408201527f652061646472657373283029000000000000000000000000000000000000000060608201526000608082019050919050565b60208152603560208201527f4c69624469616d6f6e644375743a2043616e2774206164642066756e6374696f60408201527f6e207468617420616c726561647920657869737473000000000000000000000060608201526000608082019050919050565b60006bffffffffffffffffffffffff808316818103611895576118946115a0565b5b6001810192505050919050565b60208152603860208201527f4c69624469616d6f6e644375743a2043616e2774207265706c6163652066756e60408201527f6374696f6e20776974682073616d652066756e6374696f6e000000000000000060608201526000608082019050919050565b60208152603660208201527f4c69624469616d6f6e644375743a2052656d6f7665206661636574206164647260408201527f657373206d75737420626520616464726573732830290000000000000000000060608201526000608082019050919050565b6000825161197c8184602087016115e3565b80830191505092915050565b60018060a01b03831681526040602082015260006119a9604083018461160d565b90509392505050565b60208152603760208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f76652066756e6360408201527f74696f6e207468617420646f65736e277420657869737400000000000000000060608201526000608082019050919050565b60208152602e60208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f766520696d6d7560408201527f7461626c652066756e6374696f6e00000000000000000000000000000000000060608201526000608082019050919050565b6000828203905081811115611a9257611a916115a0565b5b92915050565b634e487b7160e01b600052603160045260246000fd5b602081526000611ac1602083018461160d565b90509291505056fe4c69624469616d6f6e644375743a205f696e6974206164647265737320686173206e6f20636f64654c69624469616d6f6e644375743a204e657720666163657420686173206e6f20636f6465a264697066735822122045b771fb2128a1a34c5b052e9a86464933844b34929cf0d65bbea6a4e76e3b2764736f6c634300081200334c69624469616d6f6e644375743a205f696e6974206164647265737320686173206e6f20636f64654c69624469616d6f6e644375743a204e657720666163657420686173206e6f20636f6465".into(), + salt: 0, + factory: "0x2c97f4a366Dd5D91178ec9E36c5C1fcA393A538C".into(), + }; + let address = get_counterfactual_address(&input).unwrap(); + assert_eq!(address, "0x77F62bb3E43190253D4E198199356CD2b25063cA"); +} + +#[test] +fn test_get_counterfactual_address_non_zero_salt() { + let input = ContractAddressInput { + account_facet: "0xF6F5e5fC74905e65e3FF53c6BacEba8535dd14d1".into(), + verification_facet: "0xaB84813cbf26Fd951CB3d7E33Dccb8995027e490".into(), + entry_point: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789".into(), + facet_registry: "0x9a95d201BB8F559771784D12c01F8084278c65E5".into(), + default_fallback: "0x522cDc7558b5f798dF5D61AB09B6D95Ebd342EF9".into(), + public_key: "0xB5547FBdC56DCE45e1B8ef75569916D438e09c46".into(), + bytecode: "0x60806040526040516104c83803806104c883398101604081905261002291610163565b6000858585858560405160240161003d959493929190610264565b60408051601f198184030181529181526020820180516001600160e01b0316634a93641760e01b1790525190915060009081906001600160a01b038a16906100869085906102c3565b600060405180830381855af49150503d80600081146100c1576040519150601f19603f3d011682016040523d82523d6000602084013e6100c6565b606091505b50915091508115806100e157506100dc816102df565b600114155b156100ff57604051636ff35f8960e01b815260040160405180910390fd5b505050505050505050610306565b80516001600160a01b038116811461012457600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561015a578181015183820152602001610142565b50506000910152565b60008060008060008060c0878903121561017c57600080fd5b6101858761010d565b95506101936020880161010d565b94506101a16040880161010d565b93506101af6060880161010d565b92506101bd6080880161010d565b60a08801519092506001600160401b03808211156101da57600080fd5b818901915089601f8301126101ee57600080fd5b81518181111561020057610200610129565b604051601f8201601f19908116603f0116810190838211818310171561022857610228610129565b816040528281528c602084870101111561024157600080fd5b61025283602083016020880161013f565b80955050505050509295509295509295565b600060018060a01b0380881683528087166020840152808616604084015280851660608401525060a0608083015282518060a08401526102ab8160c085016020870161013f565b601f01601f19169190910160c0019695505050505050565b600082516102d581846020870161013f565b9190910192915050565b80516020808301519190811015610300576000198160200360031b1b821691505b50919050565b6101b3806103156000396000f3fe60806040523661000b57005b600080356001600160e01b03191681527f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f6020819052604090912054819060601c806100cf576004838101546040516366ffd66360e11b81526000356001600160e01b031916928101929092526001600160a01b03169063cdffacc690602401602060405180830381865afa1580156100a8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100cc919061014d565b90505b6001600160a01b0381166101295760405162461bcd60e51b815260206004820152601d60248201527f4261727a3a2046756e6374696f6e20646f6573206e6f74206578697374000000604482015260640160405180910390fd5b3660008037600080366000845af43d6000803e808015610148573d6000f35b3d6000fd5b60006020828403121561015f57600080fd5b81516001600160a01b038116811461017657600080fd5b939250505056fea2646970667358221220d35db061bb6ecdb7688c3674af669ce44d527cae4ded59214d06722d73da62be64736f6c63430008120033".into(), + salt: 123456, + factory: "0x96C489979E39F877BDb8637b75A25C1a5B2DE14C".into(), + }; + let address = get_counterfactual_address(&input).unwrap(); + assert_eq!(address, "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"); +} + +#[test] +fn test_get_init_code() { + let public_key = hex::decode("04e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b02").unwrap(); + + let factory_address = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800"; + let verification_facet = "0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf"; + let salt = 0; + + let init_code = get_init_code(factory_address, &public_key, verification_facet, salt).unwrap(); + assert_eq!(hex::encode(init_code, true), "0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000"); + + let salt = 1; + let init_code = get_init_code(factory_address, &public_key, verification_facet, salt).unwrap(); + assert_eq!(hex::encode(init_code, true), "0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_get_formatted_signature() { + let signature = hex::decode("3044022012d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf0220256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca276").unwrap(); + let challenge = + hex::decode("cf267a78c5adaf96f341a696eb576824284c572f3e61be619694d539db1925f9").unwrap(); + let authenticator_data = + hex::decode("1a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d00000000") + .unwrap(); + let client_data_json = "{\"type\":\"webauthn.get\",\"challenge\":\"zyZ6eMWtr5bzQaaW61doJChMVy8-Yb5hlpTVOdsZJfk\",\"origin\":\"https://trustwallet.com/"}"; + + let formatted_signature = get_formatted_signature( + &signature, + &challenge, + &authenticator_data, + client_data_json, + ) + .unwrap(); + assert_eq!(hex::encode(formatted_signature, true), "0x12d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca27600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000251a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025222c226f726967696e223a2268747470733a2f2f747275737477616c6c65742e636f6d227d000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_get_prefixed_msg_hash() { + let msg_hash = + hex::decode("a6ebe22d8c1ec7edbd7f5776e49a161f67ab97161d7b8c648d80abf365765cf2").unwrap(); + let barz_address = "0x913233BfC283ffe89a5E70ADC39c0926d240bbD9"; + let chain_id = 3604; + + let prefixed_msg_hash = get_prefixed_msg_hash(&msg_hash, barz_address, chain_id).unwrap(); + assert_eq!( + hex::encode(prefixed_msg_hash, true), + "0x0488fb3e4fdaa890bf55532fc9840fb9edef9c38244f431c9430a78a86d89157" + ); +} + +#[test] +fn test_get_prefixed_msg_hash_with_zero_chain_id() { + let msg_hash = + hex::decode("cf267a78c5adaf96f341a696eb576824284c572f3e61be619694d539db1925f9").unwrap(); + let barz_address = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; + let chain_id = 0; + + let prefixed_msg_hash = get_prefixed_msg_hash(&msg_hash, barz_address, chain_id).unwrap(); + assert_eq!( + hex::encode(prefixed_msg_hash, true), + "0xc74e78634261222af51530703048f98a1b7b995a606a624f0a008e7aaba7a21b" + ); +} + +#[test] +fn test_get_diamond_cut_code() { + let input = DiamondCutInput { + init_address: "0x0000000000000000000000000000000000000000".into(), + init_data: hex::decode("0x00").unwrap().into(), + facet_cuts: vec![FacetCut { + facet_address: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6".into(), + action: FacetCutAction::ADD, + function_selectors: vec![hex::decode("fdd8a83c").unwrap().into()], + }], + }; + + let diamond_cut_code = get_diamond_cut_code(&input).unwrap(); + assert_eq!(hex::encode(diamond_cut_code, true), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001fdd8a83c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_get_diamond_cut_code_with_multiple_cut() { + let input = DiamondCutInput { + init_address: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6".into(), + init_data: hex::decode("12341234").unwrap().into(), + facet_cuts: vec![ + FacetCut { + facet_address: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6".into(), + action: FacetCutAction::ADD, + function_selectors: vec![ + hex::decode("fdd8a83c").unwrap().into(), + hex::decode("fdd8a83c").unwrap().into(), + hex::decode("fdd8a83c").unwrap().into(), + ], + }, + FacetCut { + facet_address: "0x6e3c94d74af6227aEeF75b54a679e969189a6aEC".into(), + action: FacetCutAction::ADD, + function_selectors: vec![hex::decode("12345678").unwrap().into()], + }, + ], + }; + + let diamond_cut_code = get_diamond_cut_code(&input).unwrap(); + assert_eq!(hex::encode(diamond_cut_code, true), "0x1f931c1c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003fdd8a83c00000000000000000000000000000000000000000000000000000000fdd8a83c00000000000000000000000000000000000000000000000000000000fdd8a83c000000000000000000000000000000000000000000000000000000000000000000000000000000006e3c94d74af6227aeef75b54a679e969189a6aec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001123456780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041234123400000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_get_diamond_cut_code_with_zero_selector() { + let input = DiamondCutInput { + init_address: "0x0000000000000000000000000000000000000000".into(), + init_data: hex::decode("00").unwrap().into(), + facet_cuts: vec![FacetCut { + facet_address: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6".into(), + action: FacetCutAction::ADD, + function_selectors: vec![], + }], + }; + + let diamond_cut_code = get_diamond_cut_code(&input).unwrap(); + assert_eq!(hex::encode(diamond_cut_code, true), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_get_diamond_cut_code_with_long_init_data() { + let input = DiamondCutInput { + init_address: "0x0000000000000000000000000000000000000000".into(), + init_data: hex::decode("b61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b90600000000000000000000000000000000000000000000000000000000").unwrap().into(), + facet_cuts: vec![ + FacetCut { + facet_address: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6".into(), + action: FacetCutAction::ADD, + function_selectors: vec![], + }, + ], + }; + + let diamond_cut_code = get_diamond_cut_code(&input).unwrap(); + assert_eq!(hex::encode(diamond_cut_code, true), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4b61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b9060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_get_authorization_hash() { + let chain_id = U256::from(1u64).to_big_endian(); + let contract_address = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; + let nonce = U256::from(1u64).to_big_endian(); + + let authorization_hash = + get_authorization_hash(&chain_id[..], contract_address, &nonce[..]).unwrap(); + assert_eq!( + hex::encode(authorization_hash, true), + "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9" + ); // Verified with viem +} + +#[test] +fn test_sign_authorization() { + let chain_id = U256::from(1u64).to_big_endian_compact(); + let contract_address = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; + let nonce = U256::from(1u64).to_big_endian_compact(); + let private_key = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; + + let signed_authorization = + sign_authorization(&chain_id[..], contract_address, &nonce[..], private_key).unwrap(); + let json: serde_json::Value = serde_json::from_str(&signed_authorization).unwrap(); + + // Verified with viem + assert_eq!( + json["chainId"].as_str().unwrap(), + hex::encode(chain_id, true) + ); + assert_eq!(json["address"].as_str().unwrap(), contract_address); + assert_eq!(json["nonce"].as_str().unwrap(), hex::encode(nonce, true)); + assert_eq!(json["yParity"].as_str().unwrap(), hex::encode(&[1u8], true)); + assert_eq!( + json["r"].as_str().unwrap(), + "0x2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710" + ); + assert_eq!( + json["s"].as_str().unwrap(), + "0x5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c" + ); +} + +#[test] +fn test_get_encoded_hash() { + let chain_id = U256::from(31337u64).to_big_endian(); + let code_address = "0x2e234DAe75C793f67A35089C9d99245E1C58470b"; + let code_name = "Biz"; + let code_version = "v1.0.0"; + let type_hash = "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867"; + let domain_separator_hash = + "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472"; + let sender = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"; + let user_op_hash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb"; + + let encoded_hash = get_encoded_hash( + &chain_id[..], + code_address, + code_name, + code_version, + type_hash, + domain_separator_hash, + sender, + user_op_hash, + ) + .unwrap(); + assert_eq!( + hex::encode(encoded_hash, true), + "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635" + ); +} + +#[test] +fn test_sign_user_op_hash() { + let hash = "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635"; + let private_key = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; + let signed_hash = sign_user_op_hash(hash, private_key).unwrap(); + assert_eq!(hex::encode(signed_hash, true), "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c"); +} diff --git a/rust/tw_evm/tests/barz_ffi.rs b/rust/tw_evm/tests/barz_ffi.rs new file mode 100644 index 00000000000..433312ed458 --- /dev/null +++ b/rust/tw_evm/tests/barz_ffi.rs @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_encoding::hex; +use tw_evm::ffi::barz::{ + tw_barz_get_authorization_hash, tw_barz_get_counterfactual_address, + tw_barz_get_diamond_cut_code, tw_barz_get_encoded_hash, tw_barz_get_formatted_signature, + tw_barz_get_init_code, tw_barz_get_prefixed_msg_hash, tw_barz_get_signed_hash, + tw_barz_sign_authorization, +}; +use tw_keypair::{test_utils::tw_public_key_helper::TWPublicKeyHelper, tw::PublicKeyType}; +use tw_memory::test_utils::{tw_data_helper::TWDataHelper, tw_string_helper::TWStringHelper}; +use tw_number::U256; +use tw_proto::{ + serialize, + Barz::Proto::{ContractAddressInput, DiamondCutInput, FacetCut, FacetCutAction}, +}; + +#[test] +fn test_get_counterfactual_address_non_zero_salt_ffi() { + let input = ContractAddressInput { + account_facet: "0xF6F5e5fC74905e65e3FF53c6BacEba8535dd14d1".into(), + verification_facet: "0xaB84813cbf26Fd951CB3d7E33Dccb8995027e490".into(), + entry_point: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789".into(), + facet_registry: "0x9a95d201BB8F559771784D12c01F8084278c65E5".into(), + default_fallback: "0x522cDc7558b5f798dF5D61AB09B6D95Ebd342EF9".into(), + public_key: "0xB5547FBdC56DCE45e1B8ef75569916D438e09c46".into(), + bytecode: "0x60806040526040516104c83803806104c883398101604081905261002291610163565b6000858585858560405160240161003d959493929190610264565b60408051601f198184030181529181526020820180516001600160e01b0316634a93641760e01b1790525190915060009081906001600160a01b038a16906100869085906102c3565b600060405180830381855af49150503d80600081146100c1576040519150601f19603f3d011682016040523d82523d6000602084013e6100c6565b606091505b50915091508115806100e157506100dc816102df565b600114155b156100ff57604051636ff35f8960e01b815260040160405180910390fd5b505050505050505050610306565b80516001600160a01b038116811461012457600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561015a578181015183820152602001610142565b50506000910152565b60008060008060008060c0878903121561017c57600080fd5b6101858761010d565b95506101936020880161010d565b94506101a16040880161010d565b93506101af6060880161010d565b92506101bd6080880161010d565b60a08801519092506001600160401b03808211156101da57600080fd5b818901915089601f8301126101ee57600080fd5b81518181111561020057610200610129565b604051601f8201601f19908116603f0116810190838211818310171561022857610228610129565b816040528281528c602084870101111561024157600080fd5b61025283602083016020880161013f565b80955050505050509295509295509295565b600060018060a01b0380881683528087166020840152808616604084015280851660608401525060a0608083015282518060a08401526102ab8160c085016020870161013f565b601f01601f19169190910160c0019695505050505050565b600082516102d581846020870161013f565b9190910192915050565b80516020808301519190811015610300576000198160200360031b1b821691505b50919050565b6101b3806103156000396000f3fe60806040523661000b57005b600080356001600160e01b03191681527f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f6020819052604090912054819060601c806100cf576004838101546040516366ffd66360e11b81526000356001600160e01b031916928101929092526001600160a01b03169063cdffacc690602401602060405180830381865afa1580156100a8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100cc919061014d565b90505b6001600160a01b0381166101295760405162461bcd60e51b815260206004820152601d60248201527f4261727a3a2046756e6374696f6e20646f6573206e6f74206578697374000000604482015260640160405180910390fd5b3660008037600080366000845af43d6000803e808015610148573d6000f35b3d6000fd5b60006020828403121561015f57600080fd5b81516001600160a01b038116811461017657600080fd5b939250505056fea2646970667358221220d35db061bb6ecdb7688c3674af669ce44d527cae4ded59214d06722d73da62be64736f6c63430008120033".into(), + salt: 123456, + factory: "0x96C489979E39F877BDb8637b75A25C1a5B2DE14C".into(), + }; + let input = serialize(&input).unwrap(); + let input = TWDataHelper::create(input); + + let address = TWStringHelper::wrap(unsafe { tw_barz_get_counterfactual_address(input.ptr()) }); + assert_eq!( + address.to_string(), + Some("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1".to_string()) + ); +} + +#[test] +fn test_get_init_code_ffi() { + let public_key = TWPublicKeyHelper::with_hex("04e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b02", PublicKeyType::Nist256p1Extended); + + let factory_address = TWStringHelper::create("0x3fC708630d85A3B5ec217E53100eC2b735d4f800"); + let verification_facet = TWStringHelper::create("0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf"); + let salt = 0; + + let init_code = TWDataHelper::wrap(unsafe { + tw_barz_get_init_code( + factory_address.ptr(), + public_key.ptr(), + verification_facet.ptr(), + salt, + ) + }); + assert_eq!(hex::encode(init_code.to_vec().unwrap(), true), "0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000"); + + let salt = 1; + let init_code = TWDataHelper::wrap(unsafe { + tw_barz_get_init_code( + factory_address.ptr(), + public_key.ptr(), + verification_facet.ptr(), + salt, + ) + }); + assert_eq!(hex::encode(init_code.to_vec().unwrap(), true), "0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_get_formatted_signature_ffi() { + let signature = TWDataHelper::create(hex::decode("3044022012d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf0220256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca276").unwrap()); + let challenge = TWDataHelper::create( + hex::decode("cf267a78c5adaf96f341a696eb576824284c572f3e61be619694d539db1925f9").unwrap(), + ); + let authenticator_data = TWDataHelper::create( + hex::decode("1a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d00000000") + .unwrap(), + ); + let client_data_json = TWStringHelper::create("{\"type\":\"webauthn.get\",\"challenge\":\"zyZ6eMWtr5bzQaaW61doJChMVy8-Yb5hlpTVOdsZJfk\",\"origin\":\"https://trustwallet.com/"}"); + + let formatted_signature = TWDataHelper::wrap(unsafe { + tw_barz_get_formatted_signature( + signature.ptr(), + challenge.ptr(), + authenticator_data.ptr(), + client_data_json.ptr(), + ) + }); + + assert_eq!(hex::encode(formatted_signature.to_vec().unwrap(), true), "0x12d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca27600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000251a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025222c226f726967696e223a2268747470733a2f2f747275737477616c6c65742e636f6d227d000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_get_prefixed_msg_hash_ffi() { + let msg_hash = + hex::decode("a6ebe22d8c1ec7edbd7f5776e49a161f67ab97161d7b8c648d80abf365765cf2").unwrap(); + let barz_address = "0x913233BfC283ffe89a5E70ADC39c0926d240bbD9"; + let chain_id = 3604; + + let prefixed_msg_hash = TWDataHelper::wrap(unsafe { + tw_barz_get_prefixed_msg_hash( + TWDataHelper::create(msg_hash).ptr(), + TWStringHelper::create(barz_address).ptr(), + chain_id, + ) + }); + assert_eq!( + hex::encode(prefixed_msg_hash.to_vec().unwrap(), true), + "0x0488fb3e4fdaa890bf55532fc9840fb9edef9c38244f431c9430a78a86d89157" + ); +} + +#[test] +fn test_get_prefixed_msg_hash_with_zero_chain_id_ffi() { + let msg_hash = + hex::decode("cf267a78c5adaf96f341a696eb576824284c572f3e61be619694d539db1925f9").unwrap(); + let barz_address = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; + let chain_id = 0; + + let prefixed_msg_hash = TWDataHelper::wrap(unsafe { + tw_barz_get_prefixed_msg_hash( + TWDataHelper::create(msg_hash).ptr(), + TWStringHelper::create(barz_address).ptr(), + chain_id, + ) + }); + assert_eq!( + hex::encode(prefixed_msg_hash.to_vec().unwrap(), true), + "0xc74e78634261222af51530703048f98a1b7b995a606a624f0a008e7aaba7a21b" + ); +} + +#[test] +fn test_get_diamond_cut_code_ffi() { + let input = DiamondCutInput { + init_address: "0x0000000000000000000000000000000000000000".into(), + init_data: hex::decode("0x00").unwrap().into(), + facet_cuts: vec![FacetCut { + facet_address: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6".into(), + action: FacetCutAction::ADD, + function_selectors: vec![hex::decode("fdd8a83c").unwrap().into()], + }], + }; + let input = serialize(&input).unwrap(); + let input = TWDataHelper::create(input); + + let diamond_cut_code = TWDataHelper::wrap(unsafe { tw_barz_get_diamond_cut_code(input.ptr()) }); + assert_eq!(hex::encode(diamond_cut_code.to_vec().unwrap(), true), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001fdd8a83c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_get_diamond_cut_code_with_multiple_cut_ffi() { + let input = DiamondCutInput { + init_address: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6".into(), + init_data: hex::decode("12341234").unwrap().into(), + facet_cuts: vec![ + FacetCut { + facet_address: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6".into(), + action: FacetCutAction::ADD, + function_selectors: vec![ + hex::decode("fdd8a83c").unwrap().into(), + hex::decode("fdd8a83c").unwrap().into(), + hex::decode("fdd8a83c").unwrap().into(), + ], + }, + FacetCut { + facet_address: "0x6e3c94d74af6227aEeF75b54a679e969189a6aEC".into(), + action: FacetCutAction::ADD, + function_selectors: vec![hex::decode("12345678").unwrap().into()], + }, + ], + }; + let input = serialize(&input).unwrap(); + let input = TWDataHelper::create(input); + + let diamond_cut_code = TWDataHelper::wrap(unsafe { tw_barz_get_diamond_cut_code(input.ptr()) }); + assert_eq!(hex::encode(diamond_cut_code.to_vec().unwrap(), true), "0x1f931c1c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003fdd8a83c00000000000000000000000000000000000000000000000000000000fdd8a83c00000000000000000000000000000000000000000000000000000000fdd8a83c000000000000000000000000000000000000000000000000000000000000000000000000000000006e3c94d74af6227aeef75b54a679e969189a6aec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001123456780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041234123400000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_get_diamond_cut_code_with_zero_selector_ffi() { + let input = DiamondCutInput { + init_address: "0x0000000000000000000000000000000000000000".into(), + init_data: hex::decode("00").unwrap().into(), + facet_cuts: vec![FacetCut { + facet_address: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6".into(), + action: FacetCutAction::ADD, + function_selectors: vec![], + }], + }; + let input = serialize(&input).unwrap(); + let input = TWDataHelper::create(input); + + let diamond_cut_code = TWDataHelper::wrap(unsafe { tw_barz_get_diamond_cut_code(input.ptr()) }); + assert_eq!(hex::encode(diamond_cut_code.to_vec().unwrap(), true), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_get_diamond_cut_code_with_long_init_data_ffi() { + let input = DiamondCutInput { + init_address: "0x0000000000000000000000000000000000000000".into(), + init_data: hex::decode("b61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b90600000000000000000000000000000000000000000000000000000000").unwrap().into(), + facet_cuts: vec![ + FacetCut { + facet_address: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6".into(), + action: FacetCutAction::ADD, + function_selectors: vec![], + }, + ], + }; + let input = serialize(&input).unwrap(); + let input = TWDataHelper::create(input); + + let diamond_cut_code = TWDataHelper::wrap(unsafe { tw_barz_get_diamond_cut_code(input.ptr()) }); + assert_eq!(hex::encode(diamond_cut_code.to_vec().unwrap(), true), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4b61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b9060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_get_authorization_hash_ffi() { + let chain_id = U256::from(1u64).to_big_endian(); + let chain_id = TWDataHelper::create(chain_id.to_vec()); + + let contract_address = TWStringHelper::create("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"); + + let nonce = U256::from(1u64).to_big_endian(); + let nonce = TWDataHelper::create(nonce.to_vec()); + + let authorization_hash = TWDataHelper::wrap(unsafe { + tw_barz_get_authorization_hash(chain_id.ptr(), contract_address.ptr(), nonce.ptr()) + }); + assert_eq!( + hex::encode(authorization_hash.to_vec().unwrap(), true), + "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9" + ); // Verified with viem +} + +#[test] +fn test_sign_authorization_ffi() { + let chain_id = U256::from(1u64).to_big_endian(); + let chain_id = TWDataHelper::create(chain_id.to_vec()); + + let contract_address = TWStringHelper::create("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"); + + let nonce = U256::from(1u64).to_big_endian(); + let nonce = TWDataHelper::create(nonce.to_vec()); + + let private_key = TWStringHelper::create( + "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8", + ); + + let signed_authorization = TWStringHelper::wrap(unsafe { + tw_barz_sign_authorization( + chain_id.ptr(), + contract_address.ptr(), + nonce.ptr(), + private_key.ptr(), + ) + }); + let json: serde_json::Value = + serde_json::from_str(&signed_authorization.to_string().unwrap()).unwrap(); + + // Verified with viem + assert_eq!( + json["chainId"].as_str().unwrap(), + hex::encode(U256::from(1u64).to_big_endian_compact(), true) + ); + assert_eq!( + json["address"].as_str().unwrap(), + "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" + ); + assert_eq!( + json["nonce"].as_str().unwrap(), + hex::encode(U256::from(1u64).to_big_endian_compact(), true) + ); + assert_eq!(json["yParity"].as_str().unwrap(), hex::encode(&[1u8], true)); + assert_eq!( + json["r"].as_str().unwrap(), + "0x2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710" + ); + assert_eq!( + json["s"].as_str().unwrap(), + "0x5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c" + ); +} + +#[test] +fn test_get_encoded_hash_ffi() { + let chain_id = U256::from(31337u64).to_big_endian(); + let chain_id = TWDataHelper::create(chain_id.to_vec()); + + let code_address = TWStringHelper::create("0x2e234DAe75C793f67A35089C9d99245E1C58470b"); + let code_name = TWStringHelper::create("Biz"); + let code_version = TWStringHelper::create("v1.0.0"); + let type_hash = TWStringHelper::create( + "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867", + ); + let domain_separator_hash = TWStringHelper::create( + "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472", + ); + let sender = TWStringHelper::create("0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"); + let user_op_hash = TWStringHelper::create( + "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb", + ); + + let encoded_hash = TWDataHelper::wrap(unsafe { + tw_barz_get_encoded_hash( + chain_id.ptr(), + code_address.ptr(), + code_name.ptr(), + code_version.ptr(), + type_hash.ptr(), + domain_separator_hash.ptr(), + sender.ptr(), + user_op_hash.ptr(), + ) + }); + assert_eq!( + hex::encode(encoded_hash.to_vec().unwrap(), true), + "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635" + ); +} + +#[test] +fn test_get_signed_hash_ffi() { + let hash = TWStringHelper::create( + "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635", + ); + let private_key = TWStringHelper::create( + "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8", + ); + let signed_hash = + TWDataHelper::wrap(unsafe { tw_barz_get_signed_hash(hash.ptr(), private_key.ptr()) }); + assert_eq!(hex::encode(signed_hash.to_vec().unwrap(), true), "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c"); +} diff --git a/rust/tw_keypair/src/ecdsa/der.rs b/rust/tw_keypair/src/ecdsa/der.rs index a7caa507b0d..ef76c179ee1 100644 --- a/rust/tw_keypair/src/ecdsa/der.rs +++ b/rust/tw_keypair/src/ecdsa/der.rs @@ -71,13 +71,13 @@ impl Signature { } /// Get the `r` component of the signature. - pub fn r(&self) -> &[u8] { - self.r.as_slice() + pub fn r(&self) -> &H256 { + &self.r } /// Get the `s` component of the signature. - pub fn s(&self) -> &[u8] { - self.s.as_slice() + pub fn s(&self) -> &H256 { + &self.s } /// Returns the standard binary signature representation: @@ -85,8 +85,8 @@ impl Signature { pub fn to_bytes(&self) -> SignatureBytes { let mut sign = SignatureBytes::default(); - sign[0..R_LENGTH].copy_from_slice(self.r()); - sign[R_LENGTH..].copy_from_slice(self.s()); + sign[0..R_LENGTH].copy_from_slice(self.r().as_slice()); + sign[R_LENGTH..].copy_from_slice(self.s().as_slice()); sign } diff --git a/rust/tw_keypair/src/ecdsa/signature.rs b/rust/tw_keypair/src/ecdsa/signature.rs index 0cb06a45a65..40118ff7822 100644 --- a/rust/tw_keypair/src/ecdsa/signature.rs +++ b/rust/tw_keypair/src/ecdsa/signature.rs @@ -130,8 +130,11 @@ pub struct VerifySignature { impl VerifySignature { pub fn from_der(der_signature: der::Signature) -> KeyPairResult { - let signature = Signature::signature_from_slices(der_signature.r(), der_signature.s()) - .map_err(|_| KeyPairError::InvalidSignature)?; + let signature = Signature::signature_from_slices( + der_signature.r().as_slice(), + der_signature.s().as_slice(), + ) + .map_err(|_| KeyPairError::InvalidSignature)?; Ok(VerifySignature { signature }) } diff --git a/rust/tw_proto/src/lib.rs b/rust/tw_proto/src/lib.rs index b35d8c9138d..fd2a2720ad7 100644 --- a/rust/tw_proto/src/lib.rs +++ b/rust/tw_proto/src/lib.rs @@ -2,7 +2,7 @@ // // Copyright © 2017 Trust Wallet. -use quick_protobuf::{BytesReader, MessageInfo, Writer}; +use quick_protobuf::{MessageInfo, Writer}; use std::borrow::Cow; #[allow(non_snake_case)] @@ -25,7 +25,7 @@ pub use common::google; pub use generated::TW::*; pub use quick_protobuf::{ deserialize_from_slice as deserialize_prefixed, serialize_into_vec as serialize_prefixed, - Error as ProtoError, MessageRead, MessageWrite, Result as ProtoResult, + BytesReader, Error as ProtoError, MessageRead, MessageWrite, Result as ProtoResult, }; /// Serializes a Protobuf message without the length prefix. diff --git a/rust/wallet_core_rs/Cargo.toml b/rust/wallet_core_rs/Cargo.toml index c66a335b140..61a5387fd21 100644 --- a/rust/wallet_core_rs/Cargo.toml +++ b/rust/wallet_core_rs/Cargo.toml @@ -12,6 +12,7 @@ default = [ "any-coin", "bitcoin", "ethereum", + "evm", "keypair", "solana", "ton", @@ -20,6 +21,7 @@ default = [ any-coin = ["tw_any_coin"] bitcoin = ["tw_bitcoin", "tw_coin_registry"] ethereum = ["tw_ethereum", "tw_coin_registry"] +evm = ["tw_evm"] keypair = ["tw_keypair"] solana = ["tw_solana"] ton = ["tw_ton"] @@ -39,6 +41,7 @@ tw_bitcoin = { path = "../chains/tw_bitcoin", optional = true } tw_coin_registry = { path = "../tw_coin_registry", optional = true } tw_encoding = { path = "../tw_encoding", optional = true } tw_ethereum = { path = "../chains/tw_ethereum", optional = true } +tw_evm = { path = "../tw_evm", optional = true } tw_hash = { path = "../tw_hash", optional = true } tw_keypair = { path = "../tw_keypair", optional = true } tw_memory = { path = "../tw_memory", optional = true } diff --git a/rust/wallet_core_rs/cbindgen.toml b/rust/wallet_core_rs/cbindgen.toml index 6697f798a56..1b7d376bae0 100644 --- a/rust/wallet_core_rs/cbindgen.toml +++ b/rust/wallet_core_rs/cbindgen.toml @@ -12,6 +12,7 @@ extra_bindings = [ "tw_hash", "tw_keypair", "tw_memory", + "tw_evm", ] include = [ "tw_any_coin", @@ -19,4 +20,5 @@ include = [ "tw_hash", "tw_keypair", "tw_memory", + "tw_evm", ] diff --git a/rust/wallet_core_rs/src/lib.rs b/rust/wallet_core_rs/src/lib.rs index f26f8e9364e..9fec51c117f 100644 --- a/rust/wallet_core_rs/src/lib.rs +++ b/rust/wallet_core_rs/src/lib.rs @@ -8,6 +8,8 @@ pub extern crate tw_any_coin; pub extern crate tw_bitcoin; #[cfg(feature = "utils")] pub extern crate tw_encoding; +#[cfg(feature = "evm")] +pub extern crate tw_evm; #[cfg(feature = "utils")] pub extern crate tw_hash; #[cfg(feature = "keypair")] diff --git a/src/Ethereum/Barz.cpp b/src/Ethereum/Barz.cpp deleted file mode 100644 index b9b29eedd7e..00000000000 --- a/src/Ethereum/Barz.cpp +++ /dev/null @@ -1,337 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include -#include "ABI/ValueEncoder.h" -#include "ABI/Function.h" -#include "AddressChecksum.h" -#include "EIP1014.h" -#include "Hash.h" -#include "HexCoding.h" -#include "../proto/Barz.pb.h" -#include "AsnParser.h" -#include "Base64.h" -#include "../proto/EthereumRlp.pb.h" -#include "RLP.h" -#include "PrivateKey.h" -#include - -namespace TW::Barz { - -std::string getCounterfactualAddress(const Proto::ContractAddressInput input) { - auto encodedData = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { - std::make_shared(input.account_facet()), - std::make_shared(input.verification_facet()), - std::make_shared(input.entry_point()), - std::make_shared(input.facet_registry()), - std::make_shared(input.default_fallback()), - std::make_shared(parse_hex(input.public_key())), - }); - if (!encodedData.has_value()) { - return {}; - } - - Data initCode = parse_hex(input.bytecode()); - append(initCode, encodedData.value()); - - const Data initCodeHash = Hash::keccak256(initCode); - Data salt = store(input.salt(), 32); - return Ethereum::checksumed(Ethereum::Address(hexEncoded(Ethereum::create2Address(input.factory(), salt, initCodeHash)))); -} - -Data getInitCode(const std::string& factoryAddress, const PublicKey& publicKey, const std::string& verificationFacet, const uint32_t salt) { - auto createAccountFuncEncoded = Ethereum::ABI::Function::encodeFunctionCall("createAccount", Ethereum::ABI::BaseParams { - std::make_shared(verificationFacet), - std::make_shared(publicKey.bytes), - std::make_shared(salt), - }); - if (!createAccountFuncEncoded.has_value()) { - return {}; - } - - Data envelope; - append(envelope, parse_hex(factoryAddress)); - append(envelope, createAccountFuncEncoded.value()); - return envelope; -} - -Data getFormattedSignature(const Data& signature, const Data challenge, const Data& authenticatorData, const std::string& clientDataJSON) { - std::string challengeBase64 = TW::Base64::encodeBase64Url(challenge); - while (challengeBase64.back() == '=') { - challengeBase64.pop_back(); - } - size_t challengePos = clientDataJSON.find(challengeBase64); - if (challengePos == std::string::npos) { - return Data(); - } - - const std::string clientDataJSONPre = clientDataJSON.substr(0, challengePos); - const std::string clientDataJSONPost = clientDataJSON.substr(challengePos + challengeBase64.size()); - - const auto parsedSignatureOptional = ASN::AsnParser::ecdsa_signature_from_der(signature); - if (!parsedSignatureOptional.has_value()) { - return {}; - } - const Data parsedSignature = parsedSignatureOptional.value(); - const Data rValue = subData(parsedSignature, 0, 32); - const Data sValue = subData(parsedSignature, 32, 64); - - auto encoded = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { - std::make_shared(rValue), - std::make_shared(sValue), - std::make_shared(authenticatorData), - std::make_shared(clientDataJSONPre), - std::make_shared(clientDataJSONPost), - }); - - if (encoded.has_value()) { - return encoded.value(); - } - return {}; -} - -Data getPrefixedMsgHash(const Data msgHash, const std::string& barzAddress, const uint32_t chainId) { - // keccak256("EIP712Domain(uint256 chainId,address verifyingContract)"); - const Data& domainSeparatorTypeHashData = parse_hex("0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218"); - // keccak256("BarzMessage(bytes message)") - const Data& barzMsgHashData = parse_hex("0xb1bcb804a4a3a1af3ee7920d949bdfd417ea1b736c3552c8d6563a229a619100"); - const auto signedDataPrefix = "0x1901"; - - auto encodedDomainSeparatorData = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { - std::make_shared(domainSeparatorTypeHashData), - std::make_shared(chainId), - std::make_shared(barzAddress) - }); - if (!encodedDomainSeparatorData.has_value()) { - return {}; - } - - Data domainSeparator = encodedDomainSeparatorData.value(); - const Data domainSeparatorHash = Hash::keccak256(domainSeparator); - - auto encodedRawMessageData = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { - std::make_shared(barzMsgHashData), - std::make_shared(Hash::keccak256(msgHash)), - }); - - Data rawMessageData = encodedRawMessageData.value(); - - auto encodedMsg = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { - std::make_shared(domainSeparatorHash), - std::make_shared(Hash::keccak256(rawMessageData)) - }); - auto encodedMsgData = signedDataPrefix + hex(encodedMsg.value()); - - Data finalEncodedMsgData = parse_hex(encodedMsgData); - - const Data encodedMsgHash = Hash::keccak256(finalEncodedMsgData); - - Data envelope; - append(envelope, encodedMsgHash); - - return envelope; -} - -// Function to encode the diamondCut function call using protobuf message as input -Data getDiamondCutCode(const Proto::DiamondCutInput& input) { - const auto diamondCutSelector = "1f931c1c"; - const auto dataLocationChunk = "60"; - const char defaultPadding = '0'; - Data encoded; - - // function diamondCut( - // FacetCut[] calldata diamondCut, - // address init, - // bytes calldata _calldata // Note that Barz does not use the _calldata for initialization. - // ) - Data encodedSignature = parse_hex(diamondCutSelector); // diamondCut() function selector - encoded.insert(encoded.end(), encodedSignature.begin(), encodedSignature.end()); - - // First argument Data Location `diamondCut` - Data dataLocation = parse_hex(dataLocationChunk); - pad_left(dataLocation, 32); - append(encoded, dataLocation); - - // Encode second Parameter `init` - Data initAddress = parse_hex(input.init_address()); - pad_left(initAddress, 32); - append(encoded, initAddress); - - // Third Argument Data location `_calldata` - auto callDataDataLocation = int(hex(encoded).size()) / 2; - - Ethereum::ABI::ValueEncoder::encodeUInt256(input.facet_cuts_size(), encoded); - - // Prepend the function selector for the diamondCut function - int instructChunk = 0; - int totalInstructChunk = 0; - int prevDataPosition = 0; - const auto encodingChunk = 32; - const auto bytesChunkLine = 5; - int chunkLocation; - Data dataPosition; - // Encode each FacetCut from the input - for (const auto& facetCut : input.facet_cuts()) { - if (instructChunk == 0) { - prevDataPosition = input.facet_cuts_size() * encodingChunk; - Ethereum::ABI::ValueEncoder::encodeUInt256(prevDataPosition, encoded); - chunkLocation = int(hex(encoded).size()) / 2; - } else { - prevDataPosition = prevDataPosition + (instructChunk * encodingChunk); - Ethereum::ABI::ValueEncoder::encodeUInt256(prevDataPosition, dataPosition); - instructChunk = 0; - - encoded.insert(encoded.begin() + chunkLocation, dataPosition.begin(), dataPosition.end()); - ++instructChunk; - } - Ethereum::ABI::ValueEncoder::encodeAddress(parse_hex(facetCut.facet_address()), encoded); // facet address - ++instructChunk; - Ethereum::ABI::ValueEncoder::encodeUInt256(facetCut.action(), encoded); // FacetAction enum - ++instructChunk; - append(encoded, dataLocation); // adding 0x60 DataStorage position - ++instructChunk; - Ethereum::ABI::ValueEncoder::encodeUInt256(facetCut.function_selectors_size(), encoded); // Number of FacetSelector - ++instructChunk; - // Encode and append function selectors - for (const auto& selector : facetCut.function_selectors()) { - Ethereum::ABI::ValueEncoder::encodeBytes(parse_hex(hex(selector)), encoded); - ++instructChunk; - } - totalInstructChunk += instructChunk; - } - - Data calldataLength; - Ethereum::ABI::ValueEncoder::encodeUInt256((totalInstructChunk * encodingChunk) + (bytesChunkLine * encodingChunk), calldataLength); - - encoded.insert(encoded.begin() + callDataDataLocation, calldataLength.begin(), calldataLength.end()); - - auto initDataSize = int(hex(parse_hex(input.init_data())).size()); - if (initDataSize == 0 || initDataSize % 2 != 0) - return {}; - - auto initDataLength = initDataSize / 2; // 1 byte is encoded into 2 char - Ethereum::ABI::ValueEncoder::encodeUInt256(initDataLength, encoded); - - append(encoded, parse_hex(input.init_data())); - - const int paddingLength = (encodingChunk * 2) - (initDataSize % (encodingChunk * 2)); - const std::string padding(paddingLength, defaultPadding); - append(encoded, parse_hex(padding)); - - return encoded; -} - -Data getAuthorizationHash(const Data& chainId, const std::string& contractAddress, const Data& nonce) { - EthereumRlp::Proto::EncodingInput input; - auto* list = input.mutable_item()->mutable_list(); - - list->add_items()->set_number_u256(chainId.data(), chainId.size()); - - list->add_items()->set_address(contractAddress); - - list->add_items()->set_number_u256(nonce.data(), nonce.size()); - - auto dataOut = Ethereum::RLP::encode(input); - - Data encoded; - append(encoded, parse_hex("0x05")); - append(encoded, dataOut); - - return Hash::keccak256(encoded); -} - -std::vector getRSVY(const Data& hash, const std::string& privateKey) { - auto privateKeyData = parse_hex(privateKey); - auto privateKeyObj = PrivateKey(privateKeyData, TWCurveSECP256k1); - auto signature = privateKeyObj.sign(hash); - if (signature.empty()) { - return {}; - } - // Extract r, s, v values from the signature - Data r; - Data s; - Data v; - Data yParity; - // r value (first 32 bytes) - append(r, subData(signature, 0, 32)); - // s value (next 32 bytes) - append(s, subData(signature, 32, 32)); - // v value (last byte, should be 0 or 1, add 27 to make it 27 or 28) - uint8_t vValue = signature[64] + 27; - append(v, vValue); - // yParity value (last byte) - uint8_t yParityValue = signature[64]; - append(yParity, yParityValue); - - return {r, s, v, yParity}; -} - -std::string signAuthorization(const Data& chainId, const std::string& contractAddress, const Data& nonce, const std::string& privateKey) { - auto authorizationHash = getAuthorizationHash(chainId, contractAddress, nonce); - auto rsvy = getRSVY(authorizationHash, privateKey); - - nlohmann::json jsonObj; - jsonObj["chainId"] = hexEncoded(chainId); - jsonObj["address"] = contractAddress; - jsonObj["nonce"] = hexEncoded(nonce); - jsonObj["yParity"] = hexEncoded(rsvy[3]); - jsonObj["r"] = hexEncoded(rsvy[0]); - jsonObj["s"] = hexEncoded(rsvy[1]); - - return jsonObj.dump(); -} - -Data getEncodedHash( - const Data& chainId, - const std::string& codeAddress, - const std::string& codeName, - const std::string& codeVersion, - const std::string& typeHash, - const std::string& domainSeparatorHash, - const std::string& sender, - const std::string& userOpHash) -{ - Data codeAddressBytes32(12, 0); - append(codeAddressBytes32, parse_hex(codeAddress)); - - // Create domain separator: keccak256(abi.encode(BIZ_DOMAIN_SEPARATOR_HASH, "Biz", "v1.0.0", block.chainid, wallet, _addressToBytes32(singleton))) - auto domainSeparator = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { - std::make_shared(parse_hex(domainSeparatorHash)), - std::make_shared(Hash::keccak256(codeName)), - std::make_shared(Hash::keccak256(codeVersion)), - std::make_shared(chainId), - std::make_shared(sender), - std::make_shared(codeAddressBytes32), - }); - if (!domainSeparator.has_value()) { - return {}; - } - Data domainSeparatorEncodedHash = Hash::keccak256(domainSeparator.value()); - - // Create message hash: keccak256(abi.encode(typeHash, hash)) - Data messageToHash; - append(messageToHash, parse_hex(typeHash)); - append(messageToHash, parse_hex(userOpHash)); - Data messageHash = Hash::keccak256(messageToHash); - - // Final hash: keccak256(abi.encodePacked("\x19\x01", domainSeparator, messageHash)) - Data encoded; - append(encoded, parse_hex("0x1901")); - append(encoded, domainSeparatorEncodedHash); - append(encoded, messageHash); - return Hash::keccak256(encoded); -} - -Data getSignedHash(const std::string& hash, const std::string& privateKey) { - auto rsvy = getRSVY(parse_hex(hash), privateKey); - - Data result; - append(result, rsvy[0]); - append(result, rsvy[1]); - append(result, rsvy[2]); - - return result; -} - -} // namespace TW::Barz diff --git a/src/Ethereum/Barz.h b/src/Ethereum/Barz.h deleted file mode 100644 index 445cbd529aa..00000000000 --- a/src/Ethereum/Barz.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -#include "Data.h" -#include "uint256.h" -#include "PublicKey.h" -#include "../proto/Barz.pb.h" - -namespace TW::Barz { - -std::string getCounterfactualAddress(const Proto::ContractAddressInput input); -Data getInitCode(const std::string& factoryAddress, const PublicKey& publicKey, const std::string& verificationFacet, const uint32_t salt); -Data getFormattedSignature(const Data& signature, const Data challenge, const Data& authenticatorData, const std::string& clientDataJSON); -Data getPrefixedMsgHash(const Data msgHash, const std::string& barzAddress, const uint32_t chainId); -Data getDiamondCutCode(const Proto::DiamondCutInput& input); // action should be one of 0, 1, 2. 0 = Add, 1 = Remove, 2 = Replace -Data getAuthorizationHash(const Data& chainId, const std::string& contractAddress, const Data& nonce); -std::string signAuthorization( - const Data& chainId, - const std::string& contractAddress, - const Data& nonce, - const std::string& privateKey); -Data getEncodedHash( - const Data& chainId, - const std::string& codeAddress, - const std::string& codeName, - const std::string& codeVersion, - const std::string& typeHash, - const std::string& domainSeparatorHash, - const std::string& sender, - const std::string& userOpHash); -Data getSignedHash(const std::string& hash, const std::string& privateKey); - -} diff --git a/src/Ethereum/EIP1014.cpp b/src/Ethereum/EIP1014.cpp deleted file mode 100644 index 7a72b3e0e51..00000000000 --- a/src/Ethereum/EIP1014.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include "EIP1014.h" -#include "AddressChecksum.h" -#include "Hash.h" -#include "HexCoding.h" - -namespace TW::Ethereum { - -Data create2Address(const std::string& from, const Data& salt, const Data& initCodeHash) { - if (salt.size() != 32) { - throw std::runtime_error("Error: salt must be 32 bytes."); - } - if (initCodeHash.size() != 32) { - throw std::runtime_error("Error: initCodeHash must be 32 bytes."); - } - Data input = {0xff}; - append(input, parse_hex(from)); - append(input, salt); - append(input, initCodeHash); - auto hash = Hash::keccak256(input); - return Data(hash.end() - 20, hash.end()); -} - -} // namespace TW::Ethereum diff --git a/src/Ethereum/EIP1014.h b/src/Ethereum/EIP1014.h deleted file mode 100644 index 7471a9052e8..00000000000 --- a/src/Ethereum/EIP1014.h +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -#include "Data.h" - -namespace TW::Ethereum { - -Data create2Address(const std::string& from, const Data& salt, const Data& initCodeHash); - -} diff --git a/src/Ethereum/EIP1967.cpp b/src/Ethereum/EIP1967.cpp deleted file mode 100644 index 4fe8d527b08..00000000000 --- a/src/Ethereum/EIP1967.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include "EIP1014.h" -#include "ABI/Function.h" -#include "Hash.h" -#include "HexCoding.h" - -namespace TW::Ethereum { - -static const std::string eip1967ProxyBytecodeHex = R"(0x608060405260405162000c5138038062000c51833981810160405281019062000029919062000580565b6200003d828260006200004560201b60201c565b5050620007d7565b62000056836200008860201b60201c565b600082511180620000645750805b156200008357620000818383620000df60201b620000371760201c565b505b505050565b62000099816200011560201b60201c565b8073ffffffffffffffffffffffffffffffffffffffff167fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b60405160405180910390a250565b60606200010d838360405180606001604052806027815260200162000c2a60279139620001eb60201b60201c565b905092915050565b6200012b816200027d60201b620000641760201c565b6200016d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000164906200066d565b60405180910390fd5b80620001a77f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b620002a060201b620000871760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60606000808573ffffffffffffffffffffffffffffffffffffffff1685604051620002179190620006dc565b600060405180830381855af49150503d806000811462000254576040519150601f19603f3d011682016040523d82523d6000602084013e62000259565b606091505b50915091506200027286838387620002aa60201b60201c565b925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b606083156200031a5760008351036200031157620002ce856200027d60201b60201c565b62000310576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620003079062000745565b60405180910390fd5b5b8290506200032d565b6200032c83836200033560201b60201c565b5b949350505050565b600082511115620003495781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200037f9190620007b3565b60405180910390fd5b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620003c9826200039c565b9050919050565b620003db81620003bc565b8114620003e757600080fd5b50565b600081519050620003fb81620003d0565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b62000456826200040b565b810181811067ffffffffffffffff821117156200047857620004776200041c565b5b80604052505050565b60006200048d62000388565b90506200049b82826200044b565b919050565b600067ffffffffffffffff821115620004be57620004bd6200041c565b5b620004c9826200040b565b9050602081019050919050565b60005b83811015620004f6578082015181840152602081019050620004d9565b60008484015250505050565b6000620005196200051384620004a0565b62000481565b90508281526020810184848401111562000538576200053762000406565b5b62000545848285620004d6565b509392505050565b600082601f83011262000565576200056462000401565b5b81516200057784826020860162000502565b91505092915050565b600080604083850312156200059a576200059962000392565b5b6000620005aa85828601620003ea565b925050602083015167ffffffffffffffff811115620005ce57620005cd62000397565b5b620005dc858286016200054d565b9150509250929050565b600082825260208201905092915050565b7f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60008201527f6f74206120636f6e747261637400000000000000000000000000000000000000602082015250565b600062000655602d83620005e6565b91506200066282620005f7565b604082019050919050565b60006020820190508181036000830152620006888162000646565b9050919050565b600081519050919050565b600081905092915050565b6000620006b2826200068f565b620006be81856200069a565b9350620006d0818560208601620004d6565b80840191505092915050565b6000620006ea8284620006a5565b915081905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b60006200072d601d83620005e6565b91506200073a82620006f5565b602082019050919050565b6000602082019050818103600083015262000760816200071e565b9050919050565b600081519050919050565b60006200077f8262000767565b6200078b8185620005e6565b93506200079d818560208601620004d6565b620007a8816200040b565b840191505092915050565b60006020820190508181036000830152620007cf818462000772565b905092915050565b61044380620007e76000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b610025610091565b610035610030610093565b6100a2565b565b606061005c83836040518060600160405280602781526020016103e7602791396100c8565b905092915050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b565b600061009d61014e565b905090565b3660008037600080366000845af43d6000803e80600081146100c3573d6000f35b3d6000fd5b60606000808573ffffffffffffffffffffffffffffffffffffffff16856040516100f291906102db565b600060405180830381855af49150503d806000811461012d576040519150601f19603f3d011682016040523d82523d6000602084013e610132565b606091505b5091509150610143868383876101a5565b925050509392505050565b600061017c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b610087565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606083156102075760008351036101ff576101bf85610064565b6101fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101f59061034f565b60405180910390fd5b5b829050610212565b610211838361021a565b5b949350505050565b60008251111561022d5781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161026191906103c4565b60405180910390fd5b600081519050919050565b600081905092915050565b60005b8381101561029e578082015181840152602081019050610283565b60008484015250505050565b60006102b58261026a565b6102bf8185610275565b93506102cf818560208601610280565b80840191505092915050565b60006102e782846102aa565b915081905092915050565b600082825260208201905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b6000610339601d836102f2565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600081519050919050565b6000601f19601f8301169050919050565b60006103968261036f565b6103a081856102f2565b93506103b0818560208601610280565b6103b98161037a565b840191505092915050565b600060208201905081810360008301526103de818461038b565b90509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e57dd3eafc9985be746025b6d82d4f011b9a7bb3db56f9a1eb7eadfddd376b6064736f6c63430008110033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564)"; - -Data getEIP1967ProxyInitCode(const std::string& logicAddress, const Data& data) { - Data initCode = parse_hex(eip1967ProxyBytecodeHex); - - auto proxyConstructorParamsEncoded = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { - std::make_shared(logicAddress), - std::make_shared(data), - }); - - if (!proxyConstructorParamsEncoded.has_value()) { - return {}; - } - - append(initCode, proxyConstructorParamsEncoded.value()); - return initCode; -} - -} // namespace TW::Ethereum diff --git a/src/Ethereum/EIP1967.h b/src/Ethereum/EIP1967.h deleted file mode 100644 index 04e37fffdf8..00000000000 --- a/src/Ethereum/EIP1967.h +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -#include "Data.h" - -namespace TW::Ethereum { - -Data getEIP1967ProxyInitCode(const std::string& logicAddress, const Data& data); - -} diff --git a/src/Ethereum/EIP2645.cpp b/src/Ethereum/EIP2645.cpp deleted file mode 100644 index c9ae7aa9bde..00000000000 --- a/src/Ethereum/EIP2645.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include -#include -#include - -#include -#include - -namespace TW::Ethereum::internal { - -static std::string getIntFromBits(const std::string& hexString, std::size_t from, std::optional length = std::nullopt) { - const auto data = hex_str_to_bin_str(hexString); - const auto sub = data.substr(data.size() - from, length.value_or(std::string::npos)); - return std::to_string(std::stoll(sub, nullptr, 2)); -} - -} // namespace TW::Ethereum::internal - -namespace TW::Ethereum { - -// https://docs.starkware.co/starkex/key-derivation.html -std::string accountPathFromAddress(const std::string& ethAddress, const std::string& layer, const std::string& application, const std::string& index) noexcept { - using namespace internal; - std::stringstream out; - const auto layerHash = getIntFromBits(hex(Hash::sha256(data(layer))), 31); - const auto applicationHash = getIntFromBits(hex(Hash::sha256(data(application))), 31); - const auto ethAddress1 = getIntFromBits(ethAddress.substr(2), 31); - const auto ethAddress2 = getIntFromBits(ethAddress.substr(2), 62, 31); - out << "m/2645'/" << layerHash << "'/" << applicationHash << "'/" << ethAddress1 << "'/" << ethAddress2 << "'/" << index; - return out.str(); -} - -} // namespace TW::Ethereum diff --git a/src/Ethereum/EIP2645.h b/src/Ethereum/EIP2645.h deleted file mode 100644 index e5d51a85f24..00000000000 --- a/src/Ethereum/EIP2645.h +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -#include - -namespace TW::Ethereum { - -std::string accountPathFromAddress(const std::string& ethAddress, const std::string& layer, const std::string& application, const std::string& index) noexcept;; - -} // namespace TW::Ethereum diff --git a/src/interface/TWBarz.cpp b/src/interface/TWBarz.cpp deleted file mode 100644 index 1220d65beab..00000000000 --- a/src/interface/TWBarz.cpp +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include -#include -#include -#include "Ethereum/Barz.h" - -TWString *_Nonnull TWBarzGetCounterfactualAddress(TWData *_Nonnull input) { - TW::Barz::Proto::ContractAddressInput inputProto; - - const auto bytes = TWDataBytes(input); - const auto size = static_cast(TWDataSize(input)); - if (!inputProto.ParseFromArray(bytes, size)) { - return ""; - } - - return TWStringCreateWithUTF8Bytes(TW::Barz::getCounterfactualAddress(inputProto).c_str()); -} - -TWData *_Nonnull TWBarzGetInitCode(TWString* _Nonnull factory, struct TWPublicKey* _Nonnull publicKey, TWString* _Nonnull verificationFacet, uint32_t salt) { - const auto& factoryStr = *reinterpret_cast(factory); - const auto& publicKeyConverted = *reinterpret_cast(publicKey); - const auto& verificationFacetStr = *reinterpret_cast(verificationFacet); - - const auto initCode = TW::Barz::getInitCode(factoryStr, publicKeyConverted, verificationFacetStr, salt); - return TWDataCreateWithData(&initCode); -} - -TWData *_Nonnull TWBarzGetFormattedSignature(TWData* _Nonnull signature, TWData* _Nonnull challenge, TWData* _Nonnull authenticatorData, TWString* _Nonnull clientDataJSON) { - const auto& signatureData = *reinterpret_cast(signature); - const auto& challengeData = *reinterpret_cast(challenge); - const auto& authenticatorDataConverted = *reinterpret_cast(authenticatorData); - const auto& clientDataJSONStr = *reinterpret_cast(clientDataJSON); - - const auto initCode = TW::Barz::getFormattedSignature(signatureData, challengeData, authenticatorDataConverted, clientDataJSONStr); - return TWDataCreateWithData(&initCode); -} - -TWData *_Nonnull TWBarzGetPrefixedMsgHash(TWData* _Nonnull msgHash, TWString* _Nonnull barzAddress, uint32_t chainId) { - const auto& msgHashData = *reinterpret_cast(msgHash); - const auto& barzAddressData = *reinterpret_cast(barzAddress); - - const auto prefixedMsgHash = TW::Barz::getPrefixedMsgHash(msgHashData, barzAddressData, chainId); - return TWDataCreateWithData(&prefixedMsgHash); -} - -TWData *_Nonnull TWBarzGetDiamondCutCode(TWData *_Nonnull input) { - TW::Barz::Proto::DiamondCutInput inputProto; - - const auto bytes = TWDataBytes(input); - const auto size = static_cast(TWDataSize(input)); - if (!inputProto.ParseFromArray(bytes, size)) { - return ""; - } - - const auto diamondCutCode = TW::Barz::getDiamondCutCode(inputProto); - return TWDataCreateWithData(&diamondCutCode); -} - -TWData *_Nonnull TWBarzGetAuthorizationHash(TWData* _Nonnull chainId, TWString* _Nonnull contractAddress, TWString* _Nonnull nonce) { - const auto& chainIdData = *reinterpret_cast(chainId); - const auto& contractAddressStr = *reinterpret_cast(contractAddress); - const auto& nonceData = *reinterpret_cast(nonce); - const auto authorizationHash = TW::Barz::getAuthorizationHash(chainIdData, contractAddressStr, nonceData); - return TWDataCreateWithData(&authorizationHash); -} - -TWString *_Nonnull TWBarzSignAuthorization(TWData* _Nonnull chainId, TWString* _Nonnull contractAddress, TWData* _Nonnull nonce, TWString* _Nonnull privateKey) { - const auto& chainIdData = *reinterpret_cast(chainId); - const auto& contractAddressStr = *reinterpret_cast(contractAddress); - const auto& nonceData = *reinterpret_cast(nonce); - const auto& privateKeyStr = *reinterpret_cast(privateKey); - const auto signedAuthorization = TW::Barz::signAuthorization(chainIdData, contractAddressStr, nonceData, privateKeyStr); - return TWStringCreateWithUTF8Bytes(signedAuthorization.c_str()); -} - -TWData *_Nonnull TWBarzGetEncodedHash( - TWData* _Nonnull chainId, - TWString* _Nonnull codeAddress, - TWString* _Nonnull codeName, - TWString* _Nonnull codeVersion, - TWString* _Nonnull typeHash, - TWString* _Nonnull domainSeparatorHash, - TWString* _Nonnull sender, - TWString* _Nonnull userOpHash) { - const auto& chainIdData = *reinterpret_cast(chainId); - const auto& codeAddressStr = *reinterpret_cast(codeAddress); - const auto& codeNameStr = *reinterpret_cast(codeName); - const auto& codeVersionStr = *reinterpret_cast(codeVersion); - const auto& typeHashStr = *reinterpret_cast(typeHash); - const auto& domainSeparatorHashStr = *reinterpret_cast(domainSeparatorHash); - const auto& senderStr = *reinterpret_cast(sender); - const auto& userOpHashStr = *reinterpret_cast(userOpHash); - const auto encodedHash = TW::Barz::getEncodedHash( - chainIdData, - codeAddressStr, - codeNameStr, - codeVersionStr, typeHashStr, - domainSeparatorHashStr, - senderStr, - userOpHashStr); - return TWDataCreateWithData(&encodedHash); -} - -TWData *_Nonnull TWBarzGetSignedHash(TWString* _Nonnull hash, TWString* _Nonnull privateKey) { - const auto& hashStr = *reinterpret_cast(hash); - const auto& privateKeyStr = *reinterpret_cast(privateKey); - const auto signedHash = TW::Barz::getSignedHash(hashStr, privateKeyStr); - return TWDataCreateWithData(&signedHash); -} diff --git a/src/interface/TWEthereum.cpp b/src/interface/TWEthereum.cpp deleted file mode 100644 index 2c492c48e3f..00000000000 --- a/src/interface/TWEthereum.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include "Data.h" -#include "Ethereum/EIP1014.h" -#include "Ethereum/EIP2645.h" -#include - -#include - -TWString* TWEthereumEip2645GetPath(TWString* ethAddress, TWString* layer, TWString* application, TWString* index) { - const auto& ethAddressStr = *reinterpret_cast(ethAddress); - const auto& layerStr = *reinterpret_cast(layer); - const auto& applicationStr = *reinterpret_cast(application); - const auto& indexStr = *reinterpret_cast(index); - return new std::string(TW::Ethereum::accountPathFromAddress(ethAddressStr, layerStr, applicationStr, indexStr)); -} \ No newline at end of file diff --git a/swift/Tests/BarzTests.swift b/swift/Tests/BarzTests.swift index 53f03565314..ce362d845b8 100644 --- a/swift/Tests/BarzTests.swift +++ b/swift/Tests/BarzTests.swift @@ -13,7 +13,7 @@ class BarzTests: XCTestCase { let publicKeyData = Data(hexString: "0x04e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b02")! let publicKey = PublicKey(data: publicKeyData, type: .nist256p1Extended)! let verificationFacet = "0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf" - let result = Barz.getInitCode(factory: factoryAddress, publicKey: publicKey, verificationFacet: verificationFacet, salt: 0) + let result = Barz.getInitCode(factory: factoryAddress, publicKey: publicKey, verificationFacet: verificationFacet, salt: 0)! XCTAssertEqual(result.hexString, "3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000") } @@ -22,7 +22,7 @@ class BarzTests: XCTestCase { let publicKeyData = Data(hexString: "0x04e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b02")! let publicKey = PublicKey(data: publicKeyData, type: .nist256p1Extended)! let verificationFacet = "0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf" - let result = Barz.getInitCode(factory: factoryAddress, publicKey: publicKey, verificationFacet: verificationFacet, salt: 1) + let result = Barz.getInitCode(factory: factoryAddress, publicKey: publicKey, verificationFacet: verificationFacet, salt: 1)! XCTAssertEqual(result.hexString, "3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000") } @@ -63,7 +63,7 @@ class BarzTests: XCTestCase { let challenge = Data(hexString: "0xcf267a78c5adaf96f341a696eb576824284c572f3e61be619694d539db1925f9")! let authenticatorData = Data(hexString: "0x1a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d00000000")! let clientDataJSON = "{\"type\":\"webauthn.get\",\"challenge\":\"zyZ6eMWtr5bzQaaW61doJChMVy8-Yb5hlpTVOdsZJfk\",\"origin\":\"https://trustwallet.com/"}"; - let result = Barz.getFormattedSignature(signature: signature, challenge: challenge, authenticatorData: authenticatorData, clientDataJSON: clientDataJSON); + let result = Barz.getFormattedSignature(signature: signature, challenge: challenge, authenticatorData: authenticatorData, clientDataJson: clientDataJSON)!; XCTAssertEqual(result.hexString, "12d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca27600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000251a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025222c226f726967696e223a2268747470733a2f2f747275737477616c6c65742e636f6d227d000000000000000000000000000000000000000000000000000000") } @@ -126,7 +126,7 @@ class BarzTests: XCTestCase { publicKey: publicKey, verificationFacet: "0x5034534Efe9902779eD6eA6983F435c00f3bc510", salt: 0 - ) + )! } $0.transaction = EthereumTransaction.with { @@ -199,7 +199,7 @@ class BarzTests: XCTestCase { let contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" let nonce = Data(hexString: "0x01")! - let authorizationHash = Barz.getAuthorizationHash(chainId: chainId, contractAddress: contractAddress, nonce: nonce) + let authorizationHash = Barz.getAuthorizationHash(chainId: chainId, contractAddress: contractAddress, nonce: nonce)! XCTAssertEqual(authorizationHash.hexString, "3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9") // Verified with viem } diff --git a/swift/Tests/HDWalletTests.swift b/swift/Tests/HDWalletTests.swift index c3e885a36bc..a94fcec50a7 100644 --- a/swift/Tests/HDWalletTests.swift +++ b/swift/Tests/HDWalletTests.swift @@ -21,7 +21,7 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(ethPrivateKey.data.hexString, "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"); // StarkKey Derivation Path - let derivationPath = DerivationPath(string: starkDerivationPath)! + let derivationPath = DerivationPath(string: starkDerivationPath!)! // Retrieve Stark Private key part let ethMsg = "Only sign this request if you’ve initiated an action with Immutable X." diff --git a/tests/chains/Ethereum/BarzTests.cpp b/tests/chains/Ethereum/BarzTests.cpp index 2d7106d16f3..f521de95d1d 100644 --- a/tests/chains/Ethereum/BarzTests.cpp +++ b/tests/chains/Ethereum/BarzTests.cpp @@ -6,10 +6,11 @@ #include #include #include -#include #include #include "proto/Ethereum.pb.h" +#include "proto/Barz.pb.h" #include "HexCoding.h" +#include "uint256.h" #include #include #include @@ -19,25 +20,6 @@ namespace TW::Barz::tests { TEST(Barz, GetInitCode) { const PublicKey& publicKey = PublicKey(parse_hex("0x04e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b02"), TWPublicKeyTypeNIST256p1Extended); - // C++ - { - const std::string& factoryAddress = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800"; - const std::string& verificationFacetAddress = "0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf"; - const auto salt = 0; - - const auto& initCode = Barz::getInitCode(factoryAddress, publicKey, verificationFacetAddress, salt); - ASSERT_EQ(hexEncoded(initCode), "0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000"); - } - - { - const std::string& factoryAddress = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800"; - const std::string& verificationFacetAddress = "0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf"; - const auto salt = 1; - - const auto& initCode = Barz::getInitCode(factoryAddress, publicKey, verificationFacetAddress, salt); - ASSERT_EQ(hexEncoded(initCode), "0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000"); - } - // C { const auto factoryAddress = STRING("0x3fC708630d85A3B5ec217E53100eC2b735d4f800"); @@ -72,12 +54,6 @@ TEST(Barz, GetCounterfactualAddress) { input.set_public_key("0xB5547FBdC56DCE45e1B8ef75569916D438e09c46"); input.set_salt(0); - // C++ - { - const std::string& address = getCounterfactualAddress(input); - ASSERT_EQ(address, "0x77F62bb3E43190253D4E198199356CD2b25063cA"); - } - // C { const auto inputData = input.SerializeAsString(); @@ -99,12 +75,6 @@ TEST(Barz, GetCounterfactualAddressNonZeroSalt) { input.set_public_key("0xB5547FBdC56DCE45e1B8ef75569916D438e09c46"); input.set_salt(123456); - // C++ - { - const std::string& address = getCounterfactualAddress(input); - ASSERT_EQ(address, "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"); - } - // C { const auto inputData = input.SerializeAsString(); @@ -115,17 +85,6 @@ TEST(Barz, GetCounterfactualAddressNonZeroSalt) { } TEST(Barz, GetFormattedSignature) { - // C++ - { - const Data& signature = parse_hex("0x3044022012d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf0220256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca276"); - const Data& challenge = parse_hex("0xcf267a78c5adaf96f341a696eb576824284c572f3e61be619694d539db1925f9"); - const Data& authenticatorData = parse_hex("0x1a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d00000000"); - const std::string& clientDataJSON = "{\"type\":\"webauthn.get\",\"challenge\":\"zyZ6eMWtr5bzQaaW61doJChMVy8-Yb5hlpTVOdsZJfk\",\"origin\":\"https://trustwallet.com/"}"; - - const auto& formattedSignature = Barz::getFormattedSignature(signature, challenge, authenticatorData, clientDataJSON); - ASSERT_EQ(hexEncoded(formattedSignature), "0x12d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca27600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000251a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025222c226f726967696e223a2268747470733a2f2f747275737477616c6c65742e636f6d227d000000000000000000000000000000000000000000000000000000"); - } - // C { const auto signature = DATA("0x3044022012d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf0220256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca276"); @@ -200,11 +159,12 @@ TEST(Barz, SignR1TransferAccountNotDeployed) { auto entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; auto sender = "0x1392Ae041BfBdBAA0cFF9234a0C8F64df97B7218"; auto to = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9"; - auto factory = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800"; - auto verificationFacet = "0x5034534Efe9902779eD6eA6983F435c00f3bc510"; + auto factory = STRING("0x3fC708630d85A3B5ec217E53100eC2b735d4f800"); + auto verificationFacet = STRING( "0x5034534Efe9902779eD6eA6983F435c00f3bc510"); auto publicKey = PublicKey(parse_hex("0x04b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f"), TWPublicKeyTypeNIST256p1Extended); auto salt = 0; - auto initCode = Barz::getInitCode(factory, publicKey, verificationFacet, salt); + + const auto& initCodeData = WRAPD(TWBarzGetInitCode(factory.get(), WRAP(TWPublicKey, new TWPublicKey{ TW::PublicKey(publicKey) }).get(), verificationFacet.get(), salt)); auto key = parse_hex("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483"); @@ -221,7 +181,7 @@ TEST(Barz, SignR1TransferAccountNotDeployed) { user_operation.set_pre_verification_gas(preVerificationGas.data(), preVerificationGas.size()); user_operation.set_entry_point(entryPoint); user_operation.set_sender(sender); - user_operation.set_init_code(initCode.data(), initCode.size()); + user_operation.set_init_code(TWDataBytes(initCodeData.get()), TWDataSize(initCodeData.get())); input.set_private_key(key.data(), key.size()); auto& transfer = *input.mutable_transaction()->mutable_scw_execute()->mutable_transaction()->mutable_transfer(); @@ -315,11 +275,12 @@ TEST(Barz, SignR1BatchedTransferAccountDeployed) { TEST(Barz, GetPrefixedMsgHash) { { const Data& msgHash = parse_hex("0xa6ebe22d8c1ec7edbd7f5776e49a161f67ab97161d7b8c648d80abf365765cf2"); - const std::string& barzAddress = "0x913233BfC283ffe89a5E70ADC39c0926d240bbD9"; + const auto barzAddress = STRING("0x913233BfC283ffe89a5E70ADC39c0926d240bbD9"); const auto chainId = 3604; - const auto& prefixedMsgHash = Barz::getPrefixedMsgHash(msgHash, barzAddress, chainId); - ASSERT_EQ(hexEncoded(prefixedMsgHash), "0x0488fb3e4fdaa890bf55532fc9840fb9edef9c38244f431c9430a78a86d89157"); + const auto& prefixedMsgHash = TWBarzGetPrefixedMsgHash(WRAPD(TWDataCreateWithBytes(msgHash.data(), msgHash.size())).get(), barzAddress.get(), chainId); + const auto& prefixedMsgHashData = hexEncoded(*reinterpret_cast(WRAPD(prefixedMsgHash).get())); + ASSERT_EQ(prefixedMsgHashData, "0x0488fb3e4fdaa890bf55532fc9840fb9edef9c38244f431c9430a78a86d89157"); } } @@ -329,8 +290,9 @@ TEST(Barz, GetPrefixedMsgHashWithZeroChainId) { const std::string& barzAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; const auto chainId = 0; - const auto& prefixedMsgHash = Barz::getPrefixedMsgHash(msgHash, barzAddress, chainId); - ASSERT_EQ(hexEncoded(prefixedMsgHash), "0xc74e78634261222af51530703048f98a1b7b995a606a624f0a008e7aaba7a21b"); + const auto& prefixedMsgHash = TWBarzGetPrefixedMsgHash(WRAPD(TWDataCreateWithBytes(msgHash.data(), msgHash.size())).get(), WRAPS(TWStringCreateWithUTF8Bytes(barzAddress.c_str())).get(), chainId); + const auto& prefixedMsgHashData = hexEncoded(*reinterpret_cast(WRAPD(prefixedMsgHash).get())); + ASSERT_EQ(prefixedMsgHashData, "0xc74e78634261222af51530703048f98a1b7b995a606a624f0a008e7aaba7a21b"); } } @@ -339,7 +301,9 @@ TEST(Barz, GetDiamondCutCode) { TW::Barz::Proto::DiamondCutInput input; input.set_init_address("0x0000000000000000000000000000000000000000"); - input.set_init_data("0x00"); + + auto init_data = parse_hex("0x00"); + input.set_init_data(init_data.data(), init_data.size()); auto* facetCutAdd = input.add_facet_cuts(); facetCutAdd->set_facet_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); @@ -348,8 +312,11 @@ TEST(Barz, GetDiamondCutCode) { auto functionSelectorAdd = parse_hex("0xfdd8a83c"); facetCutAdd->add_function_selectors(functionSelectorAdd.data(), functionSelectorAdd.size()); - const auto& diamondCutCode = Barz::getDiamondCutCode(input); - ASSERT_EQ(hexEncoded(diamondCutCode), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001fdd8a83c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"); + const auto& inputData = input.SerializeAsString(); + const auto& inputTWData = WRAPD(TWDataCreateWithBytes((uint8_t*)inputData.data(), inputData.size())); + const auto& diamondCutCode = TWBarzGetDiamondCutCode(inputTWData.get()); + const auto& diamondCutCodeData = hexEncoded(*reinterpret_cast(WRAPD(diamondCutCode).get())); + ASSERT_EQ(diamondCutCodeData, "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001fdd8a83c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"); } } @@ -358,7 +325,9 @@ TEST(Barz, GetDiamondCutCodeWithMultipleCut) { TW::Barz::Proto::DiamondCutInput input; input.set_init_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); - input.set_init_data("0x12341234"); + + auto init_data = parse_hex("0x12341234"); + input.set_init_data(init_data.data(), init_data.size()); auto* facetCutMigrationFacet = input.add_facet_cuts(); facetCutMigrationFacet->set_facet_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); @@ -377,9 +346,10 @@ TEST(Barz, GetDiamondCutCodeWithMultipleCut) { auto testSignature = parse_hex("0x12345678"); facetCutTestFacet->add_function_selectors(testSignature.data(), testSignature.size()); - - const auto& diamondCutCode = Barz::getDiamondCutCode(input); - ASSERT_EQ(hexEncoded(diamondCutCode), "0x1f931c1c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003fdd8a83c00000000000000000000000000000000000000000000000000000000fdd8a83c00000000000000000000000000000000000000000000000000000000fdd8a83c000000000000000000000000000000000000000000000000000000000000000000000000000000006e3c94d74af6227aeef75b54a679e969189a6aec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001123456780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041234123400000000000000000000000000000000000000000000000000000000"); + const auto& serialized = input.SerializeAsString(); + const auto& diamondCutCode = TWBarzGetDiamondCutCode(WRAPD(TWDataCreateWithBytes((const uint8_t *)serialized.data(), serialized.size())).get()); + const auto& diamondCutCodeData = hexEncoded(*reinterpret_cast(WRAPD(diamondCutCode).get())); + ASSERT_EQ(diamondCutCodeData, "0x1f931c1c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003fdd8a83c00000000000000000000000000000000000000000000000000000000fdd8a83c00000000000000000000000000000000000000000000000000000000fdd8a83c000000000000000000000000000000000000000000000000000000000000000000000000000000006e3c94d74af6227aeef75b54a679e969189a6aec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001123456780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041234123400000000000000000000000000000000000000000000000000000000"); } } @@ -388,13 +358,17 @@ TEST(Barz, GetDiamondCutCodeWithZeroSelector) { TW::Barz::Proto::DiamondCutInput input; input.set_init_address("0x0000000000000000000000000000000000000000"); - input.set_init_data("0x00"); + auto init_data = parse_hex("0x00"); + input.set_init_data(init_data.data(), init_data.size()); + auto* facetCutAdd = input.add_facet_cuts(); facetCutAdd->set_facet_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); facetCutAdd->set_action(TW::Barz::Proto::FacetCutAction::ADD); - const auto& diamondCutCode = Barz::getDiamondCutCode(input); - ASSERT_EQ(hexEncoded(diamondCutCode), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"); + const auto& serialized = input.SerializeAsString(); + const auto& diamondCutCode = TWBarzGetDiamondCutCode(WRAPD(TWDataCreateWithBytes((const uint8_t *)serialized.data(), serialized.size())).get()); + const auto& diamondCutCodeData = hexEncoded(*reinterpret_cast(WRAPD(diamondCutCode).get())); + ASSERT_EQ(diamondCutCodeData, "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"); } } @@ -403,24 +377,30 @@ TEST(Barz, GetDiamondCutCodeWithLongInitData) { TW::Barz::Proto::DiamondCutInput input; input.set_init_address("0x0000000000000000000000000000000000000000"); - input.set_init_data("0xb61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b90600000000000000000000000000000000000000000000000000000000"); + + auto init_data = parse_hex("0xb61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b90600000000000000000000000000000000000000000000000000000000"); + input.set_init_data(init_data.data(), init_data.size()); + auto* facetCutAdd = input.add_facet_cuts(); facetCutAdd->set_facet_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); facetCutAdd->set_action(TW::Barz::Proto::FacetCutAction::ADD); - const auto& diamondCutCode = Barz::getDiamondCutCode(input); - ASSERT_EQ(hexEncoded(diamondCutCode), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4b61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b9060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + const auto& serialized = input.SerializeAsString(); + const auto& diamondCutCode = TWBarzGetDiamondCutCode(WRAPD(TWDataCreateWithBytes((const uint8_t *)serialized.data(), serialized.size())).get()); + const auto& diamondCutCodeData = hexEncoded(*reinterpret_cast(WRAPD(diamondCutCode).get())); + ASSERT_EQ(diamondCutCodeData, "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4b61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b9060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); } } TEST(Barz, GetAuthorizationHash) { { const auto chainId = store(uint256_t(1)); - const auto contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; + const auto contractAddress = STRING("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"); const auto nonce = store(uint256_t(1)); - - const auto& authorizationHash = Barz::getAuthorizationHash(chainId, contractAddress, nonce); - ASSERT_EQ(hexEncoded(authorizationHash), "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9"); // Verified with viem + + const auto& authorizationHash = TWBarzGetAuthorizationHash(WRAPD(TWDataCreateWithBytes(chainId.data(), chainId.size())).get(), contractAddress.get(), WRAPD(TWDataCreateWithBytes(nonce.data(), nonce.size())).get()); + const auto& authorizationHashData = hexEncoded(*reinterpret_cast(WRAPD(authorizationHash).get())); + ASSERT_EQ(authorizationHashData, "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9"); // Verified with viem } } @@ -431,8 +411,8 @@ TEST(Barz, SignAuthorization) { const auto nonce = store(uint256_t(1)); const auto privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; - const auto signedAuthorization = Barz::signAuthorization(chainId, contractAddress, nonce, privateKey); - auto json = nlohmann::json::parse(signedAuthorization); + const auto signedAuthorization = WRAPS(TWBarzSignAuthorization(WRAPD(TWDataCreateWithBytes(chainId.data(), chainId.size())).get(), STRING(contractAddress).get(), WRAPD(TWDataCreateWithBytes(nonce.data(), nonce.size())).get(), STRING(privateKey).get())); + auto json = nlohmann::json::parse(std::string(TWStringUTF8Bytes(signedAuthorization.get()))); // Verified with viem ASSERT_EQ(json["chainId"], hexEncoded(chainId)); ASSERT_EQ(json["address"], contractAddress); @@ -446,33 +426,35 @@ TEST(Barz, SignAuthorization) { TEST(Barz, GetEncodedHash) { { const auto chainId = store(uint256_t(31337), 32); - const auto codeAddress = "0x2e234DAe75C793f67A35089C9d99245E1C58470b"; - const auto codeName = "Biz"; - const auto codeVersion = "v1.0.0"; - const auto typeHash = "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867"; - const auto domainSeparatorHash = "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472"; - const auto sender = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"; - const auto userOpHash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb"; - - const auto& encodedHash = Barz::getEncodedHash( - chainId, - codeAddress, - codeName, - codeVersion, - typeHash, - domainSeparatorHash, - sender, - userOpHash); - ASSERT_EQ(hexEncoded(encodedHash), "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635"); + const auto codeAddress = STRING("0x2e234DAe75C793f67A35089C9d99245E1C58470b"); + const auto codeName = STRING("Biz"); + const auto codeVersion = STRING("v1.0.0"); + const auto typeHash = STRING("0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867"); + const auto domainSeparatorHash = STRING("0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472"); + const auto sender = STRING("0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"); + const auto userOpHash = STRING("0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb"); + + const auto& encodedHash = TWBarzGetEncodedHash( + WRAPD(TWDataCreateWithBytes(chainId.data(), chainId.size())).get(), + codeAddress.get(), + codeName.get(), + codeVersion.get(), + typeHash.get(), + domainSeparatorHash.get(), + sender.get(), + userOpHash.get()); + const auto& encodedHashData = hexEncoded(*reinterpret_cast(WRAPD(encodedHash).get())); + ASSERT_EQ(encodedHashData, "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635"); } } TEST(Barz, GetSignedHash) { { - const auto hash = "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635"; - const auto privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; - const auto signedHash = Barz::getSignedHash(hash, privateKey); - ASSERT_EQ(hexEncoded(signedHash), "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c"); + const auto hash = STRING("0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635"); + const auto privateKey = STRING("0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"); + const auto signedHash = TWBarzGetSignedHash(hash.get(), privateKey.get()); + const auto& signedHashData = hexEncoded(*reinterpret_cast(WRAPD(signedHash).get())); + ASSERT_EQ(signedHashData, "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c"); } } diff --git a/tests/chains/Ethereum/EIP1014Tests.cpp b/tests/chains/Ethereum/EIP1014Tests.cpp index 6b53150ee4a..1c8f7d1ab0a 100644 --- a/tests/chains/Ethereum/EIP1014Tests.cpp +++ b/tests/chains/Ethereum/EIP1014Tests.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include @@ -14,74 +13,81 @@ namespace TW::Ethereum::tests { TEST(EthereumEip1014, Example0) { - // C++ { - const std::string& from = "0x0000000000000000000000000000000000000000"; - const Data salt = parse_hex("0x0000000000000000000000000000000000000000000000000000000000000000"); - Data initCodeHash = Hash::keccak256(parse_hex("0x00")); - const auto& addressData = Ethereum::create2Address(from, salt, initCodeHash); - ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38"); + const auto from = STRING("0x0000000000000000000000000000000000000000"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x0000000000000000000000000000000000000000000000000000000000000000")).get())); + const auto initCode = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x00")).get())); + const auto initCodeHash = WRAPD(TWHashKeccak256(initCode.get())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCodeHash.get())); + assertStringsEqual(address, "0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38"); } } TEST(EthereumEip1014, Example1) { - // C++ { - const std::string& from = "0xdeadbeef00000000000000000000000000000000"; - const Data salt = parse_hex("0x0000000000000000000000000000000000000000000000000000000000000000"); - Data initCodeHash = Hash::keccak256(parse_hex("0x00")); - const auto& addressData = Ethereum::create2Address(from, salt, initCodeHash); - ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0xB928f69Bb1D91Cd65274e3c79d8986362984fDA3"); + const auto from = STRING("0xdeadbeef00000000000000000000000000000000"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x0000000000000000000000000000000000000000000000000000000000000000")).get())); + const auto initCode = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x00")).get())); + const auto initCodeHash = WRAPD(TWHashKeccak256(initCode.get())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCodeHash.get())); + assertStringsEqual(address, "0xB928f69Bb1D91Cd65274e3c79d8986362984fDA3"); } } TEST(EthereumEip1014, Example2) { - const std::string& from = "0xdeadbeef00000000000000000000000000000000"; - const Data salt = parse_hex("0x000000000000000000000000feed000000000000000000000000000000000000"); - Data initCode = parse_hex("0x00"); - initCode.resize(32); - const auto& addressData = Ethereum::create2Address(from, salt, initCode); - ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0x2DB27D1d6BE32C9abfA484BA3d591101881D4B9f"); + const auto from = STRING("0xdeadbeef00000000000000000000000000000000"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x000000000000000000000000feed000000000000000000000000000000000000")).get())); + Data initCodeData = parse_hex("0x00"); + initCodeData.resize(32); + const auto initCode = WRAPD(TWDataCreateWithBytes(initCodeData.data(), initCodeData.size())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCode.get())); + assertStringsEqual(address, "0x2DB27D1d6BE32C9abfA484BA3d591101881D4B9f"); } TEST(EthereumEip1014, Example3) { - const std::string& from = "0x0000000000000000000000000000000000000000"; - const Data salt = parse_hex("0x0000000000000000000000000000000000000000000000000000000000000000"); - Data initCode = parse_hex("0xdeadbeef"); - initCode.resize(32); - const auto& addressData = Ethereum::create2Address(from, salt, initCode); - ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0x219438aC82230Cb9A9C13Cd99D324fA1d66CF018"); + const auto from = STRING("0x0000000000000000000000000000000000000000"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x0000000000000000000000000000000000000000000000000000000000000000")).get())); + Data initCodeData = parse_hex("0xdeadbeef"); + initCodeData.resize(32); + const auto initCode = WRAPD(TWDataCreateWithBytes(initCodeData.data(), initCodeData.size())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCode.get())); + assertStringsEqual(address, "0x219438aC82230Cb9A9C13Cd99D324fA1d66CF018"); } TEST(EthereumEip1014, Example4) { - const std::string& from = "0x00000000000000000000000000000000deadbeef"; - const Data salt = parse_hex("0x00000000000000000000000000000000000000000000000000000000cafebabe"); - Data initCodeHash = Hash::keccak256(parse_hex("0xdeadbeef")); - const auto& addressData = Ethereum::create2Address(from, salt, initCodeHash); - ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0x60f3f640a8508fC6a86d45DF051962668E1e8AC7"); + const auto from = STRING("0x00000000000000000000000000000000deadbeef"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x00000000000000000000000000000000000000000000000000000000cafebabe")).get())); + Data initCodeData = parse_hex("0xdeadbeef"); + const auto initCode = WRAPD(TWDataCreateWithBytes(initCodeData.data(), initCodeData.size())); + const auto initCodeHash = WRAPD(TWHashKeccak256(initCode.get())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCodeHash.get())); + assertStringsEqual(address, "0x60f3f640a8508fC6a86d45DF051962668E1e8AC7"); } TEST(EthereumEip1014, Example5) { - const std::string& from = "0x00000000000000000000000000000000deadbeef"; - const Data salt = parse_hex("0x00000000000000000000000000000000000000000000000000000000cafebabe"); - Data initCodeHash = Hash::keccak256(parse_hex("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); - const auto& addressData = Ethereum::create2Address(from, salt, initCodeHash); - ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0x1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C"); + const auto from = STRING("0x00000000000000000000000000000000deadbeef"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x00000000000000000000000000000000000000000000000000000000cafebabe")).get())); + const auto initCode = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")).get())); + const auto initCodeHash = WRAPD(TWHashKeccak256(initCode.get())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCodeHash.get())); + assertStringsEqual(address, "0x1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C"); } TEST(EthereumEip1014, Example6) { - const std::string& from = "0x0000000000000000000000000000000000000000"; - const Data salt = parse_hex("0x0000000000000000000000000000000000000000000000000000000000000000"); - Data initCodeHash = Hash::keccak256(parse_hex("0x")); - const auto& addressData = Ethereum::create2Address(from, salt, initCodeHash); - ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0xE33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0"); + const auto from = STRING("0x0000000000000000000000000000000000000000"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x0000000000000000000000000000000000000000000000000000000000000000")).get())); + const auto initCode = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x")).get())); + const auto initCodeHash = WRAPD(TWHashKeccak256(initCode.get())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCodeHash.get())); + assertStringsEqual(address, "0xE33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0"); } TEST(EthereumEip1014, Example7) { - const std::string& from = "0x7EF2e0048f5bAeDe046f6BF797943daF4ED8CB47"; - const Data salt = parse_hex("0x0000000000000000000000000000000000000000000000000000000000000000"); - Data initCodeHash = Hash::keccak256(parse_hex("0x608060405260405162000c5138038062000c51833981810160405281019062000029919062000580565b6200003d828260006200004560201b60201c565b5050620007d7565b62000056836200008860201b60201c565b600082511180620000645750805b156200008357620000818383620000df60201b620000371760201c565b505b505050565b62000099816200011560201b60201c565b8073ffffffffffffffffffffffffffffffffffffffff167fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b60405160405180910390a250565b60606200010d838360405180606001604052806027815260200162000c2a60279139620001eb60201b60201c565b905092915050565b6200012b816200027d60201b620000641760201c565b6200016d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000164906200066d565b60405180910390fd5b80620001a77f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b620002a060201b620000871760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60606000808573ffffffffffffffffffffffffffffffffffffffff1685604051620002179190620006dc565b600060405180830381855af49150503d806000811462000254576040519150601f19603f3d011682016040523d82523d6000602084013e62000259565b606091505b50915091506200027286838387620002aa60201b60201c565b925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b606083156200031a5760008351036200031157620002ce856200027d60201b60201c565b62000310576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620003079062000745565b60405180910390fd5b5b8290506200032d565b6200032c83836200033560201b60201c565b5b949350505050565b600082511115620003495781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200037f9190620007b3565b60405180910390fd5b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620003c9826200039c565b9050919050565b620003db81620003bc565b8114620003e757600080fd5b50565b600081519050620003fb81620003d0565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b62000456826200040b565b810181811067ffffffffffffffff821117156200047857620004776200041c565b5b80604052505050565b60006200048d62000388565b90506200049b82826200044b565b919050565b600067ffffffffffffffff821115620004be57620004bd6200041c565b5b620004c9826200040b565b9050602081019050919050565b60005b83811015620004f6578082015181840152602081019050620004d9565b60008484015250505050565b6000620005196200051384620004a0565b62000481565b90508281526020810184848401111562000538576200053762000406565b5b62000545848285620004d6565b509392505050565b600082601f83011262000565576200056462000401565b5b81516200057784826020860162000502565b91505092915050565b600080604083850312156200059a576200059962000392565b5b6000620005aa85828601620003ea565b925050602083015167ffffffffffffffff811115620005ce57620005cd62000397565b5b620005dc858286016200054d565b9150509250929050565b600082825260208201905092915050565b7f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60008201527f6f74206120636f6e747261637400000000000000000000000000000000000000602082015250565b600062000655602d83620005e6565b91506200066282620005f7565b604082019050919050565b60006020820190508181036000830152620006888162000646565b9050919050565b600081519050919050565b600081905092915050565b6000620006b2826200068f565b620006be81856200069a565b9350620006d0818560208601620004d6565b80840191505092915050565b6000620006ea8284620006a5565b915081905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b60006200072d601d83620005e6565b91506200073a82620006f5565b602082019050919050565b6000602082019050818103600083015262000760816200071e565b9050919050565b600081519050919050565b60006200077f8262000767565b6200078b8185620005e6565b93506200079d818560208601620004d6565b620007a8816200040b565b840191505092915050565b60006020820190508181036000830152620007cf818462000772565b905092915050565b61044380620007e76000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b610025610091565b610035610030610093565b6100a2565b565b606061005c83836040518060600160405280602781526020016103e7602791396100c8565b905092915050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b565b600061009d61014e565b905090565b3660008037600080366000845af43d6000803e80600081146100c3573d6000f35b3d6000fd5b60606000808573ffffffffffffffffffffffffffffffffffffffff16856040516100f291906102db565b600060405180830381855af49150503d806000811461012d576040519150601f19603f3d011682016040523d82523d6000602084013e610132565b606091505b5091509150610143868383876101a5565b925050509392505050565b600061017c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b610087565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606083156102075760008351036101ff576101bf85610064565b6101fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101f59061034f565b60405180910390fd5b5b829050610212565b610211838361021a565b5b949350505050565b60008251111561022d5781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161026191906103c4565b60405180910390fd5b600081519050919050565b600081905092915050565b60005b8381101561029e578082015181840152602081019050610283565b60008484015250505050565b60006102b58261026a565b6102bf8185610275565b93506102cf818560208601610280565b80840191505092915050565b60006102e782846102aa565b915081905092915050565b600082825260208201905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b6000610339601d836102f2565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600081519050919050565b6000601f19601f8301169050919050565b60006103968261036f565b6103a081856102f2565b93506103b0818560208601610280565b6103b98161037a565b840191505092915050565b600060208201905081810360008301526103de818461038b565b90509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e57dd3eafc9985be746025b6d82d4f011b9a7bb3db56f9a1eb7eadfddd376b6064736f6c63430008110033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564000000000000000000000000d9ec9e840bb5df076dbbb488d01485058f421e5800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000be8fa0112dcb7d21dc63645b633073651e19934800000000000000000000000000000000000000000000000000000000")); - const auto& addressData = Ethereum::create2Address(from, salt, initCodeHash); - ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0x4455e5f0038795939c001aa4d296A45956C460AA"); + const auto from = STRING("0x7EF2e0048f5bAeDe046f6BF797943daF4ED8CB47"); + const auto salt = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x0000000000000000000000000000000000000000000000000000000000000000")).get())); + const auto initCode = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0x608060405260405162000c5138038062000c51833981810160405281019062000029919062000580565b6200003d828260006200004560201b60201c565b5050620007d7565b62000056836200008860201b60201c565b600082511180620000645750805b156200008357620000818383620000df60201b620000371760201c565b505b505050565b62000099816200011560201b60201c565b8073ffffffffffffffffffffffffffffffffffffffff167fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b60405160405180910390a250565b60606200010d838360405180606001604052806027815260200162000c2a60279139620001eb60201b60201c565b905092915050565b6200012b816200027d60201b620000641760201c565b6200016d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000164906200066d565b60405180910390fd5b80620001a77f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b620002a060201b620000871760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60606000808573ffffffffffffffffffffffffffffffffffffffff1685604051620002179190620006dc565b600060405180830381855af49150503d806000811462000254576040519150601f19603f3d011682016040523d82523d6000602084013e62000259565b606091505b50915091506200027286838387620002aa60201b60201c565b925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b606083156200031a5760008351036200031157620002ce856200027d60201b60201c565b62000310576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620003079062000745565b60405180910390fd5b5b8290506200032d565b6200032c83836200033560201b60201c565b5b949350505050565b600082511115620003495781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200037f9190620007b3565b60405180910390fd5b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620003c9826200039c565b9050919050565b620003db81620003bc565b8114620003e757600080fd5b50565b600081519050620003fb81620003d0565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b62000456826200040b565b810181811067ffffffffffffffff821117156200047857620004776200041c565b5b80604052505050565b60006200048d62000388565b90506200049b82826200044b565b919050565b600067ffffffffffffffff821115620004be57620004bd6200041c565b5b620004c9826200040b565b9050602081019050919050565b60005b83811015620004f6578082015181840152602081019050620004d9565b60008484015250505050565b6000620005196200051384620004a0565b62000481565b90508281526020810184848401111562000538576200053762000406565b5b62000545848285620004d6565b509392505050565b600082601f83011262000565576200056462000401565b5b81516200057784826020860162000502565b91505092915050565b600080604083850312156200059a576200059962000392565b5b6000620005aa85828601620003ea565b925050602083015167ffffffffffffffff811115620005ce57620005cd62000397565b5b620005dc858286016200054d565b9150509250929050565b600082825260208201905092915050565b7f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60008201527f6f74206120636f6e747261637400000000000000000000000000000000000000602082015250565b600062000655602d83620005e6565b91506200066282620005f7565b604082019050919050565b60006020820190508181036000830152620006888162000646565b9050919050565b600081519050919050565b600081905092915050565b6000620006b2826200068f565b620006be81856200069a565b9350620006d0818560208601620004d6565b80840191505092915050565b6000620006ea8284620006a5565b915081905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b60006200072d601d83620005e6565b91506200073a82620006f5565b602082019050919050565b6000602082019050818103600083015262000760816200071e565b9050919050565b600081519050919050565b60006200077f8262000767565b6200078b8185620005e6565b93506200079d818560208601620004d6565b620007a8816200040b565b840191505092915050565b60006020820190508181036000830152620007cf818462000772565b905092915050565b61044380620007e76000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b610025610091565b610035610030610093565b6100a2565b565b606061005c83836040518060600160405280602781526020016103e7602791396100c8565b905092915050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b565b600061009d61014e565b905090565b3660008037600080366000845af43d6000803e80600081146100c3573d6000f35b3d6000fd5b60606000808573ffffffffffffffffffffffffffffffffffffffff16856040516100f291906102db565b600060405180830381855af49150503d806000811461012d576040519150601f19603f3d011682016040523d82523d6000602084013e610132565b606091505b5091509150610143868383876101a5565b925050509392505050565b600061017c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b610087565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606083156102075760008351036101ff576101bf85610064565b6101fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101f59061034f565b60405180910390fd5b5b829050610212565b610211838361021a565b5b949350505050565b60008251111561022d5781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161026191906103c4565b60405180910390fd5b600081519050919050565b600081905092915050565b60005b8381101561029e578082015181840152602081019050610283565b60008484015250505050565b60006102b58261026a565b6102bf8185610275565b93506102cf818560208601610280565b80840191505092915050565b60006102e782846102aa565b915081905092915050565b600082825260208201905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b6000610339601d836102f2565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600081519050919050565b6000601f19601f8301169050919050565b60006103968261036f565b6103a081856102f2565b93506103b0818560208601610280565b6103b98161037a565b840191505092915050565b600060208201905081810360008301526103de818461038b565b90509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e57dd3eafc9985be746025b6d82d4f011b9a7bb3db56f9a1eb7eadfddd376b6064736f6c63430008110033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564000000000000000000000000d9ec9e840bb5df076dbbb488d01485058f421e5800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000be8fa0112dcb7d21dc63645b633073651e19934800000000000000000000000000000000000000000000000000000000")).get())); + const auto initCodeHash = WRAPD(TWHashKeccak256(initCode.get())); + const auto address = WRAPS(TWEthereumEip1014Create2Address(from.get(), salt.get(), initCodeHash.get())); + assertStringsEqual(address, "0x4455e5f0038795939c001aa4d296A45956C460AA"); } } diff --git a/tests/chains/Ethereum/EIP1967Tests.cpp b/tests/chains/Ethereum/EIP1967Tests.cpp index e0cd759e8f6..6790afaa995 100644 --- a/tests/chains/Ethereum/EIP1967Tests.cpp +++ b/tests/chains/Ethereum/EIP1967Tests.cpp @@ -5,7 +5,6 @@ #include "TestUtilities.h" #include #include -#include #include #include @@ -18,8 +17,10 @@ TEST(EthereumEip1967, Example0) { { const std::string& login_address = "0x5C9eb5D6a6C2c1B3EFc52255C0b356f116f6f66D"; const Data data = parse_hex("0xc4d66de8000000000000000000000000a5a1dddef094095afb7b6e322de72961df2e1988"); - const Data initCode = Ethereum::getEIP1967ProxyInitCode(login_address, data); - ASSERT_EQ(hexEncoded(initCode), "0x608060405260405162000c5138038062000c51833981810160405281019062000029919062000580565b6200003d828260006200004560201b60201c565b5050620007d7565b62000056836200008860201b60201c565b600082511180620000645750805b156200008357620000818383620000df60201b620000371760201c565b505b505050565b62000099816200011560201b60201c565b8073ffffffffffffffffffffffffffffffffffffffff167fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b60405160405180910390a250565b60606200010d838360405180606001604052806027815260200162000c2a60279139620001eb60201b60201c565b905092915050565b6200012b816200027d60201b620000641760201c565b6200016d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000164906200066d565b60405180910390fd5b80620001a77f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b620002a060201b620000871760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60606000808573ffffffffffffffffffffffffffffffffffffffff1685604051620002179190620006dc565b600060405180830381855af49150503d806000811462000254576040519150601f19603f3d011682016040523d82523d6000602084013e62000259565b606091505b50915091506200027286838387620002aa60201b60201c565b925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b606083156200031a5760008351036200031157620002ce856200027d60201b60201c565b62000310576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620003079062000745565b60405180910390fd5b5b8290506200032d565b6200032c83836200033560201b60201c565b5b949350505050565b600082511115620003495781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200037f9190620007b3565b60405180910390fd5b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620003c9826200039c565b9050919050565b620003db81620003bc565b8114620003e757600080fd5b50565b600081519050620003fb81620003d0565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b62000456826200040b565b810181811067ffffffffffffffff821117156200047857620004776200041c565b5b80604052505050565b60006200048d62000388565b90506200049b82826200044b565b919050565b600067ffffffffffffffff821115620004be57620004bd6200041c565b5b620004c9826200040b565b9050602081019050919050565b60005b83811015620004f6578082015181840152602081019050620004d9565b60008484015250505050565b6000620005196200051384620004a0565b62000481565b90508281526020810184848401111562000538576200053762000406565b5b62000545848285620004d6565b509392505050565b600082601f83011262000565576200056462000401565b5b81516200057784826020860162000502565b91505092915050565b600080604083850312156200059a576200059962000392565b5b6000620005aa85828601620003ea565b925050602083015167ffffffffffffffff811115620005ce57620005cd62000397565b5b620005dc858286016200054d565b9150509250929050565b600082825260208201905092915050565b7f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60008201527f6f74206120636f6e747261637400000000000000000000000000000000000000602082015250565b600062000655602d83620005e6565b91506200066282620005f7565b604082019050919050565b60006020820190508181036000830152620006888162000646565b9050919050565b600081519050919050565b600081905092915050565b6000620006b2826200068f565b620006be81856200069a565b9350620006d0818560208601620004d6565b80840191505092915050565b6000620006ea8284620006a5565b915081905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b60006200072d601d83620005e6565b91506200073a82620006f5565b602082019050919050565b6000602082019050818103600083015262000760816200071e565b9050919050565b600081519050919050565b60006200077f8262000767565b6200078b8185620005e6565b93506200079d818560208601620004d6565b620007a8816200040b565b840191505092915050565b60006020820190508181036000830152620007cf818462000772565b905092915050565b61044380620007e76000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b610025610091565b610035610030610093565b6100a2565b565b606061005c83836040518060600160405280602781526020016103e7602791396100c8565b905092915050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b565b600061009d61014e565b905090565b3660008037600080366000845af43d6000803e80600081146100c3573d6000f35b3d6000fd5b60606000808573ffffffffffffffffffffffffffffffffffffffff16856040516100f291906102db565b600060405180830381855af49150503d806000811461012d576040519150601f19603f3d011682016040523d82523d6000602084013e610132565b606091505b5091509150610143868383876101a5565b925050509392505050565b600061017c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b610087565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606083156102075760008351036101ff576101bf85610064565b6101fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101f59061034f565b60405180910390fd5b5b829050610212565b610211838361021a565b5b949350505050565b60008251111561022d5781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161026191906103c4565b60405180910390fd5b600081519050919050565b600081905092915050565b60005b8381101561029e578082015181840152602081019050610283565b60008484015250505050565b60006102b58261026a565b6102bf8185610275565b93506102cf818560208601610280565b80840191505092915050565b60006102e782846102aa565b915081905092915050565b600082825260208201905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b6000610339601d836102f2565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600081519050919050565b6000601f19601f8301169050919050565b60006103968261036f565b6103a081856102f2565b93506103b0818560208601610280565b6103b98161037a565b840191505092915050565b600060208201905081810360008301526103de818461038b565b90509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e57dd3eafc9985be746025b6d82d4f011b9a7bb3db56f9a1eb7eadfddd376b6064736f6c63430008110033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c65640000000000000000000000005c9eb5d6a6c2c1b3efc52255c0b356f116f6f66d00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000a5a1dddef094095afb7b6e322de72961df2e198800000000000000000000000000000000000000000000000000000000"); + const auto twData = WRAPD(TWDataCreateWithBytes(data.data(), data.size())); + const auto initCodeData = TWEthereumEip1967ProxyInitCode(STRING(login_address.c_str()).get(), twData.get()); + const auto& initCode = hexEncoded(*reinterpret_cast(WRAPD(initCodeData).get())); + ASSERT_EQ(initCode, "0x608060405260405162000c5138038062000c51833981810160405281019062000029919062000580565b6200003d828260006200004560201b60201c565b5050620007d7565b62000056836200008860201b60201c565b600082511180620000645750805b156200008357620000818383620000df60201b620000371760201c565b505b505050565b62000099816200011560201b60201c565b8073ffffffffffffffffffffffffffffffffffffffff167fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b60405160405180910390a250565b60606200010d838360405180606001604052806027815260200162000c2a60279139620001eb60201b60201c565b905092915050565b6200012b816200027d60201b620000641760201c565b6200016d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000164906200066d565b60405180910390fd5b80620001a77f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b620002a060201b620000871760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60606000808573ffffffffffffffffffffffffffffffffffffffff1685604051620002179190620006dc565b600060405180830381855af49150503d806000811462000254576040519150601f19603f3d011682016040523d82523d6000602084013e62000259565b606091505b50915091506200027286838387620002aa60201b60201c565b925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b606083156200031a5760008351036200031157620002ce856200027d60201b60201c565b62000310576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620003079062000745565b60405180910390fd5b5b8290506200032d565b6200032c83836200033560201b60201c565b5b949350505050565b600082511115620003495781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200037f9190620007b3565b60405180910390fd5b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620003c9826200039c565b9050919050565b620003db81620003bc565b8114620003e757600080fd5b50565b600081519050620003fb81620003d0565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b62000456826200040b565b810181811067ffffffffffffffff821117156200047857620004776200041c565b5b80604052505050565b60006200048d62000388565b90506200049b82826200044b565b919050565b600067ffffffffffffffff821115620004be57620004bd6200041c565b5b620004c9826200040b565b9050602081019050919050565b60005b83811015620004f6578082015181840152602081019050620004d9565b60008484015250505050565b6000620005196200051384620004a0565b62000481565b90508281526020810184848401111562000538576200053762000406565b5b62000545848285620004d6565b509392505050565b600082601f83011262000565576200056462000401565b5b81516200057784826020860162000502565b91505092915050565b600080604083850312156200059a576200059962000392565b5b6000620005aa85828601620003ea565b925050602083015167ffffffffffffffff811115620005ce57620005cd62000397565b5b620005dc858286016200054d565b9150509250929050565b600082825260208201905092915050565b7f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60008201527f6f74206120636f6e747261637400000000000000000000000000000000000000602082015250565b600062000655602d83620005e6565b91506200066282620005f7565b604082019050919050565b60006020820190508181036000830152620006888162000646565b9050919050565b600081519050919050565b600081905092915050565b6000620006b2826200068f565b620006be81856200069a565b9350620006d0818560208601620004d6565b80840191505092915050565b6000620006ea8284620006a5565b915081905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b60006200072d601d83620005e6565b91506200073a82620006f5565b602082019050919050565b6000602082019050818103600083015262000760816200071e565b9050919050565b600081519050919050565b60006200077f8262000767565b6200078b8185620005e6565b93506200079d818560208601620004d6565b620007a8816200040b565b840191505092915050565b60006020820190508181036000830152620007cf818462000772565b905092915050565b61044380620007e76000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b610025610091565b610035610030610093565b6100a2565b565b606061005c83836040518060600160405280602781526020016103e7602791396100c8565b905092915050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b565b600061009d61014e565b905090565b3660008037600080366000845af43d6000803e80600081146100c3573d6000f35b3d6000fd5b60606000808573ffffffffffffffffffffffffffffffffffffffff16856040516100f291906102db565b600060405180830381855af49150503d806000811461012d576040519150601f19603f3d011682016040523d82523d6000602084013e610132565b606091505b5091509150610143868383876101a5565b925050509392505050565b600061017c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b610087565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606083156102075760008351036101ff576101bf85610064565b6101fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101f59061034f565b60405180910390fd5b5b829050610212565b610211838361021a565b5b949350505050565b60008251111561022d5781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161026191906103c4565b60405180910390fd5b600081519050919050565b600081905092915050565b60005b8381101561029e578082015181840152602081019050610283565b60008484015250505050565b60006102b58261026a565b6102bf8185610275565b93506102cf818560208601610280565b80840191505092915050565b60006102e782846102aa565b915081905092915050565b600082825260208201905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b6000610339601d836102f2565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600081519050919050565b6000601f19601f8301169050919050565b60006103968261036f565b6103a081856102f2565b93506103b0818560208601610280565b6103b98161037a565b840191505092915050565b600060208201905081810360008301526103de818461038b565b90509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e57dd3eafc9985be746025b6d82d4f011b9a7bb3db56f9a1eb7eadfddd376b6064736f6c63430008110033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c65640000000000000000000000005c9eb5d6a6c2c1b3efc52255c0b356f116f6f66d00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000a5a1dddef094095afb7b6e322de72961df2e198800000000000000000000000000000000000000000000000000000000"); } } diff --git a/tests/chains/ImmutableX/StarkKeyTests.cpp b/tests/chains/ImmutableX/StarkKeyTests.cpp index 6f9efca154e..55e522a11a5 100644 --- a/tests/chains/ImmutableX/StarkKeyTests.cpp +++ b/tests/chains/ImmutableX/StarkKeyTests.cpp @@ -2,18 +2,19 @@ // // Copyright © 2017 Trust Wallet. -#include "Ethereum/EIP2645.h" #include "HexCoding.h" #include "ImmutableX/Constants.h" #include "ImmutableX/StarkKey.h" #include +#include +#include "TestUtilities.h" namespace TW::ImmutableX::tests { TEST(ImmutableX, PathFromAddress) { // https://github.com/immutable/imx-core-sdk-swift/blob/main/Tests/ImmutableXCoreTests/Crypto/Stark/StarkKeyTests.swift#L30 - auto res = Ethereum::accountPathFromAddress("0xa76e3eeb2f7143165618ab8feaabcd395b6fac7f", internal::gLayer, internal::gApplication, internal::gIndex); - ASSERT_EQ(res, "m/2645'/579218131'/211006541'/1534045311'/1431804530'/1"); + const auto& res = WRAPS(TWEthereumEip2645GetPath(STRING("0xa76e3eeb2f7143165618ab8feaabcd395b6fac7f").get(), STRING(internal::gLayer).get(), STRING(internal::gApplication).get(), STRING(internal::gIndex).get())); + assertStringsEqual(res, "m/2645'/579218131'/211006541'/1534045311'/1431804530'/1"); } TEST(ImmutableX, ExtraGrinding) { @@ -21,7 +22,8 @@ TEST(ImmutableX, ExtraGrinding) { std::string signature = "0x6d1550458c7a9a1257d73adbcf0fabc12f4497e970d9fa62dd88bf7d9e12719148c96225c1402d8707fd061b1aae2222bdf13571dfc82b3aa9974039f247f2b81b"; std::string address = "0xa4864d977b944315389d1765ffa7e66F74ee8cd7"; auto data = parse_hex(signature); - auto path = DerivationPath(Ethereum::accountPathFromAddress(address, gLayer, gApplication, gIndex)); + const auto& res = WRAPS(TWEthereumEip2645GetPath(STRING(address.c_str()).get(), STRING(internal::gLayer).get(), STRING(internal::gApplication).get(), STRING(internal::gIndex).get())); + auto path = DerivationPath(TWStringUTF8Bytes(res.get())); auto privKey = ImmutableX::getPrivateKeyFromRawSignature(parse_hex(signature), path); auto pubKey = privKey.getPublicKey(TWPublicKeyTypeStarkex); ASSERT_EQ(hexEncoded(pubKey.bytes), "0x035919acd61e97b3ecdc75ff8beed8d1803f7ea3cad2937926ae59cc3f8070d4"); @@ -46,7 +48,8 @@ TEST(ImmutableX, GetPrivateKeyFromSignature) { using namespace internal; std::string address = "0xa76e3eeb2f7143165618ab8feaabcd395b6fac7f"; std::string signature = "0x5a263fad6f17f23e7c7ea833d058f3656d3fe464baf13f6f5ccba9a2466ba2ce4c4a250231bcac7beb165aec4c9b049b4ba40ad8dd287dc79b92b1ffcf20cdcf1b"; - auto path = DerivationPath(Ethereum::accountPathFromAddress(address, gLayer, gApplication, gIndex)); + const auto& res = WRAPS(TWEthereumEip2645GetPath(STRING(address.c_str()).get(), STRING(internal::gLayer).get(), STRING(internal::gApplication).get(), STRING(internal::gIndex).get())); + auto path = DerivationPath(TWStringUTF8Bytes(res.get())); auto privKey = ImmutableX::getPrivateKeyFromRawSignature(parse_hex(signature), path); ASSERT_EQ(hex(privKey.bytes), "058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe"); ASSERT_TRUE(PrivateKey::isValid(privKey.bytes)); diff --git a/tests/common/HDWallet/HDWalletTests.cpp b/tests/common/HDWallet/HDWalletTests.cpp index 355036071c6..b795abe3334 100644 --- a/tests/common/HDWallet/HDWalletTests.cpp +++ b/tests/common/HDWallet/HDWalletTests.cpp @@ -10,7 +10,6 @@ #include "Cosmos/Address.h" #include "Coin.h" #include "Ethereum/Address.h" -#include "Ethereum/EIP2645.h" #include "Ethereum/MessageSigner.h" #include "HDWallet.h" #include "Hash.h" @@ -22,6 +21,7 @@ #include "PublicKey.h" #include "StarkEx/MessageSigner.h" #include "TestUtilities.h" +#include "TrustWalletCore/TWEthereum.h" #include @@ -474,9 +474,10 @@ TEST(HDWallet, FromSeedStark) { TEST(HDWallet, FromMnemonicStark) { // https://github.com/starkware-libs/starkware-crypto-utils/blob/d3a1e655105afd66ebc07f88a179a3042407cc7b/test/js/key_derivation.spec.js#L20 const auto mnemonic = "range mountain blast problem vibrant void vivid doctor cluster enough melody salt layer language laptop boat major space monkey unit glimpse pause change vibrant"; - const auto ethAddress = "0xA4864D977b944315389d1765Ffa7E66F74eE8cD7"; + const std::string ethAddress = "0xA4864D977b944315389d1765Ffa7E66F74eE8cD7"; HDWallet wallet = HDWallet(mnemonic, ""); - auto derivationPath = DerivationPath(Ethereum::accountPathFromAddress(ethAddress, "starkex", "starkdeployement", "0")); + const auto& res = WRAPS(TWEthereumEip2645GetPath(STRING(ethAddress.c_str()).get(), STRING("starkex").get(), STRING("starkdeployement").get(), STRING("0").get())); + auto derivationPath = DerivationPath(TWStringUTF8Bytes(res.get())); ASSERT_EQ(derivationPath.string(), "m/2645'/579218131'/891216374'/1961790679'/2135936222'/0"); // ETH @@ -500,7 +501,8 @@ TEST(HDWallet, FromMnemonicImmutableX) { const auto mnemonic = "owner erupt swamp room swift final allow unaware hint identify figure cotton"; const auto ethAddress = "0x1A817D0cC495C8157E4C734c48a1e840473CBCa1"; HDWallet wallet = HDWallet(mnemonic, ""); - auto derivationPath = DerivationPath(Ethereum::accountPathFromAddress(ethAddress, "starkex", "immutablex", "1")); + const auto& res = WRAPS(TWEthereumEip2645GetPath(STRING(ethAddress).get(), STRING("starkex").get(), STRING("immutablex").get(), STRING("1").get())); + auto derivationPath = DerivationPath(TWStringUTF8Bytes(res.get())); ASSERT_EQ(derivationPath.string(), "m/2645'/579218131'/211006541'/1195162785'/289656960'/1"); // ETH @@ -529,7 +531,8 @@ TEST(HDWallet, FromMnemonicImmutableXMainnet) { const auto mnemonic = "ocean seven canyon push fiscal banana music guess arrange edit glance school"; const auto ethAddress = "0x39E652fE9458D391737058b0dd5eCC6ec910A7dd"; HDWallet wallet = HDWallet(mnemonic, ""); - auto derivationPath = DerivationPath(Ethereum::accountPathFromAddress(ethAddress, "starkex", "immutablex", "1")); + const auto& res = WRAPS(TWEthereumEip2645GetPath(STRING(ethAddress).get(), STRING("starkex").get(), STRING("immutablex").get(), STRING("1").get())); + auto derivationPath = DerivationPath(TWStringUTF8Bytes(res.get())); ASSERT_EQ(derivationPath.string(), "m/2645'/579218131'/211006541'/1225828317'/985503965'/1"); // ETH @@ -564,7 +567,8 @@ TEST(HDWallet, FromMnemonicImmutableXMainnetFromSignature) { const auto mnemonic = "obscure opera favorite shuffle mail tip age debate dirt pact cement loyal"; const auto ethAddress = "0xd0972E2312518Ca15A2304D56ff9cc0b7ea0Ea37"; HDWallet wallet = HDWallet(mnemonic, ""); - auto derivationPath = DerivationPath(Ethereum::accountPathFromAddress(ethAddress, "starkex", "immutablex", "1")); + const auto& res = WRAPS(TWEthereumEip2645GetPath(STRING(ethAddress).get(), STRING("starkex").get(), STRING("immutablex").get(), STRING("1").get())); + auto derivationPath = DerivationPath(TWStringUTF8Bytes(res.get())); ASSERT_EQ(derivationPath.string(), "m/2645'/579218131'/211006541'/2124474935'/1609799702'/1"); // ETH + stark From 91ee958daf4b65be480029625bcd671e1d3e37e3 Mon Sep 17 00:00:00 2001 From: 10gic Date: Thu, 24 Jul 2025 16:51:06 +0800 Subject: [PATCH 43/72] Support compiling tron raw json from DApp (#4449) * Support compiling tron raw json from DApp * Support raw json in method sign * chore: change code comments * Fix signature structure in compiling output --- src/Tron/Entry.cpp | 2 +- src/Tron/Signer.cpp | 82 ++++++++++++++++++- src/Tron/Signer.h | 1 + src/proto/Tron.proto | 4 + tests/chains/Tron/SignerTests.cpp | 32 ++++++++ .../chains/Tron/TransactionCompilerTests.cpp | 73 +++++++++++++++++ 6 files changed, 190 insertions(+), 4 deletions(-) diff --git a/src/Tron/Entry.cpp b/src/Tron/Entry.cpp index 7cf7f756ca5..64c15d6d75c 100644 --- a/src/Tron/Entry.cpp +++ b/src/Tron/Entry.cpp @@ -31,7 +31,7 @@ Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInput txInputData, [](const auto& input, auto& output) { const auto signer = Signer(input); auto preImage = signer.signaturePreimage(); - auto preImageHash = Hash::sha256(preImage); + auto preImageHash = signer.signaturePreimageHash(); output.set_data_hash(preImageHash.data(), preImageHash.size()); output.set_data(preImage.data(), preImage.size()); }); diff --git a/src/Tron/Signer.cpp b/src/Tron/Signer.cpp index d0acf6706cd..3e126cdea3b 100644 --- a/src/Tron/Signer.cpp +++ b/src/Tron/Signer.cpp @@ -11,6 +11,7 @@ #include "../BinaryCoding.h" #include "../HexCoding.h" +#include #include #include @@ -405,9 +406,31 @@ Data serialize(const protocol::Transaction& tx) noexcept { Proto::SigningOutput signDirect(const Proto::SigningInput& input) { const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); - auto hash = parse_hex(input.txid()); - const auto signature = key.sign(hash); auto output = Proto::SigningOutput(); + + Data hash; + if (!input.txid().empty()) { + hash = parse_hex(input.txid()); + } else if (!input.raw_json().empty()) { + try { + auto parsed = nlohmann::json::parse(input.raw_json()); + if (parsed.contains("txID") && parsed["txID"].is_string()) { + hash = parse_hex(parsed["txID"].get()); + } else { + // If txID is not present, return an error + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("No txID found in raw JSON"); + return output; + } + } catch (const std::exception& e) { + // If parsing fails, return an error + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message(e.what()); + return output; + } + } + + const auto signature = key.sign(hash); output.set_signature(signature.data(), signature.size()); output.set_id(input.txid()); output.set_id(hash.data(), hash.size()); @@ -415,7 +438,7 @@ Proto::SigningOutput signDirect(const Proto::SigningInput& input) { } Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - if (!input.txid().empty()) { + if (!input.txid().empty() || !input.raw_json().empty()) { return signDirect(input); } @@ -455,6 +478,26 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Proto::SigningOutput Signer::compile(const Data& signature) const { Proto::SigningOutput output; + if (!input.raw_json().empty()) { + // If raw JSON is provided, we use it directly + try { + auto parsed = nlohmann::json::parse(input.raw_json()); + // Add signature to JSON and set to output + parsed["signature"] = nlohmann::json::array({hex(signature)}); + output.set_json(parsed.dump()); + output.set_signature(signature.data(), signature.size()); + // Extract txID and set to output + if (parsed.contains("txID") && parsed["txID"].is_string()) { + auto txID = parse_hex(parsed["txID"].get()); + output.set_id(txID.data(), txID.size()); + } + return output; + } catch (const std::exception& e) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message(e.what()); + return output; + } + } auto preImage = signaturePreimage(); auto hash = Hash::sha256(preImage); auto transaction = buildTransaction(input); @@ -468,7 +511,40 @@ Proto::SigningOutput Signer::compile(const Data& signature) const { } Data Signer::signaturePreimage() const { + if (!input.raw_json().empty()) { + // If raw JSON is provided, we use raw_data_hex directly + try { + auto parsed = nlohmann::json::parse(input.raw_json()); + if (parsed.contains("raw_data_hex") && parsed["raw_data_hex"].is_string()) { + return parse_hex(parsed["raw_data_hex"].get()); + } + // If raw_data_hex is not present, return an empty Data + return {}; + } catch (...) { + // Ignore parsing errors, return an empty Data + return {}; + } + } return serialize(buildTransaction(input)); } +Data Signer::signaturePreimageHash() const { + if (!input.raw_json().empty()) { + // If raw JSON is provided, we use txID directly + try { + auto parsed = nlohmann::json::parse(input.raw_json()); + if (parsed.contains("txID") && parsed["txID"].is_string()) { + return parse_hex(parsed["txID"].get()); + } + // If txID is not present, return an empty Data + return {}; + } catch (...) { + // Ignore parsing errors, return an empty Data + return {}; + } + } + auto preImage = signaturePreimage(); + return Hash::sha256(preImage); +} + } // namespace TW::Tron diff --git a/src/Tron/Signer.h b/src/Tron/Signer.h index 1d3063378af..0765cbf6626 100644 --- a/src/Tron/Signer.h +++ b/src/Tron/Signer.h @@ -21,6 +21,7 @@ class Signer { static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; Proto::SigningOutput compile(const Data& signature) const; Data signaturePreimage() const; + Data signaturePreimageHash() const; }; } // namespace TW::Tron diff --git a/src/proto/Tron.proto b/src/proto/Tron.proto index 89e6fd6529d..3130965dc82 100644 --- a/src/proto/Tron.proto +++ b/src/proto/Tron.proto @@ -263,7 +263,11 @@ message SigningInput { bytes private_key = 2; // For direct sign in Tron, we just have to sign the txId returned by the DApp json payload. + // TODO: This field can be removed in the future, as we can use raw_json.txID instead. string txId = 3; + + // Raw JSON data from the DApp, which contains fields 'txID', 'raw_data' and 'raw_data_hex' normally. + string raw_json = 4; } // Result containing the signed and encoded transaction. diff --git a/tests/chains/Tron/SignerTests.cpp b/tests/chains/Tron/SignerTests.cpp index b62ac222b73..43e58849c4e 100644 --- a/tests/chains/Tron/SignerTests.cpp +++ b/tests/chains/Tron/SignerTests.cpp @@ -24,6 +24,38 @@ TEST(TronSigner, SignDirectTransferAsset) { ASSERT_EQ(hex(output.signature()), "77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00"); } +TEST(TronSigner, SignDirectRawJsonTransferAsset) { + auto input = Proto::SigningInput(); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto rawJson = R"({ + "raw_data": { + "contract": [{ + "parameter": { + "type_url": "type.googleapis.com/protocol.TransferAssetContract", + "value": { + "amount": 4, + "asset_name": "31303030393539", + "owner_address": "415cd0fb0ab3ce40f3051414c604b27756e69e43db", + "to_address": "41521ea197907927725ef36d70f25f850d1659c7c7" + } + }, + "type": "TransferAssetContract" + }], + "expiration": 1541926116000, + "ref_block_bytes": "b801", + "ref_block_hash": "0e2bc08d550f5f58", + "timestamp": 1539295479000 + }, + "visible":false, + "txID": "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb" +})"; + input.set_raw_json(rawJson); + const auto output = Signer::sign(input); + ASSERT_EQ(hex(output.id()), "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb"); + ASSERT_EQ(hex(output.signature()), "77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00"); +} + TEST(TronSigner, SignTransferAsset) { auto input = Proto::SigningInput(); auto& transaction = *input.mutable_transaction(); diff --git a/tests/chains/Tron/TransactionCompilerTests.cpp b/tests/chains/Tron/TransactionCompilerTests.cpp index 0962f4bda07..df5fddcdc9d 100644 --- a/tests/chains/Tron/TransactionCompilerTests.cpp +++ b/tests/chains/Tron/TransactionCompilerTests.cpp @@ -108,3 +108,76 @@ TEST(TronCompiler, CompileWithSignatures) { EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); } } + +TEST(TronCompiler, CompileWithSignaturesRawJson) { + const auto privateKey = + PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + constexpr auto coin = TWCoinTypeTron; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Tron::Proto::SigningInput(); + auto rawJson = R"({ + "raw_data": { + "contract": [{ + "parameter": { + "type_url": "type.googleapis.com/protocol.TransferAssetContract", + "value": { + "amount": 4, + "asset_name": "31303030393539", + "owner_address": "415cd0fb0ab3ce40f3051414c604b27756e69e43db", + "to_address": "41521ea197907927725ef36d70f25f850d1659c7c7" + } + }, + "type": "TransferAssetContract" + }], + "expiration": 1541926116000, + "ref_block_bytes": "b801", + "ref_block_hash": "0e2bc08d550f5f58", + "timestamp": 1539295479000 + }, + "visible":false, + "txID": "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb" +})"; + input.set_raw_json(rawJson); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), static_cast(preImageHashesData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb"); + auto signature = parse_hex("77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603" + "a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preSigningOutput.data_hash()))); + /// Step 3: Compile transaction info + const auto expectedTx = R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.TransferAssetContract","value":{"amount":4,"asset_name":"31303030393539","owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db","to_address":"41521ea197907927725ef36d70f25f850d1659c7c7"}},"type":"TransferAssetContract"}],"expiration":1541926116000,"ref_block_bytes":"b801","ref_block_hash":"0e2bc08d550f5f58","timestamp":1539295479000},"signature":["77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00"],"txID":"546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb","visible":false})"; + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKey.bytes}); + + { + TW::Tron::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + EXPECT_EQ(output.json(), expectedTx); + } + + { // Negative: invalid raw json + auto input = TW::Tron::Proto::SigningInput(); + auto invalidRawJson = "not valid json"; + input.set_raw_json(invalidRawJson); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature}, {publicKey.bytes}); + Tron::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} From 6e72fe2c2a61173d83c6561eae8501105d5dd2da Mon Sep 17 00:00:00 2001 From: Sergei Boiko Date: Mon, 4 Aug 2025 12:17:34 +0200 Subject: [PATCH 44/72] fix(ci): Use macos-15-xlarge --- .github/workflows/ios-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index ac5f55c258b..2a18b249f43 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -12,7 +12,7 @@ concurrency: jobs: build: - runs-on: macos-latest-xlarge + runs-on: macos-15-xlarge if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v3 From 1bbb81be567e1b451fb3020f7ae51fb122783dd0 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Mon, 4 Aug 2025 19:21:22 +0200 Subject: [PATCH 45/72] fix(CI): Update MacOS to 15 explicitly for iOS CI (#4461) * test(ci): Print all work schemas * test(ci): Manually update macos to 15 * test(ci): Clean up Podfile.lock * test(ci): Use iOS 18.5 simulator * test(ci): Fix iOS device * fix(ci): Enale all CIs * fix(ci): Downgrade objectVersion to 56 * fix(ci): Try to use new SONAR_TOKEN * fix(ci): Use macos-latest-xlarge * fix(ci): Disable SonarCloud analysis temporarily * fix(ci): Add `xcodegen` script * fix(ci): Fix `xcodegen` script * chore(ci): Trigger build * fix(ci): Minor changes --------- Co-authored-by: Sergei Boiko --- .github/workflows/linux-ci-sonarcloud.yml | 4 +++- swift/Podfile.lock | 2 +- tools/generate-files | 5 +---- tools/ios-test | 5 +++-- tools/xcodegen | 23 +++++++++++++++++++++++ 5 files changed, 31 insertions(+), 8 deletions(-) create mode 100755 tools/xcodegen diff --git a/.github/workflows/linux-ci-sonarcloud.yml b/.github/workflows/linux-ci-sonarcloud.yml index 32522f7b7c6..f7e4643f343 100644 --- a/.github/workflows/linux-ci-sonarcloud.yml +++ b/.github/workflows/linux-ci-sonarcloud.yml @@ -12,7 +12,9 @@ concurrency: jobs: build: - if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.fork == false + # if: github.event.pull_request.draft == false + # Temporarily disabled due to issues with SonarCloud account. + if: false runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v3 diff --git a/swift/Podfile.lock b/swift/Podfile.lock index c8cb8c24b66..4e481723841 100644 --- a/swift/Podfile.lock +++ b/swift/Podfile.lock @@ -11,6 +11,6 @@ SPEC REPOS: SPEC CHECKSUMS: WalletCoreSwiftProtobuf: a4798576a2d309511fc45f81843d348732ec571d -PODFILE CHECKSUM: 1112f54f83017d2c0c1d9d4bf5c21f65c5187d0e +PODFILE CHECKSUM: bbbccdbb7b3665e060820f476dbf21d5b1f8e605 COCOAPODS: 1.16.2 diff --git a/tools/generate-files b/tools/generate-files index 44577345a57..319b4c49779 100755 --- a/tools/generate-files +++ b/tools/generate-files @@ -88,10 +88,7 @@ fi # Generate Xcode project if [ -x "$(command -v xcodegen)" ] && isTargetSpecified "ios"; then - pushd swift - xcodegen - pod install - popd + tools/xcodegen elif isTargetSpecified "ios"; then echo -e "\nWARNING: Skipped generating Xcode project because the xcodegen tool is not installed." else diff --git a/tools/ios-test b/tools/ios-test index 639c605a8d6..3c95f4ec0bb 100755 --- a/tools/ios-test +++ b/tools/ios-test @@ -5,13 +5,14 @@ set -e set -o pipefail +tools/xcodegen + pushd swift -xcodegen && pod install xcodebuild -workspace TrustWalletCore.xcworkspace \ -scheme WalletCore \ -sdk iphonesimulator \ - -destination "platform=iOS Simulator,name=iPhone 15,OS=17.2" \ + -destination "platform=iOS Simulator,name=iPhone 16,OS=18.5" \ test | xcbeautify popd diff --git a/tools/xcodegen b/tools/xcodegen new file mode 100755 index 00000000000..9271e9dce20 --- /dev/null +++ b/tools/xcodegen @@ -0,0 +1,23 @@ +#!/bin/bash +# +# This script generates the Xcode project using xcodegen. + +pushd swift + +xcodegen + +# Update project version from 70 to 56 +# This is a workaround for a bug in xcodegen 2.44.0 that causes pod install will fail with error: +# ArgumentError - [Xcodeproj] Unable to find compatibility version string for object version `70`. +echo "Updating project version from 70 to 56..." +find . -name "project.pbxproj" -type f | while read -r file; do + if grep -q "objectVersion = 70;" "$file" || grep -q "preferredProjectObjectVersion = 70;" "$file"; then + sed -i '' 's/objectVersion = 70;/objectVersion = 56;/g' "$file" + sed -i '' 's/preferredProjectObjectVersion = 70;/preferredProjectObjectVersion = 56;/g' "$file" + echo "✓ project.pbxproj objectVersion in $file to 56" + fi +done + +pod install + +popd From 6e515ac14bb08321977065d4c86647548c75d667 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Wed, 10 Sep 2025 12:01:42 +0200 Subject: [PATCH 46/72] chore(aptos): Deprecate `register_token` in Rust (#4491) * chore(aptos): Deprecate `register_token` in Rust * chore(ci): Fix brew install cmake * chore(aptos): Remove `test_aptos_register_token` test * chore(ci): CMake * chore(ci): Downgrade KMP sample CI to MacOS 14 temporarily * chore(ci): Fix padding in a script --- .github/workflows/kotlin-sample-ci.yml | 2 +- .../tw_aptos/src/aptos_move_packages.rs | 16 ------ .../tw_aptos/src/transaction_builder.rs | 16 +----- rust/chains/tw_aptos/tests/signer.rs | 51 ------------------- src/proto/Aptos.proto | 7 --- tools/install-sys-dependencies-mac | 9 +++- 6 files changed, 11 insertions(+), 90 deletions(-) diff --git a/.github/workflows/kotlin-sample-ci.yml b/.github/workflows/kotlin-sample-ci.yml index f96b2a4b280..aaa8070d8ce 100644 --- a/.github/workflows/kotlin-sample-ci.yml +++ b/.github/workflows/kotlin-sample-ci.yml @@ -12,7 +12,7 @@ concurrency: jobs: build: - runs-on: macos-latest-xlarge + runs-on: macos-14-xlarge if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v3 diff --git a/rust/chains/tw_aptos/src/aptos_move_packages.rs b/rust/chains/tw_aptos/src/aptos_move_packages.rs index 6362875763e..913b3393b9c 100644 --- a/rust/chains/tw_aptos/src/aptos_move_packages.rs +++ b/rust/chains/tw_aptos/src/aptos_move_packages.rs @@ -192,22 +192,6 @@ pub fn token_transfers_claim_script( ))) } -pub fn managed_coin_register(coin_type: TypeTag) -> TransactionPayload { - TransactionPayload::EntryFunction(EntryFunction::new( - ModuleId::new( - AccountAddress::new([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, - ]), - ident_str!("managed_coin").to_owned(), - ), - ident_str!("register").to_owned(), - vec![coin_type], - vec![], - json!([]), - )) -} - pub fn fungible_asset_transfer( metadata_address: AccountAddress, to: AccountAddress, diff --git a/rust/chains/tw_aptos/src/transaction_builder.rs b/rust/chains/tw_aptos/src/transaction_builder.rs index 5449f86608d..7fc0fb96eb5 100644 --- a/rust/chains/tw_aptos/src/transaction_builder.rs +++ b/rust/chains/tw_aptos/src/transaction_builder.rs @@ -5,9 +5,8 @@ use crate::address::from_account_error; use crate::aptos_move_packages::{ aptos_account_create_account, aptos_account_transfer, aptos_account_transfer_coins, - coin_transfer, fungible_asset_transfer, managed_coin_register, - token_transfers_cancel_offer_script, token_transfers_claim_script, - token_transfers_offer_script, + coin_transfer, fungible_asset_transfer, token_transfers_cancel_offer_script, + token_transfers_claim_script, token_transfers_offer_script, }; use crate::constants::{GAS_UNIT_PRICE, MAX_GAS_AMOUNT}; use crate::liquid_staking::{ @@ -124,13 +123,6 @@ impl TransactionFactory { OneOftransaction_payload::nft_message(nft_message) => { factory.nft_ops(NftOperation::try_from(nft_message)?) }, - OneOftransaction_payload::register_token(register_token) => { - let function = register_token - .function - .or_tw_err(SigningErrorType::Error_invalid_params) - .context("'ManagedTokensRegisterMessage::function' is not set")?; - Ok(factory.register_token(convert_proto_struct_tag_to_type_tag(function)?)) - }, OneOftransaction_payload::liquid_staking_message(msg) => { factory.liquid_staking_ops(LiquidStakingOperation::try_from(msg)?) }, @@ -200,10 +192,6 @@ impl TransactionFactory { Ok(self.payload(aptos_account_create_account(to)?)) } - pub fn register_token(&self, coin_type: TypeTag) -> TransactionBuilder { - self.payload(managed_coin_register(coin_type)) - } - pub fn nft_ops(&self, operation: NftOperation) -> SigningResult { match operation { NftOperation::Claim(claim) => Ok(self.payload(token_transfers_claim_script( diff --git a/rust/chains/tw_aptos/tests/signer.rs b/rust/chains/tw_aptos/tests/signer.rs index eb4f41c8232..cdbf9b59160 100644 --- a/rust/chains/tw_aptos/tests/signer.rs +++ b/rust/chains/tw_aptos/tests/signer.rs @@ -122,17 +122,6 @@ fn setup_proto_transaction<'a>( panic!("Unsupported arguments") } }, - "register_token" => { - if let OpsDetails::RegisterToken(register_token) = ops_details.unwrap() { - Proto::mod_SigningInput::OneOftransaction_payload::register_token( - Proto::ManagedTokensRegisterMessage { - function: Some(convert_type_tag_to_struct_tag(register_token.coin_type)), - }, - ) - } else { - panic!("Unsupported arguments") - } - }, "liquid_staking_ops" => { if let OpsDetails::LiquidStakingOps(liquid_staking_ops) = ops_details.unwrap() { Proto::mod_SigningInput::OneOftransaction_payload::liquid_staking_message( @@ -598,46 +587,6 @@ fn test_aptos_nft_claim() { }"#); } -// Successfully broadcasted https://explorer.aptoslabs.com/txn/0xe591252daed785641bfbbcf72a5d17864568cf32e04c0cc9129f3a13834d0e8e?network=testnet -#[test] -fn test_aptos_register_token() { - let input = setup_proto_transaction("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address - "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair - "register_token", - 23, // Sequence number - 2, - 2000000, - 3664390082, - 100, - "", - "", - Some(OpsDetails::RegisterToken(RegisterToken { coin_type: TypeTag::from_str("0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379::move_coin::MoveCoin").unwrap() })), - ); - let output = Signer::sign_proto(input); - test_tx_result(output, - "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3017000000000000000200000000000000000000000000000000000000000000000000000000000000010c6d616e616765645f636f696e0872656769737465720107e4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379096d6f76655f636f696e084d6f7665436f696e000080841e00000000006400000000000000c2276ada0000000002", // Expected raw transaction bytes - "e230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", // Expected signature - "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3017000000000000000200000000000000000000000000000000000000000000000000000000000000010c6d616e616765645f636f696e0872656769737465720107e4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379096d6f76655f636f696e084d6f7665436f696e000080841e00000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40e230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", // Expected encoded transaction - r#"{ - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "2000000", - "payload": { - "arguments": [], - "function": "0x1::managed_coin::register", - "type": "entry_function_payload", - "type_arguments": ["0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379::move_coin::MoveCoin"] - }, - "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "23", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0xe230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", - "type": "ed25519_signature" - } - }"#); -} - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x25dca849cb4ebacbff223139f7ad5d24c37c225d9506b8b12a925de70429e685/userTxnOverview?network=mainnet #[test] fn test_aptos_tortuga_stake() { diff --git a/src/proto/Aptos.proto b/src/proto/Aptos.proto index db2baac9d5d..9b0e1533867 100644 --- a/src/proto/Aptos.proto +++ b/src/proto/Aptos.proto @@ -57,12 +57,6 @@ message FungibleAssetTransferMessage { uint64 amount = 3; } -// Necessary fields to process a ManagedTokensRegisterMessage -message ManagedTokensRegisterMessage { - // token function to register, e.g BTC: 0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC - StructTag function = 1; -} - // Necessary fields to process a CreateAccountMessage message CreateAccountMessage { // auth account address to create @@ -171,7 +165,6 @@ message SigningInput { TokenTransferMessage token_transfer = 10; CreateAccountMessage create_account = 11; NftMessage nft_message = 12; - ManagedTokensRegisterMessage register_token = 13; LiquidStaking liquid_staking_message = 14; TokenTransferCoinsMessage token_transfer_coins = 15; FungibleAssetTransferMessage fungible_asset_transfer = 16; diff --git a/tools/install-sys-dependencies-mac b/tools/install-sys-dependencies-mac index 28dc34cb356..1edd26eca34 100755 --- a/tools/install-sys-dependencies-mac +++ b/tools/install-sys-dependencies-mac @@ -2,7 +2,14 @@ set -e -brew install cmake boost ninja xcodegen xcbeautify +brew install boost ninja xcodegen xcbeautify + +if command -v cmake &> /dev/null +then + echo "Skip installing CMake." +else + brew install cmake +fi if [[ "$BOOST_ROOT" == "" ]]; then echo "export BOOST_ROOT=$(brew --prefix boost)" >> ~/.zprofile From 3f9c90c1a99835293dc2a21674ad25687fc9b270 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:05:20 +0200 Subject: [PATCH 47/72] fiux(ci): Use `iPhone 16` in ios-release (#4493) --- tools/ios-doc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ios-doc b/tools/ios-doc index 60c5a41b1cd..2edbefe7ea2 100755 --- a/tools/ios-doc +++ b/tools/ios-doc @@ -9,7 +9,7 @@ pushd swift mkdir -p build && rm -rf build/*.doccarchive export DOCC_JSON_PRETTYPRINT="YES" -xcodebuild -workspace TrustWalletCore.xcworkspace -derivedDataPath build/docsData -scheme WalletCore -destination 'platform=iOS Simulator,name=iPhone 14' -parallelizeTargets docbuild | xcbeautify +xcodebuild -workspace TrustWalletCore.xcworkspace -derivedDataPath build/docsData -scheme WalletCore -destination 'platform=iOS Simulator,name=iPhone 16' -parallelizeTargets docbuild | xcbeautify pushd build From 3c1f86c4e33059b397e4a265949697d591ffd296 Mon Sep 17 00:00:00 2001 From: 10gic Date: Tue, 16 Sep 2025 16:30:39 +0800 Subject: [PATCH 48/72] Throw exception rather than SIGSEGV (#4443) * Throw exception rather than SIGSEGV * Fix test cases * Add NULL checker for exceptionClass --- .../lib/templates/jni/parameter_access.erb | 20 +++++++++-- .../templates/kotlin_jni/parameter_access.erb | 20 +++++++++-- jni/android/AnySigner.c | 4 +++ jni/cpp/TWJNI.h | 33 +++++++++++++++++++ jni/kotlin/AnySigner.c | 9 +++++ 5 files changed, 82 insertions(+), 4 deletions(-) diff --git a/codegen/lib/templates/jni/parameter_access.erb b/codegen/lib/templates/jni/parameter_access.erb index 4c3ceb9f2fa..ea9e3c13c5d 100644 --- a/codegen/lib/templates/jni/parameter_access.erb +++ b/codegen/lib/templates/jni/parameter_access.erb @@ -6,8 +6,24 @@ parameters = method.parameters.drop(1) end - parameters.each do |param| - if param.type.name == :data -%> + parameters.each do |param| -%> +<% if !param.type.is_nullable && (JNIHelper.type(param.type) == 'jobject' || JNIHelper.type(param.type) == 'jstring' || JNIHelper.type(param.type) == 'jbyteArray') + # In case of constructor (starts with Create), it always returns jlong type. + if method.name.start_with?('Create') -%> + JNI_CHECK_NULL_AND_RETURN_ZERO(env, <%= param.name %>, "<%= param.name %>"); +<% elsif JNIHelper.type(method.return_type) == 'void' -%> + JNI_CHECK_NULL_AND_RETURN_VOID(env, <%= param.name %>, "<%= param.name %>"); +<% elsif JNIHelper.type(method.return_type) == 'jbyteArray' -%> + JNI_CHECK_NULL_AND_RETURN_NULL(env, <%= param.name %>, "<%= param.name %>"); +<% elsif JNIHelper.type(method.return_type) == 'jstring' -%> + JNI_CHECK_NULL_AND_RETURN_NULL(env, <%= param.name %>, "<%= param.name %>"); +<% elsif JNIHelper.type(method.return_type) == 'jobject' -%> + JNI_CHECK_NULL_AND_RETURN_NULL(env, <%= param.name %>, "<%= param.name %>"); +<% else -%> + JNI_CHECK_NULL_AND_RETURN_ZERO(env, <%= param.name %>, "<%= param.name %>"); +<% end -%> +<% end -%> +<% if param.type.name == :data -%> TWData *<%= param.name %>Data = TWDataCreateWithJByteArray(env, <%= param.name %>); <% elsif param.type.name == :string -%> TWString *<%= param.name %>String = TWStringCreateWithJString(env, <%= param.name %>); diff --git a/codegen/lib/templates/kotlin_jni/parameter_access.erb b/codegen/lib/templates/kotlin_jni/parameter_access.erb index a5dcae13aa1..790b3115d87 100644 --- a/codegen/lib/templates/kotlin_jni/parameter_access.erb +++ b/codegen/lib/templates/kotlin_jni/parameter_access.erb @@ -6,8 +6,24 @@ parameters = method.parameters.drop(1) end - parameters.each do |param| - if param.type.name == :data -%> + parameters.each do |param| -%> +<% if !param.type.is_nullable && (KotlinJniHelper.type(param.type) == 'jobject' || KotlinJniHelper.type(param.type) == 'jstring' || KotlinJniHelper.type(param.type) == 'jbyteArray') + # In case of constructor (starts with Create), it always returns jlong type. + if method.name.start_with?('Create') -%> + JNI_CHECK_NULL_AND_RETURN_ZERO(env, <%= param.name %>, "<%= param.name %>"); +<% elsif KotlinJniHelper.type(method.return_type) == 'void' -%> + JNI_CHECK_NULL_AND_RETURN_VOID(env, <%= param.name %>, "<%= param.name %>"); +<% elsif KotlinJniHelper.type(method.return_type) == 'jbyteArray' -%> + JNI_CHECK_NULL_AND_RETURN_NULL(env, <%= param.name %>, "<%= param.name %>"); +<% elsif KotlinJniHelper.type(method.return_type) == 'jstring' -%> + JNI_CHECK_NULL_AND_RETURN_NULL(env, <%= param.name %>, "<%= param.name %>"); +<% elsif KotlinJniHelper.type(method.return_type) == 'jobject' -%> + JNI_CHECK_NULL_AND_RETURN_NULL(env, <%= param.name %>, "<%= param.name %>"); +<% else -%> + JNI_CHECK_NULL_AND_RETURN_ZERO(env, <%= param.name %>, "<%= param.name %>"); +<% end -%> +<% end -%> +<% if param.type.name == :data -%> TWData *<%= param.name %>Data = TWDataCreateWithJByteArray(env, <%= param.name %>); <% elsif param.type.name == :string -%> TWString *<%= param.name %>String = TWStringCreateWithJString(env, <%= param.name %>); diff --git a/jni/android/AnySigner.c b/jni/android/AnySigner.c index fd1704562f5..b02c28c9941 100644 --- a/jni/android/AnySigner.c +++ b/jni/android/AnySigner.c @@ -10,6 +10,7 @@ #include "TWJNI.h" jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeSign(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin) { + JNI_CHECK_NULL_AND_RETURN_NULL(env, input, "input"); TWData *inputData = TWDataCreateWithJByteArray(env, input); TWData *outputData = TWAnySignerSign(inputData, coin); jbyteArray resultData = TWDataJByteArray(outputData, env); @@ -22,7 +23,9 @@ jboolean JNICALL Java_wallet_core_java_AnySigner_supportsJSON(JNIEnv *env, jclas } jstring JNICALL Java_wallet_core_java_AnySigner_signJSON(JNIEnv *env, jclass thisClass, jstring json, jbyteArray key, jint coin) { + JNI_CHECK_NULL_AND_RETURN_NULL(env, json, "json"); TWString *jsonString = TWStringCreateWithJString(env, json); + JNI_CHECK_NULL_AND_RETURN_NULL(env, key, "key"); TWData *keyData = TWDataCreateWithJByteArray(env, key); TWString *result = TWAnySignerSignJSON(jsonString, keyData, coin); TWDataDelete(keyData); @@ -31,6 +34,7 @@ jstring JNICALL Java_wallet_core_java_AnySigner_signJSON(JNIEnv *env, jclass thi } jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativePlan(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin) { + JNI_CHECK_NULL_AND_RETURN_NULL(env, input, "input"); TWData *inputData = TWDataCreateWithJByteArray(env, input); TWData *outputData = TWAnySignerPlan(inputData, coin); jbyteArray resultData = TWDataJByteArray(outputData, env); diff --git a/jni/cpp/TWJNI.h b/jni/cpp/TWJNI.h index 86fc962ca29..938b691c5b3 100644 --- a/jni/cpp/TWJNI.h +++ b/jni/cpp/TWJNI.h @@ -19,3 +19,36 @@ #include #include "TWJNIData.h" #include "TWJNIString.h" + +#define JNI_CHECK_NULL_AND_RETURN_VOID(env, param, paramName) \ + do { \ + if (param == NULL) { \ + jclass exceptionClass = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); \ + if (exceptionClass != NULL) { \ + (*env)->ThrowNew(env, exceptionClass, paramName " parameter cannot be null"); \ + } \ + return; \ + } \ + } while(0) + +#define JNI_CHECK_NULL_AND_RETURN_ZERO(env, param, paramName) \ + do { \ + if (param == NULL) { \ + jclass exceptionClass = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); \ + if (exceptionClass != NULL) { \ + (*env)->ThrowNew(env, exceptionClass, paramName " parameter cannot be null"); \ + } \ + return 0; \ + } \ + } while(0) + +#define JNI_CHECK_NULL_AND_RETURN_NULL(env, param, paramName) \ + do { \ + if (param == NULL) { \ + jclass exceptionClass = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); \ + if (exceptionClass != NULL) { \ + (*env)->ThrowNew(env, exceptionClass, paramName " parameter cannot be null"); \ + } \ + return NULL; \ + } \ + } while(0) diff --git a/jni/kotlin/AnySigner.c b/jni/kotlin/AnySigner.c index 83211fb3512..6d64fa4ebf4 100644 --- a/jni/kotlin/AnySigner.c +++ b/jni/kotlin/AnySigner.c @@ -10,10 +10,12 @@ #include "TWJNI.h" jbyteArray JNICALL Java_com_trustwallet_core_AnySigner_sign(JNIEnv *env, jclass thisClass, jbyteArray input, jobject coin) { + JNI_CHECK_NULL_AND_RETURN_NULL(env, coin, "coin"); jclass coinClass = (*env)->GetObjectClass(env, coin); jmethodID coinValueMethodID = (*env)->GetMethodID(env, coinClass, "value", "()I"); uint32_t coinValue = (*env)->CallIntMethod(env, coin, coinValueMethodID); + JNI_CHECK_NULL_AND_RETURN_NULL(env, input, "input"); TWData *inputData = TWDataCreateWithJByteArray(env, input); TWData *outputData = TWAnySignerSign(inputData, coinValue); jbyteArray resultData = TWDataJByteArray(outputData, env); @@ -22,6 +24,7 @@ jbyteArray JNICALL Java_com_trustwallet_core_AnySigner_sign(JNIEnv *env, jclass } jboolean JNICALL Java_com_trustwallet_core_AnySigner_supportsJson(JNIEnv *env, jclass thisClass, jobject coin) { + JNI_CHECK_NULL_AND_RETURN_ZERO(env, coin, "coin"); jclass coinClass = (*env)->GetObjectClass(env, coin); jmethodID coinValueMethodID = (*env)->GetMethodID(env, coinClass, "value", "()I"); uint32_t coinValue = (*env)->CallIntMethod(env, coin, coinValueMethodID); @@ -29,11 +32,15 @@ jboolean JNICALL Java_com_trustwallet_core_AnySigner_supportsJson(JNIEnv *env, j } jstring JNICALL Java_com_trustwallet_core_AnySigner_signJson(JNIEnv *env, jclass thisClass, jstring json, jbyteArray key, jobject coin) { + JNI_CHECK_NULL_AND_RETURN_NULL(env, coin, "coin"); jclass coinClass = (*env)->GetObjectClass(env, coin); jmethodID coinValueMethodID = (*env)->GetMethodID(env, coinClass, "value", "()I"); uint32_t coinValue = (*env)->CallIntMethod(env, coin, coinValueMethodID); + JNI_CHECK_NULL_AND_RETURN_NULL(env, json, "json"); TWString *jsonString = TWStringCreateWithJString(env, json); + + JNI_CHECK_NULL_AND_RETURN_NULL(env, key, "key"); TWData *keyData = TWDataCreateWithJByteArray(env, key); TWString *result = TWAnySignerSignJSON(jsonString, keyData, coinValue); TWDataDelete(keyData); @@ -42,10 +49,12 @@ jstring JNICALL Java_com_trustwallet_core_AnySigner_signJson(JNIEnv *env, jclass } jbyteArray JNICALL Java_com_trustwallet_core_AnySigner_plan(JNIEnv *env, jclass thisClass, jbyteArray input, jobject coin) { + JNI_CHECK_NULL_AND_RETURN_NULL(env, coin, "coin"); jclass coinClass = (*env)->GetObjectClass(env, coin); jmethodID coinValueMethodID = (*env)->GetMethodID(env, coinClass, "value", "()I"); uint32_t coinValue = (*env)->CallIntMethod(env, coin, coinValueMethodID); + JNI_CHECK_NULL_AND_RETURN_NULL(env, input, "input"); TWData *inputData = TWDataCreateWithJByteArray(env, input); TWData *outputData = TWAnySignerPlan(inputData, coinValue); jbyteArray resultData = TWDataJByteArray(outputData, env); From 793ae1c43aba3a55844406cd6b0560962269cf5b Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Mon, 22 Sep 2025 11:10:32 +0200 Subject: [PATCH 49/72] feat(Plasma): Add Plasma Mainnet (#4499) * feat(plasma): Add Plasma Mainnet * feat(plasma): Add mobile tests --- .../blockchains/CoinAddressDerivationTests.kt | 2 +- docs/registry.md | 1 + include/TrustWalletCore/TWCoinType.h | 1 + .../core/test/CoinAddressDerivationTests.kt | 2 +- registry.json | 30 +++++++++++++++++++ .../tests/coin_address_derivation_test.rs | 1 + swift/Tests/CoinAddressDerivationTests.swift | 3 +- tests/chains/Plasma/TWCoinTypeTests.cpp | 29 ++++++++++++++++++ tests/common/CoinAddressDerivationTests.cpp | 1 + 9 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 tests/chains/Plasma/TWCoinTypeTests.cpp diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt index af4411e737e..8bce32bcd03 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt @@ -47,7 +47,7 @@ class CoinAddressDerivationTests { FANTOM, CELO, CRONOSCHAIN, SMARTBITCOINCASH, KUCOINCOMMUNITYCHAIN, BOBA, METIS, AURORA, EVMOS, MOONRIVER, MOONBEAM, KAVAEVM, KAIA, METER, OKXCHAIN, POLYGONZKEVM, SCROLL, CONFLUXESPACE, ACALAEVM, OPBNB, NEON, BASE, LINEA, GREENFIELD, MANTLE, ZENEON, MANTAPACIFIC, - ZETAEVM, MERLIN, LIGHTLINK, BLAST, BOUNCEBIT, ZKLINKNOVA, SONIC, + ZETAEVM, MERLIN, LIGHTLINK, BLAST, BOUNCEBIT, ZKLINKNOVA, SONIC, PLASMA, -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address) RONIN -> assertEquals("ronin:8f348F300873Fd5DA36950B2aC75a26584584feE", address) diff --git a/docs/registry.md b/docs/registry.md index 2fcea9b71a2..60633e10aa1 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -97,6 +97,7 @@ This list is generated from [./registry.json](../registry.json) | 7332 | Zen EON | ZEN | | | | 8453 | Base | ETH | | | | 8964 | NULS | NULS | | | +| 9745 | Plasma Mainnet | XPL | | | | 14001 | WAX | WAXP | | | | 18000 | Meter | MTR | | | | 19167 | Flux | FLUX | | | diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index b81e22582ea..216feee507a 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -189,6 +189,7 @@ enum TWCoinType { TWCoinTypePactus = 21888, TWCoinTypeSonic = 10000146, TWCoinTypePolymesh = 595, + TWCoinTypePlasma = 9745, // end_of_tw_coin_type_marker_do_not_modify }; diff --git a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt index 44a3417d139..222faf0847a 100644 --- a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt +++ b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt @@ -40,7 +40,7 @@ class CoinAddressDerivationTests { Fantom, Celo, CronosChain, SmartBitcoinCash, KuCoinCommunityChain, Boba, Metis, Aurora, Evmos, Moonriver, Moonbeam, KavaEvm, Kaia, Meter, OKXChain, PolygonzkEVM, Scroll, ConfluxeSpace, AcalaEVM, OpBNB, Neon, Base, Linea, Greenfield, Mantle, ZenEON, MantaPacific, - ZetaEVM, Merlin, Lightlink, Blast, BounceBit, ZkLinkNova, Sonic, + ZetaEVM, Merlin, Lightlink, Blast, BounceBit, ZkLinkNova, Sonic, Plasma, -> "0x8f348F300873Fd5DA36950B2aC75a26584584feE" Ronin -> "ronin:8f348F300873Fd5DA36950B2aC75a26584584feE" diff --git a/registry.json b/registry.json index faca84e65ad..4568ea9a4f0 100644 --- a/registry.json +++ b/registry.json @@ -4879,5 +4879,35 @@ "rpc": "wss://rpc.polymesh.network/", "documentation": "https://developers.polymesh.network/" } + }, + { + "id": "plasma", + "name": "Plasma", + "displayName": "Plasma Mainnet", + "coinId": 9745, + "symbol": "XPL", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "9745", + "addressHasher": "keccak256", + "explorer": { + "url": "https://plasmascan.to/", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x3700212ec535b4c804363be87ba8a5f5668de6314ed41978f6ad71c5340d4d77", + "sampleAccount": "0x30A3E1F27B60c095E2c87bce1e2ffB24f381C8cd" + }, + "info": { + "url": "https://plasma.to/", + "rpc": "https://rpc.plasma.to/", + "documentation": "https://plasmascan.to/documentation" + } } ] diff --git a/rust/tw_tests/tests/coin_address_derivation_test.rs b/rust/tw_tests/tests/coin_address_derivation_test.rs index 026b260128c..0980941715c 100644 --- a/rust/tw_tests/tests/coin_address_derivation_test.rs +++ b/rust/tw_tests/tests/coin_address_derivation_test.rs @@ -92,6 +92,7 @@ fn test_coin_address_derivation() { | CoinType::BounceBit | CoinType::ZkLinkNova | CoinType::Sonic + | CoinType::Plasma // end_of_evm_address_derivation_tests_marker_do_not_modify => "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309", CoinType::Bitcoin => "bc1qten42eesehw0ktddcp0fws7d3ycsqez3f7d5yt", diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index fa1a04747fe..32ce65d9c0f 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -120,7 +120,8 @@ class CoinAddressDerivationTests: XCTestCase { .blast, .bounceBit, .zkLinkNova, - .sonic: + .sonic, + .plasma: let expectedResult = "0x8f348F300873Fd5DA36950B2aC75a26584584feE" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ronin: diff --git a/tests/chains/Plasma/TWCoinTypeTests.cpp b/tests/chains/Plasma/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..3d4113ccca5 --- /dev/null +++ b/tests/chains/Plasma/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWPlasmaCoinType, TWCoinType) { + const auto coin = TWCoinTypePlasma; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x3700212ec535b4c804363be87ba8a5f5668de6314ed41978f6ad71c5340d4d77")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x30A3E1F27B60c095E2c87bce1e2ffB24f381C8cd")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "plasma"); + assertStringsEqual(name, "Plasma Mainnet"); + assertStringsEqual(symbol, "XPL"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); + assertStringsEqual(txUrl, "https://plasmascan.to/tx/0x3700212ec535b4c804363be87ba8a5f5668de6314ed41978f6ad71c5340d4d77"); + assertStringsEqual(accUrl, "https://plasmascan.to/address/0x30A3E1F27B60c095E2c87bce1e2ffB24f381C8cd"); +} diff --git a/tests/common/CoinAddressDerivationTests.cpp b/tests/common/CoinAddressDerivationTests.cpp index fd35b42420c..27beb151012 100644 --- a/tests/common/CoinAddressDerivationTests.cpp +++ b/tests/common/CoinAddressDerivationTests.cpp @@ -90,6 +90,7 @@ TEST(Coin, DeriveAddress) { case TWCoinTypeBounceBit: case TWCoinTypeZkLinkNova: case TWCoinTypeSonic: + case TWCoinTypePlasma: // end_of_evm_address_derivation_tests_marker_do_not_modify EXPECT_EQ(address, "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); break; From c2fd67da9e950e36bb313df903393b2713bde40e Mon Sep 17 00:00:00 2001 From: gupnik Date: Fri, 26 Sep 2025 13:11:40 +0530 Subject: [PATCH 50/72] [Solana]: Adds ability to transfer tokens to the feepayer (#4503) * Initial setup * Minor updates * Restructure --- .../tw_solana/src/modules/message_builder.rs | 66 +++++++++++++++---- .../tests/chains/solana/solana_sign.rs | 36 ++++++++++ src/proto/Solana.proto | 23 +++++++ 3 files changed, 113 insertions(+), 12 deletions(-) diff --git a/rust/chains/tw_solana/src/modules/message_builder.rs b/rust/chains/tw_solana/src/modules/message_builder.rs index 8346d67557c..bf25e01d14f 100644 --- a/rust/chains/tw_solana/src/modules/message_builder.rs +++ b/rust/chains/tw_solana/src/modules/message_builder.rs @@ -160,7 +160,7 @@ impl<'a> MessageBuilder<'a> { let transfer_ix = SystemInstructionBuilder::transfer(from, to, transfer.value) .with_references(references); - let mut builder = InstructionBuilder::default(); + let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?; builder .maybe_advance_nonce(self.nonce_account()?, from) .maybe_priority_fee_price(self.priority_fee_price()) @@ -197,7 +197,7 @@ impl<'a> MessageBuilder<'a> { space: DEFAULT_SPACE, }); - let mut builder = InstructionBuilder::default(); + let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?; builder .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) @@ -217,7 +217,7 @@ impl<'a> MessageBuilder<'a> { let deactivate_ix = StakeInstructionBuilder::deactivate(stake_account, sender); - let mut builder = InstructionBuilder::default(); + let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?; builder .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) @@ -241,7 +241,7 @@ impl<'a> MessageBuilder<'a> { .collect::>>() .context("Invalid stake account(s)")?; - let mut builder = InstructionBuilder::default(); + let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?; builder .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) @@ -269,7 +269,7 @@ impl<'a> MessageBuilder<'a> { custodian_account, ); - let mut builder = InstructionBuilder::default(); + let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?; builder .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) @@ -303,7 +303,7 @@ impl<'a> MessageBuilder<'a> { }) .collect::>>()?; - let mut builder = InstructionBuilder::default(); + let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?; builder .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) @@ -337,7 +337,7 @@ impl<'a> MessageBuilder<'a> { token_address, match_program_id(create_token_acc.token_program_id), ); - let mut builder = InstructionBuilder::default(); + let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?; builder .maybe_advance_nonce(self.nonce_account()?, funding_account) .maybe_priority_fee_price(self.priority_fee_price()) @@ -384,7 +384,7 @@ impl<'a> MessageBuilder<'a> { ) .with_references(references); - let mut builder = InstructionBuilder::default(); + let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?; builder .maybe_advance_nonce(self.nonce_account()?, signer) .maybe_priority_fee_price(self.priority_fee_price()) @@ -448,7 +448,7 @@ impl<'a> MessageBuilder<'a> { ) .with_references(references); - let mut builder = InstructionBuilder::default(); + let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?; builder .maybe_advance_nonce(self.nonce_account()?, signer) .maybe_priority_fee_price(self.priority_fee_price()) @@ -479,7 +479,7 @@ impl<'a> MessageBuilder<'a> { .context("Invalid nonce account")? }; - let mut builder = InstructionBuilder::default(); + let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?; builder .maybe_advance_nonce(prev_nonce_account, signer) .maybe_priority_fee_price(self.priority_fee_price()) @@ -506,7 +506,7 @@ impl<'a> MessageBuilder<'a> { .into_tw() .context("Invalid recipient")?; - let mut builder = InstructionBuilder::default(); + let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?; builder .maybe_advance_nonce(self.nonce_account()?, signer) .maybe_priority_fee_price(self.priority_fee_price()) @@ -529,7 +529,7 @@ impl<'a> MessageBuilder<'a> { .into_tw() .context("Invalid nonce account")?; - let mut builder = InstructionBuilder::default(); + let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?; builder .maybe_advance_nonce(Some(nonce_account), signer) .maybe_priority_fee_price(self.priority_fee_price()) @@ -580,6 +580,48 @@ impl<'a> MessageBuilder<'a> { self.signer_address() } + fn builder_with_token_transfer_to_fee_payer_if_applicable( + &self, + ) -> SigningResult { + let Some(sponsored_transfer_token) = self.input.token_transfer_to_fee_payer.as_ref() else { + return Ok(InstructionBuilder::default()); + }; + let signer = self.signer_address()?; + + let fee_mint_address = + SolanaAddress::from_str(sponsored_transfer_token.fee_token_mint_address.as_ref()) + .into_tw() + .context("Invalid fee mint address")?; + + let sponsor_token_address = + SolanaAddress::from_str(sponsored_transfer_token.fee_sponsor_token_address.as_ref()) + .into_tw() + .context("Invalid sponsor token address")?; + + let fee_sender_token_address = + SolanaAddress::from_str(sponsored_transfer_token.fee_sender_token_address.as_ref()) + .into_tw() + .context("Invalid fee sender token address")?; + + let fee_decimals = sponsored_transfer_token + .fee_decimals + .try_into() + .tw_err(SigningErrorType::Error_invalid_params) + .context("Invalid fee decimals. Expected lower than 256")?; + + let mut builder = InstructionBuilder::default(); + builder.add_instruction(TokenInstructionBuilder::transfer_checked( + fee_sender_token_address, + fee_mint_address, + sponsor_token_address, + signer, + sponsored_transfer_token.fee_amount, + fee_decimals, + match_program_id(sponsored_transfer_token.fee_token_program_id), + )); + Ok(builder) + } + fn recent_blockhash(&self) -> SigningResult { Blockhash::from_str(&self.input.recent_blockhash) .map_err(SigningError::from) diff --git a/rust/tw_tests/tests/chains/solana/solana_sign.rs b/rust/tw_tests/tests/chains/solana/solana_sign.rs index 27c09741d3f..43240277eda 100644 --- a/rust/tw_tests/tests/chains/solana/solana_sign.rs +++ b/rust/tw_tests/tests/chains/solana/solana_sign.rs @@ -973,3 +973,39 @@ fn test_solana_sign_transfer_token_2022() { assert_eq!(output.encoded, "SAXNFUd7dNBu956Gi4XNuvMkKKjS9vp6puz45ErYMHFpMNwC3AQxDxGbweXt4GzY2FnUZ6ubm231NrdwWa8dg9bqgRMaHPLuPiy99YwtvcQ1E6mHxHqq8nL5VaN8wiVnrMU57zCLfHsSsVCHZc5peHHAPXMDE318uMCLLBwgDWuD1FfAvUAyXRSYniXzWG3jtBdDhuDohh13E2TMrtqTcKVv3crejFqFjtsNuW7KCqrZwxCv1ASNiiL2XScQBdHwStyjH2UTqLmT6wjGLiDYy7PZ88Tbz65r8NLr4Vb1aYSTChasfVjMLdybetfNaf4nJuBE4ZuXca7W66txKbHesxQbzrjUCXX12JFbKyaA8KJKBpbgkc9jWJjQkzyn"); // https://explorer.solana.com/tx/Lg1xWzsC9GatQMu1ZXv23t7snC92RRvbKJe22bsS76GUb8C8a9q3HPkiUnFoK6AWKSoNSsmko1EBnvKkCnL8b7w?cluster=devnet } + +#[test] +fn test_solana_sign_sponsored_transfer_token_with_external_fee_payer() { + let token_transfer_to_fee_payer = Proto::TokenTransferToFeePayer { + fee_token_mint_address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".into(), + fee_sponsor_token_address: "reqtRKoVXCKVtvCL47VBxwncifQE8oJL6ZUzo1a28hD".into(), + fee_sender_token_address: "5Wa3KnBQAGs2KKZvHGZ7TcWJHexKCgG3F2H6RFMars67".into(), + fee_amount: 1, + fee_decimals: 6, + ..Proto::TokenTransferToFeePayer::default() + }; + + let create_transfer_token = Proto::TokenTransfer { + token_mint_address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".into(), + recipient_token_address: "8CFJPb4MNuUXCn3nmau2VSKY4RyvRhd8796GQgvchdtW".into(), + sender_token_address: "5Wa3KnBQAGs2KKZvHGZ7TcWJHexKCgG3F2H6RFMars67".into(), + amount: 1, + decimals: 6, + ..Proto::TokenTransfer::default() + }; + let input = Proto::SigningInput { + private_key: b58("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"), + recent_blockhash: "Csc3aso6RQS9qav9aCrpCAyLiQXnyGDL1ZymcH1SE9pU".into(), + transaction_type: TransactionType::token_transfer_transaction(create_transfer_token), + fee_payer_private_key: b58("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c"), + token_transfer_to_fee_payer: Some(token_transfer_to_fee_payer), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Solana, input); + + assert_eq!(output.error, SigningError::OK); + // https://solscan.io/tx/3z7beuRPcr6WmTRvCDNgSNXBaEUTAy8EYHN93eUiLXoFoj2VbWuPbvf7nQoZxTHbG6ChQuTJDqwaQnUzK4WxYaQA + assert_eq!(output.encoded, "gnSfLvpTeWGFvEKGDNAwQpQYczANiAHcpj4ghRgKsJfTXJjqaGYnNG2Ay2JwR5XdRvdkeLjHdht7VctoJkxDcYLRNjWmFcb3khwZqV4oRcU3HCxqnjGbiFmBCTjsupUt4ZzsJs8DS9WGHPgQGfRVQdmq1Zv6Kd4KDR88aT3uLmdNsu1XP5Es5SFAqByGnwAnkthDfNvcmpW9iAsZdf4v7gTsgFZV14ZfsNh66TGzVJLepz689D4jKb19AyvPwBPYYsvpRLxeEaa3zJvsdBBoVkWMZzC2Y8oqxoPXCRXnxzKX9gJSew1P2bgZDN3j3BvFQ19zTYsdugGtRetV94yQgx5xh8Vk9Asbj3YCmEZpFMZborqeanvgK2mWs2rQmbanMY6Fi6FB1xN24YN2B38pK2g3DCYp6nNh1ueacrDakbyrRFCpyKo26yqrkqnbbKZ9roAgvrvm5zhju2GhWU5t5cPc4ADfZbfRWaV2ojETv1a9W838MB7h4N5a97kgkdnuuR5A4fJr5K4jizC2rNeLciDoZQuzoNE3TpnYqxpnJPQWQQB1vGHqdXTTiDc47i7kLm"); +} diff --git a/src/proto/Solana.proto b/src/proto/Solana.proto index c759769d2b1..f63aeb14229 100644 --- a/src/proto/Solana.proto +++ b/src/proto/Solana.proto @@ -146,6 +146,27 @@ message CreateAndTransferToken { TokenProgramId token_program_id = 9; } +// Transfer tokens to the feepayer +message TokenTransferToFeePayer { + // Mint address of the fee token + string fee_token_mint_address = 1; + + // Token account address of the fee sponsor for the fee mint + string fee_sponsor_token_address = 2; + + // Fee amount + uint64 fee_amount = 3; + + // Sender's fee token address + string fee_sender_token_address = 4; + + // Note: 8-bit value + uint32 fee_decimals = 5; + + // optional token program id + TokenProgramId fee_token_program_id = 6; +} + message CreateNonceAccount { // Required for building pre-signing hash of a transaction string nonce_account = 1; @@ -286,6 +307,8 @@ message SigningInput { // fee for higher transaction prioritization. // https://solana.com/docs/intro/transaction_fees#prioritization-fee PriorityFeeLimit priority_fee_limit = 23; + // Optional token transfer to fee payer + TokenTransferToFeePayer token_transfer_to_fee_payer = 24; } // Result containing the signed and encoded transaction. From 7f6004bdc91089583d77112bb57f4a2d11fb9e82 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Fri, 26 Sep 2025 11:05:30 +0200 Subject: [PATCH 51/72] fix(barz): Replace u32 parameters with i32 (#4504) * Disallow to use unsigned integer parameters within class methods --- rust/tw_evm/src/ffi/barz.rs | 16 ++++++++++------ rust/tw_macros/src/lib.rs | 1 + rust/tw_macros/src/tw_ffi.rs | 8 ++++++++ rust/tw_macros/src/utils.rs | 7 +++++++ 4 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 rust/tw_macros/src/utils.rs diff --git a/rust/tw_evm/src/ffi/barz.rs b/rust/tw_evm/src/ffi/barz.rs index 5cbca2be829..c7280f58216 100644 --- a/rust/tw_evm/src/ffi/barz.rs +++ b/rust/tw_evm/src/ffi/barz.rs @@ -41,10 +41,10 @@ pub unsafe extern "C" fn tw_barz_get_counterfactual_address( /// Returns the init code parameter of ERC-4337 User Operation /// -/// \param factory The address of the factory contract. +/// \param factory The address of the factory contract /// \param public_key Public key for the verification facet -/// \param verification_facet The address of the verification facet. -/// \param salt The salt of the init code. +/// \param verification_facet The address of the verification facet +/// \param salt The salt of the init code; Must be non-negative /// \return The init code. #[tw_ffi(ty = static_function, class = TWBarz, name = GetInitCode)] #[no_mangle] @@ -52,7 +52,7 @@ pub unsafe extern "C" fn tw_barz_get_init_code( factory: Nonnull, public_key: NonnullMut, verification_facet: Nonnull, - salt: u32, + salt: i32, ) -> NullableMut { let factory_address = try_or_else!(TWString::from_ptr_as_ref(factory), std::ptr::null_mut); let factory_address = try_or_else!(factory_address.as_str(), std::ptr::null_mut); @@ -63,6 +63,8 @@ pub unsafe extern "C" fn tw_barz_get_init_code( std::ptr::null_mut ); let verification_facet = try_or_else!(verification_facet.as_str(), std::ptr::null_mut); + let salt = try_or_else!(salt.try_into(), std::ptr::null_mut); + let init_code = try_or_else!( get_init_code( factory_address, @@ -117,18 +119,20 @@ pub unsafe extern "C" fn tw_barz_get_formatted_signature( /// /// \param msg_hash Original msgHash /// \param barzAddress The address of Barz wallet signing the message -/// \param chainId The chainId of the network the verification will happen +/// \param chainId The chainId of the network the verification will happen; Must be non-negative /// \return The final hash to be signed. #[tw_ffi(ty = static_function, class = TWBarz, name = GetPrefixedMsgHash)] #[no_mangle] pub unsafe extern "C" fn tw_barz_get_prefixed_msg_hash( msg_hash: Nonnull, barz_address: Nonnull, - chain_id: u32, + chain_id: i32, ) -> NullableMut { let msg_hash = try_or_else!(TWData::from_ptr_as_ref(msg_hash), std::ptr::null_mut); let barz_address = try_or_else!(TWString::from_ptr_as_ref(barz_address), std::ptr::null_mut); let barz_address = try_or_else!(barz_address.as_str(), std::ptr::null_mut); + let chain_id = try_or_else!(chain_id.try_into(), std::ptr::null_mut); + let prefixed_msg_hash = try_or_else!( get_prefixed_msg_hash(msg_hash.as_slice(), barz_address, chain_id), std::ptr::null_mut diff --git a/rust/tw_macros/src/lib.rs b/rust/tw_macros/src/lib.rs index 8c0cd9533ed..71b99daf764 100644 --- a/rust/tw_macros/src/lib.rs +++ b/rust/tw_macros/src/lib.rs @@ -2,6 +2,7 @@ use proc_macro::TokenStream; mod code_gen; mod tw_ffi; +mod utils; #[proc_macro_attribute] pub fn tw_ffi(attr: TokenStream, item: TokenStream) -> TokenStream { diff --git a/rust/tw_macros/src/tw_ffi.rs b/rust/tw_macros/src/tw_ffi.rs index b0ecba0fdbc..3ddebe7b6d9 100644 --- a/rust/tw_macros/src/tw_ffi.rs +++ b/rust/tw_macros/src/tw_ffi.rs @@ -12,6 +12,7 @@ use std::fs; use std::path::Path; use crate::code_gen::{TWArg, TWConfig, TWFunction}; +use crate::utils::is_uint; pub mod keywords { use syn::custom_keyword; @@ -134,6 +135,13 @@ pub fn tw_ffi(attr: TokenStream2, item: TokenStream2) -> Result { }; let class = args.class.unwrap().to_string(); + // TODO add support for structs. + if func_args.iter().any(|arg| is_uint(&arg.ty)) { + return Err(syn::Error::new( + proc_macro2::Span::call_site(), + "Unsigned integers are not supported within class methods. Consider using 'struct' or signed integers. See https://kotlinlang.org/docs/inline-classes.html#mangling" + )); + } let docs = func .attrs .iter() diff --git a/rust/tw_macros/src/utils.rs b/rust/tw_macros/src/utils.rs new file mode 100644 index 00000000000..32f96fdb55e --- /dev/null +++ b/rust/tw_macros/src/utils.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub fn is_uint(ty: &str) -> bool { + matches!(ty, "u8" | "u16" | "u32" | "u64" | "u128" | "usize") +} From 6d1a0ed42e9a54e666c6b20f971bee3763a53f65 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Mon, 29 Sep 2025 18:45:02 +0200 Subject: [PATCH 52/72] fix(data): Allocate empty array on `tw_data_create_with_bytes` if data is NULL (#4508) * fix(data): Allocate empty array on `tw_data_create_with_bytes` if data is NULL * fix(data): Add TWData test --- rust/tw_keypair/src/ffi/asn.rs | 4 +--- rust/tw_keypair/src/ffi/privkey.rs | 9 +++------ rust/tw_keypair/src/ffi/pubkey.rs | 6 +++--- rust/tw_memory/src/ffi/c_byte_array_ref.rs | 10 +++++----- rust/tw_memory/src/ffi/tw_data.rs | 8 +++----- rust/tw_memory/src/ffi/tw_string.rs | 4 +--- tests/chains/Ethereum/BarzTests.cpp | 18 ++++++++++++++++++ tests/common/DataTests.cpp | 10 ++++++++++ 8 files changed, 44 insertions(+), 25 deletions(-) diff --git a/rust/tw_keypair/src/ffi/asn.rs b/rust/tw_keypair/src/ffi/asn.rs index 7befaa9c10f..63cebff4432 100644 --- a/rust/tw_keypair/src/ffi/asn.rs +++ b/rust/tw_keypair/src/ffi/asn.rs @@ -32,9 +32,7 @@ pub unsafe extern "C" fn ecdsa_signature_from_asn_der( encoded_len: usize, ) -> CByteArrayResult { let encoded_ref = CByteArrayRef::new(encoded, encoded_len); - let Some(encoded) = encoded_ref.to_vec() else { - return CByteArrayResult::error(CKeyPairError::InvalidSignature); - }; + let encoded = encoded_ref.to_vec(); der::Signature::from_bytes(encoded.as_slice()) .map(|sign| CByteArray::from(sign.to_vec())) diff --git a/rust/tw_keypair/src/ffi/privkey.rs b/rust/tw_keypair/src/ffi/privkey.rs index 6451e9c958e..1bef684f815 100644 --- a/rust/tw_keypair/src/ffi/privkey.rs +++ b/rust/tw_keypair/src/ffi/privkey.rs @@ -33,7 +33,7 @@ pub unsafe extern "C" fn tw_private_key_create_with_data( input_len: usize, ) -> *mut TWPrivateKey { let bytes_ref = CByteArrayRef::new(input, input_len); - let bytes = try_or_else!(bytes_ref.to_vec(), std::ptr::null_mut); + let bytes = bytes_ref.to_vec(); PrivateKey::new(bytes) .map(|private| TWPrivateKey(private).into_ptr()) @@ -85,7 +85,7 @@ pub unsafe extern "C" fn tw_private_key_is_valid( curve: u32, ) -> bool { let curve = try_or_false!(Curve::from_raw(curve)); - let priv_key_slice = try_or_false!(CByteArrayRef::new(key, key_len).as_slice()); + let priv_key_slice = CByteArrayRef::new(key, key_len).as_slice(); PrivateKey::is_valid(priv_key_slice, curve) } @@ -105,10 +105,7 @@ pub unsafe extern "C" fn tw_private_key_sign( ) -> CByteArray { let curve = try_or_else!(Curve::from_raw(curve), CByteArray::default); let private = try_or_else!(TWPrivateKey::from_ptr_as_ref(key), CByteArray::default); - let message_to_sign = try_or_else!( - CByteArrayRef::new(message, message_len).as_slice(), - CByteArray::default - ); + let message_to_sign = CByteArrayRef::new(message, message_len).as_slice(); // Return an empty signature if an error occurs. let sig = private.0.sign(message_to_sign, curve).unwrap_or_default(); diff --git a/rust/tw_keypair/src/ffi/pubkey.rs b/rust/tw_keypair/src/ffi/pubkey.rs index 7f05f5f0fe9..898c04412e9 100644 --- a/rust/tw_keypair/src/ffi/pubkey.rs +++ b/rust/tw_keypair/src/ffi/pubkey.rs @@ -34,7 +34,7 @@ pub unsafe extern "C" fn tw_public_key_create_with_data( ty: u32, ) -> *mut TWPublicKey { let bytes_ref = CByteArrayRef::new(input, input_len); - let bytes = try_or_else!(bytes_ref.to_vec(), std::ptr::null_mut); + let bytes = bytes_ref.to_vec(); let ty = try_or_else!(PublicKeyType::from_raw(ty), std::ptr::null_mut); PublicKey::new(bytes, ty) .map(|public| TWPublicKey(public).into_ptr()) @@ -67,8 +67,8 @@ pub unsafe extern "C" fn tw_public_key_verify( msg_len: usize, ) -> bool { let public = try_or_false!(TWPublicKey::from_ptr_as_ref(key)); - let sig = try_or_false!(CByteArrayRef::new(sig, sig_len).as_slice()); - let msg = try_or_false!(CByteArrayRef::new(msg, msg_len).as_slice()); + let sig = CByteArrayRef::new(sig, sig_len).as_slice(); + let msg = CByteArrayRef::new(msg, msg_len).as_slice(); public.0.verify(sig, msg) } diff --git a/rust/tw_memory/src/ffi/c_byte_array_ref.rs b/rust/tw_memory/src/ffi/c_byte_array_ref.rs index 7f5e3e0b4a0..35f5f94235f 100644 --- a/rust/tw_memory/src/ffi/c_byte_array_ref.rs +++ b/rust/tw_memory/src/ffi/c_byte_array_ref.rs @@ -22,8 +22,8 @@ impl CByteArrayRef { /// # Safety /// /// The inner data must be valid. - pub unsafe fn to_vec(&self) -> Option> { - self.as_slice().map(|data| data.to_vec()) + pub unsafe fn to_vec(&self) -> Vec { + self.as_slice().to_vec() } /// Returns a slice. @@ -32,10 +32,10 @@ impl CByteArrayRef { /// # Safety /// /// The inner data must be valid. - pub unsafe fn as_slice(&self) -> Option<&'static [u8]> { + pub unsafe fn as_slice(&self) -> &'static [u8] { if self.data.is_null() { - return None; + return &[]; } - Some(std::slice::from_raw_parts(self.data, self.size)) + std::slice::from_raw_parts(self.data, self.size) } } diff --git a/rust/tw_memory/src/ffi/tw_data.rs b/rust/tw_memory/src/ffi/tw_data.rs index 7a573d8fe4b..00c7c723040 100644 --- a/rust/tw_memory/src/ffi/tw_data.rs +++ b/rust/tw_memory/src/ffi/tw_data.rs @@ -20,8 +20,8 @@ impl TWData { } /// Creates a `TWData` from a raw byte array. - pub unsafe fn from_raw_data(bytes: *const u8, size: usize) -> Option { - CByteArrayRef::new(bytes, size).to_vec().map(TWData) + pub unsafe fn from_raw_data(bytes: *const u8, size: usize) -> TWData { + TWData(CByteArrayRef::new(bytes, size).to_vec()) } /// Converts `TWData` into `Data` without additional allocation. @@ -65,9 +65,7 @@ impl RawPtrTrait for TWData {} /// \return Non-null filled block of data. #[no_mangle] pub unsafe extern "C" fn tw_data_create_with_bytes(bytes: *const u8, size: usize) -> *mut TWData { - TWData::from_raw_data(bytes, size) - .map(|data| data.into_ptr()) - .unwrap_or_else(std::ptr::null_mut) + TWData::from_raw_data(bytes, size).into_ptr() } /// Deletes a block of data created with a `TWDataCreate*` method. diff --git a/rust/tw_memory/src/ffi/tw_string.rs b/rust/tw_memory/src/ffi/tw_string.rs index 49932593f6c..50b71fae216 100644 --- a/rust/tw_memory/src/ffi/tw_string.rs +++ b/rust/tw_memory/src/ffi/tw_string.rs @@ -16,9 +16,7 @@ pub struct TWString(CString); impl TWString { pub unsafe fn is_utf8_string(bytes: *const u8, size: usize) -> bool { - let Some(bytes) = CByteArrayRef::new(bytes, size).to_vec() else { - return false; - }; + let bytes = CByteArrayRef::new(bytes, size).to_vec(); String::from_utf8(bytes).is_ok() } diff --git a/tests/chains/Ethereum/BarzTests.cpp b/tests/chains/Ethereum/BarzTests.cpp index f521de95d1d..aa60e478024 100644 --- a/tests/chains/Ethereum/BarzTests.cpp +++ b/tests/chains/Ethereum/BarzTests.cpp @@ -423,6 +423,24 @@ TEST(Barz, SignAuthorization) { } } +TEST(Barz, SignAuthorizationZeroNonce) { + { + const auto chainId = store(uint256_t(1)); + const auto contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; + const Data nonce; + const auto privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; + + const auto signedAuthorization = WRAPS(TWBarzSignAuthorization(WRAPD(TWDataCreateWithBytes(chainId.data(), chainId.size())).get(), STRING(contractAddress).get(), WRAPD(TWDataCreateWithBytes(nonce.data(), nonce.size())).get(), STRING(privateKey).get())); + auto json = nlohmann::json::parse(std::string(TWStringUTF8Bytes(signedAuthorization.get()))); + ASSERT_EQ(json["chainId"], hexEncoded(chainId)); + ASSERT_EQ(json["address"], contractAddress); + ASSERT_EQ(json["nonce"], "0x00"); + ASSERT_EQ(json["yParity"], hexEncoded(store(uint256_t(0)))); + ASSERT_EQ(json["r"], "0x2269a34ea41b8898fb28196c3548836e2df0efe5968574be1cefc0355af11c24"); + ASSERT_EQ(json["s"], "0x601b4443deafb48303d4f4a580505485e3fa7f516472675227494a88e9d0a5b5"); + } +} + TEST(Barz, GetEncodedHash) { { const auto chainId = store(uint256_t(31337), 32); diff --git a/tests/common/DataTests.cpp b/tests/common/DataTests.cpp index 9d0a573ecc7..2bb39713506 100644 --- a/tests/common/DataTests.cpp +++ b/tests/common/DataTests.cpp @@ -92,3 +92,13 @@ TEST(DataTests, hasPrefix) { const Data prefix23 = parse_hex("bb"); EXPECT_FALSE(has_prefix(data, prefix23)); } + +TEST(DataTests, rustEmptyArray) { + Data empty; + EXPECT_EQ(empty.size(), 0ul); + EXPECT_EQ(empty.data(), nullptr); + auto* rustPtr = Rust::tw_data_create_with_bytes(empty.data(), empty.size()); + EXPECT_NE(rustPtr, nullptr); + EXPECT_EQ(Rust::tw_data_size(rustPtr), 0); + Rust::tw_data_delete(rustPtr); +} From 5198377644a54b4b77a1b25c0e39d297aada75dd Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Fri, 3 Oct 2025 09:49:16 +0200 Subject: [PATCH 53/72] feat(Biz): Add helper functions to support `BizPasskeySessionAccount` (#4516) * feat(biz-passkey): Add `TWBarzEncodeRegisterSessionCall` FFI * feat(biz-passkey): Add `TWBarzEncodeRemoveSessionCall` FFI * feat(biz-passkey): Add `TWBarzEncodePasskeySessionNonce` FFI * feat(biz-passkey): Add `TWBarzEncodeExecuteWithPasskeySessionCall` FFI * refactor(barz): Move some functions to TWBiz and TWEip7702 modules * feat(biz): Add WebAuthn * feat(biz): Add `TWWebAuthnGetMessageHash` and `TWWebAuthnGetFormattedSignature` * feat(biz): Fix C++ and Mobile * Rename `TWWebAuthn` module to `TWWebAuthnSolidity` * feat(biz): Adjust `executeWithPasskeySession` arguments * feat(biz): Fix fmt * feat(biz): Add Biz Android tests * feat(biz): Add final Biz Android test * feat(biz): Fix lints * feat(biz): Minor change * feat(biz): fmt --- .gitignore | 6 + .../core/app/blockchains/ethereum/TestBarz.kt | 212 ---- .../core/app/blockchains/ethereum/TestBiz.kt | 365 +++++++ .../app/blockchains/ethereum/TestEip7702.kt | 43 + rust/chains/tw_aptos/tests/signer.rs | 4 +- rust/tw_evm/src/abi/encode.rs | 15 + rust/tw_evm/src/abi/prebuild/biz.rs | 16 +- .../src/abi/prebuild/biz_passkey_session.rs | 66 ++ rust/tw_evm/src/abi/prebuild/mod.rs | 1 + .../resource/biz_passkey_session.abi.json | 947 ++++++++++++++++++ rust/tw_evm/src/ffi/barz.rs | 145 +-- rust/tw_evm/src/ffi/biz.rs | 176 ++++ rust/tw_evm/src/ffi/eip7702.rs | 76 ++ rust/tw_evm/src/ffi/mod.rs | 3 + rust/tw_evm/src/ffi/webauthn_solidity.rs | 76 ++ rust/tw_evm/src/modules/barz/core.rs | 122 +-- rust/tw_evm/src/modules/barz/error.rs | 1 + rust/tw_evm/src/modules/biz.rs | 154 +++ rust/tw_evm/src/modules/eip7702.rs | 51 + rust/tw_evm/src/modules/mod.rs | 3 + rust/tw_evm/src/modules/tx_builder.rs | 6 +- rust/tw_evm/src/modules/webauthn.rs | 140 +++ rust/tw_evm/tests/barz.rs | 80 -- rust/tw_evm/tests/barz_ffi.rs | 125 +-- rust/tw_evm/tests/biz.rs | 135 +++ rust/tw_evm/tests/biz_ffi.rs | 143 +++ rust/tw_evm/tests/eip7702.rs | 50 + rust/tw_evm/tests/eip7702_ffi.rs | 77 ++ rust/tw_evm/tests/webauthn_ffi.rs | 67 ++ rust/tw_keypair/src/tw/public.rs | 10 + src/proto/Barz.proto | 1 - src/proto/Biz.proto | 33 + swift/Tests/BarzTests.swift | 9 - swift/Tests/Eip7702Tests.swift | 17 + tests/chains/Ethereum/BarzTests.cpp | 25 +- 35 files changed, 2699 insertions(+), 701 deletions(-) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBiz.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEip7702.kt create mode 100644 rust/tw_evm/src/abi/prebuild/biz_passkey_session.rs create mode 100644 rust/tw_evm/src/abi/prebuild/resource/biz_passkey_session.abi.json create mode 100644 rust/tw_evm/src/ffi/biz.rs create mode 100644 rust/tw_evm/src/ffi/eip7702.rs create mode 100644 rust/tw_evm/src/ffi/webauthn_solidity.rs create mode 100644 rust/tw_evm/src/modules/biz.rs create mode 100644 rust/tw_evm/src/modules/eip7702.rs create mode 100644 rust/tw_evm/src/modules/webauthn.rs create mode 100644 rust/tw_evm/tests/biz.rs create mode 100644 rust/tw_evm/tests/biz_ffi.rs create mode 100644 rust/tw_evm/tests/eip7702.rs create mode 100644 rust/tw_evm/tests/eip7702_ffi.rs create mode 100644 rust/tw_evm/tests/webauthn_ffi.rs create mode 100644 src/proto/Biz.proto create mode 100644 swift/Tests/Eip7702Tests.swift diff --git a/.gitignore b/.gitignore index b5eaf7dd735..1a9598980ee 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,9 @@ include/TrustWalletCore/TWCryptoBoxPublicKey.h include/TrustWalletCore/TWCryptoBoxSecretKey.h include/TrustWalletCore/TWEthereum.h include/TrustWalletCore/TWBarz.h +include/TrustWalletCore/TWBiz.h +include/TrustWalletCore/TWEip7702.h +include/TrustWalletCore/TWWebAuthnSolidity.h # Wasm emsdk/ @@ -83,3 +86,6 @@ samples/cpp/sample # Rust target build **/target/ + +# Kotlin +kotlin/.kotlin diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt index b793671d644..47097ce42dd 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt @@ -6,7 +6,6 @@ import org.junit.Assert.assertEquals import org.junit.Test import wallet.core.jni.* import wallet.core.java.AnySigner -import wallet.core.jni.CoinType import wallet.core.jni.CoinType.ETHEREUM import wallet.core.jni.proto.Ethereum import wallet.core.jni.EthereumAbi @@ -17,7 +16,6 @@ import wallet.core.jni.PrivateKey import wallet.core.jni.PublicKey import wallet.core.jni.PublicKeyType import com.trustwallet.core.app.utils.Numeric -import org.junit.Assert.assertArrayEquals import wallet.core.jni.proto.Barz import wallet.core.jni.Barz as WCBarz @@ -234,214 +232,4 @@ class TestBarz { assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x84d0464f5a2b191e06295443970ecdcd2d18f565d0d52b5a79443192153770ab"); assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1E6c542ebC7c960c6A155A9094DB838cEf842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}") } - - // https://bscscan.com/tx/0x425eb17a8e1dee2fcee8352a772d83cbb069c2e03f2c5d9d00da3b3ef66ce48b - @Test - fun testSignEip7702EoaBatched() { - val transferFunc1 = EthereumAbiFunction("transfer") - transferFunc1.addParamAddress("0x2EF648D7C03412B832726fd4683E2625deA047Ba".toHexByteArray(), false) - // 100_000_000_000_000 - transferFunc1.addParamUInt256("0x5af3107a4000".toHexByteArray(), false) - val transferPayload1 = EthereumAbi.encode(transferFunc1) - - val transferFunc2 = EthereumAbiFunction("transfer") - transferFunc2.addParamAddress("0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e".toHexByteArray(), false) - // 500_000_000_000_000 - transferFunc2.addParamUInt256("0x1c6bf52634000".toHexByteArray(), false) - val transferPayload2 = EthereumAbi.encode(transferFunc2) - - val signingInput = Ethereum.SigningInput.newBuilder() - signingInput.apply { - privateKey = ByteString.copyFrom(PrivateKey("0xe148e40f06ee3ba316cdb2571f33486cf879c0ffd2b279ce9f9a88c41ce962e7".toHexByteArray()).data()) - chainId = ByteString.copyFrom("0x38".toHexByteArray()) - nonce = ByteString.copyFrom("0x12".toHexByteArray()) - txMode = TransactionMode.SetCode - - gasLimit = ByteString.copyFrom("0x186a0".toHexByteArray()) - maxFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) - maxInclusionFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) - - transaction = Ethereum.Transaction.newBuilder().apply { - scwBatch = Ethereum.Transaction.SCWalletBatch.newBuilder().apply { - walletType = Ethereum.SCWalletType.Biz - addAllCalls(listOf( - Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { - // TWT - address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" - amount = ByteString.copyFrom("0x00".toHexByteArray()) - payload = ByteString.copyFrom(transferPayload1) - }.build(), - Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { - // TWT - address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" - amount = ByteString.copyFrom("0x00".toHexByteArray()) - payload = ByteString.copyFrom(transferPayload2) - }.build() - )) - }.build() - }.build() - - eip7702Authorization = Ethereum.Authorization.newBuilder().apply { - address = "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7" - }.build() - } - - val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) - - assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x00b2d13719df301927ddcbdad5b6bc6214f2007c6408df883c9ea483b45e6f44") - assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x04f9030f3812843b9aca00843b9aca00830186a0945132829820b44dc3e8586cec926a16fca0a5608480b9024434fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000002ef648d7c03412b832726fd4683e2625dea047ba00000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000c0f85cf85a3894117bc8454756456a0f83dbd130bb94d793d3f3f71380a0073afc661c158a2dccf4183f87e1e4d62b4d406af418cfd69959368ec9bec2a6a064292fd61d4d16b840470a86fc4f7a89413f9126d897f2268eb76a1d887c6d7a01a0e8bcbd96323c9d3e67b74366b2f43299100996d9e8874a6fd87186ac8f580d4ca07c25b4f0619af77fb953e8f0e4372bfbee62616ad419697516108eeb9bcebb28") - } - - // https://bscscan.com/tx/0x6f8b2c8d50e8bb543d7124703b75d9e495832116a1a61afabf40b9b0ac43c980 - @Test - fun testSignEnvelopedBiz() { - val signingInput = Ethereum.SigningInput.newBuilder() - signingInput.apply { - privateKey = ByteString.copyFrom(PrivateKey("0xe762e91cc4889a9fce79b2d2ffc079f86c48331f57b2cd16a33bee060fe448e1".toHexByteArray()).data()) - chainId = ByteString.copyFrom("0x38".toHexByteArray()) - nonce = ByteString.copyFrom("0x02".toHexByteArray()) - txMode = TransactionMode.Enveloped - - gasLimit = ByteString.copyFrom("0x186a0".toHexByteArray()) - maxFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) - maxInclusionFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) - - transaction = Ethereum.Transaction.newBuilder().apply { - scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { - walletType = Ethereum.SCWalletType.Biz - transaction = Ethereum.Transaction.newBuilder().apply { - erc20Transfer = Ethereum.Transaction.ERC20Transfer.newBuilder().apply { - to = "0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e" - amount = ByteString.copyFrom("0xb5e620f48000".toHexByteArray()) - }.build() - }.build() - }.build() - }.build() - - // TWT token. - toAddress = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" - } - - val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) - - assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x60260356568ae70838bd80085b971e1e4ebe42046688fd8511a268986e522121") - assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x02f901503802843b9aca00843b9aca00830186a0946e860086bba8fdeafb553815af0f09a854cc887a80b8e4b61d27f60000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000000b5e620f4800000000000000000000000000000000000000000000000000000000000c080a0fb45762a262f4c32090576e9de087482d25cd00b6ea2522eb7d5a40f435acdbaa0151dbd48a4f4bf06080313775fe32ececd68869d721518a92bf292e4a84322f9") - } - - @Test - fun testAuthorizationHash() { - val chainId = "0x01".toHexByteArray() - val contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" - val nonce = "0x01".toHexByteArray() - - val authorizationHash = WCBarz.getAuthorizationHash(chainId, contractAddress, nonce) - assertEquals(Numeric.toHexString(authorizationHash), "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9") // Verified with viem - } - - @Test - fun testSignAuthorization() { - val chainId = "0x01".toHexByteArray() - val contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" - val nonce = "0x01".toHexByteArray() - val privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8" - - val signedAuthorization = WCBarz.signAuthorization(chainId, contractAddress, nonce, privateKey) - val json = org.json.JSONObject(signedAuthorization) - - // Verified with viem - assertEquals(Numeric.toHexString(chainId), json.getString("chainId")) - assertEquals(contractAddress, json.getString("address")) - assertEquals(Numeric.toHexString(nonce), json.getString("nonce")) - assertEquals("0x01", json.getString("yParity")) - assertEquals("0x2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710", json.getString("r")) - assertEquals("0x5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c", json.getString("s")) - } - - @Test - fun testBarzTransferAccountDeployedV07() { - val chainIdByteArray = "0x7A69".toHexByteArray() // 31337 - val wallet = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029" - - val transfer = Ethereum.Transaction.Transfer.newBuilder().apply { - amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) - data = ByteString.EMPTY - }.build() - - val userOpV07 = Ethereum.UserOperationV0_7.newBuilder().apply { - entryPoint = "0x0000000071727De22E5E9d8BAf0edAc6f37da032" - sender = wallet - preVerificationGas = ByteString.copyFrom("0xF4240".toHexByteArray()) // 1000000 - verificationGasLimit = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 - factory = "0xf471789937856d80e589f5996cf8b0511ddd9de4" - factoryData = ByteString.copyFrom("0xf471789937856d80e589f5996cf8b0511ddd9de4".toHexByteArray()) - paymaster = "0xf62849f9a0b5bf2913b396098f7c7019b51a820a" - paymasterVerificationGasLimit = ByteString.copyFrom("0x1869F".toHexByteArray()) // 99999 - paymasterPostOpGasLimit = ByteString.copyFrom("0x15B38".toHexByteArray()) // 88888 - paymasterData = ByteString.copyFrom( - "0x00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b" - .toHexByteArray() - ) - }.build() - - // Create signing input - val signingInput = Ethereum.SigningInput.newBuilder().apply { - privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray()).data()) - chainId = ByteString.copyFrom(chainIdByteArray) // 31337 - nonce = ByteString.copyFrom("0x00".toHexByteArray()) - txMode = Ethereum.TransactionMode.UserOp - gasLimit = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 - maxFeePerGas = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 - maxInclusionFeePerGas = ByteString.copyFrom("0x186A0".toHexByteArray()) - toAddress = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9" - - transaction = Ethereum.Transaction.newBuilder().apply { - scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { - transaction = Ethereum.Transaction.newBuilder().apply { - this.transfer = transfer - }.build() - }.build() - }.build() - - userOperationV07 = userOpV07 - }.build() - - val output = AnySigner.sign(signingInput, CoinType.ETHEREUM, Ethereum.SigningOutput.parser()) - - assertEquals( - "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb", - Numeric.toHexString(output.preHash.toByteArray()) - ) - - val codeAddress = "0x2e234DAe75C793f67A35089C9d99245E1C58470b" - val codeName = "Biz" - val codeVersion = "v1.0.0" - val typeHash = "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867" - val domainSeparatorHash = "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472" - val hash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb" - - val encodedHash = WCBarz.getEncodedHash( - chainIdByteArray, - codeAddress, - codeName, - codeVersion, - typeHash, - domainSeparatorHash, - wallet, - hash - ) - assertEquals( - "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635", - Numeric.toHexString(encodedHash) - ) - - val privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8" - val signedHash = WCBarz.getSignedHash( - "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635", - privateKey - ) - assertEquals( - "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c", - Numeric.toHexString(signedHash) - ) - } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBiz.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBiz.kt new file mode 100644 index 00000000000..0966dc81d71 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBiz.kt @@ -0,0 +1,365 @@ +package com.trustwallet.core.app.blockchains.ethereum + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.* +import wallet.core.jni.CoinType.ETHEREUM +import wallet.core.jni.proto.Ethereum +import wallet.core.jni.proto.Ethereum.SigningOutput +import wallet.core.jni.proto.Ethereum.TransactionMode +import wallet.core.jni.Biz as WCBiz + +class TestBiz { + + init { + System.loadLibrary("TrustWalletCore") + } + + // https://bscscan.com/tx/0x425eb17a8e1dee2fcee8352a772d83cbb069c2e03f2c5d9d00da3b3ef66ce48b + @Test + fun testSignEip7702EoaBatched() { + val transferFunc1 = EthereumAbiFunction("transfer") + transferFunc1.addParamAddress("0x2EF648D7C03412B832726fd4683E2625deA047Ba".toHexByteArray(), false) + // 100_000_000_000_000 + transferFunc1.addParamUInt256("0x5af3107a4000".toHexByteArray(), false) + val transferPayload1 = EthereumAbi.encode(transferFunc1) + + val transferFunc2 = EthereumAbiFunction("transfer") + transferFunc2.addParamAddress("0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e".toHexByteArray(), false) + // 500_000_000_000_000 + transferFunc2.addParamUInt256("0x1c6bf52634000".toHexByteArray(), false) + val transferPayload2 = EthereumAbi.encode(transferFunc2) + + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("0xe148e40f06ee3ba316cdb2571f33486cf879c0ffd2b279ce9f9a88c41ce962e7".toHexByteArray()).data()) + chainId = ByteString.copyFrom("0x38".toHexByteArray()) + nonce = ByteString.copyFrom("0x12".toHexByteArray()) + txMode = TransactionMode.SetCode + + gasLimit = ByteString.copyFrom("0x186a0".toHexByteArray()) + maxFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + maxInclusionFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + + transaction = Ethereum.Transaction.newBuilder().apply { + scwBatch = Ethereum.Transaction.SCWalletBatch.newBuilder().apply { + walletType = Ethereum.SCWalletType.Biz + addAllCalls(listOf( + Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { + // TWT + address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" + amount = ByteString.copyFrom("0x00".toHexByteArray()) + payload = ByteString.copyFrom(transferPayload1) + }.build(), + Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { + // TWT + address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" + amount = ByteString.copyFrom("0x00".toHexByteArray()) + payload = ByteString.copyFrom(transferPayload2) + }.build() + )) + }.build() + }.build() + + eip7702Authorization = Ethereum.Authorization.newBuilder().apply { + address = "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7" + }.build() + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x00b2d13719df301927ddcbdad5b6bc6214f2007c6408df883c9ea483b45e6f44") + assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x04f9030f3812843b9aca00843b9aca00830186a0945132829820b44dc3e8586cec926a16fca0a5608480b9024434fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000002ef648d7c03412b832726fd4683e2625dea047ba00000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000c0f85cf85a3894117bc8454756456a0f83dbd130bb94d793d3f3f71380a0073afc661c158a2dccf4183f87e1e4d62b4d406af418cfd69959368ec9bec2a6a064292fd61d4d16b840470a86fc4f7a89413f9126d897f2268eb76a1d887c6d7a01a0e8bcbd96323c9d3e67b74366b2f43299100996d9e8874a6fd87186ac8f580d4ca07c25b4f0619af77fb953e8f0e4372bfbee62616ad419697516108eeb9bcebb28") + } + + // https://bscscan.com/tx/0x6f8b2c8d50e8bb543d7124703b75d9e495832116a1a61afabf40b9b0ac43c980 + @Test + fun testSignEnvelopedBiz() { + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("0xe762e91cc4889a9fce79b2d2ffc079f86c48331f57b2cd16a33bee060fe448e1".toHexByteArray()).data()) + chainId = ByteString.copyFrom("0x38".toHexByteArray()) + nonce = ByteString.copyFrom("0x02".toHexByteArray()) + txMode = TransactionMode.Enveloped + + gasLimit = ByteString.copyFrom("0x186a0".toHexByteArray()) + maxFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + maxInclusionFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + + transaction = Ethereum.Transaction.newBuilder().apply { + scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { + walletType = Ethereum.SCWalletType.Biz + transaction = Ethereum.Transaction.newBuilder().apply { + erc20Transfer = Ethereum.Transaction.ERC20Transfer.newBuilder().apply { + to = "0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e" + amount = ByteString.copyFrom("0xb5e620f48000".toHexByteArray()) + }.build() + }.build() + }.build() + }.build() + + // TWT token. + toAddress = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x60260356568ae70838bd80085b971e1e4ebe42046688fd8511a268986e522121") + assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x02f901503802843b9aca00843b9aca00830186a0946e860086bba8fdeafb553815af0f09a854cc887a80b8e4b61d27f60000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000000b5e620f4800000000000000000000000000000000000000000000000000000000000c080a0fb45762a262f4c32090576e9de087482d25cd00b6ea2522eb7d5a40f435acdbaa0151dbd48a4f4bf06080313775fe32ececd68869d721518a92bf292e4a84322f9") + } + + @Test + fun testBizTransferAccountDeployedV07() { + val chainIdByteArray = "0x7A69".toHexByteArray() // 31337 + val wallet = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029" + + val transfer = Ethereum.Transaction.Transfer.newBuilder().apply { + amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + data = ByteString.EMPTY + }.build() + + val userOpV07 = Ethereum.UserOperationV0_7.newBuilder().apply { + entryPoint = "0x0000000071727De22E5E9d8BAf0edAc6f37da032" + sender = wallet + preVerificationGas = ByteString.copyFrom("0xF4240".toHexByteArray()) // 1000000 + verificationGasLimit = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 + factory = "0xf471789937856d80e589f5996cf8b0511ddd9de4" + factoryData = ByteString.copyFrom("0xf471789937856d80e589f5996cf8b0511ddd9de4".toHexByteArray()) + paymaster = "0xf62849f9a0b5bf2913b396098f7c7019b51a820a" + paymasterVerificationGasLimit = ByteString.copyFrom("0x1869F".toHexByteArray()) // 99999 + paymasterPostOpGasLimit = ByteString.copyFrom("0x15B38".toHexByteArray()) // 88888 + paymasterData = ByteString.copyFrom( + "0x00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b" + .toHexByteArray() + ) + }.build() + + // Create signing input + val signingInput = Ethereum.SigningInput.newBuilder().apply { + privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray()).data()) + chainId = ByteString.copyFrom(chainIdByteArray) // 31337 + nonce = ByteString.copyFrom("0x00".toHexByteArray()) + txMode = Ethereum.TransactionMode.UserOp + gasLimit = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 + maxFeePerGas = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 + maxInclusionFeePerGas = ByteString.copyFrom("0x186A0".toHexByteArray()) + toAddress = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9" + + transaction = Ethereum.Transaction.newBuilder().apply { + scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { + transaction = Ethereum.Transaction.newBuilder().apply { + this.transfer = transfer + }.build() + }.build() + }.build() + + userOperationV07 = userOpV07 + }.build() + + val output = AnySigner.sign(signingInput, CoinType.ETHEREUM, Ethereum.SigningOutput.parser()) + + assertEquals( + "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb", + Numeric.toHexString(output.preHash.toByteArray()) + ) + + val codeAddress = "0x2e234DAe75C793f67A35089C9d99245E1C58470b" + val codeName = "Biz" + val codeVersion = "v1.0.0" + val typeHash = "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867" + val domainSeparatorHash = "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472" + val hash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb" + + val encodedHash = WCBiz.getEncodedHash( + chainIdByteArray, + codeAddress, + codeName, + codeVersion, + typeHash, + domainSeparatorHash, + wallet, + hash + ) + assertEquals( + "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635", + Numeric.toHexString(encodedHash) + ) + + val privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8" + val signedHash = WCBiz.getSignedHash( + "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635", + privateKey + ) + assertEquals( + "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c", + Numeric.toHexString(signedHash) + ) + } + + @Test + fun testBizEncodeRegisterSessionCall() { + val publicKey = PublicKey( + "0x041c05286fe694493eae33312f2d2e0d0abeda8db76238b7a204be1fb87f54ce4228fef61ef4ac300f631657635c28e59bfb2fe71bce1634c81c65642042f6dc4d".toHexByteArray(), + PublicKeyType.NIST256P1EXTENDED + ) + val validUntil = "0x15181" // 86_401 + val encoded = WCBiz.encodeRegisterSessionCall(publicKey, Numeric.hexStringToByteArray(validUntil)) + assertEquals( + "0x826491fb000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000151810000000000000000000000000000000000000000000000000000000000000041041c05286fe694493eae33312f2d2e0d0abeda8db76238b7a204be1fb87f54ce4228fef61ef4ac300f631657635c28e59bfb2fe71bce1634c81c65642042f6dc4d00000000000000000000000000000000000000000000000000000000000000", + Numeric.toHexString(encoded) + ) + } + + @Test + fun testBizEncodeRemoveSessionCall() { + val publicKey = PublicKey( + "0x041c05286fe694493eae33312f2d2e0d0abeda8db76238b7a204be1fb87f54ce4228fef61ef4ac300f631657635c28e59bfb2fe71bce1634c81c65642042f6dc4d".toHexByteArray(), + PublicKeyType.NIST256P1EXTENDED + ) + val encoded = WCBiz.encodeRemoveSessionCall(publicKey) + assertEquals( + "0xe1c06abd00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000041041c05286fe694493eae33312f2d2e0d0abeda8db76238b7a204be1fb87f54ce4228fef61ef4ac300f631657635c28e59bfb2fe71bce1634c81c65642042f6dc4d00000000000000000000000000000000000000000000000000000000000000", + Numeric.toHexString(encoded) + ) + } + + @Test + fun testBizEncodePasskeyNonce() { + val nonce = "0x7b" // 123 + val passkeyNonce = WCBiz.encodePasskeySessionNonce(Numeric.hexStringToByteArray(nonce)) + assertEquals( + "0x00000000000000000000000000000000050041d6a66939a8000000000000007b", + Numeric.toHexString(passkeyNonce) + ) + } + + @Test + fun testSignUserOperationV7WithPasskeySession() { + val chainIdByteArray = "0x7A69".toHexByteArray() // 31337 + val wallet = "0x336Cd992a83242D91f556C1F7e855AcA366193e0" + val bizPasskeySessionAccount = "0xa0Cb889707d426A7A386870A03bc70d1b0697598" + val bizPasskeySessionCodeName = "PasskeySession" + val codeVersion = "v1.0.0" + // keccak("PasskeySession(bytes32 userOpHash)") + val typeHash = "0x3463fe66e4d03af5b942aebde2b191eff52d291c0a2c8cc302d786854f34bfc9" + // keccak("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)") + val domainSeparatorHash = "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472" + + // Step 1. Generate UserOperation and its hash. + + val batch = Ethereum.Transaction.SCWalletBatch.newBuilder() + .setWalletType(Ethereum.SCWalletType.Biz4337) + .addCalls(Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { + address = "0x0000000000000000000000000000000000000001" + amount = ByteString.copyFrom("0xde0b6b3a7640000".toHexByteArray()) + payload = ByteString.EMPTY + }) + .addCalls(Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { + address = "0x0000000000000000000000000000000000000002" + amount = ByteString.copyFrom("0xde0b6b3a7640000".toHexByteArray()) + payload = ByteString.EMPTY + }) + .addCalls(Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { + address = "0x0000000000000000000000000000000000000003" + amount = ByteString.copyFrom("0xde0b6b3a7640000".toHexByteArray()) + payload = ByteString.EMPTY + }) + .build() + + val actualNonce = Numeric.hexStringToByteArray("0x01") + val passkeyNonce = WCBiz.encodePasskeySessionNonce(actualNonce) + + val userOpV07 = Ethereum.UserOperationV0_7.newBuilder().apply { + entryPoint = "0x0000000071727De22E5E9d8BAf0edAc6f37da032" + sender = wallet + preVerificationGas = ByteString.copyFrom("0x186a0".toHexByteArray()) // 100000 + verificationGasLimit = ByteString.copyFrom("0x186a0".toHexByteArray()) // 100000 + paymaster = "0xf62849f9a0b5bf2913b396098f7c7019b51a820a" + paymasterVerificationGasLimit = ByteString.copyFrom("0x1869F".toHexByteArray()) // 99999 + paymasterPostOpGasLimit = ByteString.copyFrom("0x15B38".toHexByteArray()) // 88888 + paymasterData = ByteString.copyFrom( + "0x00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b" + .toHexByteArray() + ) + }.build() + + // Create signing input + val signingInput = Ethereum.SigningInput.newBuilder().apply { + // Dummy private key. + privateKey = ByteString.copyFrom("0x03d99692017473e2d631945a812607b23269d85721e0f370b8d3e7d29a874fd2".toHexByteArray()) + chainId = ByteString.copyFrom(chainIdByteArray) // 31337 + nonce = ByteString.copyFrom(passkeyNonce) + txMode = Ethereum.TransactionMode.UserOp + gasLimit = ByteString.copyFrom("0x186a0".toHexByteArray()) // 100000 + maxFeePerGas = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 + maxInclusionFeePerGas = ByteString.copyFrom("0x186A0".toHexByteArray()) // 100000 + toAddress = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9" + + transaction = Ethereum.Transaction.newBuilder().apply { + scwBatch = batch + }.build() + + userOperationV07 = userOpV07 + }.build() + + val output = AnySigner.sign(signingInput, CoinType.ETHEREUM, Ethereum.SigningOutput.parser()) + + val userOpHash = output.preHash.toByteArray() + assertEquals( + "0x7762e85586107f2bca787a9163b71f0584eabd84258a93cca0e896589a193571", + Numeric.toHexString(userOpHash) + ) + + // Step 2. Encode UserOperation hash in Biz format. + + val encodedUserOpHash = WCBiz.getEncodedHash( + chainIdByteArray, + bizPasskeySessionAccount, + bizPasskeySessionCodeName, + codeVersion, + typeHash, + domainSeparatorHash, + wallet, + Numeric.toHexString(userOpHash) + ) + assertEquals( + "0x7d130331f16bb3d2bc3d72db02879d0745d4452592e56723de8b27cf1ee006c7", + Numeric.toHexString(encodedUserOpHash) + ) + + // Step 3. Generate a WebAuthn with the given challenge (encodedUserOpHash) and passkey signature. + + val clientJsonFirstPart = "{\"type\":\"webauthn.get\",\"challenge\":\"" + val challengeBase64 = Base64.encodeUrl(encodedUserOpHash) + val challengeBase64NoPad = challengeBase64.trimEnd('=') + val clientJsonLastPart = "\",\"origin\":\"https://sign.coinbase.com/",\"crossOrigin\":false}" + val clientJson = clientJsonFirstPart + challengeBase64NoPad + clientJsonLastPart + assertEquals(clientJson, "{\"type\":\"webauthn.get\",\"challenge\":\"fRMDMfFrs9K8PXLbAoedB0XURSWS5Wcj3osnzx7gBsc\",\"origin\":\"https://sign.coinbase.com/",\"crossOrigin\":false}") + + // Authenticator data for Chrome Profile touchID signature + val authenticatorData = "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000" + // Signature computed on a device using passkey. + val derSignature = "0x3045022073f8762dd6fb0eb39aea829525658fca612d1c433db6381c9d63a52ee15a26be022100e091f452b74519a2894a96d142bdd1888ac6513f5dff76e48c0312144ef9b382".toHexByteArray() + + val passkeySignature = WebAuthnSolidity.getFormattedSignature(authenticatorData, clientJson, derSignature) + assertEquals( + "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000017000000000000000000000000000000000000000000000000000000000000000173f8762dd6fb0eb39aea829525658fca612d1c433db6381c9d63a52ee15a26be1f6e0bac48bae65e76b5692ebd422e773220a96e491827a067b6b8aead6971cf000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008a7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2266524d444d66467273394b3850584c62416f656442305855525357533557636a336f736e7a783767427363222c226f726967696e223a2268747470733a2f2f7369676e2e636f696e626173652e636f6d222c2263726f73734f726967696e223a66616c73657d00000000000000000000000000000000000000000000", + Numeric.toHexString(passkeySignature) + ) + + // Step 4. Final step. Biz-specific adjustments. + + // `passkeyIndex` can be gotten by using `Biz.getPasskeySessionIndexForValidation()` view function. + // https://github.com/trustwallet/7702-passkey-session/blob/b5c85a5c9a72c19195d1d60a1c90b3908a6a0371/src/BizPasskeySession.sol#L412 + val passkeyIndex: Byte = 0x00 + val passkeyIndexAttachedSignature = byteArrayOf(passkeyIndex) + passkeySignature + assertEquals( + "0x00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000017000000000000000000000000000000000000000000000000000000000000000173f8762dd6fb0eb39aea829525658fca612d1c433db6381c9d63a52ee15a26be1f6e0bac48bae65e76b5692ebd422e773220a96e491827a067b6b8aead6971cf000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008a7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2266524d444d66467273394b3850584c62416f656442305855525357533557636a336f736e7a783767427363222c226f726967696e223a2268747470733a2f2f7369676e2e636f696e626173652e636f6d222c2263726f73734f726967696e223a66616c73657d00000000000000000000000000000000000000000000", + Numeric.toHexString(passkeyIndexAttachedSignature) + ) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEip7702.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEip7702.kt new file mode 100644 index 00000000000..7f9bb514b3f --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEip7702.kt @@ -0,0 +1,43 @@ +package com.trustwallet.core.app.blockchains.ethereum + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import com.trustwallet.core.app.utils.Numeric +import wallet.core.jni.Eip7702 + +class TestEip7702 { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAuthorizationHash() { + val chainId = "0x01".toHexByteArray() + val contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" + val nonce = "0x01".toHexByteArray() + + val authorizationHash = Eip7702.getAuthorizationHash(chainId, contractAddress, nonce) + assertEquals(Numeric.toHexString(authorizationHash), "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9") // Verified with viem + } + + @Test + fun testSignAuthorization() { + val chainId = "0x01".toHexByteArray() + val contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" + val nonce = "0x01".toHexByteArray() + val privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8" + + val signedAuthorization = Eip7702.signAuthorization(chainId, contractAddress, nonce, privateKey) + val json = org.json.JSONObject(signedAuthorization) + + // Verified with viem + assertEquals(Numeric.toHexString(chainId), json.getString("chainId")) + assertEquals(contractAddress, json.getString("address")) + assertEquals(Numeric.toHexString(nonce), json.getString("nonce")) + assertEquals("0x01", json.getString("yParity")) + assertEquals("0x2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710", json.getString("r")) + assertEquals("0x5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c", json.getString("s")) + } +} diff --git a/rust/chains/tw_aptos/tests/signer.rs b/rust/chains/tw_aptos/tests/signer.rs index cdbf9b59160..93a7c0de474 100644 --- a/rust/chains/tw_aptos/tests/signer.rs +++ b/rust/chains/tw_aptos/tests/signer.rs @@ -35,9 +35,7 @@ pub struct FungibleAssetTransfer { amount: u64, } -pub struct RegisterToken { - coin_type: TypeTag, -} +pub struct RegisterToken; pub enum OpsDetails { RegisterToken(RegisterToken), diff --git a/rust/tw_evm/src/abi/encode.rs b/rust/tw_evm/src/abi/encode.rs index dbb3ee44025..0e3ee2f4f8d 100644 --- a/rust/tw_evm/src/abi/encode.rs +++ b/rust/tw_evm/src/abi/encode.rs @@ -2,6 +2,8 @@ // // Copyright © 2017 Trust Wallet. +use crate::abi::param::Param; +use crate::abi::param_token::NamedToken; use crate::abi::token::Token; use tw_hash::H256; use tw_memory::Data; @@ -15,6 +17,19 @@ pub fn encode_tokens(tokens: &[Token]) -> Data { .collect() } +pub fn encode_tuple(tokens: Vec) -> Data { + let token_params: Vec<_> = tokens + .into_iter() + .map(|token| { + let param = Param::with_type(token.to_param_type()); + NamedToken::with_param_and_token(¶m, token) + }) + .collect(); + encode_tokens(&[Token::Tuple { + params: token_params, + }]) +} + #[derive(Debug)] enum Mediate<'a> { // head diff --git a/rust/tw_evm/src/abi/prebuild/biz.rs b/rust/tw_evm/src/abi/prebuild/biz.rs index 36edfa95e68..f856d583ab3 100644 --- a/rust/tw_evm/src/abi/prebuild/biz.rs +++ b/rust/tw_evm/src/abi/prebuild/biz.rs @@ -4,6 +4,7 @@ use crate::abi::contract::Contract; use crate::abi::function::Function; +use crate::abi::param::Param; use crate::abi::param_token::NamedToken; use crate::abi::param_type::ParamType; use crate::abi::prebuild::ExecuteArgs; @@ -58,7 +59,7 @@ impl BizAccount { } } -fn encode_batch(function: &Function, args: I) -> AbiResult +pub fn encode_batch(function: &Function, batch_calls: I) -> AbiResult where I: IntoIterator, { @@ -69,6 +70,15 @@ where .or_tw_err(AbiErrorKind::Error_internal) .context("'Biz.execute4337Ops()' should contain only one argument")?; + let array_token = batch_calls_into_array_token(array_param, batch_calls)?; + function.encode_input(&[array_token]) +} + +/// Converts a batch of calls into a single Token representing an array of tuples. +pub fn batch_calls_into_array_token(array_param: &Param, batch_calls: I) -> AbiResult +where + I: IntoIterator, +{ let ParamType::Array { kind: array_elem_type, } = array_param.kind.clone() @@ -100,7 +110,7 @@ where }); } - let array_tokens = args + let array_tokens = batch_calls .into_iter() .map(|call| Token::Tuple { params: vec![ @@ -111,5 +121,5 @@ where }) .collect(); - function.encode_input(&[Token::array(*array_elem_type, array_tokens)]) + Ok(Token::array(*array_elem_type, array_tokens)) } diff --git a/rust/tw_evm/src/abi/prebuild/biz_passkey_session.rs b/rust/tw_evm/src/abi/prebuild/biz_passkey_session.rs new file mode 100644 index 00000000000..0f445c418af --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/biz_passkey_session.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::abi::contract::Contract; +use crate::abi::prebuild::biz::batch_calls_into_array_token; +use crate::abi::prebuild::ExecuteArgs; +use crate::abi::token::Token; +use crate::abi::{AbiErrorKind, AbiResult}; +use lazy_static::lazy_static; +use tw_coin_entry::error::prelude::{OrTWError, ResultContext}; +use tw_hash::H520; +use tw_memory::Data; +use tw_number::U256; + +const BIZ_PASSKEY_SESSION_ACCOUNT_ABI: &str = include_str!("resource/biz_passkey_session.abi.json"); + +lazy_static! { + static ref BIZ_PASSKEY_SESSION_ACCOUNT: Contract = + serde_json::from_str(BIZ_PASSKEY_SESSION_ACCOUNT_ABI).unwrap(); +} + +pub struct BizPasskeySessionAccount; + +impl BizPasskeySessionAccount { + pub fn register_session( + session_passkey_public_key: H520, + valid_until_timestamp: U256, + ) -> AbiResult { + let func = BIZ_PASSKEY_SESSION_ACCOUNT.function("registerSession")?; + func.encode_input(&[ + Token::Bytes(session_passkey_public_key.to_vec()), + Token::u256(valid_until_timestamp), + ]) + } + + pub fn remove_session(session_passkey_public_key: H520) -> AbiResult { + let func = BIZ_PASSKEY_SESSION_ACCOUNT.function("removeSession")?; + func.encode_input(&[Token::Bytes(session_passkey_public_key.to_vec())]) + } + + pub fn execute_with_passkey_session( + executions: I, + validity_timestamp: U256, + signature: Data, + ) -> AbiResult + where + I: IntoIterator, + { + let func = BIZ_PASSKEY_SESSION_ACCOUNT.function("executeWithPasskeySession")?; + + // `tuple[]`, where each item is a tuple of (address, uint256, bytes). + let array_param = func + .inputs + .first() + .or_tw_err(AbiErrorKind::Error_internal) + .context("'Biz.execute4337Ops()' should contain only one argument")?; + let array_token = batch_calls_into_array_token(array_param, executions)?; + + func.encode_input(&[ + array_token, + Token::u256(validity_timestamp), + Token::Bytes(signature), + ]) + } +} diff --git a/rust/tw_evm/src/abi/prebuild/mod.rs b/rust/tw_evm/src/abi/prebuild/mod.rs index cc9012e5833..02376586497 100644 --- a/rust/tw_evm/src/abi/prebuild/mod.rs +++ b/rust/tw_evm/src/abi/prebuild/mod.rs @@ -7,6 +7,7 @@ use tw_memory::Data; use tw_number::U256; pub mod biz; +pub mod biz_passkey_session; pub mod erc1155; pub mod erc1967; pub mod erc20; diff --git a/rust/tw_evm/src/abi/prebuild/resource/biz_passkey_session.abi.json b/rust/tw_evm/src/abi/prebuild/resource/biz_passkey_session.abi.json new file mode 100644 index 00000000000..c7b03c35332 --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/resource/biz_passkey_session.abi.json @@ -0,0 +1,947 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "_circuitBreaker", + "type": "address", + "internalType": "contract CircuitBreaker" + }, + { + "name": "_bizGuard", + "type": "address", + "internalType": "contract BizGuard" + }, + { + "name": "_storageManager", + "type": "address", + "internalType": "contract StorageManager" + }, + { + "name": "_maxSessionTimeLength", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "fallback", + "stateMutability": "payable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "accountId", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "accountStorageBases", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32[]", + "internalType": "bytes32[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "entryPoint", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "execute", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "execute4337Op", + "inputs": [ + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "execute4337Ops", + "inputs": [ + { + "name": "executions", + "type": "tuple[]", + "internalType": "struct Execution[]", + "components": [ + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "executeBatch", + "inputs": [ + { + "name": "executions", + "type": "tuple[]", + "internalType": "struct Execution[]", + "components": [ + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "executeWithPasskeySession", + "inputs": [ + { + "name": "executions", + "type": "tuple[]", + "internalType": "struct Execution[]", + "components": [ + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "validityTimestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "executeWithSignature", + "inputs": [ + { + "name": "executions", + "type": "tuple[]", + "internalType": "struct Execution[]", + "components": [ + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "validityTimestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getActiveSessions", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple[]", + "internalType": "struct P256Session[]", + "components": [ + { + "name": "Qx", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "Qy", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "validUntil", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getActiveSessionsCount", + "inputs": [], + "outputs": [ + { + "name": "activeSessionsCount", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getPasskeySessionIndexForValidation", + "inputs": [ + { + "name": "sessionPublicKey", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getSessionWithIndex", + "inputs": [ + { + "name": "index", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "session", + "type": "tuple", + "internalType": "struct P256Session", + "components": [ + { + "name": "Qx", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "Qy", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "validUntil", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getSessionsLength", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "isValidSignature", + "inputs": [ + { + "name": "hash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "isValid", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "onERC1155BatchReceived", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "onERC1155Received", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "onERC721Received", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "onTokenTransfer", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "registerSession", + "inputs": [ + { + "name": "sessionPasskeyPublicKey", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "validUntilTimestamp", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "removeAllSessions", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "removeSession", + "inputs": [ + { + "name": "sessionPasskeyPublicKey", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "removeSessionWithIndex", + "inputs": [ + { + "name": "index", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "singletonSalt", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "storageOpCallback", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "supportsInterface", + "inputs": [ + { + "name": "interfaceId", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "tokensReceived", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "validateUserOp", + "inputs": [ + { + "name": "userOp", + "type": "tuple", + "internalType": "struct PackedUserOperation", + "components": [ + { + "name": "sender", + "type": "address", + "internalType": "address" + }, + { + "name": "nonce", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "initCode", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "callData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "accountGasLimits", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "preVerificationGas", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "gasFees", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "paymasterAndData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "userOpHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "missingAccountFunds", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "validationData", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "Executed", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "PasskeySessionRegistered", + "inputs": [ + { + "name": "p256Session", + "type": "tuple", + "indexed": false, + "internalType": "struct P256Session", + "components": [ + { + "name": "Qx", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "Qy", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "validUntil", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RemovedAllSessions", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "RemovedSession", + "inputs": [ + { + "name": "p256Session", + "type": "tuple", + "indexed": false, + "internalType": "struct P256Session", + "components": [ + { + "name": "Qx", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "Qy", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "validUntil", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "BeyondMaxSessionTimeLength", + "inputs": [] + }, + { + "type": "error", + "name": "Disabled", + "inputs": [] + }, + { + "type": "error", + "name": "ECDSAInvalidSignature", + "inputs": [] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureLength", + "inputs": [ + { + "name": "length", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureS", + "inputs": [ + { + "name": "s", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "ERC4337Disabled", + "inputs": [] + }, + { + "type": "error", + "name": "Invalid4337ExecutionSelector", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidERC4337Flag", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidKeyFormat", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidPasskeyIndex", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidPasskeyPublicKeyLength", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidPasskeyPublicKeyLength", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidSignature", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidTimeValidity", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidWebAuthnSignatureLength", + "inputs": [] + }, + { + "type": "error", + "name": "MaxSessionCountReached", + "inputs": [] + }, + { + "type": "error", + "name": "OnlyEntryPoint", + "inputs": [] + }, + { + "type": "error", + "name": "OnlySelf", + "inputs": [] + }, + { + "type": "error", + "name": "OnlySelfOrEntryPoint", + "inputs": [] + }, + { + "type": "error", + "name": "PasskeyCannotCallSelf", + "inputs": [] + }, + { + "type": "error", + "name": "PasskeySessionExpired", + "inputs": [] + } +] \ No newline at end of file diff --git a/rust/tw_evm/src/ffi/barz.rs b/rust/tw_evm/src/ffi/barz.rs index c7280f58216..91433a7e6b9 100644 --- a/rust/tw_evm/src/ffi/barz.rs +++ b/rust/tw_evm/src/ffi/barz.rs @@ -5,9 +5,8 @@ #![allow(clippy::missing_safety_doc)] use crate::modules::barz::core::{ - get_authorization_hash, get_counterfactual_address, get_diamond_cut_code, get_encoded_hash, - get_formatted_signature, get_init_code, get_prefixed_msg_hash, sign_authorization, - sign_user_op_hash, + get_counterfactual_address, get_diamond_cut_code, get_formatted_signature, get_init_code, + get_prefixed_msg_hash, }; use tw_keypair::ffi::pubkey::{tw_public_key_data, TWPublicKey}; use tw_macros::tw_ffi; @@ -158,143 +157,3 @@ pub unsafe extern "C" fn tw_barz_get_diamond_cut_code( let diamond_cut_code = try_or_else!(get_diamond_cut_code(&input), std::ptr::null_mut); TWData::from(diamond_cut_code).into_ptr() } - -/// Computes an Authorization hash in [EIP-7702 format](https://eips.ethereum.org/EIPS/eip-7702) -/// `keccak256('0x05' || rlp([chain_id, address, nonce]))`. -/// -/// \param chain_id The chain ID of the user. -/// \param contract_address The address of the smart contract wallet. -/// \param nonce The nonce of the user. -/// \return The authorization hash. -#[tw_ffi(ty = static_function, class = TWBarz, name = GetAuthorizationHash)] -#[no_mangle] -pub unsafe extern "C" fn tw_barz_get_authorization_hash( - chain_id: Nonnull, - contract_address: Nonnull, - nonce: Nonnull, -) -> NullableMut { - let chain_id = try_or_else!(TWData::from_ptr_as_ref(chain_id), std::ptr::null_mut); - let contract_address = try_or_else!( - TWString::from_ptr_as_ref(contract_address), - std::ptr::null_mut - ); - let contract_address = try_or_else!(contract_address.as_str(), std::ptr::null_mut); - let nonce = try_or_else!(TWData::from_ptr_as_ref(nonce), std::ptr::null_mut); - let authorization_hash = try_or_else!( - get_authorization_hash(chain_id.as_slice(), contract_address, nonce.as_slice()), - std::ptr::null_mut - ); - TWData::from(authorization_hash).into_ptr() -} - -/// Returns the signed authorization hash -/// -/// \param chain_id The chain ID of the user. -/// \param contract_address The address of the smart contract wallet. -/// \param nonce The nonce of the user. -/// \param private_key The private key of the user. -/// \return The signed authorization. -#[tw_ffi(ty = static_function, class = TWBarz, name = SignAuthorization)] -#[no_mangle] -pub unsafe extern "C" fn tw_barz_sign_authorization( - chain_id: Nonnull, - contract_address: Nonnull, - nonce: Nonnull, - private_key: Nonnull, -) -> NullableMut { - let chain_id = try_or_else!(TWData::from_ptr_as_ref(chain_id), std::ptr::null_mut); - let contract_address = try_or_else!( - TWString::from_ptr_as_ref(contract_address), - std::ptr::null_mut - ); - let contract_address = try_or_else!(contract_address.as_str(), std::ptr::null_mut); - let nonce = try_or_else!(TWData::from_ptr_as_ref(nonce), std::ptr::null_mut); - let private_key = try_or_else!(TWString::from_ptr_as_ref(private_key), std::ptr::null_mut); - let private_key = try_or_else!(private_key.as_str(), std::ptr::null_mut); - let signed_authorization = try_or_else!( - sign_authorization( - chain_id.as_slice(), - contract_address, - nonce.as_slice(), - private_key - ), - std::ptr::null_mut - ); - TWString::from(signed_authorization).into_ptr() -} - -/// Returns the encoded hash of the user operation -/// -/// \param chain_id The chain ID of the user. -/// \param code_address The address of the smart contract wallet. -/// \param code_name The name of the smart contract wallet. -/// \param code_version The version of the smart contract wallet. -/// \param type_hash The type hash of the smart contract wallet. -/// \param domain_separator_hash The domain separator hash of the smart contract wallet. -/// \param sender The sender of the smart contract wallet. -/// \param user_op_hash The user operation hash of the smart contract wallet. -/// \return The encoded hash. -#[tw_ffi(ty = static_function, class = TWBarz, name = GetEncodedHash)] -#[no_mangle] -pub unsafe extern "C" fn tw_barz_get_encoded_hash( - chain_id: Nonnull, - code_address: Nonnull, - code_name: Nonnull, - code_version: Nonnull, - type_hash: Nonnull, - domain_separator_hash: Nonnull, - sender: Nonnull, - user_op_hash: Nonnull, -) -> NullableMut { - let chain_id = try_or_else!(TWData::from_ptr_as_ref(chain_id), std::ptr::null_mut); - let code_address = try_or_else!(TWString::from_ptr_as_ref(code_address), std::ptr::null_mut); - let code_address = try_or_else!(code_address.as_str(), std::ptr::null_mut); - let code_name = try_or_else!(TWString::from_ptr_as_ref(code_name), std::ptr::null_mut); - let code_name = try_or_else!(code_name.as_str(), std::ptr::null_mut); - let code_version = try_or_else!(TWString::from_ptr_as_ref(code_version), std::ptr::null_mut); - let code_version = try_or_else!(code_version.as_str(), std::ptr::null_mut); - let type_hash = try_or_else!(TWString::from_ptr_as_ref(type_hash), std::ptr::null_mut); - let type_hash = try_or_else!(type_hash.as_str(), std::ptr::null_mut); - let domain_separator_hash = try_or_else!( - TWString::from_ptr_as_ref(domain_separator_hash), - std::ptr::null_mut - ); - let domain_separator_hash = try_or_else!(domain_separator_hash.as_str(), std::ptr::null_mut); - let sender = try_or_else!(TWString::from_ptr_as_ref(sender), std::ptr::null_mut); - let sender = try_or_else!(sender.as_str(), std::ptr::null_mut); - let user_op_hash = try_or_else!(TWString::from_ptr_as_ref(user_op_hash), std::ptr::null_mut); - let user_op_hash = try_or_else!(user_op_hash.as_str(), std::ptr::null_mut); - let encoded_hash = try_or_else!( - get_encoded_hash( - chain_id.as_slice(), - code_address, - code_name, - code_version, - type_hash, - domain_separator_hash, - sender, - user_op_hash - ), - std::ptr::null_mut - ); - TWData::from(encoded_hash).into_ptr() -} - -/// Signs a message using the private key -/// -/// \param hash The hash of the user. -/// \param private_key The private key of the user. -/// \return The signed hash. -#[tw_ffi(ty = static_function, class = TWBarz, name = GetSignedHash)] -#[no_mangle] -pub unsafe extern "C" fn tw_barz_get_signed_hash( - hash: Nonnull, - private_key: Nonnull, -) -> NullableMut { - let hash = try_or_else!(TWString::from_ptr_as_ref(hash), std::ptr::null_mut); - let hash = try_or_else!(hash.as_str(), std::ptr::null_mut); - let private_key = try_or_else!(TWString::from_ptr_as_ref(private_key), std::ptr::null_mut); - let private_key = try_or_else!(private_key.as_str(), std::ptr::null_mut); - let signed_hash = try_or_else!(sign_user_op_hash(hash, private_key), std::ptr::null_mut); - TWData::from(signed_hash).into_ptr() -} diff --git a/rust/tw_evm/src/ffi/biz.rs b/rust/tw_evm/src/ffi/biz.rs new file mode 100644 index 00000000000..8b954f80cfb --- /dev/null +++ b/rust/tw_evm/src/ffi/biz.rs @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#![allow(clippy::missing_safety_doc)] + +use crate::modules::biz::{ + encode_execute_with_passkey_session_call, encode_passkey_nonce, + encode_register_passkey_session_call, encode_remove_passkey_session_call, get_encoded_hash, + sign_user_op_hash, +}; +use tw_keypair::ffi::pubkey::TWPublicKey; +use tw_macros::tw_ffi; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::tw_string::TWString; +use tw_memory::ffi::{Nonnull, NullableMut, RawPtrTrait}; +use tw_misc::try_or_else; +use tw_proto::deserialize; +use tw_proto::Biz::Proto::ExecuteWithPasskeySessionInput; + +/// Returns the encoded hash of the user operation +/// +/// \param chain_id The chain ID of the user. +/// \param code_address The address of the smart contract wallet. +/// \param code_name The name of the smart contract wallet. +/// \param code_version The version of the smart contract wallet. +/// \param type_hash The type hash of the smart contract wallet. +/// \param domain_separator_hash The domain separator hash of the smart contract wallet. +/// \param sender The sender of the smart contract wallet. +/// \param user_op_hash The user operation hash of the smart contract wallet. +/// \return The encoded hash. +#[tw_ffi(ty = static_function, class = TWBiz, name = GetEncodedHash)] +#[no_mangle] +pub unsafe extern "C" fn tw_biz_get_encoded_hash( + chain_id: Nonnull, + code_address: Nonnull, + code_name: Nonnull, + code_version: Nonnull, + type_hash: Nonnull, + domain_separator_hash: Nonnull, + sender: Nonnull, + user_op_hash: Nonnull, +) -> NullableMut { + let chain_id = try_or_else!(TWData::from_ptr_as_ref(chain_id), std::ptr::null_mut); + let code_address = try_or_else!(TWString::from_ptr_as_ref(code_address), std::ptr::null_mut); + let code_address = try_or_else!(code_address.as_str(), std::ptr::null_mut); + let code_name = try_or_else!(TWString::from_ptr_as_ref(code_name), std::ptr::null_mut); + let code_name = try_or_else!(code_name.as_str(), std::ptr::null_mut); + let code_version = try_or_else!(TWString::from_ptr_as_ref(code_version), std::ptr::null_mut); + let code_version = try_or_else!(code_version.as_str(), std::ptr::null_mut); + let type_hash = try_or_else!(TWString::from_ptr_as_ref(type_hash), std::ptr::null_mut); + let type_hash = try_or_else!(type_hash.as_str(), std::ptr::null_mut); + let domain_separator_hash = try_or_else!( + TWString::from_ptr_as_ref(domain_separator_hash), + std::ptr::null_mut + ); + let domain_separator_hash = try_or_else!(domain_separator_hash.as_str(), std::ptr::null_mut); + let sender = try_or_else!(TWString::from_ptr_as_ref(sender), std::ptr::null_mut); + let sender = try_or_else!(sender.as_str(), std::ptr::null_mut); + let user_op_hash = try_or_else!(TWString::from_ptr_as_ref(user_op_hash), std::ptr::null_mut); + let user_op_hash = try_or_else!(user_op_hash.as_str(), std::ptr::null_mut); + let encoded_hash = try_or_else!( + get_encoded_hash( + chain_id.as_slice(), + code_address, + code_name, + code_version, + type_hash, + domain_separator_hash, + sender, + user_op_hash + ), + std::ptr::null_mut + ); + TWData::from(encoded_hash).into_ptr() +} + +/// Signs a message using the private key +/// +/// \param hash The hash of the user. +/// \param private_key The private key of the user. +/// \return The signed hash. +#[tw_ffi(ty = static_function, class = TWBiz, name = GetSignedHash)] +#[no_mangle] +pub unsafe extern "C" fn tw_biz_get_signed_hash( + hash: Nonnull, + private_key: Nonnull, +) -> NullableMut { + let hash = try_or_else!(TWString::from_ptr_as_ref(hash), std::ptr::null_mut); + let hash = try_or_else!(hash.as_str(), std::ptr::null_mut); + let private_key = try_or_else!(TWString::from_ptr_as_ref(private_key), std::ptr::null_mut); + let private_key = try_or_else!(private_key.as_str(), std::ptr::null_mut); + let signed_hash = try_or_else!(sign_user_op_hash(hash, private_key), std::ptr::null_mut); + TWData::from(signed_hash).into_ptr() +} + +/// Encodes `Biz.registerSession` function call to register a session passkey public key. +/// +/// \param session_passkey_public_key The nist256p1 (aka secp256p1) public key of the session passkey. +/// \param valid_until_timestamp The timestamp until which the session is valid. Big endian uint64. +/// \return ABI-encoded function call. +#[tw_ffi(ty = static_function, class = TWBiz, name = EncodeRegisterSessionCall)] +#[no_mangle] +pub unsafe extern "C" fn tw_biz_encode_register_session_call( + session_passkey_public_key: Nonnull, + valid_until_timestamp: Nonnull, +) -> NullableMut { + let session_passkey_public_key = try_or_else!( + TWPublicKey::from_ptr_as_ref(session_passkey_public_key), + std::ptr::null_mut + ); + let valid_until_timestamp = try_or_else!( + TWData::from_ptr_as_ref(valid_until_timestamp), + std::ptr::null_mut + ); + let encoded = try_or_else!( + encode_register_passkey_session_call( + session_passkey_public_key.as_ref(), + valid_until_timestamp.as_slice() + ), + std::ptr::null_mut + ); + TWData::from(encoded).into_ptr() +} + +/// Encodes `Biz.removeSession` function call to deregister a session passkey public key. +/// +/// \param session_passkey_public_key The nist256p1 (aka secp256p1) public key of the session passkey. +/// \return ABI-encoded function call. +#[tw_ffi(ty = static_function, class = TWBiz, name = EncodeRemoveSessionCall)] +#[no_mangle] +pub unsafe extern "C" fn tw_biz_encode_remove_session_call( + session_passkey_public_key: Nonnull, +) -> NullableMut { + let session_passkey_public_key = try_or_else!( + TWPublicKey::from_ptr_as_ref(session_passkey_public_key), + std::ptr::null_mut + ); + let encoded = try_or_else!( + encode_remove_passkey_session_call(session_passkey_public_key.as_ref(),), + std::ptr::null_mut + ); + TWData::from(encoded).into_ptr() +} + +/// Encodes Biz Passkey Session nonce. +/// +/// \param nonce The nonce of the Biz Passkey Session account. +/// \return uint256 represented as [passkey_nonce_key_192, nonce_64]. +#[tw_ffi(ty = static_function, class = TWBiz, name = EncodePasskeySessionNonce)] +#[no_mangle] +pub unsafe extern "C" fn tw_biz_encode_passkey_session_nonce( + nonce: Nonnull, +) -> NullableMut { + let nonce = try_or_else!(TWData::from_ptr_as_ref(nonce), std::ptr::null_mut); + let encoded = try_or_else!(encode_passkey_nonce(nonce.as_slice()), std::ptr::null_mut); + TWData::from(encoded).into_ptr() +} + +/// Encodes `Biz.executeWithPasskeySession` function call to execute a batch of transactions. +/// +/// \param input The serialized data of `Biz.ExecuteWithPasskeySessionInput` protobuf message. +/// \return ABI-encoded function call. +#[tw_ffi(ty = static_function, class = TWBiz, name = EncodeExecuteWithPasskeySessionCall)] +#[no_mangle] +pub unsafe extern "C" fn tw_biz_encode_execute_with_passkey_session_call( + input: Nonnull, +) -> NullableMut { + let input = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let input: ExecuteWithPasskeySessionInput = deserialize(input.as_slice()).unwrap(); + let encoded = try_or_else!( + encode_execute_with_passkey_session_call(&input), + std::ptr::null_mut + ); + TWData::from(encoded).into_ptr() +} diff --git a/rust/tw_evm/src/ffi/eip7702.rs b/rust/tw_evm/src/ffi/eip7702.rs new file mode 100644 index 00000000000..3e0722aac25 --- /dev/null +++ b/rust/tw_evm/src/ffi/eip7702.rs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#![allow(clippy::missing_safety_doc)] + +use crate::modules::eip7702::{get_authorization_hash, sign_authorization}; +use tw_macros::tw_ffi; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::tw_string::TWString; +use tw_memory::ffi::{Nonnull, NullableMut, RawPtrTrait}; +use tw_misc::try_or_else; + +/// Signs an Authorization hash in [EIP-7702 format](https://eips.ethereum.org/EIPS/eip-7702) +/// +/// \param chain_id The chain ID of the user. +/// \param contract_address The address of the smart contract wallet. +/// \param nonce The nonce of the user. +/// \param private_key The private key of the user. +/// \return The signed authorization. +#[tw_ffi(ty = static_function, class = TWEip7702, name = SignAuthorization)] +#[no_mangle] +pub unsafe extern "C" fn tw_eip7702_sign_authorization( + chain_id: Nonnull, + contract_address: Nonnull, + nonce: Nonnull, + private_key: Nonnull, +) -> NullableMut { + let chain_id = try_or_else!(TWData::from_ptr_as_ref(chain_id), std::ptr::null_mut); + let contract_address = try_or_else!( + TWString::from_ptr_as_ref(contract_address), + std::ptr::null_mut + ); + let contract_address = try_or_else!(contract_address.as_str(), std::ptr::null_mut); + let nonce = try_or_else!(TWData::from_ptr_as_ref(nonce), std::ptr::null_mut); + let private_key = try_or_else!(TWString::from_ptr_as_ref(private_key), std::ptr::null_mut); + let private_key = try_or_else!(private_key.as_str(), std::ptr::null_mut); + let signed_authorization = try_or_else!( + sign_authorization( + chain_id.as_slice(), + contract_address, + nonce.as_slice(), + private_key + ), + std::ptr::null_mut + ); + TWString::from(signed_authorization).into_ptr() +} + +/// Computes an Authorization hash in [EIP-7702 format](https://eips.ethereum.org/EIPS/eip-7702) +/// `keccak256('0x05' || rlp([chain_id, address, nonce]))`. +/// +/// \param chain_id The chain ID of the user. +/// \param contract_address The address of the smart contract wallet. +/// \param nonce The nonce of the user. +/// \return The authorization hash. +#[tw_ffi(ty = static_function, class = TWEip7702, name = GetAuthorizationHash)] +#[no_mangle] +pub unsafe extern "C" fn tw_eip7702_get_authorization_hash( + chain_id: Nonnull, + contract_address: Nonnull, + nonce: Nonnull, +) -> NullableMut { + let chain_id = try_or_else!(TWData::from_ptr_as_ref(chain_id), std::ptr::null_mut); + let contract_address = try_or_else!( + TWString::from_ptr_as_ref(contract_address), + std::ptr::null_mut + ); + let contract_address = try_or_else!(contract_address.as_str(), std::ptr::null_mut); + let nonce = try_or_else!(TWData::from_ptr_as_ref(nonce), std::ptr::null_mut); + let authorization_hash = try_or_else!( + get_authorization_hash(chain_id.as_slice(), contract_address, nonce.as_slice()), + std::ptr::null_mut + ); + TWData::from(authorization_hash).into_ptr() +} diff --git a/rust/tw_evm/src/ffi/mod.rs b/rust/tw_evm/src/ffi/mod.rs index 9271ddea0d7..64de96fa2c2 100644 --- a/rust/tw_evm/src/ffi/mod.rs +++ b/rust/tw_evm/src/ffi/mod.rs @@ -3,4 +3,7 @@ // Copyright © 2017 Trust Wallet. pub mod barz; +pub mod biz; +pub mod eip7702; pub mod ethereum_address; +pub mod webauthn_solidity; diff --git a/rust/tw_evm/src/ffi/webauthn_solidity.rs b/rust/tw_evm/src/ffi/webauthn_solidity.rs new file mode 100644 index 00000000000..8087d7ebc7b --- /dev/null +++ b/rust/tw_evm/src/ffi/webauthn_solidity.rs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#![allow(clippy::missing_safety_doc)] + +use crate::modules::webauthn::{get_formatted_webauthn_signature, get_webauthn_message_hash}; +use tw_macros::tw_ffi; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::tw_string::TWString; +use tw_memory::ffi::{Nonnull, NullableMut, RawPtrTrait}; +use tw_misc::try_or_else; + +/// Computes WebAuthn message hash to be signed with secp256p1 private key. +/// +/// \param authenticator_data The authenticator data in hex format. +/// \param client_data_json The client data JSON string with a challenge. +/// \return WebAuthn message hash. +#[tw_ffi(ty = static_function, class = TWWebAuthnSolidity, name = GetMessageHash)] +#[no_mangle] +pub unsafe extern "C" fn tw_webauthn_solidity_get_message_hash( + authenticator_data: Nonnull, + client_data_json: Nonnull, +) -> NullableMut { + let authenticator_data = try_or_else!( + TWString::from_ptr_as_ref(authenticator_data), + std::ptr::null_mut + ); + let authenticator_data = try_or_else!(authenticator_data.as_str(), std::ptr::null_mut); + + let client_data_json = try_or_else!( + TWString::from_ptr_as_ref(client_data_json), + std::ptr::null_mut + ); + let client_data_json = try_or_else!(client_data_json.as_str(), std::ptr::null_mut); + + let message_hash = try_or_else!( + get_webauthn_message_hash(authenticator_data, client_data_json), + std::ptr::null_mut + ); + TWData::from(message_hash).into_ptr() +} + +/// Converts the original ASN-encoded signature from webauthn to the format accepted by Barz +/// +/// \param authenticator_data The authenticator data in hex format. +/// \param client_data_json The client data JSON string with a challenge. +/// \param der_signature original ASN-encoded signature from webauthn. +/// \return WebAuthn ABI-encoded data. +#[tw_ffi(ty = static_function, class = TWWebAuthnSolidity, name = GetFormattedSignature)] +#[no_mangle] +pub unsafe extern "C" fn tw_webauthn_solidity_get_formatted_signature( + authenticator_data: Nonnull, + client_data_json: Nonnull, + der_signature: Nonnull, +) -> NullableMut { + let authenticator_data = try_or_else!( + TWString::from_ptr_as_ref(authenticator_data), + std::ptr::null_mut + ); + let authenticator_data = try_or_else!(authenticator_data.as_str(), std::ptr::null_mut); + + let client_data_json = try_or_else!( + TWString::from_ptr_as_ref(client_data_json), + std::ptr::null_mut + ); + let client_data_json = try_or_else!(client_data_json.as_str(), std::ptr::null_mut); + let der_signature = try_or_else!(TWData::from_ptr_as_ref(der_signature), std::ptr::null_mut); + let der_signature = der_signature.as_slice(); + + let encoded = try_or_else!( + get_formatted_webauthn_signature(authenticator_data, client_data_json, der_signature), + std::ptr::null_mut + ); + TWData::from(encoded).into_ptr() +} diff --git a/rust/tw_evm/src/modules/barz/core.rs b/rust/tw_evm/src/modules/barz/core.rs index ea1b84be951..9b993775b72 100644 --- a/rust/tw_evm/src/modules/barz/core.rs +++ b/rust/tw_evm/src/modules/barz/core.rs @@ -2,6 +2,7 @@ // // Copyright © 2017 Trust Wallet. +use super::error::BarzResult; use crate::abi::function::Function; use crate::abi::non_empty_array::NonEmptyBytes; use crate::abi::param::Param; @@ -9,28 +10,23 @@ use crate::abi::param_type::ParamType; use crate::abi::uint::UintBits; use crate::abi::{encode, token::Token}; use crate::address::Address; -use crate::message::EthMessage; -use crate::transaction::authorization_list::{Authorization, SignedAuthorization}; use std::str::FromStr; use tw_encoding::{base64, hex}; use tw_hash::sha3::keccak256; use tw_hash::H256; use tw_keypair::ecdsa::der; -use tw_keypair::ecdsa::secp256k1::PrivateKey; -use tw_keypair::traits::SigningKeyTrait; use tw_misc::traits::ToBytesVec; use tw_number::U256; use tw_proto::Barz::Proto::{ContractAddressInput, DiamondCutInput}; -use super::error::BarzResult; - -const BARZ_DOMAIN_SEPARATOR_TYPE_HASH: &str = +pub const BARZ_SIGNED_DATA_PREFIX: &str = "1901"; +pub const BARZ_DOMAIN_SEPARATOR_TYPE_HASH: &str = "47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218"; -const BARZ_MSG_HASH_DATA: &str = "b1bcb804a4a3a1af3ee7920d949bdfd417ea1b736c3552c8d6563a229a619100"; -const BARZ_SIGNED_DATA_PREFIX: &str = "1901"; +pub const BARZ_MSG_HASH_DATA: &str = + "b1bcb804a4a3a1af3ee7920d949bdfd417ea1b736c3552c8d6563a229a619100"; -const BARZ_DIAMOND_CUT_SELECTOR: &str = "1f931c1c"; -const BARZ_DATA_LOCATION_CHUNK: &str = "60"; +pub const BARZ_DIAMOND_CUT_SELECTOR: &str = "1f931c1c"; +pub const BARZ_DATA_LOCATION_CHUNK: &str = "60"; pub fn get_counterfactual_address(input: &ContractAddressInput) -> BarzResult { let encoded_data = encode::encode_tokens(&[ @@ -287,107 +283,3 @@ pub fn get_diamond_cut_code(input: &DiamondCutInput) -> BarzResult> { encoded.extend_from_slice(&padding); Ok(encoded) } - -#[allow(clippy::too_many_arguments)] -pub fn get_encoded_hash( - chain_id: &[u8], - code_address: &str, - code_name: &str, - code_version: &str, - type_hash: &str, - domain_separator_hash: &str, - sender: &str, - user_op_hash: &str, -) -> BarzResult> { - // Create code_address_bytes32 by padding with zeros - let mut code_address_bytes32 = vec![0u8; 12]; - code_address_bytes32.extend_from_slice(&hex::decode(code_address.trim_start_matches("0x"))?); - - // Create domain separator - let tokens = vec![ - Token::FixedBytes(NonEmptyBytes::new(hex::decode( - domain_separator_hash.trim_start_matches("0x"), - )?)?), - Token::FixedBytes(NonEmptyBytes::new( - keccak256(code_name.as_bytes()).to_vec(), - )?), - Token::FixedBytes(NonEmptyBytes::new( - keccak256(code_version.as_bytes()).to_vec(), - )?), - Token::Uint { - uint: U256::from_big_endian_slice(chain_id)?, - bits: UintBits::default(), - }, - Token::Address(Address::from_str(sender)?), - Token::FixedBytes(NonEmptyBytes::new(code_address_bytes32)?), - ]; - - let domain_separator = encode::encode_tokens(&tokens); - let domain_separator_encoded_hash = keccak256(&domain_separator); - - // Create message hash - let mut message_to_hash = Vec::new(); - message_to_hash.extend_from_slice(&hex::decode(type_hash)?); - message_to_hash.extend_from_slice(&hex::decode(user_op_hash)?); - let message_hash = keccak256(&message_to_hash); - - // Create final encoded hash - let mut encoded = Vec::new(); - encoded.extend_from_slice(&hex::decode(BARZ_SIGNED_DATA_PREFIX)?); - encoded.extend_from_slice(&domain_separator_encoded_hash); - encoded.extend_from_slice(&message_hash); - - Ok(keccak256(&encoded)) -} - -pub fn sign_user_op_hash(hash: &str, private_key: &str) -> BarzResult> { - let private_key = PrivateKey::try_from(private_key)?; - let message = H256::from_str(hash)?; - let signature = private_key.sign(message)?; - - let mut result = signature.to_vec(); - // v value (last byte, should be 0 or 1, add 27 to make it 27 or 28) - let v_value = result[64] + 27; - result[64] = v_value; - - Ok(result) -} - -pub fn get_authorization_hash( - chain_id: &[u8], - contract_address: &str, - nonce: &[u8], -) -> BarzResult> { - let authorization = Authorization { - chain_id: U256::from_big_endian_slice(chain_id)?, - address: Address::from_str(contract_address)?, - nonce: U256::from_big_endian_slice(nonce)?, - }; - - Ok(authorization.hash()?.to_vec()) -} - -pub fn sign_authorization( - chain_id: &[u8], - contract_address: &str, - nonce: &[u8], - private_key: &str, -) -> BarzResult { - let authorization = Authorization { - chain_id: U256::from_big_endian_slice(chain_id)?, - address: Address::from_str(contract_address)?, - nonce: U256::from_big_endian_slice(nonce)?, - }; - let authorization_hash = authorization.hash()?; - let private_key = PrivateKey::try_from(private_key)?; - let signature = private_key.sign(authorization_hash)?; - - let signed_authorization = SignedAuthorization { - authorization, - y_parity: signature.v(), - r: U256::from_big_endian(signature.r()), - s: U256::from_big_endian(signature.s()), - }; - - Ok(serde_json::to_string(&signed_authorization)?) -} diff --git a/rust/tw_evm/src/modules/barz/error.rs b/rust/tw_evm/src/modules/barz/error.rs index 2e6424f6d32..c7576584741 100644 --- a/rust/tw_evm/src/modules/barz/error.rs +++ b/rust/tw_evm/src/modules/barz/error.rs @@ -14,6 +14,7 @@ pub type BarzResult = Result; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum BarzError { InvalidInput, + InvalidPublicKey, AddressError, FromHexError, AbiError, diff --git a/rust/tw_evm/src/modules/biz.rs b/rust/tw_evm/src/modules/biz.rs new file mode 100644 index 00000000000..24cd20f4f82 --- /dev/null +++ b/rust/tw_evm/src/modules/biz.rs @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::abi::encode; +use crate::abi::non_empty_array::NonEmptyBytes; +use crate::abi::prebuild::biz_passkey_session::BizPasskeySessionAccount; +use crate::abi::prebuild::ExecuteArgs; +use crate::abi::token::Token; +use crate::abi::uint::UintBits; +use crate::address::Address; +use crate::modules::barz::core::BARZ_SIGNED_DATA_PREFIX; +use crate::modules::barz::error::{BarzError, BarzResult}; +use std::str::FromStr; +use tw_encoding::hex; +use tw_hash::sha3::keccak256; +use tw_hash::H256; +use tw_keypair::ecdsa::secp256k1::PrivateKey; +use tw_keypair::traits::SigningKeyTrait; +use tw_keypair::tw; +use tw_memory::Data; +use tw_misc::traits::ToBytesVec; +use tw_number::U256; +use tw_proto::Biz::Proto::ExecuteWithPasskeySessionInput; + +const BIZ_PASSKEY_USEROP_VALIDATION_NONCE_KEY: u64 = 360_360_360_360_360_360_u64; + +#[allow(clippy::too_many_arguments)] +pub fn get_encoded_hash( + chain_id: &[u8], + code_address: &str, + code_name: &str, + code_version: &str, + type_hash: &str, + domain_separator_hash: &str, + sender: &str, + user_op_hash: &str, +) -> BarzResult> { + // Create code_address_bytes32 by padding with zeros + let mut code_address_bytes32 = vec![0u8; 12]; + code_address_bytes32.extend_from_slice(&hex::decode(code_address.trim_start_matches("0x"))?); + + // Create domain separator + let tokens = vec![ + Token::FixedBytes(NonEmptyBytes::new(hex::decode( + domain_separator_hash.trim_start_matches("0x"), + )?)?), + Token::FixedBytes(NonEmptyBytes::new( + keccak256(code_name.as_bytes()).to_vec(), + )?), + Token::FixedBytes(NonEmptyBytes::new( + keccak256(code_version.as_bytes()).to_vec(), + )?), + Token::Uint { + uint: U256::from_big_endian_slice(chain_id)?, + bits: UintBits::default(), + }, + Token::Address(Address::from_str(sender)?), + Token::FixedBytes(NonEmptyBytes::new(code_address_bytes32)?), + ]; + + let domain_separator = encode::encode_tokens(&tokens); + let domain_separator_encoded_hash = keccak256(&domain_separator); + + // Create message hash + let mut message_to_hash = Vec::new(); + message_to_hash.extend_from_slice(&hex::decode(type_hash)?); + message_to_hash.extend_from_slice(&hex::decode(user_op_hash)?); + let message_hash = keccak256(&message_to_hash); + + // Create final encoded hash + let mut encoded = Vec::new(); + encoded.extend_from_slice(&hex::decode(BARZ_SIGNED_DATA_PREFIX)?); + encoded.extend_from_slice(&domain_separator_encoded_hash); + encoded.extend_from_slice(&message_hash); + + Ok(keccak256(&encoded)) +} + +pub fn sign_user_op_hash(hash: &str, private_key: &str) -> BarzResult> { + let private_key = PrivateKey::try_from(private_key)?; + let message = H256::from_str(hash)?; + let signature = private_key.sign(message)?; + + let mut result = signature.to_vec(); + // v value (last byte, should be 0 or 1, add 27 to make it 27 or 28) + let v_value = result[64] + 27; + result[64] = v_value; + + Ok(result) +} + +pub fn encode_register_passkey_session_call( + session_passkey_public_key: &tw::PublicKey, + valid_until_timestamp: &[u8], +) -> BarzResult { + let session_passkey_public_key = session_passkey_public_key + .to_nist256p1() + .ok_or(BarzError::InvalidPublicKey)? + .uncompressed(); + let valid_until = U256::from_big_endian_slice(valid_until_timestamp)?; + Ok(BizPasskeySessionAccount::register_session( + session_passkey_public_key, + valid_until, + )?) +} + +pub fn encode_remove_passkey_session_call( + session_passkey_public_key: &tw::PublicKey, +) -> BarzResult { + let session_passkey_public_key = session_passkey_public_key + .to_nist256p1() + .ok_or(BarzError::InvalidPublicKey)? + .uncompressed(); + Ok(BizPasskeySessionAccount::remove_session( + session_passkey_public_key, + )?) +} + +pub fn encode_passkey_nonce(nonce: &[u8]) -> BarzResult { + let nonce = U256::from_big_endian_slice(nonce)?; + // Check if nonce fits in u64. + u64::try_from(nonce)?; + + let passkey_nonce_key = U256::from(BIZ_PASSKEY_USEROP_VALIDATION_NONCE_KEY) << 64; + let passkey_nonce = passkey_nonce_key | nonce; + Ok(passkey_nonce.to_big_endian().to_vec()) +} + +pub fn encode_execute_with_passkey_session_call( + input: &ExecuteWithPasskeySessionInput, +) -> BarzResult { + let executions: Vec<_> = input + .executions + .iter() + .map(|exec| { + Ok(ExecuteArgs { + to: Address::from_str(&exec.address)?, + value: U256::from_big_endian_slice(&exec.amount)?, + data: exec.payload.to_vec(), + }) + }) + .collect::>()?; + + let valid_after = U256::from(input.valid_after); + let valid_until = U256::from(input.valid_until); + let validity_timestamp = (valid_until << 128) | valid_after; + let signature = input.passkey_signature.to_vec(); + Ok(BizPasskeySessionAccount::execute_with_passkey_session( + executions, + validity_timestamp, + signature, + )?) +} diff --git a/rust/tw_evm/src/modules/eip7702.rs b/rust/tw_evm/src/modules/eip7702.rs new file mode 100644 index 00000000000..fdf61260755 --- /dev/null +++ b/rust/tw_evm/src/modules/eip7702.rs @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::Address; +use crate::message::EthMessage; +use crate::modules::barz::error::BarzResult; +use crate::transaction::authorization_list::{Authorization, SignedAuthorization}; +use std::str::FromStr; +use tw_keypair::ecdsa::secp256k1::PrivateKey; +use tw_keypair::traits::SigningKeyTrait; +use tw_number::U256; + +pub fn get_authorization_hash( + chain_id: &[u8], + contract_address: &str, + nonce: &[u8], +) -> BarzResult> { + let authorization = Authorization { + chain_id: U256::from_big_endian_slice(chain_id)?, + address: Address::from_str(contract_address)?, + nonce: U256::from_big_endian_slice(nonce)?, + }; + + Ok(authorization.hash()?.to_vec()) +} + +pub fn sign_authorization( + chain_id: &[u8], + contract_address: &str, + nonce: &[u8], + private_key: &str, +) -> BarzResult { + let authorization = Authorization { + chain_id: U256::from_big_endian_slice(chain_id)?, + address: Address::from_str(contract_address)?, + nonce: U256::from_big_endian_slice(nonce)?, + }; + let authorization_hash = authorization.hash()?; + let private_key = PrivateKey::try_from(private_key)?; + let signature = private_key.sign(authorization_hash)?; + + let signed_authorization = SignedAuthorization { + authorization, + y_parity: signature.v(), + r: U256::from_big_endian(signature.r()), + s: U256::from_big_endian(signature.s()), + }; + + Ok(serde_json::to_string(&signed_authorization)?) +} diff --git a/rust/tw_evm/src/modules/mod.rs b/rust/tw_evm/src/modules/mod.rs index 49a56aa1a12..7b9a1ca6aec 100644 --- a/rust/tw_evm/src/modules/mod.rs +++ b/rust/tw_evm/src/modules/mod.rs @@ -4,9 +4,12 @@ pub mod abi_encoder; pub mod barz; +pub mod biz; pub mod compiler; +pub mod eip7702; pub mod message_signer; pub mod rlp_encoder; pub mod signer; pub mod transaction_util; pub mod tx_builder; +pub mod webauthn; diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs index 33a32e37d39..c7a1ccc9a57 100644 --- a/rust/tw_evm/src/modules/tx_builder.rs +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -569,14 +569,16 @@ impl TxBuilder { // Destination address is not used when generating UserOperation. (TxMode::UserOp, SCWalletType::SimpleAccount | SCWalletType::Biz4337) => Ok(None), (TxMode::UserOp, _) => SigningError::err(SigningErrorType::Error_invalid_params) - .context("Biz account cannot be used in UserOperation flow"), + .context( + "Biz account cannot be used in UserOperation flow. Consider using Biz4337 instead", + ), (TxMode::Legacy | TxMode::Enveloped | TxMode::SetCode, SCWalletType::Biz) => { Self::signer_address(input).map(Some) }, (TxMode::Legacy | TxMode::Enveloped | TxMode::SetCode, _) => SigningError::err( SigningErrorType::Error_invalid_params, ) - .context("Biz account can only be used in Legacy/Enveloped/SetCode transactions flow"), + .context("Biz account can only be used in Legacy/Enveloped/SetCode transactions flow. Consider using Biz4337 instead"), } } diff --git a/rust/tw_evm/src/modules/webauthn.rs b/rust/tw_evm/src/modules/webauthn.rs new file mode 100644 index 00000000000..61f3b828b2e --- /dev/null +++ b/rust/tw_evm/src/modules/webauthn.rs @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::abi::encode; +use crate::abi::token::Token; +use crate::modules::barz::error::{BarzError, BarzResult}; +use std::str::FromStr; +use tw_encoding::hex; +use tw_hash::sha2::sha256; +use tw_hash::H256; +use tw_keypair::ecdsa::{der, nist256p1}; +use tw_memory::Data; +use tw_number::U256; + +pub fn get_webauthn_message_hash( + authenticator_data: &str, + client_data_json: &str, +) -> BarzResult { + WebAuthnMessage::new(authenticator_data, client_data_json.to_string()) + .map(|m| m.message_hash.to_vec()) +} + +pub fn get_formatted_webauthn_signature( + authenticator_data: &str, + client_data_json: &str, + der_signature: &[u8], +) -> BarzResult { + let signature = der::Signature::from_bytes(der_signature)?; + let signature = nist256p1::VerifySignature::from_der(signature)?; + let (r, s) = signature.rs(); + let normalized_s = NormalizedS::new(U256::from_big_endian(s)); + + let webauthn_message = WebAuthnMessage::new(authenticator_data, client_data_json.to_string())?; + let webauthn_auth = + WebAuthnAuth::new(webauthn_message, U256::from_big_endian(r), normalized_s)?; + + Ok(webauthn_auth.abi_encode()) +} + +pub struct WebAuthnMessage { + pub authenticator_data: Data, + pub client_data_json: String, + pub message_hash: H256, +} + +impl WebAuthnMessage { + pub fn new(authenticator_data: &str, client_data_json: String) -> BarzResult { + let authenticator_data = hex::decode(authenticator_data)?; + let client_data_json_hash = sha256(client_data_json.as_bytes()); + let mut message_to_hash = Vec::new(); + message_to_hash.extend_from_slice(&authenticator_data); + message_to_hash.extend_from_slice(&client_data_json_hash); + let message_hash = + H256::try_from(sha256(&message_to_hash).as_slice()).expect("sha256 output is 32 bytes"); + + Ok(WebAuthnMessage { + authenticator_data, + client_data_json, + message_hash, + }) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct NormalizedS(U256); + +impl AsRef for NormalizedS { + fn as_ref(&self) -> &U256 { + &self.0 + } +} + +impl NormalizedS { + pub const FCL_ELLIPTIC_ZZ_STR: &'static str = + "0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"; + + pub fn fcl_elliptic_zz() -> U256 { + U256::from_str(Self::FCL_ELLIPTIC_ZZ_STR).expect("Invalid FCL_Elliptic_ZZ.n value") + } + + pub fn p256_n_div_2() -> U256 { + Self::fcl_elliptic_zz() / 2 + } + + pub fn new(s: U256) -> Self { + if s > Self::p256_n_div_2() { + return NormalizedS(Self::fcl_elliptic_zz() - s); + } + NormalizedS(s) + } +} + +pub struct WebAuthnAuth { + /// The WebAuthn authenticator data. + /// https://www.w3.org/TR/webauthn-2/#dom-authenticatorassertionresponse-authenticatordata. + pub authenticator_data: Data, + /// The WebAuthn client data JSON. + /// https://www.w3.org/TR/webauthn-2/#dom-authenticatorresponse-clientdatajson. + pub client_data_json: String, + /// The index at which "challenge":"..." occurs in `clientDataJSON`. + pub challenge_index: U256, + /// The index at which "type":"..." occurs in `clientDataJSON`. + pub type_index: U256, + /// The r value of secp256r1 signature. + pub r: U256, + /// The s value of secp256r1 signature. + pub s: NormalizedS, +} + +impl WebAuthnAuth { + pub fn new(message: WebAuthnMessage, r: U256, s: NormalizedS) -> BarzResult { + let challenge_index = find_json_key_index(&message.client_data_json, "challenge")?; + let type_index = find_json_key_index(&message.client_data_json, "type")?; + Ok(WebAuthnAuth { + authenticator_data: message.authenticator_data, + client_data_json: message.client_data_json, + challenge_index: U256::from(challenge_index), + type_index: U256::from(type_index), + r, + s, + }) + } + + pub fn abi_encode(&self) -> Data { + encode::encode_tuple(vec![ + Token::Bytes(self.authenticator_data.clone()), + Token::String(self.client_data_json.clone()), + Token::u256(self.challenge_index), + Token::u256(self.type_index), + Token::u256(self.r), + Token::u256(self.s.0), + ]) + } +} + +fn find_json_key_index(json: &str, key: &str) -> BarzResult { + let key_pattern = format!(r#""{}""#, key); + json.find(&key_pattern).ok_or(BarzError::InvalidInput) +} diff --git a/rust/tw_evm/tests/barz.rs b/rust/tw_evm/tests/barz.rs index c24927365ef..20e8ecc463c 100644 --- a/rust/tw_evm/tests/barz.rs +++ b/rust/tw_evm/tests/barz.rs @@ -855,83 +855,3 @@ fn test_get_diamond_cut_code_with_long_init_data() { let diamond_cut_code = get_diamond_cut_code(&input).unwrap(); assert_eq!(hex::encode(diamond_cut_code, true), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4b61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b9060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); } - -#[test] -fn test_get_authorization_hash() { - let chain_id = U256::from(1u64).to_big_endian(); - let contract_address = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; - let nonce = U256::from(1u64).to_big_endian(); - - let authorization_hash = - get_authorization_hash(&chain_id[..], contract_address, &nonce[..]).unwrap(); - assert_eq!( - hex::encode(authorization_hash, true), - "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9" - ); // Verified with viem -} - -#[test] -fn test_sign_authorization() { - let chain_id = U256::from(1u64).to_big_endian_compact(); - let contract_address = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; - let nonce = U256::from(1u64).to_big_endian_compact(); - let private_key = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; - - let signed_authorization = - sign_authorization(&chain_id[..], contract_address, &nonce[..], private_key).unwrap(); - let json: serde_json::Value = serde_json::from_str(&signed_authorization).unwrap(); - - // Verified with viem - assert_eq!( - json["chainId"].as_str().unwrap(), - hex::encode(chain_id, true) - ); - assert_eq!(json["address"].as_str().unwrap(), contract_address); - assert_eq!(json["nonce"].as_str().unwrap(), hex::encode(nonce, true)); - assert_eq!(json["yParity"].as_str().unwrap(), hex::encode(&[1u8], true)); - assert_eq!( - json["r"].as_str().unwrap(), - "0x2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710" - ); - assert_eq!( - json["s"].as_str().unwrap(), - "0x5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c" - ); -} - -#[test] -fn test_get_encoded_hash() { - let chain_id = U256::from(31337u64).to_big_endian(); - let code_address = "0x2e234DAe75C793f67A35089C9d99245E1C58470b"; - let code_name = "Biz"; - let code_version = "v1.0.0"; - let type_hash = "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867"; - let domain_separator_hash = - "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472"; - let sender = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"; - let user_op_hash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb"; - - let encoded_hash = get_encoded_hash( - &chain_id[..], - code_address, - code_name, - code_version, - type_hash, - domain_separator_hash, - sender, - user_op_hash, - ) - .unwrap(); - assert_eq!( - hex::encode(encoded_hash, true), - "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635" - ); -} - -#[test] -fn test_sign_user_op_hash() { - let hash = "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635"; - let private_key = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; - let signed_hash = sign_user_op_hash(hash, private_key).unwrap(); - assert_eq!(hex::encode(signed_hash, true), "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c"); -} diff --git a/rust/tw_evm/tests/barz_ffi.rs b/rust/tw_evm/tests/barz_ffi.rs index 433312ed458..e6e0ba3275d 100644 --- a/rust/tw_evm/tests/barz_ffi.rs +++ b/rust/tw_evm/tests/barz_ffi.rs @@ -4,14 +4,11 @@ use tw_encoding::hex; use tw_evm::ffi::barz::{ - tw_barz_get_authorization_hash, tw_barz_get_counterfactual_address, - tw_barz_get_diamond_cut_code, tw_barz_get_encoded_hash, tw_barz_get_formatted_signature, - tw_barz_get_init_code, tw_barz_get_prefixed_msg_hash, tw_barz_get_signed_hash, - tw_barz_sign_authorization, + tw_barz_get_counterfactual_address, tw_barz_get_diamond_cut_code, + tw_barz_get_formatted_signature, tw_barz_get_init_code, tw_barz_get_prefixed_msg_hash, }; use tw_keypair::{test_utils::tw_public_key_helper::TWPublicKeyHelper, tw::PublicKeyType}; use tw_memory::test_utils::{tw_data_helper::TWDataHelper, tw_string_helper::TWStringHelper}; -use tw_number::U256; use tw_proto::{ serialize, Barz::Proto::{ContractAddressInput, DiamondCutInput, FacetCut, FacetCutAction}, @@ -218,121 +215,3 @@ fn test_get_diamond_cut_code_with_long_init_data_ffi() { let diamond_cut_code = TWDataHelper::wrap(unsafe { tw_barz_get_diamond_cut_code(input.ptr()) }); assert_eq!(hex::encode(diamond_cut_code.to_vec().unwrap(), true), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4b61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b9060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); } - -#[test] -fn test_get_authorization_hash_ffi() { - let chain_id = U256::from(1u64).to_big_endian(); - let chain_id = TWDataHelper::create(chain_id.to_vec()); - - let contract_address = TWStringHelper::create("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"); - - let nonce = U256::from(1u64).to_big_endian(); - let nonce = TWDataHelper::create(nonce.to_vec()); - - let authorization_hash = TWDataHelper::wrap(unsafe { - tw_barz_get_authorization_hash(chain_id.ptr(), contract_address.ptr(), nonce.ptr()) - }); - assert_eq!( - hex::encode(authorization_hash.to_vec().unwrap(), true), - "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9" - ); // Verified with viem -} - -#[test] -fn test_sign_authorization_ffi() { - let chain_id = U256::from(1u64).to_big_endian(); - let chain_id = TWDataHelper::create(chain_id.to_vec()); - - let contract_address = TWStringHelper::create("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"); - - let nonce = U256::from(1u64).to_big_endian(); - let nonce = TWDataHelper::create(nonce.to_vec()); - - let private_key = TWStringHelper::create( - "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8", - ); - - let signed_authorization = TWStringHelper::wrap(unsafe { - tw_barz_sign_authorization( - chain_id.ptr(), - contract_address.ptr(), - nonce.ptr(), - private_key.ptr(), - ) - }); - let json: serde_json::Value = - serde_json::from_str(&signed_authorization.to_string().unwrap()).unwrap(); - - // Verified with viem - assert_eq!( - json["chainId"].as_str().unwrap(), - hex::encode(U256::from(1u64).to_big_endian_compact(), true) - ); - assert_eq!( - json["address"].as_str().unwrap(), - "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" - ); - assert_eq!( - json["nonce"].as_str().unwrap(), - hex::encode(U256::from(1u64).to_big_endian_compact(), true) - ); - assert_eq!(json["yParity"].as_str().unwrap(), hex::encode(&[1u8], true)); - assert_eq!( - json["r"].as_str().unwrap(), - "0x2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710" - ); - assert_eq!( - json["s"].as_str().unwrap(), - "0x5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c" - ); -} - -#[test] -fn test_get_encoded_hash_ffi() { - let chain_id = U256::from(31337u64).to_big_endian(); - let chain_id = TWDataHelper::create(chain_id.to_vec()); - - let code_address = TWStringHelper::create("0x2e234DAe75C793f67A35089C9d99245E1C58470b"); - let code_name = TWStringHelper::create("Biz"); - let code_version = TWStringHelper::create("v1.0.0"); - let type_hash = TWStringHelper::create( - "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867", - ); - let domain_separator_hash = TWStringHelper::create( - "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472", - ); - let sender = TWStringHelper::create("0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"); - let user_op_hash = TWStringHelper::create( - "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb", - ); - - let encoded_hash = TWDataHelper::wrap(unsafe { - tw_barz_get_encoded_hash( - chain_id.ptr(), - code_address.ptr(), - code_name.ptr(), - code_version.ptr(), - type_hash.ptr(), - domain_separator_hash.ptr(), - sender.ptr(), - user_op_hash.ptr(), - ) - }); - assert_eq!( - hex::encode(encoded_hash.to_vec().unwrap(), true), - "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635" - ); -} - -#[test] -fn test_get_signed_hash_ffi() { - let hash = TWStringHelper::create( - "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635", - ); - let private_key = TWStringHelper::create( - "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8", - ); - let signed_hash = - TWDataHelper::wrap(unsafe { tw_barz_get_signed_hash(hash.ptr(), private_key.ptr()) }); - assert_eq!(hex::encode(signed_hash.to_vec().unwrap(), true), "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c"); -} diff --git a/rust/tw_evm/tests/biz.rs b/rust/tw_evm/tests/biz.rs new file mode 100644 index 00000000000..8fc644f6a45 --- /dev/null +++ b/rust/tw_evm/tests/biz.rs @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_encoding::hex; +use tw_encoding::hex::ToHex; +use tw_evm::modules::biz::{get_encoded_hash, sign_user_op_hash}; +use tw_evm::modules::webauthn::{NormalizedS, WebAuthnAuth, WebAuthnMessage}; +use tw_keypair::ecdsa::nist256p1; +use tw_keypair::traits::SigningKeyTrait; +use tw_number::U256; + +#[test] +fn test_get_encoded_hash() { + let chain_id = U256::from(31337u64).to_big_endian(); + let code_address = "0x2e234DAe75C793f67A35089C9d99245E1C58470b"; + let code_name = "Biz"; + let code_version = "v1.0.0"; + let type_hash = "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867"; + let domain_separator_hash = + "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472"; + let sender = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"; + let user_op_hash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb"; + + let encoded_hash = get_encoded_hash( + &chain_id[..], + code_address, + code_name, + code_version, + type_hash, + domain_separator_hash, + sender, + user_op_hash, + ) + .unwrap(); + assert_eq!( + hex::encode(encoded_hash, true), + "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635" + ); +} + +#[test] +fn test_sign_user_op_hash() { + let hash = "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635"; + let private_key = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; + let signed_hash = sign_user_op_hash(hash, private_key).unwrap(); + assert_eq!(hex::encode(signed_hash, true), "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c"); +} + +#[test] +fn test_sign_user_op_with_passkey_session() { + let chain_id = U256::from(31_337u64).to_big_endian(); + let code_address = "0xa0Cb889707d426A7A386870A03bc70d1b0697598"; + let code_name = "PasskeySession"; + let code_version = "v1.0.0"; + // keccak("PasskeySession(bytes32 userOpHash)") + let type_hash = "0x3463fe66e4d03af5b942aebde2b191eff52d291c0a2c8cc302d786854f34bfc9"; + // keccak("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)") + let domain_separator_hash = + "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472"; + let sender = "0x336Cd992a83242D91f556C1F7e855AcA366193e0"; + let user_op_hash = "0x7762e85586107f2bca787a9163b71f0584eabd84258a93cca0e896589a193571"; + + // WebAuthn Specific data. + let authenticator_data = + "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000"; + let client_data_json = r#"{"type":"webauthn.get","challenge":"fRMDMfFrs9K8PXLbAoedB0XURSWS5Wcj3osnzx7gBsc","origin":"https://sign.coinbase.com/","crossOrigin":false}"#; + + // secp256p1 (nist256p1) private key. + let passkey_private = "0x03d99692017473e2d631945a812607b23269d85721e0f370b8d3e7d29a874fd2"; + + // Step 1. Encode UserOperation hash. + let encoded_user_op_hash = get_encoded_hash( + &chain_id[..], + code_address, + code_name, + code_version, + type_hash, + domain_separator_hash, + sender, + user_op_hash, + ) + .unwrap(); + assert_eq!( + encoded_user_op_hash.to_hex_prefixed(), + "0x7d130331f16bb3d2bc3d72db02879d0745d4452592e56723de8b27cf1ee006c7" + ); + + // Step 2. Generate WebAuthn message hash. + let webauthn = WebAuthnMessage::new(authenticator_data, client_data_json.to_string()).unwrap(); + assert_eq!( + webauthn.message_hash.to_hex_prefixed(), + "0x0b8772b952d27e2a8d6a51b0177b18b8ed1c3ebede2d0d7992646841b25322ac" + ); + + // Step 3. Sign WebAuthn message hash. + let private_key = nist256p1::KeyPair::try_from(passkey_private).unwrap(); + let message_signature = private_key.sign(webauthn.message_hash).unwrap(); + assert_eq!( + message_signature.r().to_hex_prefixed(), + "0x73f8762dd6fb0eb39aea829525658fca612d1c433db6381c9d63a52ee15a26be" + ); + assert_eq!( + message_signature.s().to_hex_prefixed(), + "0xe091f452b74519a2894a96d142bdd1888ac6513f5dff76e48c0312144ef9b382" + ); + + // Step 4. Encode WebAuthn Auth struct. + let normalized_z = NormalizedS::new(U256::from_big_endian(message_signature.s())); + let webauthn = WebAuthnAuth::new( + webauthn, + U256::from_big_endian(message_signature.r()), + normalized_z, + ) + .unwrap(); + assert_eq!( + webauthn.authenticator_data.to_hex_prefixed(), + authenticator_data + ); + assert_eq!(webauthn.client_data_json, client_data_json); + assert_eq!(webauthn.challenge_index, U256::from(23_u32)); + assert_eq!(webauthn.type_index, U256::from(1_u32)); + assert_eq!( + webauthn.r.to_big_endian().to_hex_prefixed(), + "0x73f8762dd6fb0eb39aea829525658fca612d1c433db6381c9d63a52ee15a26be" + ); + assert_eq!( + webauthn.s.as_ref().to_big_endian().to_hex_prefixed(), + "0x1f6e0bac48bae65e76b5692ebd422e773220a96e491827a067b6b8aead6971cf" + ); + + let encoded_webauthn = webauthn.abi_encode(); + let expected = "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000017000000000000000000000000000000000000000000000000000000000000000173f8762dd6fb0eb39aea829525658fca612d1c433db6381c9d63a52ee15a26be1f6e0bac48bae65e76b5692ebd422e773220a96e491827a067b6b8aead6971cf000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008a7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2266524d444d66467273394b3850584c62416f656442305855525357533557636a336f736e7a783767427363222c226f726967696e223a2268747470733a2f2f7369676e2e636f696e626173652e636f6d222c2263726f73734f726967696e223a66616c73657d00000000000000000000000000000000000000000000"; + assert_eq!(encoded_webauthn.to_hex_prefixed(), expected); +} diff --git a/rust/tw_evm/tests/biz_ffi.rs b/rust/tw_evm/tests/biz_ffi.rs new file mode 100644 index 00000000000..9c238b8b63e --- /dev/null +++ b/rust/tw_evm/tests/biz_ffi.rs @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_encoding::hex; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_evm::ffi::biz::{ + tw_biz_encode_execute_with_passkey_session_call, tw_biz_encode_passkey_session_nonce, + tw_biz_encode_register_session_call, tw_biz_encode_remove_session_call, + tw_biz_get_encoded_hash, tw_biz_get_signed_hash, +}; +use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; +use tw_keypair::tw::PublicKeyType; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_string_helper::TWStringHelper; +use tw_number::U256; +use tw_proto::serialize; +use tw_proto::Biz::Proto::mod_ExecuteWithPasskeySessionInput::Execution; +use tw_proto::Biz::Proto::ExecuteWithPasskeySessionInput; + +const ONE_ETH: u64 = 1_000_000_000_000_000_000; + +#[test] +fn test_get_encoded_hash_ffi() { + let chain_id = U256::from(31337u64).to_big_endian(); + let chain_id = TWDataHelper::create(chain_id.to_vec()); + + let code_address = TWStringHelper::create("0x2e234DAe75C793f67A35089C9d99245E1C58470b"); + let code_name = TWStringHelper::create("Biz"); + let code_version = TWStringHelper::create("v1.0.0"); + let type_hash = TWStringHelper::create( + "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867", + ); + let domain_separator_hash = TWStringHelper::create( + "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472", + ); + let sender = TWStringHelper::create("0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"); + let user_op_hash = TWStringHelper::create( + "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb", + ); + + let encoded_hash = TWDataHelper::wrap(unsafe { + tw_biz_get_encoded_hash( + chain_id.ptr(), + code_address.ptr(), + code_name.ptr(), + code_version.ptr(), + type_hash.ptr(), + domain_separator_hash.ptr(), + sender.ptr(), + user_op_hash.ptr(), + ) + }); + assert_eq!( + hex::encode(encoded_hash.to_vec().unwrap(), true), + "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635" + ); +} + +#[test] +fn test_get_signed_hash_ffi() { + let hash = TWStringHelper::create( + "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635", + ); + let private_key = TWStringHelper::create( + "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8", + ); + let signed_hash = + TWDataHelper::wrap(unsafe { tw_biz_get_signed_hash(hash.ptr(), private_key.ptr()) }); + assert_eq!(hex::encode(signed_hash.to_vec().unwrap(), true), "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c"); +} + +#[test] +fn test_register_session_ffi() { + let public_key = TWPublicKeyHelper::with_hex( + "0x041c05286fe694493eae33312f2d2e0d0abeda8db76238b7a204be1fb87f54ce4228fef61ef4ac300f631657635c28e59bfb2fe71bce1634c81c65642042f6dc4d", + PublicKeyType::Nist256p1Extended + ); + let valid_until = TWDataHelper::create(U256::from(86_401_u64).to_big_endian_compact()); + let data = TWDataHelper::wrap(unsafe { + tw_biz_encode_register_session_call(public_key.ptr(), valid_until.ptr()) + }); + assert_eq!( + data.to_vec().unwrap().to_hex(), + "826491fb000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000151810000000000000000000000000000000000000000000000000000000000000041041c05286fe694493eae33312f2d2e0d0abeda8db76238b7a204be1fb87f54ce4228fef61ef4ac300f631657635c28e59bfb2fe71bce1634c81c65642042f6dc4d00000000000000000000000000000000000000000000000000000000000000" + ); +} + +#[test] +fn test_remove_session_ffi() { + let public_key = TWPublicKeyHelper::with_hex( + "0x041c05286fe694493eae33312f2d2e0d0abeda8db76238b7a204be1fb87f54ce4228fef61ef4ac300f631657635c28e59bfb2fe71bce1634c81c65642042f6dc4d", + PublicKeyType::Nist256p1Extended + ); + let data = TWDataHelper::wrap(unsafe { tw_biz_encode_remove_session_call(public_key.ptr()) }); + assert_eq!( + data.to_vec().unwrap().to_hex(), + "e1c06abd00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000041041c05286fe694493eae33312f2d2e0d0abeda8db76238b7a204be1fb87f54ce4228fef61ef4ac300f631657635c28e59bfb2fe71bce1634c81c65642042f6dc4d00000000000000000000000000000000000000000000000000000000000000" + ); +} + +#[test] +fn test_encode_passkey_nonce_ffi() { + let nonce = TWDataHelper::create(U256::from(123_u64).to_big_endian_compact()); + let data = TWDataHelper::wrap(unsafe { tw_biz_encode_passkey_session_nonce(nonce.ptr()) }); + assert_eq!( + data.to_vec().unwrap().to_hex(), + "00000000000000000000000000000000050041d6a66939a8000000000000007b" + ); +} + +#[test] +fn test_execute_with_passkey_session_ffi() { + let passkey_signature = "0x00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000017000000000000000000000000000000000000000000000000000000000000000188458d3b608d1195be386bc6017e389d46624b50cb450792fb8c91e84e3e1ff912aabc684a855da9e83cb6cafe74950dcf6d81e8b3b2b7a5437abc1762ca736d000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008a7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22343368697353614c5553757a6547785330662d705443686731736334616873307949645f7162463141634d222c226f726967696e223a2268747470733a2f2f7369676e2e636f696e626173652e636f6d222c2263726f73734f726967696e223a66616c73657d00000000000000000000000000000000000000000000"; + let input = ExecuteWithPasskeySessionInput { + executions: vec![ + Execution { + address: "0x0000000000000000000000000000000000000001".into(), + amount: U256::from(ONE_ETH).to_big_endian_compact().into(), + payload: Default::default(), + }, + Execution { + address: "0x0000000000000000000000000000000000000002".into(), + amount: U256::from(ONE_ETH).to_big_endian_compact().into(), + payload: Default::default(), + }, + Execution { + address: "0x0000000000000000000000000000000000000003".into(), + amount: U256::from(ONE_ETH).to_big_endian_compact().into(), + payload: Default::default(), + }, + ], + valid_after: 0, + valid_until: 61, + passkey_signature: passkey_signature.decode_hex().unwrap().into(), + }; + let input = TWDataHelper::create(serialize(&input).unwrap()); + + let encoded = + TWDataHelper::wrap(unsafe { tw_biz_encode_execute_with_passkey_session_call(input.ptr()) }); + let expected = "0x38892f8800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000003d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020100000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000017000000000000000000000000000000000000000000000000000000000000000188458d3b608d1195be386bc6017e389d46624b50cb450792fb8c91e84e3e1ff912aabc684a855da9e83cb6cafe74950dcf6d81e8b3b2b7a5437abc1762ca736d000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008a7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22343368697353614c5553757a6547785330662d705443686731736334616873307949645f7162463141634d222c226f726967696e223a2268747470733a2f2f7369676e2e636f696e626173652e636f6d222c2263726f73734f726967696e223a66616c73657d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + assert_eq!(encoded.to_vec().unwrap().to_hex_prefixed(), expected); +} diff --git a/rust/tw_evm/tests/eip7702.rs b/rust/tw_evm/tests/eip7702.rs new file mode 100644 index 00000000000..556bbee663a --- /dev/null +++ b/rust/tw_evm/tests/eip7702.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_encoding::hex; +use tw_evm::modules::eip7702::{get_authorization_hash, sign_authorization}; +use tw_number::U256; + +#[test] +fn test_get_authorization_hash() { + let chain_id = U256::from(1u64).to_big_endian(); + let contract_address = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; + let nonce = U256::from(1u64).to_big_endian(); + + let authorization_hash = + get_authorization_hash(&chain_id[..], contract_address, &nonce[..]).unwrap(); + assert_eq!( + hex::encode(authorization_hash, true), + "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9" + ); // Verified with viem +} + +#[test] +fn test_sign_authorization() { + let chain_id = U256::from(1u64).to_big_endian_compact(); + let contract_address = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; + let nonce = U256::from(1u64).to_big_endian_compact(); + let private_key = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; + + let signed_authorization = + sign_authorization(&chain_id[..], contract_address, &nonce[..], private_key).unwrap(); + let json: serde_json::Value = serde_json::from_str(&signed_authorization).unwrap(); + + // Verified with viem + assert_eq!( + json["chainId"].as_str().unwrap(), + hex::encode(chain_id, true) + ); + assert_eq!(json["address"].as_str().unwrap(), contract_address); + assert_eq!(json["nonce"].as_str().unwrap(), hex::encode(nonce, true)); + assert_eq!(json["yParity"].as_str().unwrap(), hex::encode(&[1u8], true)); + assert_eq!( + json["r"].as_str().unwrap(), + "0x2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710" + ); + assert_eq!( + json["s"].as_str().unwrap(), + "0x5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c" + ); +} diff --git a/rust/tw_evm/tests/eip7702_ffi.rs b/rust/tw_evm/tests/eip7702_ffi.rs new file mode 100644 index 00000000000..9cf92c02b6f --- /dev/null +++ b/rust/tw_evm/tests/eip7702_ffi.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_encoding::hex; +use tw_evm::ffi::eip7702::{tw_eip7702_get_authorization_hash, tw_eip7702_sign_authorization}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_string_helper::TWStringHelper; +use tw_number::U256; + +#[test] +fn test_get_authorization_hash_ffi() { + let chain_id = U256::from(1u64).to_big_endian(); + let chain_id = TWDataHelper::create(chain_id.to_vec()); + + let contract_address = TWStringHelper::create("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"); + + let nonce = U256::from(1u64).to_big_endian(); + let nonce = TWDataHelper::create(nonce.to_vec()); + + let authorization_hash = TWDataHelper::wrap(unsafe { + tw_eip7702_get_authorization_hash(chain_id.ptr(), contract_address.ptr(), nonce.ptr()) + }); + assert_eq!( + hex::encode(authorization_hash.to_vec().unwrap(), true), + "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9" + ); // Verified with viem +} + +#[test] +fn test_sign_authorization_ffi() { + let chain_id = U256::from(1u64).to_big_endian(); + let chain_id = TWDataHelper::create(chain_id.to_vec()); + + let contract_address = TWStringHelper::create("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"); + + let nonce = U256::from(1u64).to_big_endian(); + let nonce = TWDataHelper::create(nonce.to_vec()); + + let private_key = TWStringHelper::create( + "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8", + ); + + let signed_authorization = TWStringHelper::wrap(unsafe { + tw_eip7702_sign_authorization( + chain_id.ptr(), + contract_address.ptr(), + nonce.ptr(), + private_key.ptr(), + ) + }); + let json: serde_json::Value = + serde_json::from_str(&signed_authorization.to_string().unwrap()).unwrap(); + + // Verified with viem + assert_eq!( + json["chainId"].as_str().unwrap(), + hex::encode(U256::from(1u64).to_big_endian_compact(), true) + ); + assert_eq!( + json["address"].as_str().unwrap(), + "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" + ); + assert_eq!( + json["nonce"].as_str().unwrap(), + hex::encode(U256::from(1u64).to_big_endian_compact(), true) + ); + assert_eq!(json["yParity"].as_str().unwrap(), hex::encode(&[1u8], true)); + assert_eq!( + json["r"].as_str().unwrap(), + "0x2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710" + ); + assert_eq!( + json["s"].as_str().unwrap(), + "0x5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c" + ); +} diff --git a/rust/tw_evm/tests/webauthn_ffi.rs b/rust/tw_evm/tests/webauthn_ffi.rs new file mode 100644 index 00000000000..53f89742876 --- /dev/null +++ b/rust/tw_evm/tests/webauthn_ffi.rs @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_evm::ffi::webauthn_solidity::tw_webauthn_solidity_get_message_hash; +use tw_hash::H256; +use tw_keypair::ecdsa::{der, nist256p1}; +use tw_keypair::traits::SigningKeyTrait; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_string_helper::TWStringHelper; + +#[test] +fn test_get_webauthn_message_hash() { + let authenticator_data = TWStringHelper::create( + "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000", + ); + let client_data_json = TWStringHelper::create( + r#"{"type":"webauthn.get","challenge":"GFQOrZ9hRIfwzt9nUt8zxrl6vVQmMnhLK9klXhDFP6c","origin":"https://sign.coinbase.com/","crossOrigin":false}"#, + ); + + let message_hash = TWDataHelper::wrap(unsafe { + tw_webauthn_solidity_get_message_hash(authenticator_data.ptr(), client_data_json.ptr()) + }); + assert_eq!( + message_hash.to_vec().unwrap().to_hex_prefixed(), + "0x03301a2660d1cea1c268c237ccd63ecfc8ca32573d7a6cf702cbbbaab9d7ac2a" + ); +} + +#[test] +fn test_get_webauthn_formatted_signature() { + let authenticator_data = TWStringHelper::create( + "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000", + ); + let client_data_json = TWStringHelper::create( + r#"{"type":"webauthn.get","challenge":"GFQOrZ9hRIfwzt9nUt8zxrl6vVQmMnhLK9klXhDFP6c","origin":"https://sign.coinbase.com/","crossOrigin":false}"#, + ); + let der_signature = TWDataHelper::create( + "304502203d0ece361a0a304ed87dbcccbcd0ae5924ed497bf0500cc850e57b76d4f7d6c1022100a87345592418c98dcff8e159f5e0d9fefe8636d95c784a1a740e514ab2c7fb87" + .decode_hex() + .unwrap() + ); + let pk = nist256p1::KeyPair::try_from( + "0x03d99692017473e2d631945a812607b23269d85721e0f370b8d3e7d29a874fd2", + ) + .unwrap(); + let sig = pk + .sign(H256::from( + "0x0b8772b952d27e2a8d6a51b0177b18b8ed1c3ebede2d0d7992646841b25322ac", + )) + .unwrap(); + let ss = der::Signature::new(sig.r(), sig.s()).unwrap(); + println!("{}", ss.to_hex_prefixed()); + + let formatted_signature = TWDataHelper::wrap(unsafe { + tw_evm::ffi::webauthn_solidity::tw_webauthn_solidity_get_formatted_signature( + authenticator_data.ptr(), + client_data_json.ptr(), + der_signature.ptr(), + ) + }); + assert_eq!( + formatted_signature.to_vec().unwrap().to_hex_prefixed(), + "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000013d0ece361a0a304ed87dbcccbcd0ae5924ed497bf0500cc850e57b76d4f7d6c1578cbaa5dbe7367330071ea60a1f2600be60c3d44a9f546a7fab7978499b29ca000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008a7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a224746514f725a3968524966777a74396e5574387a78726c367656516d4d6e684c4b396b6c58684446503663222c226f726967696e223a2268747470733a2f2f7369676e2e636f696e626173652e636f6d222c2263726f73734f726967696e223a66616c73657d00000000000000000000000000000000000000000000" + ); +} diff --git a/rust/tw_keypair/src/tw/public.rs b/rust/tw_keypair/src/tw/public.rs index 2ab54f8820a..cb7c0dab931 100644 --- a/rust/tw_keypair/src/tw/public.rs +++ b/rust/tw_keypair/src/tw/public.rs @@ -143,6 +143,16 @@ impl PublicKey { } } + /// Returns a `nist256p1` public key if the key type is matched. + pub fn to_nist256p1(&self) -> Option<&nist256p1::PublicKey> { + match self { + PublicKey::Nist256p1(nist256p1) | PublicKey::Nist256p1Extended(nist256p1) => { + Some(nist256p1) + }, + _ => None, + } + } + pub fn to_ed25519(&self) -> Option<&ed25519::sha512::PublicKey> { match self { PublicKey::Ed25519(ed25519) => Some(ed25519), diff --git a/src/proto/Barz.proto b/src/proto/Barz.proto index 6c8342054e7..fc6d52b7fc3 100644 --- a/src/proto/Barz.proto +++ b/src/proto/Barz.proto @@ -49,4 +49,3 @@ message DiamondCutInput { string init_address = 2; // Address to call with `init` data after applying cuts bytes init_data = 3; // Data to pass to `init` function call } - diff --git a/src/proto/Biz.proto b/src/proto/Biz.proto new file mode 100644 index 00000000000..0c95a354d2d --- /dev/null +++ b/src/proto/Biz.proto @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.Biz.Proto; +option java_package = "wallet.core.jni.proto"; + +message ExecuteWithPasskeySessionInput { + message Execution { + // Recipient addresses. + string address = 1; + + // Amounts to send in wei (uint256, serialized big endian) + bytes amount = 2; + + // Contract call payloads data + bytes payload = 3; + } + + // Batched executions to be executed on the smart contract wallet. + repeated Execution executions = 1; + + // Transaction valid after this timestamp. + uint64 valid_after = 2; + + // Transaction valid until this timestamp. + uint64 valid_until = 3; + + // Passkey session signature. + bytes passkey_signature = 4; +} diff --git a/swift/Tests/BarzTests.swift b/swift/Tests/BarzTests.swift index ce362d845b8..d26db9b58fb 100644 --- a/swift/Tests/BarzTests.swift +++ b/swift/Tests/BarzTests.swift @@ -193,15 +193,6 @@ class BarzTests: XCTestCase { XCTAssertEqual(output.preHash.hexString, "84d0464f5a2b191e06295443970ecdcd2d18f565d0d52b5a79443192153770ab") XCTAssertEqual(String(data: output.encoded, encoding: .utf8), "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1E6c542ebC7c960c6A155A9094DB838cEf842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}") } - - func testAuthorizationHash() { - let chainId = Data(hexString: "0x01")! - let contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" - let nonce = Data(hexString: "0x01")! - - let authorizationHash = Barz.getAuthorizationHash(chainId: chainId, contractAddress: contractAddress, nonce: nonce)! - XCTAssertEqual(authorizationHash.hexString, "3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9") // Verified with viem - } let factory = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800" let diamondCutFacet = "0x312382b3B302bDcC0711fe710314BE298426296f" diff --git a/swift/Tests/Eip7702Tests.swift b/swift/Tests/Eip7702Tests.swift new file mode 100644 index 00000000000..69bee61f564 --- /dev/null +++ b/swift/Tests/Eip7702Tests.swift @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class Eip7702Tests: XCTestCase { + func testAuthorizationHash() { + let chainId = Data(hexString: "0x01")! + let contractAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1" + let nonce = Data(hexString: "0x01")! + + let authorizationHash = Eip7702.getAuthorizationHash(chainId: chainId, contractAddress: contractAddress, nonce: nonce)! + XCTAssertEqual(authorizationHash.hexString, "3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9") // Verified with viem + } +} diff --git a/tests/chains/Ethereum/BarzTests.cpp b/tests/chains/Ethereum/BarzTests.cpp index aa60e478024..bce95a5f11c 100644 --- a/tests/chains/Ethereum/BarzTests.cpp +++ b/tests/chains/Ethereum/BarzTests.cpp @@ -2,15 +2,20 @@ // // Copyright © 2017 Trust Wallet. +#include "HexCoding.h" #include "TestUtilities.h" -#include +#include "TrustWalletCore/TWEip7702.h" +#include "proto/Barz.pb.h" +#include "proto/Ethereum.pb.h" +#include "uint256.h" + #include +#include +#include +#include #include #include -#include "proto/Ethereum.pb.h" -#include "proto/Barz.pb.h" -#include "HexCoding.h" -#include "uint256.h" + #include #include #include @@ -398,7 +403,7 @@ TEST(Barz, GetAuthorizationHash) { const auto contractAddress = STRING("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"); const auto nonce = store(uint256_t(1)); - const auto& authorizationHash = TWBarzGetAuthorizationHash(WRAPD(TWDataCreateWithBytes(chainId.data(), chainId.size())).get(), contractAddress.get(), WRAPD(TWDataCreateWithBytes(nonce.data(), nonce.size())).get()); + const auto& authorizationHash = TWEip7702GetAuthorizationHash(WRAPD(TWDataCreateWithBytes(chainId.data(), chainId.size())).get(), contractAddress.get(), WRAPD(TWDataCreateWithBytes(nonce.data(), nonce.size())).get()); const auto& authorizationHashData = hexEncoded(*reinterpret_cast(WRAPD(authorizationHash).get())); ASSERT_EQ(authorizationHashData, "0x3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9"); // Verified with viem } @@ -411,7 +416,7 @@ TEST(Barz, SignAuthorization) { const auto nonce = store(uint256_t(1)); const auto privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; - const auto signedAuthorization = WRAPS(TWBarzSignAuthorization(WRAPD(TWDataCreateWithBytes(chainId.data(), chainId.size())).get(), STRING(contractAddress).get(), WRAPD(TWDataCreateWithBytes(nonce.data(), nonce.size())).get(), STRING(privateKey).get())); + const auto signedAuthorization = WRAPS(TWEip7702SignAuthorization(WRAPD(TWDataCreateWithBytes(chainId.data(), chainId.size())).get(), STRING(contractAddress).get(), WRAPD(TWDataCreateWithBytes(nonce.data(), nonce.size())).get(), STRING(privateKey).get())); auto json = nlohmann::json::parse(std::string(TWStringUTF8Bytes(signedAuthorization.get()))); // Verified with viem ASSERT_EQ(json["chainId"], hexEncoded(chainId)); @@ -430,7 +435,7 @@ TEST(Barz, SignAuthorizationZeroNonce) { const Data nonce; const auto privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; - const auto signedAuthorization = WRAPS(TWBarzSignAuthorization(WRAPD(TWDataCreateWithBytes(chainId.data(), chainId.size())).get(), STRING(contractAddress).get(), WRAPD(TWDataCreateWithBytes(nonce.data(), nonce.size())).get(), STRING(privateKey).get())); + const auto signedAuthorization = WRAPS(TWEip7702SignAuthorization(WRAPD(TWDataCreateWithBytes(chainId.data(), chainId.size())).get(), STRING(contractAddress).get(), WRAPD(TWDataCreateWithBytes(nonce.data(), nonce.size())).get(), STRING(privateKey).get())); auto json = nlohmann::json::parse(std::string(TWStringUTF8Bytes(signedAuthorization.get()))); ASSERT_EQ(json["chainId"], hexEncoded(chainId)); ASSERT_EQ(json["address"], contractAddress); @@ -452,7 +457,7 @@ TEST(Barz, GetEncodedHash) { const auto sender = STRING("0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"); const auto userOpHash = STRING("0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb"); - const auto& encodedHash = TWBarzGetEncodedHash( + const auto& encodedHash = TWBizGetEncodedHash( WRAPD(TWDataCreateWithBytes(chainId.data(), chainId.size())).get(), codeAddress.get(), codeName.get(), @@ -470,7 +475,7 @@ TEST(Barz, GetSignedHash) { { const auto hash = STRING("0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635"); const auto privateKey = STRING("0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"); - const auto signedHash = TWBarzGetSignedHash(hash.get(), privateKey.get()); + const auto signedHash = TWBizGetSignedHash(hash.get(), privateKey.get()); const auto& signedHashData = hexEncoded(*reinterpret_cast(WRAPD(signedHash).get())); ASSERT_EQ(signedHashData, "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c"); } From bb49d06d1b2fb1ffa85c39b7b01fb427755ea178 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Fri, 10 Oct 2025 09:44:02 +0200 Subject: [PATCH 54/72] feat(solana): Add `SolanaTransaction.insertTransferInstruction()` (#4523) * feat(solana): Add `transfer_to_fee_payer` instruction * feat(solana): Add `tw_solana_transaction_insert_transfer_instruction` FFI * feat(solana): Add a note comment * feat(solana): Add one more comment --- rust/Cargo.lock | 1 + rust/chains/tw_solana/Cargo.toml | 1 + .../src/modules/insert_instruction.rs | 34 +++++++--- .../src/modules/instruction_builder/mod.rs | 7 ++ .../tw_solana/src/modules/message_builder.rs | 54 +++++++++++---- rust/chains/tw_solana/src/modules/utils.rs | 53 ++++++++++++++- .../tests/chains/solana/solana_sign.rs | 39 +++++++++++ .../chains/solana/solana_transaction_ffi.rs | 65 ++++++++++++++++++- .../src/ffi/solana/transaction.rs | 37 +++++++++++ src/proto/Solana.proto | 5 +- 10 files changed, 269 insertions(+), 27 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index d1e1d9c7423..52d2b3813ba 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2285,6 +2285,7 @@ dependencies = [ "tw_keypair", "tw_memory", "tw_misc", + "tw_number", "tw_proto", ] diff --git a/rust/chains/tw_solana/Cargo.toml b/rust/chains/tw_solana/Cargo.toml index dde71aa2cce..15d9178994b 100644 --- a/rust/chains/tw_solana/Cargo.toml +++ b/rust/chains/tw_solana/Cargo.toml @@ -15,4 +15,5 @@ tw_hash = { path = "../../tw_hash" } tw_keypair = { path = "../../tw_keypair" } tw_memory = { path = "../../tw_memory" } tw_misc = { path = "../../tw_misc" } +tw_number = { path = "../../tw_number" } tw_proto = { path = "../../tw_proto" } diff --git a/rust/chains/tw_solana/src/modules/insert_instruction.rs b/rust/chains/tw_solana/src/modules/insert_instruction.rs index 9e05140e088..23077a1fdc3 100644 --- a/rust/chains/tw_solana/src/modules/insert_instruction.rs +++ b/rust/chains/tw_solana/src/modules/insert_instruction.rs @@ -132,10 +132,10 @@ pub trait InsertInstruction { .iter() .position(|key| *key == account.pubkey) { - let is_signer = + let existing_is_signer = existing_index < self.message_header_mut().num_required_signatures as usize; - let is_writable = if is_signer { + let existing_is_writable = if existing_is_signer { existing_index < (self.message_header_mut().num_required_signatures - self.message_header_mut().num_readonly_signed_accounts) @@ -146,16 +146,30 @@ pub trait InsertInstruction { - self.message_header_mut().num_readonly_unsigned_accounts as usize) }; - if account.is_signer != is_signer { - return SigningError::err(SigningErrorType::Error_internal).context( - "Account already exists but the `is_signer` attribute does not match", - ); + match (existing_is_signer, account.is_signer) { + // If new account requires weaker or the same signing permissions, it's ok. + (true, false) | (false, false) | (true, true) => (), + // If new account requires stronger signing permissions than we have already, then would need to reorder accounts. + // TODO: Implement reordering accounts if needed. + (false, true) => { + return SigningError::err(SigningErrorType::Error_internal).context( + "Account already exists but the `is_signer` attribute does not match", + ); + }, } - if account.is_writable != is_writable { - return SigningError::err(SigningErrorType::Error_internal).context( - "Account already exists but the `is_writable` attribute does not match", - ); + + match (existing_is_writable, account.is_writable) { + // If new account requires weaker or the same writable permissions, it's ok. + (true, false) | (false, false) | (true, true) => (), + // If new account requires stronger writable permissions than we have already, then would need to reorder accounts. + // TODO: Implement reordering accounts if needed. + (false, true) => { + return SigningError::err(SigningErrorType::Error_internal).context( + "Account already exists but the `is_writable` attribute does not match", + ); + }, } + // Return the existing index if validation passes return try_into_u8(existing_index); } diff --git a/rust/chains/tw_solana/src/modules/instruction_builder/mod.rs b/rust/chains/tw_solana/src/modules/instruction_builder/mod.rs index 9a4da04d11d..670d381dd88 100644 --- a/rust/chains/tw_solana/src/modules/instruction_builder/mod.rs +++ b/rust/chains/tw_solana/src/modules/instruction_builder/mod.rs @@ -66,6 +66,13 @@ impl InstructionBuilder { self } + pub fn maybe_add_instruction(&mut self, instruction: Option) -> &mut Self { + if let Some(instruction) = instruction { + self.instructions.push(instruction); + } + self + } + pub fn add_instructions(&mut self, instructions: I) -> &mut Self where I: IntoIterator, diff --git a/rust/chains/tw_solana/src/modules/message_builder.rs b/rust/chains/tw_solana/src/modules/message_builder.rs index bf25e01d14f..281a7973850 100644 --- a/rust/chains/tw_solana/src/modules/message_builder.rs +++ b/rust/chains/tw_solana/src/modules/message_builder.rs @@ -166,7 +166,8 @@ impl<'a> MessageBuilder<'a> { .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) .maybe_memo(transfer.memo.as_ref()) - .add_instruction(transfer_ix); + .add_instruction(transfer_ix) + .maybe_add_instruction(self.transfer_to_fee_payer()?); Ok(builder.output()) } @@ -202,7 +203,8 @@ impl<'a> MessageBuilder<'a> { .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .add_instructions(deposit_ixs); + .add_instructions(deposit_ixs) + .maybe_add_instruction(self.transfer_to_fee_payer()?); Ok(builder.output()) } @@ -222,7 +224,8 @@ impl<'a> MessageBuilder<'a> { .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .add_instruction(deactivate_ix); + .add_instruction(deactivate_ix) + .maybe_add_instruction(self.transfer_to_fee_payer()?); Ok(builder.output()) } @@ -246,7 +249,8 @@ impl<'a> MessageBuilder<'a> { .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .add_instructions(deactivate_ixs); + .add_instructions(deactivate_ixs) + .maybe_add_instruction(self.transfer_to_fee_payer()?); Ok(builder.output()) } @@ -274,7 +278,8 @@ impl<'a> MessageBuilder<'a> { .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .add_instruction(withdraw_ix); + .add_instruction(withdraw_ix) + .maybe_add_instruction(self.transfer_to_fee_payer()?); Ok(builder.output()) } @@ -308,7 +313,8 @@ impl<'a> MessageBuilder<'a> { .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .add_instructions(withdraw_ixs); + .add_instructions(withdraw_ixs) + .maybe_add_instruction(self.transfer_to_fee_payer()?); Ok(builder.output()) } @@ -342,7 +348,8 @@ impl<'a> MessageBuilder<'a> { .maybe_advance_nonce(self.nonce_account()?, funding_account) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .add_instruction(instruction); + .add_instruction(instruction) + .maybe_add_instruction(self.transfer_to_fee_payer()?); Ok(builder.output()) } @@ -390,7 +397,8 @@ impl<'a> MessageBuilder<'a> { .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) .maybe_memo(token_transfer.memo.as_ref()) - .add_instruction(transfer_instruction); + .add_instruction(transfer_instruction) + .maybe_add_instruction(self.transfer_to_fee_payer()?); Ok(builder.output()) } @@ -456,7 +464,8 @@ impl<'a> MessageBuilder<'a> { .add_instruction(create_account_instruction) // Optional memo. Order: before transfer, as per documentation. .maybe_memo(create_and_transfer.memo.as_ref()) - .add_instruction(transfer_instruction); + .add_instruction(transfer_instruction) + .maybe_add_instruction(self.transfer_to_fee_payer()?); Ok(builder.output()) } @@ -489,7 +498,8 @@ impl<'a> MessageBuilder<'a> { new_nonce_account, create_nonce.rent, DEFAULT_CREATE_NONCE_SPACE, - )); + )) + .maybe_add_instruction(self.transfer_to_fee_payer()?); Ok(builder.output()) } @@ -516,7 +526,8 @@ impl<'a> MessageBuilder<'a> { signer, recipient, withdraw_nonce.value, - )); + )) + .maybe_add_instruction(self.transfer_to_fee_payer()?); Ok(builder.output()) } @@ -533,7 +544,8 @@ impl<'a> MessageBuilder<'a> { builder .maybe_advance_nonce(Some(nonce_account), signer) .maybe_priority_fee_price(self.priority_fee_price()) - .maybe_priority_fee_limit(self.priority_fee_limit()); + .maybe_priority_fee_limit(self.priority_fee_limit()) + .maybe_add_instruction(self.transfer_to_fee_payer()?); Ok(builder.output()) } @@ -648,6 +660,24 @@ impl<'a> MessageBuilder<'a> { .map(|proto| proto.limit) } + fn transfer_to_fee_payer(&self) -> SigningResult> { + let Some(ref transfer_to_fee_payer) = self.input.transfer_to_fee_payer else { + return Ok(None); + }; + + let from = self.signer_address()?; + let to = SolanaAddress::from_str(&transfer_to_fee_payer.recipient) + .into_tw() + .context("Invalid 'transfer_to_fee_payer.recipient' address")?; + + let references = Self::parse_references(&transfer_to_fee_payer.references)?; + + Ok(Some( + SystemInstructionBuilder::transfer(from, to, transfer_to_fee_payer.value) + .with_references(references), + )) + } + fn parse_references(refs: &[Cow<'_, str>]) -> SigningResult> { refs.iter() .map(|addr| SolanaAddress::from_str(addr).map_err(SigningError::from)) diff --git a/rust/chains/tw_solana/src/modules/utils.rs b/rust/chains/tw_solana/src/modules/utils.rs index a4bf539ab5f..8e5da814a3a 100644 --- a/rust/chains/tw_solana/src/modules/utils.rs +++ b/rust/chains/tw_solana/src/modules/utils.rs @@ -9,7 +9,9 @@ use crate::modules::insert_instruction::InsertInstruction; use crate::modules::instruction_builder::compute_budget_instruction::{ ComputeBudgetInstruction, ComputeBudgetInstructionBuilder, UnitLimit, UnitPrice, }; -use crate::modules::instruction_builder::system_instruction::SystemInstruction; +use crate::modules::instruction_builder::system_instruction::{ + SystemInstruction, SystemInstructionBuilder, +}; use crate::modules::message_decompiler::{InstructionWithoutAccounts, MessageDecompiler}; use crate::modules::proto_builder::ProtoBuilder; use crate::modules::tx_signer::TxSigner; @@ -17,6 +19,7 @@ use crate::modules::PubkeySignatureMap; use crate::transaction::versioned::VersionedTransaction; use crate::SOLANA_ALPHABET; use std::borrow::Cow; +use std::str::FromStr; use tw_coin_entry::error::prelude::*; use tw_coin_entry::signing_output_error; use tw_encoding::base58; @@ -24,6 +27,7 @@ use tw_encoding::base64::{self, STANDARD}; use tw_hash::H256; use tw_keypair::{ed25519, KeyPairResult}; use tw_memory::Data; +use tw_number::U256; use tw_proto::Solana::Proto; pub struct SolanaTransaction; @@ -210,6 +214,53 @@ impl SolanaTransaction { .to_base64() .tw_err(SigningErrorType::Error_internal) } + + /// Inserts a SOL transfer instruction to the given transaction at the specified position, returning the updated transaction. + /// Please note that compute price and limit instructions should always be the first instructions if they are present in the transaction. + /// If you don't care about the position, use -1. + pub fn insert_transfer_instruction( + encoded_tx: &str, + insert_at: i32, + from: &str, + to: &str, + lamports: &str, + ) -> SigningResult { + let tx_bytes = base64::decode(encoded_tx, STANDARD)?; + let from = + SolanaAddress::from_str(from).map_err(|_| SigningErrorType::Error_input_parse)?; + let to = SolanaAddress::from_str(to).map_err(|_| SigningErrorType::Error_input_parse)?; + let lamports = U256::from_str(lamports) + .and_then(u64::try_from) + .map_err(|_| SigningErrorType::Error_input_parse)?; + + let mut tx: VersionedTransaction = + bincode::deserialize(&tx_bytes).map_err(|_| SigningErrorType::Error_input_parse)?; + + if insert_at >= 0 && insert_at as usize > tx.message.instructions().len() { + return Err(SigningError::from(SigningErrorType::Error_invalid_params)); + } + + let final_insert_at = if insert_at < 0 { + tx.message.instructions().len() // Append to the end if negative + } else { + insert_at as usize // Use the specified position + }; + + // Create transfer instruction and insert it at the specified position. + let transfer_ix = SystemInstructionBuilder::transfer(from, to, lamports); + tx.message.insert_instruction( + final_insert_at, + transfer_ix.program_id, + transfer_ix.accounts, + transfer_ix.data, + )?; + + // Set the correct number of zero signatures + let unsigned_tx = VersionedTransaction::unsigned(tx.message); + unsigned_tx + .to_base64() + .tw_err(SigningErrorType::Error_internal) + } } fn try_instruction_as_compute_budget( diff --git a/rust/tw_tests/tests/chains/solana/solana_sign.rs b/rust/tw_tests/tests/chains/solana/solana_sign.rs index 43240277eda..020f759e6bf 100644 --- a/rust/tw_tests/tests/chains/solana/solana_sign.rs +++ b/rust/tw_tests/tests/chains/solana/solana_sign.rs @@ -1009,3 +1009,42 @@ fn test_solana_sign_sponsored_transfer_token_with_external_fee_payer() { // https://solscan.io/tx/3z7beuRPcr6WmTRvCDNgSNXBaEUTAy8EYHN93eUiLXoFoj2VbWuPbvf7nQoZxTHbG6ChQuTJDqwaQnUzK4WxYaQA assert_eq!(output.encoded, "gnSfLvpTeWGFvEKGDNAwQpQYczANiAHcpj4ghRgKsJfTXJjqaGYnNG2Ay2JwR5XdRvdkeLjHdht7VctoJkxDcYLRNjWmFcb3khwZqV4oRcU3HCxqnjGbiFmBCTjsupUt4ZzsJs8DS9WGHPgQGfRVQdmq1Zv6Kd4KDR88aT3uLmdNsu1XP5Es5SFAqByGnwAnkthDfNvcmpW9iAsZdf4v7gTsgFZV14ZfsNh66TGzVJLepz689D4jKb19AyvPwBPYYsvpRLxeEaa3zJvsdBBoVkWMZzC2Y8oqxoPXCRXnxzKX9gJSew1P2bgZDN3j3BvFQ19zTYsdugGtRetV94yQgx5xh8Vk9Asbj3YCmEZpFMZborqeanvgK2mWs2rQmbanMY6Fi6FB1xN24YN2B38pK2g3DCYp6nNh1ueacrDakbyrRFCpyKo26yqrkqnbbKZ9roAgvrvm5zhju2GhWU5t5cPc4ADfZbfRWaV2ojETv1a9W838MB7h4N5a97kgkdnuuR5A4fJr5K4jizC2rNeLciDoZQuzoNE3TpnYqxpnJPQWQQB1vGHqdXTTiDc47i7kLm"); } + +#[test] +fn test_solana_sign_sponsored_transfer() { + const SOL_AMOUNT: u64 = 200_000; + const TOKEN_AMOUNT: u64 = 10_000; + const PRIVATE_KEY: &str = "7537978967203bdca1bcde4caa811c2771b36043a303824110cf240b10d9fde8"; + const RECENT_BLOCKHASH: &str = "BTuHSa3pK17vmLR8NFGsn8uJdsYFvQXi5YhrNZKA5ggP"; + + let create_transfer_token = Proto::TokenTransfer { + token_mint_address: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB".into(), + recipient_token_address: "DwvTHUygHJ2xdHBHT1HgvHaJfqHkPbS5omrGEEGpLHDc".into(), + sender_token_address: "DK8JGfS6Acsjh7KCdT7rEZ4ECNXztNmbJMs1mgjdZCXE".into(), + amount: TOKEN_AMOUNT, + decimals: 6, + ..Proto::TokenTransfer::default() + }; + + let transfer_to_fee_payer = Proto::Transfer { + recipient: "EkBtoCtDihccznHSF3P64kvcTt5xNxQ2jxYMjPXVH3DX".into(), + value: SOL_AMOUNT, + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + private_key: PRIVATE_KEY.decode_hex().unwrap().into(), + recent_blockhash: RECENT_BLOCKHASH.into(), + transaction_type: TransactionType::token_transfer_transaction(create_transfer_token), + transfer_to_fee_payer: Some(transfer_to_fee_payer), + tx_encoding: Proto::Encoding::Base64, + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Solana, input); + + assert_eq!(output.error, SigningError::OK); + // https://solscan.io/tx/52s8gt34WfZyJv1cDdadwA3V9PeRwazNqhVKDJ43F9JyxTE7ncqMSmqYAi1u4TsG2AyXNPbswG7krBHqWAhstCtL + assert_eq!(output.encoded, "Acms/WrZj/mOpUTTBFHLtBFKrMSAJPBBkD8qYK+oqgE0gFT5aoEfw5dlJZZl1edVde325gi0qVPai7ddgoP2WQUBAAMHgKRCBGe2W59ezw1CyHDoV4KZVuJFTtmsN0F25EL3I8228PMELJSiAl2nvEzyY4v/LfSQnxkFNlH4pjU2F3nMpcBeC+e4AaQzF6GCh63HW7KnhG+YuIbF1AvkM6nwOXMjzDg4vU3/xSfZJTqguxQVNsgitkm5dHCm6V6l4NCCDp7OAQ5gr+2yJxe9YxkvVBRaP5ZaM7uC0scCnrLOHiCCZAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACbeRZf0sbtT6rwdYyf6Z9h66lKwNsb48euTgiPa0h+WAIFBAEEAgAKDBAnAAAAAAAABgYCAAMMAgAAAEANAwAAAAAA"); +} diff --git a/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs b/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs index ceda3aec625..18397d83530 100644 --- a/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs +++ b/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs @@ -2,6 +2,7 @@ // // Copyright © 2017 Trust Wallet. +use tw_any_coin::ffi::tw_transaction_decoder::tw_transaction_decoder_decode; use tw_any_coin::test_utils::sign_utils::{AnySignerHelper, CompilerHelper, PreImageHelper}; use tw_any_coin::test_utils::transaction_decode_utils::TransactionDecoderHelper; use tw_coin_registry::coin_type::CoinType; @@ -19,9 +20,9 @@ use tw_proto::Solana::Proto::{self}; use tw_solana::SOLANA_ALPHABET; use wallet_core_rs::ffi::solana::transaction::{ tw_solana_transaction_get_compute_unit_limit, tw_solana_transaction_get_compute_unit_price, - tw_solana_transaction_insert_instruction, tw_solana_transaction_set_compute_unit_limit, - tw_solana_transaction_set_compute_unit_price, tw_solana_transaction_set_fee_payer, - tw_solana_transaction_update_blockhash_and_sign, + tw_solana_transaction_insert_instruction, tw_solana_transaction_insert_transfer_instruction, + tw_solana_transaction_set_compute_unit_limit, tw_solana_transaction_set_compute_unit_price, + tw_solana_transaction_set_fee_payer, tw_solana_transaction_update_blockhash_and_sign, }; #[test] @@ -574,3 +575,61 @@ fn test_insert_instruction_helper( assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded, expected_signed_tx); } + +#[test] +fn test_solana_transaction_insert_transfer_instruction_and_sign() { + const PRIVATE_KEY: &str = "7537978967203bdca1bcde4caa811c2771b36043a303824110cf240b10d9fde8"; + + // base64 encoded + let encoded_tx = "AZCD8pcHQwSjqVChVPKlihneVA2OSM8Y4rM+0+uXtj7goUcDHWR4/TrAvDfaPRlHNOF0EYln3VoplQF6b5ZoRwUBAAIFgKRCBGe2W59ezw1CyHDoV4KZVuJFTtmsN0F25EL3I8228PMELJSiAl2nvEzyY4v/LfSQnxkFNlH4pjU2F3nMpcBeC+e4AaQzF6GCh63HW7KnhG+YuIbF1AvkM6nwOXMjzgEOYK/tsicXvWMZL1QUWj+WWjO7gtLHAp6yzh4ggmQG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqZt5Fl/Sxu1PqvB1jJ/pn2HrqUrA2xvjx65OCI9rSH5YAQQEAQMCAAoMECcAAAAAAAAG"; + let encoded_tx = TWStringHelper::create(encoded_tx); + + let from = "9fATSMy2QhUjd1RpJgGTcs6kJzeSHzzL6DzCCEFv5Xvc"; + let from = TWStringHelper::create(from); + + let to = "EkBtoCtDihccznHSF3P64kvcTt5xNxQ2jxYMjPXVH3DX"; + let to = TWStringHelper::create(to); + + let lamports = "200000"; + let lamports = TWStringHelper::create(lamports); + + // Insert the transfer instruction at position 1 (the end of the instructions list). + let insert_at = 1; + + let updated_encoded_tx = unsafe { + TWStringHelper::wrap(tw_solana_transaction_insert_transfer_instruction( + encoded_tx.ptr(), + insert_at, + from.ptr(), + to.ptr(), + lamports.ptr(), + )) + .to_string() + .unwrap() + }; + let updated_tx = base64::decode(&updated_encoded_tx, STANDARD).unwrap(); + let updated_tx = TWDataHelper::create(updated_tx); + + let decode_output = TWDataHelper::wrap(unsafe { + tw_transaction_decoder_decode(CoinType::Solana as u32, updated_tx.ptr()) + }) + .to_vec() + .unwrap(); + + let output: Proto::DecodingTransactionOutput = tw_proto::deserialize(&decode_output).unwrap(); + assert_eq!(output.error, SigningError::OK); + + let signing_input = Proto::SigningInput { + private_key: PRIVATE_KEY.decode_hex().unwrap().into(), + raw_message: Some(output.transaction.unwrap()), + tx_encoding: Proto::Encoding::Base64, + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Solana, signing_input); + + // https://solscan.io/tx/52s8gt34WfZyJv1cDdadwA3V9PeRwazNqhVKDJ43F9JyxTE7ncqMSmqYAi1u4TsG2AyXNPbswG7krBHqWAhstCtL + let expected = "Acms/WrZj/mOpUTTBFHLtBFKrMSAJPBBkD8qYK+oqgE0gFT5aoEfw5dlJZZl1edVde325gi0qVPai7ddgoP2WQUBAAMHgKRCBGe2W59ezw1CyHDoV4KZVuJFTtmsN0F25EL3I8228PMELJSiAl2nvEzyY4v/LfSQnxkFNlH4pjU2F3nMpcBeC+e4AaQzF6GCh63HW7KnhG+YuIbF1AvkM6nwOXMjzDg4vU3/xSfZJTqguxQVNsgitkm5dHCm6V6l4NCCDp7OAQ5gr+2yJxe9YxkvVBRaP5ZaM7uC0scCnrLOHiCCZAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACbeRZf0sbtT6rwdYyf6Z9h66lKwNsb48euTgiPa0h+WAIFBAEEAgAKDBAnAAAAAAAABgYCAAMMAgAAAEANAwAAAAAA"; + assert_eq!(output.encoded, expected); +} diff --git a/rust/wallet_core_rs/src/ffi/solana/transaction.rs b/rust/wallet_core_rs/src/ffi/solana/transaction.rs index 28ebe708d08..99c297fb01c 100644 --- a/rust/wallet_core_rs/src/ffi/solana/transaction.rs +++ b/rust/wallet_core_rs/src/ffi/solana/transaction.rs @@ -188,3 +188,40 @@ pub unsafe extern "C" fn tw_solana_transaction_insert_instruction( _ => std::ptr::null_mut(), } } + +/// Inserts a SOL transfer instruction to the given transaction at the specified position, returning the updated transaction. +/// Please note that compute price and limit instructions should always be the first instructions if they are present in the transaction. +/// +/// \param encoded_tx base64 encoded Solana transaction. +/// \param insert_at index where the instruction should be inserted. If you don't care about the position, use -1. +/// \param from sender account from which the lamports will be debited. +/// \param to receiver account to which the lamports will be transferred. +/// \param lamports amount of lamports to transfer, as a decimal string. +/// \return base64 encoded Solana transaction. Null if an error occurred. +#[tw_ffi(ty = static_function, class = TWSolanaTransaction, name = InsertTransferInstruction)] +#[no_mangle] +pub unsafe extern "C" fn tw_solana_transaction_insert_transfer_instruction( + encoded_tx: Nonnull, + insert_at: i32, + from: Nonnull, + to: Nonnull, + lamports: Nonnull, +) -> NullableMut { + let encoded_tx = try_or_else!(TWString::from_ptr_as_ref(encoded_tx), std::ptr::null_mut); + let encoded_tx = try_or_else!(encoded_tx.as_str(), std::ptr::null_mut); + + let from = try_or_else!(TWString::from_ptr_as_ref(from), std::ptr::null_mut); + let from = try_or_else!(from.as_str(), std::ptr::null_mut); + + let to = try_or_else!(TWString::from_ptr_as_ref(to), std::ptr::null_mut); + let to = try_or_else!(to.as_str(), std::ptr::null_mut); + + let lamports = try_or_else!(TWString::from_ptr_as_ref(lamports), std::ptr::null_mut); + let lamports = try_or_else!(lamports.as_str(), std::ptr::null_mut); + + match SolanaTransaction::insert_transfer_instruction(encoded_tx, insert_at, from, to, lamports) + { + Ok(updated_tx) => TWString::from(updated_tx).into_ptr(), + _ => std::ptr::null_mut(), + } +} diff --git a/src/proto/Solana.proto b/src/proto/Solana.proto index f63aeb14229..616facf6c2b 100644 --- a/src/proto/Solana.proto +++ b/src/proto/Solana.proto @@ -307,8 +307,11 @@ message SigningInput { // fee for higher transaction prioritization. // https://solana.com/docs/intro/transaction_fees#prioritization-fee PriorityFeeLimit priority_fee_limit = 23; - // Optional token transfer to fee payer + // Optional token transfer to fee payer. TokenTransferToFeePayer token_transfer_to_fee_payer = 24; + // Optional SOL transfer to fee payer. This will be the last instruction in the transaction. + // https://docs.blinklabs.xyz/blink/solana/gas-sponsorship/api-reference#active-sponsorship + Transfer transfer_to_fee_payer = 25; } // Result containing the signed and encoded transaction. From 181ca6b3ed659e9a98d28d440677d5f4f33cdd05 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:52:23 +0200 Subject: [PATCH 55/72] feat(zcash): Add support for TEX address (#4527) * feat(zcash): Add `TexAddress` in Rust * feat(zcash): Add successfully broadcasted transaction test * feat(zcash): Add successfully broadcasted transaction C++ test * feat(zcash): Fix C++ tests * feat(zcash): Reuse code in `TexAddress::isValid` * feat(zcash): Fix `TexAddress:isValid` --- registry.json | 1 + rust/Cargo.lock | 2 + rust/chains/tw_zcash/Cargo.toml | 2 + rust/chains/tw_zcash/src/address.rs | 101 ++++++++++++++ rust/chains/tw_zcash/src/context.rs | 9 +- rust/chains/tw_zcash/src/entry.rs | 17 ++- rust/chains/tw_zcash/src/lib.rs | 2 + rust/chains/tw_zcash/src/t_address.rs | 5 +- rust/chains/tw_zcash/src/tex_address.rs | 126 ++++++++++++++++++ rust/chains/tw_zcash/tests/tex_address.rs | 21 +++ .../tests/chains/zcash/zcash_address.rs | 24 ++++ .../tw_tests/tests/chains/zcash/zcash_sign.rs | 82 +++++++++++- src/Bitcoin/Script.cpp | 5 + src/Zcash/Entry.cpp | 9 +- src/Zcash/TexAddress.cpp | 41 ++++++ src/Zcash/TexAddress.h | 39 ++++++ tests/chains/Zcash/AddressTests.cpp | 9 ++ tests/chains/Zcash/TWZcashAddressTests.cpp | 9 ++ .../chains/Zcash/TWZcashTransactionTests.cpp | 41 ++++++ tests/interface/TWHRPTests.cpp | 2 +- 20 files changed, 532 insertions(+), 15 deletions(-) create mode 100644 rust/chains/tw_zcash/src/address.rs create mode 100644 rust/chains/tw_zcash/src/tex_address.rs create mode 100644 rust/chains/tw_zcash/tests/tex_address.rs create mode 100644 src/Zcash/TexAddress.cpp create mode 100644 src/Zcash/TexAddress.h diff --git a/registry.json b/registry.json index 4568ea9a4f0..8e3fa4fbaac 100644 --- a/registry.json +++ b/registry.json @@ -1343,6 +1343,7 @@ "p2shPrefix": 189, "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", + "hrp": "tex", "explorer": { "url": "https://blockchair.com/zcash", "txPath": "/transaction/", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 52d2b3813ba..06a94a4a6b5 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2430,7 +2430,9 @@ dependencies = [ name = "tw_zcash" version = "0.1.0" dependencies = [ + "bech32", "tw_base58_address", + "tw_bech32_address", "tw_bitcoin", "tw_coin_entry", "tw_encoding", diff --git a/rust/chains/tw_zcash/Cargo.toml b/rust/chains/tw_zcash/Cargo.toml index 9cc7ed926b9..0a2f4a849a8 100644 --- a/rust/chains/tw_zcash/Cargo.toml +++ b/rust/chains/tw_zcash/Cargo.toml @@ -4,7 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] +bech32 = "0.9.1" tw_base58_address = { path = "../../tw_base58_address" } +tw_bech32_address = { path = "../../tw_bech32_address" } tw_bitcoin = { path = "../../chains/tw_bitcoin" } tw_coin_entry = { path = "../../tw_coin_entry" } tw_encoding = { path = "../../tw_encoding" } diff --git a/rust/chains/tw_zcash/src/address.rs b/rust/chains/tw_zcash/src/address.rs new file mode 100644 index 00000000000..40caf70c6c1 --- /dev/null +++ b/rust/chains/tw_zcash/src/address.rs @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::t_address::TAddress; +use crate::tex_address::TexAddress; +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::{AddressError, AddressResult}; +use tw_keypair::ecdsa; +use tw_memory::Data; +use tw_utxo::address::standard_bitcoin::StandardBitcoinPrefix; + +pub enum ZcashAddress { + T(TAddress), + Tex(TexAddress), +} + +impl ZcashAddress { + pub fn from_str_with_coin_and_prefix( + coin: &dyn CoinContext, + address_str: &str, + prefix: Option, + ) -> AddressResult { + // Check whether the prefix is set and specifies the legacy address prefixes. + match prefix { + Some(StandardBitcoinPrefix::Base58(prefix)) => { + return TAddress::from_str_with_coin_and_prefix(coin, address_str, Some(prefix)) + .map(ZcashAddress::T); + }, + Some(StandardBitcoinPrefix::Bech32(prefix)) => { + return TexAddress::from_str_with_coin_and_prefix(coin, address_str, Some(prefix)) + .map(ZcashAddress::Tex); + }, + None => (), + } + + // Otherwise, try to parse address as either Cash or Legacy. + if let Ok(t) = TAddress::from_str_with_coin_and_prefix(coin, address_str, None) { + return Ok(ZcashAddress::T(t)); + } + if let Ok(tex) = TexAddress::from_str_with_coin_and_prefix(coin, address_str, None) { + return Ok(ZcashAddress::Tex(tex)); + } + Err(AddressError::InvalidInput) + } + + pub fn address_with_coin_and_prefix( + coin: &dyn CoinContext, + public_key: &ecdsa::secp256k1::PublicKey, + prefix: Option, + ) -> AddressResult { + match prefix { + // Check whether the prefix is set and specifies the legacy address prefixes. + Some(StandardBitcoinPrefix::Base58(prefix)) => { + TAddress::p2pkh_with_public_key(prefix.p2pkh, public_key).map(ZcashAddress::T) + }, + Some(StandardBitcoinPrefix::Bech32(prefix)) => { + TexAddress::with_public_key(prefix.hrp, public_key).map(ZcashAddress::Tex) + }, + None => { + let p2pkh_prefix = coin.p2pkh_prefix().ok_or(AddressError::InvalidRegistry)?; + TAddress::p2pkh_with_public_key(p2pkh_prefix, public_key).map(ZcashAddress::T) + }, + } + } +} + +impl FromStr for ZcashAddress { + type Err = AddressError; + + fn from_str(address_str: &str) -> Result { + if let Ok(t) = TAddress::from_str(address_str) { + return Ok(ZcashAddress::T(t)); + } + if let Ok(tex) = TexAddress::from_str(address_str) { + return Ok(ZcashAddress::Tex(tex)); + } + Err(AddressError::InvalidInput) + } +} + +impl CoinAddress for ZcashAddress { + fn data(&self) -> Data { + match self { + ZcashAddress::T(t) => t.data(), + ZcashAddress::Tex(tex) => tex.data(), + } + } +} + +impl fmt::Display for ZcashAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ZcashAddress::T(t) => write!(f, "{t}"), + ZcashAddress::Tex(tex) => write!(f, "{tex}"), + } + } +} diff --git a/rust/chains/tw_zcash/src/context.rs b/rust/chains/tw_zcash/src/context.rs index 001fc40b18b..01156600eb9 100644 --- a/rust/chains/tw_zcash/src/context.rs +++ b/rust/chains/tw_zcash/src/context.rs @@ -2,10 +2,10 @@ // // Copyright © 2017 Trust Wallet. +use crate::address::ZcashAddress; use crate::modules::protobuf_builder::ZcashProtobufBuilder; use crate::modules::signing_request::ZcashSigningRequestBuilder; use crate::modules::zcash_fee_estimator::ZcashFeeEstimator; -use crate::t_address::TAddress; use crate::transaction::ZcashTransaction; use tw_bitcoin::context::BitcoinSigningContext; use tw_bitcoin::modules::psbt_request::NoPsbtRequestBuilder; @@ -17,7 +17,7 @@ use tw_utxo::script::Script; pub struct ZcashContext; impl UtxoContext for ZcashContext { - type Address = TAddress; + type Address = ZcashAddress; type Transaction = ZcashTransaction; type FeeEstimator = ZcashFeeEstimator; @@ -25,7 +25,10 @@ impl UtxoContext for ZcashContext { addr: &Self::Address, prefixes: AddressPrefixes, ) -> SigningResult