From af67cae30f132ec139b72b4b93d2b336b84a8c49 Mon Sep 17 00:00:00 2001 From: 10gic Date: Thu, 29 Aug 2024 16:07:41 +0800 Subject: [PATCH] [TON] Support TON tx compiler (#3995) * Support TON tx compiler * Adjust the code according to the comments --- rust/chains/tw_ton/Cargo.toml | 1 + rust/chains/tw_ton/src/compiler.rs | 65 +++++++++++-- rust/chains/tw_ton/src/signer.rs | 4 +- rust/chains/tw_ton/src/wallet/mod.rs | 21 +++- .../tests/chains/ton/ton_compile.rs | 97 ++++++++++++++++--- .../TheOpenNetwork/TWAnySignerTests.cpp | 4 +- 6 files changed, 165 insertions(+), 27 deletions(-) diff --git a/rust/chains/tw_ton/Cargo.toml b/rust/chains/tw_ton/Cargo.toml index 10bfc3d0297..076a7d84f61 100644 --- a/rust/chains/tw_ton/Cargo.toml +++ b/rust/chains/tw_ton/Cargo.toml @@ -11,5 +11,6 @@ tw_hash = { path = "../../tw_hash" } tw_keypair = { path = "../../tw_keypair" } tw_memory = { path = "../../tw_memory" } tw_number = { path = "../../tw_number" } +tw_misc = { path = "../../tw_misc" } tw_proto = { path = "../../tw_proto" } tw_ton_sdk = { path = "../../frameworks/tw_ton_sdk" } diff --git a/rust/chains/tw_ton/src/compiler.rs b/rust/chains/tw_ton/src/compiler.rs index 060e6879acd..2bcea271351 100644 --- a/rust/chains/tw_ton/src/compiler.rs +++ b/rust/chains/tw_ton/src/compiler.rs @@ -2,12 +2,20 @@ // // Copyright © 2017 Trust Wallet. +use crate::signing_request::builder::SigningRequestBuilder; +use crate::signing_request::cell_creator::ExternalMessageCreator; +use std::borrow::Cow; use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; use tw_coin_entry::error::prelude::*; use tw_coin_entry::signing_output_error; +use tw_keypair::ed25519::Signature; use tw_proto::TheOpenNetwork::Proto; use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_ton_sdk::boc::BagOfCells; +use tw_ton_sdk::error::cell_to_signing_error; + +pub(crate) const HAS_CRC32: bool = true; pub struct TheOpenNetworkCompiler; @@ -23,10 +31,18 @@ impl TheOpenNetworkCompiler { fn preimage_hashes_impl( _coin: &dyn CoinContext, - _input: Proto::SigningInput<'_>, + input: Proto::SigningInput<'_>, ) -> SigningResult> { - SigningError::err(SigningErrorType::Error_not_supported) - .context("Transaction pre-image hashing is not supported for TON blockchain yet") + let signing_request = SigningRequestBuilder::build(&input)?; + + let external_message = + ExternalMessageCreator::create_external_message_to_sign(&signing_request) + .map_err(cell_to_signing_error)?; + + Ok(CompilerProto::PreSigningOutput { + data: Cow::from(external_message.cell_hash().to_vec()), + ..CompilerProto::PreSigningOutput::default() + }) } #[inline] @@ -42,11 +58,46 @@ impl TheOpenNetworkCompiler { fn compile_impl( _coin: &dyn CoinContext, - _input: Proto::SigningInput<'_>, - _signatures: Vec, + input: Proto::SigningInput<'_>, + signatures: Vec, _public_keys: Vec, ) -> SigningResult> { - SigningError::err(SigningErrorType::Error_not_supported) - .context("Transaction compiling is not supported for TON blockchain yet") + if signatures.len() != 1 { + return TWError::err(SigningErrorType::Error_signatures_count) + .context("Expected exactly one signature"); + } + let signature = Signature::try_from(signatures[0].as_slice())?; + + let signing_request = SigningRequestBuilder::build(&input)?; + + let external_message = + ExternalMessageCreator::create_external_message_to_sign(&signing_request) + .map_err(cell_to_signing_error)?; + + let signed_external_message = signing_request + .wallet + .compile_signed_external_message(external_message, signature)?; + + // Whether to add 'StateInit' reference. + let state_init = signing_request.seqno == 0; + let signed_tx = signing_request + .wallet + .compile_transaction(signed_external_message, state_init) + .context("Error compiling an external message")? + .build() + .context("Error generating signed message cell") + .map_err(cell_to_signing_error)?; + + let signed_tx_hash = signed_tx.cell_hash(); + let signed_tx_encoded = BagOfCells::from_root(signed_tx) + .to_base64(HAS_CRC32) + .context("Error serializing signed transaction as BoC") + .map_err(cell_to_signing_error)?; + + Ok(Proto::SigningOutput { + encoded: signed_tx_encoded.into(), + hash: signed_tx_hash.to_vec().into(), + ..Proto::SigningOutput::default() + }) } } diff --git a/rust/chains/tw_ton/src/signer.rs b/rust/chains/tw_ton/src/signer.rs index 3a1417b7088..44306434b73 100644 --- a/rust/chains/tw_ton/src/signer.rs +++ b/rust/chains/tw_ton/src/signer.rs @@ -2,17 +2,17 @@ // // Copyright © 2017 Trust Wallet. +use crate::compiler::HAS_CRC32; use crate::signing_request::builder::SigningRequestBuilder; use crate::signing_request::cell_creator::ExternalMessageCreator; use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::error::prelude::*; use tw_coin_entry::signing_output_error; +use tw_misc::traits::ToBytesVec; use tw_proto::TheOpenNetwork::Proto; use tw_ton_sdk::boc::BagOfCells; use tw_ton_sdk::error::cell_to_signing_error; -const HAS_CRC32: bool = true; - pub struct TheOpenNetworkSigner; impl TheOpenNetworkSigner { diff --git a/rust/chains/tw_ton/src/wallet/mod.rs b/rust/chains/tw_ton/src/wallet/mod.rs index 4266ccec612..8b4a01311b7 100644 --- a/rust/chains/tw_ton/src/wallet/mod.rs +++ b/rust/chains/tw_ton/src/wallet/mod.rs @@ -8,6 +8,7 @@ use crate::message::signed_message::signed_message_v4::SignedMessageV4; use crate::message::signed_message::signed_message_v5::SignedMessageV5; use crate::transaction::SignedTransaction; use tw_coin_entry::error::prelude::*; +use tw_keypair::ed25519::Signature; use tw_keypair::traits::SigningKeyTrait; use tw_number::U256; use tw_ton_sdk::cell::Cell; @@ -66,6 +67,14 @@ impl VersionedTonWallet { .context("'TonWallet' should be initialized with a key-pair to be able to sign a message")? .sign(message_hash.to_vec())?; + self.compile_signed_external_message(external_message, sig) + } + + pub fn compile_signed_external_message( + &self, + external_message: Cell, + sig: Signature, + ) -> SigningResult { match self { Self::V4R2(_) => Ok(SignedMessageV4 { signature: sig.to_bytes(), @@ -87,6 +96,15 @@ impl VersionedTonWallet { &self, external_message: Cell, state_init: bool, + ) -> SigningResult { + let signed_external_message = self.sign_external_message(external_message.clone())?; + self.compile_transaction(signed_external_message, state_init) + } + + pub fn compile_transaction( + &self, + signed_external_message: Cell, + state_init: bool, ) -> SigningResult { let state_init = if state_init { let state_init = self.state_init().map_err(cell_to_signing_error)?; @@ -95,14 +113,13 @@ impl VersionedTonWallet { None }; - let signed_body = self.sign_external_message(external_message)?; Ok(SignedTransaction { src_address: TonAddress::null(), // The wallet contract address. dest_address: self.address().clone(), import_fee: U256::zero(), state_init, - signed_body, + signed_body: signed_external_message, }) } } diff --git a/rust/tw_any_coin/tests/chains/ton/ton_compile.rs b/rust/tw_any_coin/tests/chains/ton/ton_compile.rs index 43fa1e9daf6..b13d6fc472e 100644 --- a/rust/tw_any_coin/tests/chains/ton/ton_compile.rs +++ b/rust/tw_any_coin/tests/chains/ton/ton_compile.rs @@ -2,35 +2,104 @@ // // Copyright © 2017 Trust Wallet. +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; +use tw_encoding::hex::{DecodeHex, ToHex}; use tw_proto::Common::Proto::SigningError; use tw_proto::TheOpenNetwork::Proto; use tw_proto::TxCompiler::Proto as CompilerProto; #[test] -fn test_ton_compile_not_supported() { - let input = Proto::SigningInput::default(); +fn test_ton_compile_wallet_v4r2_transfer_and_deploy() { + let public_key = "f42c77f931bea20ec5d0150731276bbb2e2860947661245b2319ef8133ee8d41"; + + let transfer = Proto::Transfer { + dest: "EQDYW_1eScJVxtitoBRksvoV9cCYo4uKGWLVNIHB1JqRR3n0".into(), + amount: 10, + mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 + | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, + bounceable: true, + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + public_key: public_key.decode_hex().unwrap().into(), + messages: vec![transfer], + expire_at: 1671135440, + wallet_version: Proto::WalletVersion::WALLET_V4_R2, + ..Proto::SigningInput::default() + }; // Step 2: Obtain preimage hash let mut pre_imager = PreImageHelper::::default(); let preimage_output = pre_imager.pre_image_hashes(CoinType::TON, &input); - assert_eq!(preimage_output.error, SigningError::Error_not_supported); + assert_eq!(preimage_output.error, SigningError::OK); + assert_eq!( + preimage_output.data.to_hex(), + "9da0ab29120fd96b48b2bb76843e77c0a1770b47c2f78da23299e2c32f50192e" + ); // Step 3: Compile transaction info - let signature_bytes = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".decode_hex().unwrap(); - let public_key = "0000000000000000000000000000000000000000000000000000000000000000" - .decode_hex() - .unwrap(); + + // Simulate signature, normally obtained from signature server. + let signature_bytes = "e7c6f18186845784fa20c4d63c4261e58a1256c60e6de8aa6da9c008665564b35afe67eaf56af0963427aa78b0bfa1a895378983c7f135f99862df71b6e6b708".decode_hex().unwrap(); let mut compiler = CompilerHelper::::default(); - let output = compiler.compile( - CoinType::TON, - &input, - vec![signature_bytes], - vec![public_key], + let output = compiler.compile(CoinType::TON, &input, vec![signature_bytes], vec![]); + + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); + // Successfully broadcasted: https://tonviewer.com/transaction/6ZzWOFKZt_m3kZjbwfbATwLaVwmUOdDp0xjhuY7PO3k= + assert_eq_boc(&output.encoded, "te6ccgICABoAAQAAA8sAAAJFiADN98eLgHfrkE8l8gmT8X5REpTVR6QnqDhArTbKlVvbZh4ABAABAZznxvGBhoRXhPogxNY8QmHlihJWxg5t6KptqcAIZlVks1r+Z+r1avCWNCeqeLC/oaiVN4mDx/E1+Zhi33G25rcIKamjF/////8AAAAAAAMAAgFiYgBsLf6vJOEq42xW0AoyWX0K+uBMUcXFDLFqmkDg6k1Io4hQAAAAAAAAAAAAAAAAAQADAAACATQABgAFAFEAAAAAKamjF/Qsd/kxvqIOxdAVBzEna7suKGCUdmEkWyMZ74Ez7o1BQAEU/wD0pBP0vPLICwAHAgEgAA0ACAT48oMI1xgg0x/TH9MfAvgju/Jk7UTQ0x/TH9P/9ATRUUO68qFRUbryogX5AVQQZPkQ8qP4ACSkyMsfUkDLH1Iwy/9SEPQAye1U+A8B0wchwACfbFGTINdKltMH1AL7AOgw4CHAAeMAIcAC4wABwAORMOMNA6TIyx8Syx/L/wAMAAsACgAJAAr0AMntVABsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBu0gf6ANTUIvkABcjKBxXL/8nQd3SAGMjLBcsCIs8WUAX6AhTLaxLMzMlz+wDIQBSBAQj0UfKnAgIBSAAXAA4CASAAEAAPAFm9JCtvaiaECAoGuQ+gIYRw1AgIR6STfSmRDOaQPp/5g3gSgBt4EBSJhxWfMYQCASAAEgARABG4yX7UTQ1wsfgCAVgAFgATAgEgABUAFAAZrx32omhAEGuQ64WPwAAZrc52omhAIGuQ64X/wAA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYALm0AHQ0wMhcbCSXwTgItdJwSCSXwTgAtMfIYIQcGx1Z70ighBkc3RyvbCSXwXgA/pAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8H4AXTP8glghBwbHVnupI4MOMNA4IQZHN0crqSXwbjDQAZABgAilAEgQEI9Fkw7UTQgQFA1yDIAc8W9ADJ7VQBcrCOI4IQZHN0coMesXCAGFAFywVQA88WI/oCE8tqyx/LP8mAQPsAkl8D4gB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAG"); + assert_eq!( + output.hash.to_hex(), + "b3d9462c13a8c67e19b62002447839c386de51415ace3ff6473b1e6294299819" ); +} - assert_eq!(output.error, SigningError::Error_not_supported); +#[test] +fn test_ton_compile_wallet_v5r1_transfer_and_deploy() { + let public_key = "55b837ceefc94d1865b763149c4751b43e297cea65dabfe20febed13d56072e4"; + + let transfer = Proto::Transfer { + dest: "EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu".into(), + amount: 10, + mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 + | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, + bounceable: true, + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + public_key: public_key.decode_hex().unwrap().into(), + messages: vec![transfer], + expire_at: 0xffffffff, + wallet_version: Proto::WalletVersion::WALLET_V5_R1, + ..Proto::SigningInput::default() + }; + + // Step 2: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::TON, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + assert_eq!( + preimage_output.data.to_hex(), + "d3b8dbedb049074d90bcae341484e0385789e19f69cd86d570939b94043739a7" + ); + + // Step 3: Compile transaction info + + // Simulate signature, normally obtained from signature server. + let signature_bytes = "00239fa98ecbde5dda29ce7c78dc6e25369e1ff7e0070d9a1b472833e8630e98d684929b60a9ac003f2fd39e07612b8eeaf3776d09d5e54d7d061b067ded4c0e".decode_hex().unwrap(); + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile(CoinType::TON, &input, vec![signature_bytes], vec![]); + + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); + // Successfully broadcasted: https://tonviewer.com/transaction/6ZzWOFKZt_m3kZjbwfbATwLaVwmUOdDp0xjhuY7PO3k= + assert_eq_boc(&output.encoded, "te6cckECGwEAA2sAAkWIACm9HPyVOpjCNOG6nbf+EwCONRHHpeMQsIlCoWNKhUaaHgECAgE0AwQBoXNpZ25///8R/////wAAAACACOfqY7L3l3aKc58eNxuJTaeH/fgBw2aG0coM+hjDpjWhJKbYKmsAD8v054HYSuO6vN3bQnV5U19BhsGfe1MDoAUBFP8A9KQT9LzyyAsGAFGAAAAAP///iKrcG+d35KaMMtuxik4jqNofFL51Mu1f8Qf19onqsDlyIAIKDsPIbQMWBwIBIAgJAWJiAC90HaFSSy94Zxb85WYu97uTKIxAlLvolycpcYkWc+8giFAAAAAAAAAAAAAAAAABFgIBSAoLAQLyDALc0CDXScEgkVuPYyDXCx8gghBleHRuvSGCEHNpbnS9sJJfA+CCEGV4dG66jrSAINchAdB01yH6QDD6RPgo+kQwWL2RW+DtRNCBAUHXIfQFgwf0Dm+hMZEw4YBA1yFwf9s84DEg10mBAoC5kTDgcOIXDQIBIA4PAR4g1wsfghBzaWduuvLgin8NAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYFwIBIBARABm+Xw9qJoQICg65D6AsAgFuEhMCAUgUFQAZrc52omhAIOuQ64X/wAAZrx32omhAEOuQ64WPwAAXsyX7UTQcdch1wsfgABGyYvtRNDXCgCAAAAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKGBkaAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNB8Ui06"); + assert_eq!( + output.hash.to_hex(), + "437dae441a95a6bccdcdcea2560c313de24f13dd85c76d5d7ecaab1e70a1e52b" + ); } diff --git a/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp b/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp index 9a9ea9e3ade..8bdb62fd2bb 100644 --- a/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp +++ b/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp @@ -12,7 +12,7 @@ namespace TW::TheOpenNetwork::tests { -TEST(TWAnySignerTheOpenNetwork, SingMessageToTransferAndDeployWalletV4R2) { +TEST(TWAnySignerTheOpenNetwork, SignMessageToTransferAndDeployWalletV4R2) { Proto::SigningInput input; auto& transfer = *input.add_messages(); @@ -37,7 +37,7 @@ TEST(TWAnySignerTheOpenNetwork, SingMessageToTransferAndDeployWalletV4R2) { ASSERT_EQ(output.encoded(), "te6cckECGgEAA7IAAkWIAM33x4uAd+uQTyXyCZPxflESlNVHpCeoOECtNsqVW9tmHgECAgE0AwQBnOfG8YGGhFeE+iDE1jxCYeWKElbGDm3oqm2pwAhmVWSzWv5n6vVq8JY0J6p4sL+hqJU3iYPH8TX5mGLfcbbmtwgpqaMX/////wAAAAAAAwUBFP8A9KQT9LzyyAsGAFEAAAAAKamjF/Qsd/kxvqIOxdAVBzEna7suKGCUdmEkWyMZ74Ez7o1BQAFiYgBsLf6vJOEq42xW0AoyWX0K+uBMUcXFDLFqmkDg6k1Io4hQAAAAAAAAAAAAAAAAAQcCASAICQAAAgFICgsE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8MDQ4PAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNEBECASASEwBu0gf6ANTUIvkABcjKBxXL/8nQd3SAGMjLBcsCIs8WUAX6AhTLaxLMzMlz+wDIQBSBAQj0UfKnAgBwgQEI1xj6ANM/yFQgR4EBCPRR8qeCEG5vdGVwdIAYyMsFywJQBs8WUAT6AhTLahLLH8s/yXP7AAIAbIEBCNcY+gDTPzBSJIEBCPRZ8qeCEGRzdHJwdIAYyMsFywJQBc8WUAP6AhPLassfEss/yXP7AAAK9ADJ7VQAeAH6APQEMPgnbyIwUAqhIb7y4FCCEHBsdWeDHrFwgBhQBMsFJs8WWPoCGfQAy2kXyx9SYMs/IMmAQPsABgCKUASBAQj0WTDtRNCBAUDXIMgBzxb0AMntVAFysI4jghBkc3Rygx6xcIAYUAXLBVADzxYj+gITy2rLH8s/yYBA+wCSXwPiAgEgFBUAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAIBWBYXABG4yX7UTQ1wsfgAPbKd+1E0IEBQNch9AQwAsjKB8v/ydABgQEI9ApvoTGACASAYGQAZrc52omhAIGuQ64X/wAAZrx32omhAEGuQ64WPwJiaP4Q="); } -TEST(TWAnySignerTheOpenNetwork, SingMessageToTransferAndDeployWalletV5R1) { +TEST(TWAnySignerTheOpenNetwork, SignMessageToTransferAndDeployWalletV5R1) { Proto::SigningInput input; auto& transfer = *input.add_messages();