diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 22c05fba..10977b44 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -207,6 +207,7 @@ set(SMC_ENVELOPE_SOURCE smc-envelope/TestGiver.cpp smc-envelope/TestWallet.cpp smc-envelope/Wallet.cpp + smc-envelope/WalletV3.cpp smc-envelope/GenericAccount.h smc-envelope/MultisigWallet.h @@ -215,6 +216,7 @@ set(SMC_ENVELOPE_SOURCE smc-envelope/TestGiver.h smc-envelope/TestWallet.h smc-envelope/Wallet.h + smc-envelope/WalletV3.h ) set(ED25519_TEST_SOURCE diff --git a/crypto/smartcont/wallet-v3.fif b/crypto/smartcont/wallet-v3.fif new file mode 100644 index 00000000..b23f941a --- /dev/null +++ b/crypto/smartcont/wallet-v3.fif @@ -0,0 +1,50 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include + +{ ."usage: " @' $0 type ." [-B ] []" cr + ."Creates a request to advanced wallet created by new-wallet-v3.fif, with private key loaded from file .pk " + ."and address from .addr, and saves it into .boc ('wallet-query.boc' by default)" cr 1 halt +} : usage +def? $7 { @' $6 "-B" $= { @' $7 =: body-boc-file [forget] $7 def? $8 { @' $8 =: $6 [forget] $8 } { [forget] $6 } cond + @' $# 2- =: $# } if } if +$# dup 5 < swap 6 > or ' usage if + +true constant bounce + +$1 =: file-base +$2 bounce parse-load-address =: bounce 2=: dest_addr +$3 parse-int =: subwallet_id +$4 parse-int =: seqno +$5 $>GR =: amount +def? $6 { @' $6 } { "wallet-query" } cond constant savefile +3 constant send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors +60 constant timeout // external message expires in 60 seconds + +file-base +".addr" load-address +2dup 2constant wallet_addr +."Source wallet address = " 2dup .addr cr 6 .Addr cr +file-base +".pk" load-keypair nip constant wallet_pk + +def? body-boc-file { @' body-boc-file file>B B>boc } { } cond +constant body-cell + +."Transferring " amount .GR ."to account " +dest_addr 2dup bounce 7 + .Addr ." = " .addr +."subwallet_id=0x" subwallet_id x. +."seqno=0x" seqno x. ."bounce=" bounce . cr +."Body of transfer message is " body-cell + +dup ."signing message: " +dup ."resulting external message: " B dup Bx. cr +savefile +".boc" tuck B>file +."Query expires in " timeout . ."seconds" cr +."(Saved to file " type .")" cr diff --git a/crypto/smc-envelope/WalletV3.cpp b/crypto/smc-envelope/WalletV3.cpp new file mode 100644 index 00000000..db39c725 --- /dev/null +++ b/crypto/smc-envelope/WalletV3.cpp @@ -0,0 +1,130 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#include "WalletV3.h" +#include "GenericAccount.h" + +#include "vm/boc.h" +#include "vm/cells/CellString.h" +#include "td/utils/base64.h" + +#include + +namespace ton { +td::Ref WalletV3::get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept { + auto code = get_init_code(); + auto data = get_init_data(public_key, wallet_id); + return GenericAccount::get_init_state(std::move(code), std::move(data)); +} + +td::Ref WalletV3::get_init_message(const td::Ed25519::PrivateKey& private_key, + td::uint32 wallet_id) noexcept { + td::uint32 seqno = 0; + td::uint32 valid_until = std::numeric_limits::max(); + auto signature = private_key + .sign(vm::CellBuilder() + .store_long(wallet_id, 32) + .store_long(valid_until, 32) + .store_long(seqno, 32) + .finalize() + ->get_hash() + .as_slice()) + .move_as_ok(); + return vm::CellBuilder() + .store_bytes(signature) + .store_long(wallet_id, 32) + .store_long(valid_until, 32) + .store_long(seqno, 32) + .finalize(); +} + +td::Ref WalletV3::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, + td::uint32 seqno, td::uint32 valid_until, td::int64 gramms, + td::Slice message, const block::StdAddress& dest_address) noexcept { + td::int32 send_mode = 3; + if (gramms == -1) { + gramms = 0; + send_mode += 128; + } + vm::CellBuilder cb; + GenericAccount::store_int_message(cb, dest_address, gramms); + cb.store_bytes("\0\0\0\0", 4); + vm::CellString::store(cb, message, 35 * 8).ensure(); + auto message_inner = cb.finalize(); + + auto message_outer = vm::CellBuilder() + .store_long(wallet_id, 32) + .store_long(valid_until, 32) + .store_long(seqno, 32) + .store_long(send_mode, 8) + .store_ref(message_inner) + .finalize(); + auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok(); + return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); +} + +td::Ref WalletV3::get_init_code() noexcept { + static auto res = [] { + auto serialized_code = td::base64_decode( + "te6ccgEBAQEAYgAAwP8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/" + "9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA==") + .move_as_ok(); + return vm::std_boc_deserialize(serialized_code).move_as_ok(); + }(); + return res; +} + +vm::CellHash WalletV3::get_init_code_hash() noexcept { + return get_init_code()->get_hash(); +} + +td::Ref WalletV3::get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept { + return vm::CellBuilder() + .store_long(0, 32) + .store_long(wallet_id, 32) + .store_bytes(public_key.as_octet_string()) + .finalize(); +} + +td::Result WalletV3::get_seqno() const { + return TRY_VM(get_seqno_or_throw()); +} + +td::Result WalletV3::get_seqno_or_throw() const { + if (state_.data.is_null()) { + return 0; + } + //FIXME use get method + return static_cast(vm::load_cell_slice(state_.data).fetch_ulong(32)); +} + +td::Result WalletV3::get_wallet_id() const { + return TRY_VM(get_wallet_id_or_throw()); +} + +td::Result WalletV3::get_wallet_id_or_throw() const { + if (state_.data.is_null()) { + return 0; + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(32); + return static_cast(cs.fetch_ulong(32)); +} + +} // namespace ton diff --git a/crypto/smc-envelope/WalletV3.h b/crypto/smc-envelope/WalletV3.h new file mode 100644 index 00000000..a6e4162d --- /dev/null +++ b/crypto/smc-envelope/WalletV3.h @@ -0,0 +1,50 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#pragma once + +#include "smc-envelope/SmartContract.h" +#include "vm/cells.h" +#include "Ed25519.h" +#include "block/block.h" +#include "vm/cells/CellString.h" + +namespace ton { +class WalletV3 : ton::SmartContract { + public: + explicit WalletV3(State state) : ton::SmartContract(std::move(state)) { + } + static constexpr unsigned max_message_size = vm::CellString::max_bytes; + static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept; + static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id) noexcept; + static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, + td::uint32 seqno, td::uint32 valid_until, td::int64 gramms, + td::Slice message, const block::StdAddress& dest_address) noexcept; + + static td::Ref get_init_code() noexcept; + static vm::CellHash get_init_code_hash() noexcept; + static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept; + + td::Result get_seqno() const; + td::Result get_wallet_id() const; + + private: + td::Result get_seqno_or_throw() const; + td::Result get_wallet_id_or_throw() const; +}; +} // namespace ton diff --git a/crypto/test/test-smartcont.cpp b/crypto/test/test-smartcont.cpp index f9dd8652..c0b991b6 100644 --- a/crypto/test/test-smartcont.cpp +++ b/crypto/test/test-smartcont.cpp @@ -34,6 +34,7 @@ #include "smc-envelope/TestGiver.h" #include "smc-envelope/TestWallet.h" #include "smc-envelope/Wallet.h" +#include "smc-envelope/WalletV3.h" #include "td/utils/base64.h" #include "td/utils/crypto.h" @@ -114,6 +115,33 @@ SETCP0 DUP IFNOTRET // return if recv_internal )ABCD"; return fift::compile_asm(code).move_as_ok(); } +td::Ref get_wallet_v3_source() { + std::string code = R"ABCD( +SETCP0 DUP IFNOTRET // return if recv_internal + DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method + DROP c4 PUSHCTR CTOS 32 PLDU // cnt + }> + INC 32 THROWIF // fail unless recv_external + 9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU 32 LDU // signature in_msg subwallet_id valid_until msg_seqno cs + NOW s1 s3 XCHG LEQ 35 THROWIF // signature in_msg subwallet_id cs msg_seqno + c4 PUSH CTOS 32 LDU 32 LDU 256 LDU ENDS // signature in_msg subwallet_id cs msg_seqno stored_seqno stored_subwallet public_key + s3 s2 XCPU EQUAL 33 THROWIFNOT // signature in_msg subwallet_id cs public_key stored_seqno stored_subwallet + s4 s4 XCPU EQUAL 34 THROWIFNOT // signature in_msg stored_subwallet cs public_key stored_seqno + s0 s4 XCHG HASHSU // signature stored_seqno stored_subwallet cs public_key msg_hash + s0 s5 s5 XC2PU // public_key stored_seqno stored_subwallet cs msg_hash signature public_key + CHKSIGNU 35 THROWIFNOT // public_key stored_seqno stored_subwallet cs + ACCEPT + WHILE:<{ + DUP SREFS // public_key stored_seqno stored_subwallet cs _51 + }>DO<{ // public_key stored_seqno stored_subwallet cs + 8 LDU LDREF s0 s2 XCHG // public_key stored_seqno stored_subwallet cs _56 mode + SENDRAWMSG + }> // public_key stored_seqno stored_subwallet cs + ENDS SWAP INC // public_key stored_subwallet seqno' + NEWC 32 STU 32 STU 256 STU ENDC c4 POP +)ABCD"; + return fift::compile_asm(code).move_as_ok(); +} TEST(Tonlib, TestWallet) { LOG(ERROR) << td::base64_encode(std_boc_serialize(get_test_wallet_source()).move_as_ok()); @@ -209,6 +237,55 @@ TEST(Tonlib, Wallet) { CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash()); } +TEST(Tonlib, WalletV3) { + LOG(ERROR) << td::base64_encode(std_boc_serialize(get_wallet_v3_source()).move_as_ok()); + CHECK(get_wallet_v3_source()->get_hash() == ton::WalletV3::get_init_code()->get_hash()); + + auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet-v3.fif"), {"aba", "0", "239"}).move_as_ok(); + + auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data; + auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data; + auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data; + + td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}}; + auto pub_key = priv_key.get_public_key().move_as_ok(); + auto init_state = ton::WalletV3::get_init_state(pub_key, 239); + auto init_message = ton::WalletV3::get_init_message(priv_key, 239); + auto address = ton::GenericAccount::get_address(0, init_state); + + CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); + + td::Ref res = ton::GenericAccount::create_ext_message(address, init_state, init_message); + + LOG(ERROR) << "-------"; + vm::load_cell_slice(res).print_rec(std::cerr); + LOG(ERROR) << "-------"; + vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr); + CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash()); + + fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet-v3.fif")).ensure(); + class ZeroOsTime : public fift::OsTime { + public: + td::uint32 now() override { + return 0; + } + }; + fift_output.source_lookup.set_os_time(std::make_unique()); + auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok(); + fift_output = + fift::mem_run_fift(std::move(fift_output.source_lookup), + {"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "239", "123", "321"}) + .move_as_ok(); + auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; + auto gift_message = ton::GenericAccount::create_ext_message( + address, {}, ton::WalletV3::make_a_gift_message(priv_key, 239, 123, 60, 321000000000ll, "TESTv3", dest)); + LOG(ERROR) << "-------"; + vm::load_cell_slice(gift_message).print_rec(std::cerr); + LOG(ERROR) << "-------"; + vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr); + CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash()); +} + TEST(Tonlib, TestGiver) { auto address = block::StdAddress::parse("-1:60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0").move_as_ok(); diff --git a/tl/generate/scheme/tonlib_api.tl b/tl/generate/scheme/tonlib_api.tl index 01ab5c62..e6778df5 100644 --- a/tl/generate/scheme/tonlib_api.tl +++ b/tl/generate/scheme/tonlib_api.tl @@ -22,6 +22,7 @@ keyStoreTypeInMemory = KeyStoreType; config config:string blockchain_name:string use_callbacks_for_network:Bool ignore_cache:Bool = Config; options config:config keystore_type:KeyStoreType = Options; +options.configInfo default_wallet_id:int53 = options.ConfigInfo; key public_key:string secret:secureBytes = Key; inputKeyRegular key:key local_password:secureBytes = InputKey; @@ -50,6 +51,9 @@ testWallet.accountState balance:int64 seqno:int32 last_transaction_id:internal.t wallet.initialAccountState public_key:string = wallet.InitialAccountState; wallet.accountState balance:int64 seqno:int32 last_transaction_id:internal.transactionId sync_utime:int53 = wallet.AccountState; +wallet.v3.initialAccountState public_key:string wallet_id:int53 = wallet.v3.InitialAccountState; +wallet.v3.accountState balance:int64 wallet_id:int53 seqno:int32 last_transaction_id:internal.transactionId sync_utime:int53 = wallet.v3.AccountState; + testGiver.accountState balance:int64 seqno:int32 last_transaction_id:internal.transactionId sync_utime:int53= testGiver.AccountState; uninited.accountState balance:int64 last_transaction_id:internal.transactionId frozen_hash:bytes sync_utime:int53 = uninited.AccountState; @@ -61,6 +65,7 @@ uninited.accountState balance:int64 last_transaction_id:internal.transactionId f generic.accountStateRaw account_state:raw.accountState = generic.AccountState; generic.accountStateTestWallet account_state:testWallet.accountState = generic.AccountState; generic.accountStateWallet account_state:wallet.accountState = generic.AccountState; +generic.accountStateWalletV3 account_state:wallet.v3.accountState = generic.AccountState; generic.accountStateTestGiver account_state:testGiver.accountState = generic.AccountState; generic.accountStateUninited account_state:uninited.accountState = generic.AccountState; @@ -120,7 +125,7 @@ init options:options = Ok; close = Ok; options.setConfig config:config = Ok; -options.validateConfig config:config = Ok; +options.validateConfig config:config = options.ConfigInfo; createNewKey local_password:secureBytes mnemonic_password:secureBytes random_extra_seed:secureBytes = Key; deleteKey key:key = Ok; @@ -159,6 +164,8 @@ wallet.getAccountAddress initital_account_state:wallet.initialAccountState = Acc wallet.getAccountState account_address:accountAddress = wallet.AccountState; wallet.sendGrams private_key:InputKey destination:accountAddress seqno:int32 valid_until:int53 amount:int64 message:bytes = SendGramsResult; +wallet.v3.getAccountAddress initital_account_state:wallet.v3.initialAccountState = AccountAddress; + testGiver.getAccountState = testGiver.AccountState; testGiver.getAccountAddress = AccountAddress; testGiver.sendGrams destination:accountAddress seqno:int32 amount:int64 message:bytes = SendGramsResult; diff --git a/tl/generate/scheme/tonlib_api.tlo b/tl/generate/scheme/tonlib_api.tlo index 0e727228..a670da5a 100644 Binary files a/tl/generate/scheme/tonlib_api.tlo and b/tl/generate/scheme/tonlib_api.tlo differ diff --git a/tonlib/test/online.cpp b/tonlib/test/online.cpp index c96fc75e..8eabc452 100644 --- a/tonlib/test/online.cpp +++ b/tonlib/test/online.cpp @@ -133,9 +133,11 @@ void sync(Client& client) { sync_send(client, make_object()).ensure(); } +static td::uint32 default_wallet_id{0}; std::string wallet_address(Client& client, const Key& key) { - return sync_send(client, make_object( - make_object(key.public_key))) + return sync_send(client, + make_object( + make_object(key.public_key, default_wallet_id))) .move_as_ok() ->account_address_; } @@ -171,6 +173,7 @@ AccountState get_account_state(Client& client, std::string address) { case tonlib_api::generic_accountStateUninited::ID: res.type = AccountState::Empty; break; + case tonlib_api::generic_accountStateWalletV3::ID: case tonlib_api::generic_accountStateWallet::ID: res.type = AccountState::Wallet; break; @@ -358,8 +361,9 @@ Wallet create_empty_wallet(Client& client) { Wallet wallet{"", {key->public_key_, std::move(key->secret_)}}; auto account_address = - sync_send(client, make_object( - make_object(wallet.key.public_key))) + sync_send(client, + make_object( + make_object(wallet.key.public_key, default_wallet_id))) .move_as_ok(); wallet.address = account_address->account_address_; @@ -529,6 +533,10 @@ int main(int argc, char* argv[]) { Client client; { + auto info = sync_send(client, make_object( + make_object(global_config_str, "", false, false))) + .move_as_ok(); + default_wallet_id = static_cast(info->default_wallet_id_); sync_send(client, make_object(make_object( make_object(global_config_str, "", false, false), make_object(keystore_dir)))) diff --git a/tonlib/tonlib/TonlibClient.cpp b/tonlib/tonlib/TonlibClient.cpp index 74483fa3..95a089d7 100644 --- a/tonlib/tonlib/TonlibClient.cpp +++ b/tonlib/tonlib/TonlibClient.cpp @@ -31,6 +31,7 @@ #include "smc-envelope/GenericAccount.h" #include "smc-envelope/TestWallet.h" #include "smc-envelope/Wallet.h" +#include "smc-envelope/WalletV3.h" #include "smc-envelope/TestGiver.h" #include "auto/tl/tonlib_api.hpp" @@ -41,6 +42,7 @@ #include "vm/boc.h" +#include "td/utils/as.h" #include "td/utils/Random.h" #include "td/utils/optional.h" #include "td/utils/overloaded.h" @@ -121,7 +123,8 @@ std::string to_bytes(td::Ref cell) { class AccountState { public: - AccountState(block::StdAddress address, RawAccountState&& raw) : address_(std::move(address)), raw_(std::move(raw)) { + AccountState(block::StdAddress address, RawAccountState&& raw, td::uint32 wallet_id) + : address_(std::move(address)), raw_(std::move(raw)), wallet_id_(wallet_id) { wallet_type_ = guess_type(); } @@ -162,6 +165,17 @@ class AccountState { return tonlib_api::make_object(get_balance(), static_cast(seqno), to_transaction_id(raw().info), get_sync_time()); } + td::Result> to_wallet_v3_accountState() const { + if (wallet_type_ != WalletV3) { + return TonlibError::AccountTypeUnexpected("WalletV3"); + } + auto wallet = ton::WalletV3(get_smc_state()); + TRY_RESULT(seqno, wallet.get_seqno()); + TRY_RESULT(wallet_id, wallet.get_wallet_id()); + return tonlib_api::make_object( + get_balance(), static_cast(wallet_id), static_cast(seqno), + to_transaction_id(raw().info), get_sync_time()); + } td::Result> to_testGiver_accountState() const { if (wallet_type_ != Giver) { @@ -191,11 +205,15 @@ class AccountState { TRY_RESULT(res, to_wallet_accountState()); return tonlib_api::make_object(std::move(res)); } + case WalletV3: { + TRY_RESULT(res, to_wallet_v3_accountState()); + return tonlib_api::make_object(std::move(res)); + } } UNREACHABLE(); } - enum WalletType { Empty, Unknown, Giver, SimpleWallet, Wallet }; + enum WalletType { Empty, Unknown, Giver, SimpleWallet, Wallet, WalletV3 }; WalletType get_wallet_type() const { return wallet_type_; } @@ -235,6 +253,10 @@ class AccountState { address_.addr) { set_new_state({ton::Wallet::get_init_code(), ton::Wallet::get_init_data(key)}); wallet_type_ = WalletType::Wallet; + } else if (ton::GenericAccount::get_address(address_.workchain, ton::WalletV3::get_init_state(key, wallet_id_)) + .addr == address_.addr) { + set_new_state({ton::WalletV3::get_init_code(), ton::WalletV3::get_init_data(key, wallet_id_)}); + wallet_type_ = WalletType::WalletV3; } return wallet_type_; } @@ -243,8 +265,8 @@ class AccountState { if (wallet_type_ != WalletType::Empty) { return wallet_type_; } - set_new_state({ton::Wallet::get_init_code(), ton::Wallet::get_init_data(key)}); - wallet_type_ = WalletType::Wallet; + set_new_state({ton::WalletV3::get_init_code(), ton::WalletV3::get_init_data(key, wallet_id_)}); + wallet_type_ = WalletType::WalletV3; return wallet_type_; } @@ -274,6 +296,7 @@ class AccountState { block::StdAddress address_; RawAccountState raw_; WalletType wallet_type_{Unknown}; + td::uint32 wallet_id_{0}; bool has_new_state_{false}; WalletType guess_type() const { @@ -290,6 +313,9 @@ class AccountState { if (code_hash == ton::Wallet::get_init_code_hash()) { return WalletType::Wallet; } + if (code_hash == ton::WalletV3::get_init_code_hash()) { + return WalletType::WalletV3; + } LOG(WARNING) << "Unknown code hash: " << td::base64_encode(code_hash.as_slice()); return WalletType::Unknown; } @@ -999,6 +1025,7 @@ bool TonlibClient::is_static_request(td::int32 id) { case tonlib_api::raw_getAccountAddress::ID: case tonlib_api::testWallet_getAccountAddress::ID: case tonlib_api::wallet_getAccountAddress::ID: + case tonlib_api::wallet_v3_getAccountAddress::ID: case tonlib_api::testGiver_getAccountAddress::ID: case tonlib_api::packAccountAddress::ID: case tonlib_api::unpackAccountAddress::ID: @@ -1062,6 +1089,12 @@ td::Result get_account_address(const tonlib_api::wallet_initi auto key = td::Ed25519::PublicKey(td::SecureString(key_bytes.key)); return ton::GenericAccount::get_address(0 /*zerochain*/, ton::Wallet::get_init_state(key)); } +td::Result get_account_address(const tonlib_api::wallet_v3_initialAccountState& test_wallet_state) { + TRY_RESULT(key_bytes, get_public_key(test_wallet_state.public_key_)); + auto key = td::Ed25519::PublicKey(td::SecureString(key_bytes.key)); + return ton::GenericAccount::get_address( + 0 /*zerochain*/, ton::WalletV3::get_init_state(key, static_cast(test_wallet_state.wallet_id_))); +} td::Result get_account_address(td::Slice account_address) { TRY_RESULT_PREFIX(address, block::StdAddress::parse(account_address), TonlibError::InvalidAccountAddress()); @@ -1070,6 +1103,9 @@ td::Result get_account_address(td::Slice account_address) { tonlib_api::object_ptr TonlibClient::do_static_request( const tonlib_api::raw_getAccountAddress& request) { + if (!request.initital_account_state_) { + return status_to_tonlib_api(TonlibError::EmptyField("initial_account_state")); + } auto r_account_address = get_account_address(*request.initital_account_state_); if (r_account_address.is_error()) { return status_to_tonlib_api(r_account_address.error()); @@ -1079,6 +1115,12 @@ tonlib_api::object_ptr TonlibClient::do_static_request( tonlib_api::object_ptr TonlibClient::do_static_request( const tonlib_api::testWallet_getAccountAddress& request) { + if (!request.initital_account_state_) { + return status_to_tonlib_api(TonlibError::EmptyField("initial_account_state")); + } + if (!request.initital_account_state_) { + return status_to_tonlib_api(TonlibError::EmptyField("initial_account_state")); + } auto r_account_address = get_account_address(*request.initital_account_state_); if (r_account_address.is_error()) { return status_to_tonlib_api(r_account_address.error()); @@ -1088,6 +1130,20 @@ tonlib_api::object_ptr TonlibClient::do_static_request( tonlib_api::object_ptr TonlibClient::do_static_request( const tonlib_api::wallet_getAccountAddress& request) { + if (!request.initital_account_state_) { + return status_to_tonlib_api(TonlibError::EmptyField("initial_account_state")); + } + auto r_account_address = get_account_address(*request.initital_account_state_); + if (r_account_address.is_error()) { + return status_to_tonlib_api(r_account_address.error()); + } + return tonlib_api::make_object(r_account_address.ok().rserialize(true)); +} +tonlib_api::object_ptr TonlibClient::do_static_request( + const tonlib_api::wallet_v3_getAccountAddress& request) { + if (!request.initital_account_state_) { + return status_to_tonlib_api(TonlibError::EmptyField("initial_account_state")); + } auto r_account_address = get_account_address(*request.initital_account_state_); if (r_account_address.is_error()) { return status_to_tonlib_api(r_account_address.error()); @@ -1249,12 +1305,14 @@ td::Result TonlibClient::validate_config(tonlib_api::o res.o_master_config = std::move(o_master_config); res.ignore_cache = config->ignore_cache_; res.use_callbacks_for_network = config->use_callbacks_for_network_; + res.wallet_id = td::as(res.config.zero_state_id.root_hash.as_slice().data()); return std::move(res); } void TonlibClient::set_config(FullConfig full_config) { config_ = std::move(full_config.config); config_generation_++; + wallet_id_ = full_config.wallet_id; blockchain_name_ = config_.zero_state_id.root_hash.as_slice().str(); use_callbacks_for_network_ = full_config.use_callbacks_for_network; @@ -1280,7 +1338,7 @@ tonlib_api::object_ptr TonlibClient::do_static_request( if (r_config.is_error()) { return status_to_tonlib_api(r_config.move_as_error()); } - return tonlib_api::make_object(); + return tonlib_api::make_object(r_config.ok().wallet_id); } td::Status TonlibClient::do_request(tonlib_api::options_setConfig& request, @@ -1893,6 +1951,23 @@ class GenericCreateSendGrams : public TonlibQueryActor { destination_->get_address()); break; } + case AccountState::WalletV3: { + if (!private_key_) { + return TonlibError::EmptyField("private_key"); + } + if (message.size() > ton::WalletV3::max_message_size) { + return TonlibError::MessageTooLong(); + } + auto wallet = ton::WalletV3(source_->get_smc_state()); + TRY_RESULT(seqno, wallet.get_seqno()); + TRY_RESULT(wallet_id, wallet.get_wallet_id()); + auto valid_until = source_->get_sync_time(); + valid_until += send_grams_.timeout_ == 0 ? 60 : send_grams_.timeout_; + raw.valid_until = valid_until; + raw.message_body = ton::WalletV3::make_a_gift_message(private_key_.unwrap(), wallet_id, seqno, valid_until, + amount, message, destination_->get_address()); + break; + } } raw.new_state = source_->get_new_state(); @@ -2433,8 +2508,8 @@ td::Status TonlibClient::do_request(int_api::GetAccountState request, auto actor_id = actor_id_++; actors_[actor_id] = td::actor::create_actor( "GetAccountState", client_.get_client(), request.address, actor_shared(this, actor_id), - promise.wrap([address = request.address](auto&& state) mutable { - return td::make_unique(std::move(address), std::move(state)); + promise.wrap([address = request.address, wallet_id = wallet_id_](auto&& state) mutable { + return td::make_unique(std::move(address), std::move(state), wallet_id); })); return td::Status::OK(); } @@ -2481,6 +2556,11 @@ td::Status TonlibClient::do_request(const tonlib_api::wallet_getAccountAddress& return TonlibError::Internal(); } template +td::Status TonlibClient::do_request(const tonlib_api::wallet_v3_getAccountAddress& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template td::Status TonlibClient::do_request(const tonlib_api::testGiver_getAccountAddress& request, P&&) { UNREACHABLE(); return TonlibError::Internal(); diff --git a/tonlib/tonlib/TonlibClient.h b/tonlib/tonlib/TonlibClient.h index fe006620..68936a43 100644 --- a/tonlib/tonlib/TonlibClient.h +++ b/tonlib/tonlib/TonlibClient.h @@ -65,6 +65,7 @@ class TonlibClient : public td::actor::Actor { // Config Config config_; td::uint32 config_generation_{0}; + td::uint32 wallet_id_; std::string blockchain_name_; bool ignore_cache_{false}; bool use_callbacks_for_network_{false}; @@ -123,6 +124,7 @@ class TonlibClient : public td::actor::Actor { static object_ptr do_static_request(const tonlib_api::raw_getAccountAddress& request); static object_ptr do_static_request(const tonlib_api::testWallet_getAccountAddress& request); static object_ptr do_static_request(const tonlib_api::wallet_getAccountAddress& request); + static object_ptr do_static_request(const tonlib_api::wallet_v3_getAccountAddress& request); static object_ptr do_static_request(const tonlib_api::testGiver_getAccountAddress& request); static object_ptr do_static_request(const tonlib_api::packAccountAddress& request); static object_ptr do_static_request(const tonlib_api::unpackAccountAddress& request); @@ -151,6 +153,8 @@ class TonlibClient : public td::actor::Actor { template td::Status do_request(const tonlib_api::wallet_getAccountAddress& request, P&&); template + td::Status do_request(const tonlib_api::wallet_v3_getAccountAddress& request, P&&); + template td::Status do_request(const tonlib_api::testGiver_getAccountAddress& request, P&&); template td::Status do_request(const tonlib_api::packAccountAddress& request, P&&); @@ -198,6 +202,7 @@ class TonlibClient : public td::actor::Actor { td::optional o_master_config; bool use_callbacks_for_network; bool ignore_cache; + td::uint32 wallet_id; }; static td::Result validate_config(tonlib_api::object_ptr config); void set_config(FullConfig config); diff --git a/tonlib/tonlib/TonlibError.h b/tonlib/tonlib/TonlibError.h index 65681123..dca5eecf 100644 --- a/tonlib/tonlib/TonlibError.h +++ b/tonlib/tonlib/TonlibError.h @@ -79,6 +79,9 @@ struct TonlibError { static td::Status InvalidPemKey() { return td::Status::Error(400, "INVALID_PEM_KEY"); } + static td::Status NeedConfig() { + return td::Status::Error(400, "NeedConfig"); + } static td::Status MessageTooLong() { return td::Status::Error(400, "MESSAGE_TOO_LONG"); } diff --git a/tonlib/tonlib/tonlib-cli.cpp b/tonlib/tonlib/tonlib-cli.cpp index 8e1e74d3..716fae30 100644 --- a/tonlib/tonlib/tonlib-cli.cpp +++ b/tonlib/tonlib/tonlib-cli.cpp @@ -81,7 +81,7 @@ class TonlibCli : public td::actor::Actor { std::string key_dir{"."}; bool in_memory{false}; bool use_callbacks_for_network{false}; - bool use_simple_wallet{false}; + td::int32 wallet_version = 2; bool ignore_cache{false}; bool one_shot{false}; @@ -96,6 +96,7 @@ class TonlibCli : public td::actor::Actor { td::actor::ActorOwn client_; std::uint64_t next_query_id_{1}; td::Promise cont_; + td::uint32 wallet_id_; struct KeyInfo { std::string public_key; @@ -176,6 +177,10 @@ class TonlibCli : public td::actor::Actor { ? make_object(options_.config, options_.name, options_.use_callbacks_for_network, options_.ignore_cache) : nullptr; + auto config2 = !options_.config.empty() + ? make_object(options_.config, options_.name, + options_.use_callbacks_for_network, options_.ignore_cache) + : nullptr; tonlib_api::object_ptr ks_type; if (options_.in_memory) { @@ -183,6 +188,14 @@ class TonlibCli : public td::actor::Actor { } else { ks_type = make_object(options_.key_dir); } + auto obj = + tonlib::TonlibClient::static_request(make_object(std::move(config2))); + if (obj->get_id() != tonlib_api::error::ID) { + auto info = ton::move_tl_object_as(obj); + wallet_id_ = static_cast(info->default_wallet_id_); + } else { + LOG(ERROR) << "Invalid config"; + } send_query(make_object(make_object(std::move(config), std::move(ks_type))), [](auto r_ok) { LOG_IF(ERROR, r_ok.is_error()) << r_ok.error(); @@ -519,7 +532,7 @@ class TonlibCli : public td::actor::Actor { })); } else { send_query(make_object(std::move(config)), promise.wrap([](auto&& info) { - td::TerminalIO::out() << "Config is valid\n"; + td::TerminalIO::out() << "Config is valid: " << to_string(info) << "\n"; return td::Unit(); })); } @@ -824,11 +837,19 @@ class TonlibCli : public td::actor::Actor { auto r_key_i = to_key_i(key); using tonlib_api::make_object; if (r_key_i.is_ok()) { - auto obj = options_.use_simple_wallet - ? tonlib::TonlibClient::static_request(make_object( - make_object(keys_[r_key_i.ok()].public_key))) - : tonlib::TonlibClient::static_request(make_object( - make_object(keys_[r_key_i.ok()].public_key))); + auto obj = [&](td::int32 version) { + if (version == 1) { + return tonlib::TonlibClient::static_request(make_object( + make_object(keys_[r_key_i.ok()].public_key))); + } + if (version == 2) { + return tonlib::TonlibClient::static_request(make_object( + make_object(keys_[r_key_i.ok()].public_key))); + } + return tonlib::TonlibClient::static_request(make_object( + make_object(keys_[r_key_i.ok()].public_key, wallet_id_))); + UNREACHABLE(); + }(options_.wallet_version); if (obj->get_id() != tonlib_api::error::ID) { Address res; res.address = ton::move_tl_object_as(obj); @@ -1166,7 +1187,7 @@ class TonlibCli : public td::actor::Actor { void init_simple_wallet(std::string key, size_t key_i, td::Slice password) { using tonlib_api::make_object; - if (options_.use_simple_wallet) { + if (options_.wallet_version == 1) { send_query(make_object(make_object( make_object(keys_[key_i].public_key, keys_[key_i].secret.copy()), td::SecureString(password))), @@ -1259,8 +1280,8 @@ int main(int argc, char* argv[]) { options.use_callbacks_for_network = true; return td::Status::OK(); }); - p.add_option('S', "use-simple-wallet", "do not use this", [&]() { - options.use_simple_wallet = true; + p.add_option('W', "wallet-version", "do not use this", [&](td::Slice arg) { + options.wallet_version = td::to_integer(arg); return td::Status::OK(); });