/* 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 "fift/Fift.h" #include "fift/words.h" #include "fift/utils.h" #include "block/block.h" #include "block/block-auto.h" #include "vm/cells.h" #include "vm/boc.h" #include "vm/cells/MerkleProof.h" #include "tonlib/utils.h" #include "tonlib/TestGiver.h" #include "tonlib/TestWallet.h" #include "tonlib/GenericAccount.h" #include "tonlib/TonlibClient.h" #include "tonlib/Client.h" #include "auto/tl/ton_api_json.h" #include "auto/tl/tonlib_api_json.h" #include "td/utils/benchmark.h" #include "td/utils/filesystem.h" #include "td/utils/optional.h" #include "td/utils/port/path.h" #include "td/utils/PathView.h" #include "td/utils/tests.h" // KeyManager #include "tonlib/keys/bip39.h" #include "tonlib/keys/DecryptedKey.h" #include "tonlib/keys/EncryptedKey.h" #include "tonlib/keys/Mnemonic.h" #include "tonlib/keys/SimpleEncryption.h" using namespace tonlib; std::string current_dir() { return td::PathView(td::realpath(__FILE__).move_as_ok()).parent_dir().str(); } std::string load_source(std::string name) { return td::read_file_str(current_dir() + "../../crypto/" + name).move_as_ok(); } td::Ref get_test_wallet_source() { std::string code = R"ABCD( SETCP0 DUP IFNOTRET INC 32 THROWIF // return if recv_internal, fail unless recv_external 512 INT LDSLICEX DUP 32 PLDU // sign cs cnt c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS // sign cs cnt cnt' pubk s1 s2 XCPU // sign cs cnt pubk cnt' cnt EQUAL 33 THROWIFNOT // ( seqno mismatch? ) s2 PUSH HASHSU // sign cs cnt pubk hash s0 s4 s4 XC2PU // pubk cs cnt hash sign pubk CHKSIGNU // pubk cs cnt ? 34 THROWIFNOT // signature mismatch ACCEPT SWAP 32 LDU NIP DUP SREFS IF:<{ 8 LDU LDREF // pubk cnt mode msg cs s0 s2 XCHG SENDRAWMSG // pubk cnt cs ; ( message sent ) }> ENDS INC NEWC 32 STU 256 STU ENDC c4 POPCTR )ABCD"; return fift::compile_asm(code).move_as_ok(); } TEST(Tonlib, TestWallet) { CHECK(get_test_wallet_source()->get_hash() == TestWallet::get_init_code()->get_hash()); auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet.fif"), {"aba", "0"}).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 = TestWallet::get_init_state(pub_key); auto init_message = TestWallet::get_init_message(priv_key); auto address = GenericAccount::get_address(0, init_state); CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); td::Ref res = 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()); } TEST(Tonlib, TestGiver) { auto address = block::StdAddress::parse("-1:60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0").move_as_ok(); LOG(ERROR) << address.bounceable; auto fift_output = fift::mem_run_fift(load_source("smartcont/testgiver.fif"), {"aba", address.rserialize(), "0", "6.666", "wallet-query"}) .move_as_ok(); LOG(ERROR) << fift_output.output; auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; auto res = GenericAccount::create_ext_message(TestGiver::address(), {}, TestGiver::make_a_gift_message(0, 1000000000ll * 6666 / 1000, address)); vm::CellSlice(vm::NoVm(), res).print_rec(std::cerr); CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == res->get_hash()); } TEST(Tonlib, Address) { auto a = block::StdAddress::parse("-1:538fa7cc24ff8eaa101d84a5f1ab7e832fe1d84b309cdfef4ee94373aac80f7d").move_as_ok(); auto b = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok(); auto c = block::StdAddress::parse("Ef9Tj6fMJP-OqhAdhKXxq36DL-HYSzCc3-9O6UNzqsgPfYFX").move_as_ok(); CHECK(a == b); CHECK(a == c); CHECK(block::StdAddress::parse("Ef9Tj6fMJp-OqhAdhKXxq36DL-HYSzCc3-9O6UNzqsgPfYFX").is_error()); CHECK(block::StdAddress::parse("Ef9Tj6fMJp+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").is_error()); CHECK(block::StdAddress::parse(a.rserialize()).move_as_ok() == a); } static auto sync_send = [](auto &client, auto query) { using ReturnTypePtr = typename std::decay_t::ReturnType; using ReturnType = typename ReturnTypePtr::element_type; client.send({1, std::move(query)}); while (true) { auto response = client.receive(100); if (response.object) { CHECK(response.id == 1); if (response.object->get_id() == tonlib_api::error::ID) { auto error = tonlib_api::move_object_as(response.object); return td::Result(td::Status::Error(error->code_, error->message_)); } return td::Result(tonlib_api::move_object_as(response.object)); } } }; TEST(Tonlib, InitClose) { using tonlib_api::make_object; { Client client; sync_send(client, make_object()).ensure(); sync_send(client, make_object(make_object("", "."))).ensure_error(); } { Client client; sync_send(client, make_object(nullptr)).ensure_error(); sync_send(client, make_object(make_object("fdajkfldsjkafld", "."))) .ensure_error(); sync_send(client, make_object(make_object("", "fdhskfds"))).ensure_error(); sync_send(client, make_object(make_object("", "."))).ensure(); sync_send(client, make_object(make_object("", "."))).ensure_error(); td::Slice bad_config = R"abc( { "@type": "config.global", "liteclients": [ ] } )abc"; sync_send(client, make_object(bad_config.str())).ensure_error(); sync_send(client, make_object()).ensure_error(); sync_send(client, make_object()).ensure(); sync_send(client, make_object()).ensure_error(); sync_send(client, make_object(make_object("", "."))).ensure_error(); } } TEST(Tonlib, SimpleEncryption) { std::string secret = "secret"; { std::string data = "some private data"; std::string wrong_secret = "wrong secret"; auto encrypted_data = SimpleEncryption::encrypt_data(data, secret); LOG(ERROR) << encrypted_data.size(); auto decrypted_data = SimpleEncryption::decrypt_data(encrypted_data, secret).move_as_ok(); CHECK(data == decrypted_data); SimpleEncryption::decrypt_data(encrypted_data, wrong_secret).ensure_error(); SimpleEncryption::decrypt_data("", secret).ensure_error(); SimpleEncryption::decrypt_data(std::string(32, 'a'), secret).ensure_error(); SimpleEncryption::decrypt_data(std::string(33, 'a'), secret).ensure_error(); SimpleEncryption::decrypt_data(std::string(64, 'a'), secret).ensure_error(); SimpleEncryption::decrypt_data(std::string(128, 'a'), secret).ensure_error(); } for (size_t i = 0; i < 255; i++) { auto data = td::rand_string('a', 'z', static_cast(i)); auto encrypted_data = SimpleEncryption::encrypt_data(data, secret); auto decrypted_data = SimpleEncryption::decrypt_data(encrypted_data, secret).move_as_ok(); CHECK(data == decrypted_data); } } class MnemonicBench : public td::Benchmark { public: std::string get_description() const override { return "mnemonic is_password_seed"; } void start_up() override { Mnemonic::Options options; options.password = td::SecureString("qwerty"); mnemonic_ = Mnemonic::create_new(std::move(options)).move_as_ok(); } void run(int n) override { int x = 0; for (int i = 0; i < n; i++) { x += mnemonic_.value().is_password_seed(); } td::do_not_optimize_away(x); } td::optional mnemonic_; }; TEST(Tonlib, Mnemonic) { td::bench(MnemonicBench()); //for (int i = 0; i < 20; i++) { //td::PerfWarningTimer perf("Mnemonic::create", 0.01); //Mnemonic::Options options; //options.password = td::SecureString("qwerty"); //Mnemonic::create_new(std::move(options)).move_as_ok(); //} // FIXME //auto tmp = std::vector{"hello", "world"}; //CHECK(tmp[0].as_slice() == "hello"); auto a = Mnemonic::create(td::SecureString(" Hello, . $^\n# World! "), td::SecureString("cucumber")).move_as_ok(); auto get_word_list = [] { std::vector words; words.emplace_back("hello"); words.emplace_back("world"); return words; }; auto b = Mnemonic::create(get_word_list(), td::SecureString("cucumber")).move_as_ok(); CHECK(a.get_words() == b.get_words()); CHECK(a.get_words() == get_word_list()); Mnemonic::Options options; options.password = td::SecureString("qwerty"); auto password = options.password.copy(); auto c = Mnemonic::create_new(std::move(options)).move_as_ok(); auto d = Mnemonic::create(c.get_words(), std::move(password)).move_as_ok(); CHECK(c.to_private_key().as_octet_string() == d.to_private_key().as_octet_string()); } TEST(Tonlib, Keys) { auto a = Mnemonic::create(td::SecureString(" Hello, . $^\n# World! "), td::SecureString("cucumber")).move_as_ok(); DecryptedKey decrypted_key(std::move(a)); EncryptedKey encrypted_key = decrypted_key.encrypt("qwerty"); auto other_decrypted_key = encrypted_key.decrypt("qwerty").move_as_ok(); encrypted_key.decrypt("abcde").ensure_error(); CHECK(decrypted_key.mnemonic_words == other_decrypted_key.mnemonic_words); CHECK(decrypted_key.private_key.as_octet_string() == other_decrypted_key.private_key.as_octet_string()); } TEST(Tonlib, KeysApi) { using tonlib_api::make_object; Client client; // init sync_send(client, make_object(make_object("", "."))).ensure(); auto local_password = td::SecureString("local password"); auto mnemonic_password = td::SecureString("mnemonic password"); { auto key = sync_send(client, make_object(local_password.copy(), td::SecureString{})) .move_as_ok(); } //createNewKey local_password:bytes mnemonic_password:bytes = Key; auto key = sync_send(client, make_object(local_password.copy(), mnemonic_password.copy())) .move_as_ok(); sync_send(client, make_object(make_object( make_object(key->public_key_, key->secret_.copy()), td::SecureString("wrong password")))) .ensure_error(); //exportKey input_key:inputKey = ExportedKey; auto exported_key = sync_send(client, make_object(make_object( make_object(key->public_key_, key->secret_.copy()), local_password.copy()))) .move_as_ok(); LOG(ERROR) << to_string(exported_key); auto copy_word_list = [&] { std::vector word_list_copy; for (auto &w : exported_key->word_list_) { word_list_copy.push_back(w.copy()); } return word_list_copy; }; //changeLocalPassword input_key:inputKey new_local_password:bytes = Key; auto new_key = sync_send(client, make_object( make_object( make_object(key->public_key_, key->secret_.copy()), local_password.copy()), td::SecureString("tmp local password"))) .move_as_ok(); sync_send(client, make_object(make_object( make_object(key->public_key_, new_key->secret_.copy()), local_password.copy()))) .ensure_error(); auto exported_key2 = sync_send(client, make_object(make_object( make_object(key->public_key_, new_key->secret_.copy()), td::SecureString("tmp local password")))) .move_as_ok(); CHECK(exported_key2->word_list_ == exported_key->word_list_); //importKey local_password:bytes mnemonic_password:bytes exported_key:exportedKey = Key; auto new_local_password = td::SecureString("new_local_password"); // import already existed key sync_send(client, make_object(new_local_password.copy(), mnemonic_password.copy(), make_object(copy_word_list()))) .ensure_error(); { auto export_password = td::SecureString("export password"); auto wrong_export_password = td::SecureString("wrong_export password"); auto exported_encrypted_key = sync_send(client, make_object( make_object( make_object(key->public_key_, new_key->secret_.copy()), td::SecureString("tmp local password")), export_password.copy())) .move_as_ok(); sync_send(client, make_object(key->public_key_)).move_as_ok(); sync_send(client, make_object( new_local_password.copy(), wrong_export_password.copy(), make_object(exported_encrypted_key->data_.copy()))) .ensure_error(); auto imported_encrypted_key = sync_send(client, make_object( new_local_password.copy(), export_password.copy(), make_object(exported_encrypted_key->data_.copy()))) .move_as_ok(); CHECK(imported_encrypted_key->public_key_ == key->public_key_); } //deleteKey public_key:bytes = Ok; sync_send(client, make_object(key->public_key_)).move_as_ok(); auto err1 = sync_send(client, make_object( new_local_password.copy(), td::SecureString("wrong password"), make_object(copy_word_list()))) .move_as_error(); auto err2 = sync_send(client, make_object(new_local_password.copy(), td::SecureString(), make_object(copy_word_list()))) .move_as_error(); LOG(INFO) << err1 << " | " << err2; auto imported_key = sync_send(client, make_object(new_local_password.copy(), mnemonic_password.copy(), make_object(copy_word_list()))) .move_as_ok(); CHECK(imported_key->public_key_ == key->public_key_); CHECK(imported_key->secret_ != key->secret_); //exportPemKey input_key:inputKey key_password:bytes = ExportedPemKey; auto pem_password = td::SecureString("pem password"); auto r_exported_pem_key = sync_send( client, make_object( make_object( make_object(key->public_key_, imported_key->secret_.copy()), new_local_password.copy()), pem_password.copy())); if (r_exported_pem_key.is_error() && r_exported_pem_key.error().message() == "Not supported") { return; } auto exported_pem_key = r_exported_pem_key.move_as_ok(); LOG(ERROR) << to_string(exported_pem_key); //importPemKey exported_key:exportedPemKey key_password:bytes = Key; sync_send(client, make_object( new_local_password.copy(), pem_password.copy(), make_object(exported_pem_key->pem_.copy()))) .ensure_error(); sync_send(client, make_object(key->public_key_)).move_as_ok(); sync_send(client, make_object( new_local_password.copy(), td::SecureString("wrong pem password"), make_object(exported_pem_key->pem_.copy()))) .ensure_error(); auto new_imported_key = sync_send(client, make_object( new_local_password.copy(), pem_password.copy(), make_object(exported_pem_key->pem_.copy()))) .move_as_ok(); CHECK(new_imported_key->public_key_ == key->public_key_); CHECK(new_imported_key->secret_ != key->secret_); }