Skip to content

Commit

Permalink
[TON] Support TON tx compiler (#3995)
Browse files Browse the repository at this point in the history
* Support TON tx compiler

* Adjust the code according to the comments
  • Loading branch information
10gic authored Aug 29, 2024
1 parent 75c73b1 commit af67cae
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 27 deletions.
1 change: 1 addition & 0 deletions rust/chains/tw_ton/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
65 changes: 58 additions & 7 deletions rust/chains/tw_ton/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -23,10 +31,18 @@ impl TheOpenNetworkCompiler {

fn preimage_hashes_impl(
_coin: &dyn CoinContext,
_input: Proto::SigningInput<'_>,
input: Proto::SigningInput<'_>,
) -> SigningResult<CompilerProto::PreSigningOutput<'static>> {
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]
Expand All @@ -42,11 +58,46 @@ impl TheOpenNetworkCompiler {

fn compile_impl(
_coin: &dyn CoinContext,
_input: Proto::SigningInput<'_>,
_signatures: Vec<SignatureBytes>,
input: Proto::SigningInput<'_>,
signatures: Vec<SignatureBytes>,
_public_keys: Vec<PublicKeyBytes>,
) -> SigningResult<Proto::SigningOutput<'static>> {
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()
})
}
}
4 changes: 2 additions & 2 deletions rust/chains/tw_ton/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
21 changes: 19 additions & 2 deletions rust/chains/tw_ton/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Cell> {
match self {
Self::V4R2(_) => Ok(SignedMessageV4 {
signature: sig.to_bytes(),
Expand All @@ -87,6 +96,15 @@ impl VersionedTonWallet {
&self,
external_message: Cell,
state_init: bool,
) -> SigningResult<SignedTransaction> {
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<SignedTransaction> {
let state_init = if state_init {
let state_init = self.state_init().map_err(cell_to_signing_error)?;
Expand All @@ -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,
})
}
}
97 changes: 83 additions & 14 deletions rust/tw_any_coin/tests/chains/ton/ton_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<CompilerProto::PreSigningOutput>::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::<Proto::SigningOutput>::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::<CompilerProto::PreSigningOutput>::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::<Proto::SigningOutput>::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"
);
}
4 changes: 2 additions & 2 deletions tests/chains/TheOpenNetwork/TWAnySignerTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace TW::TheOpenNetwork::tests {

TEST(TWAnySignerTheOpenNetwork, SingMessageToTransferAndDeployWalletV4R2) {
TEST(TWAnySignerTheOpenNetwork, SignMessageToTransferAndDeployWalletV4R2) {
Proto::SigningInput input;

auto& transfer = *input.add_messages();
Expand All @@ -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();
Expand Down

0 comments on commit af67cae

Please sign in to comment.