aboutsummaryrefslogtreecommitdiffhomepage
path: root/tde2e/td/e2e/e2e_api.cpp
diff options
context:
space:
mode:
authorArseny Smirnov <arseny30@gmail.com>2024-10-04 01:19:51 +0200
committerArseny Smirnov <arseny30@gmail.com>2025-04-10 17:18:46 +0300
commit6a46f51bdf5e5052ddae26b61cdf9d4864238442 (patch)
treebd7d1af6a2b68689cb97fc9391eeaee8851dada6 /tde2e/td/e2e/e2e_api.cpp
parent6571af5d49bf9b05361187599b156e93face92df (diff)
Add tde2e library
Diffstat (limited to 'tde2e/td/e2e/e2e_api.cpp')
-rw-r--r--tde2e/td/e2e/e2e_api.cpp857
1 files changed, 857 insertions, 0 deletions
diff --git a/tde2e/td/e2e/e2e_api.cpp b/tde2e/td/e2e/e2e_api.cpp
new file mode 100644
index 000000000..f87aa05bd
--- /dev/null
+++ b/tde2e/td/e2e/e2e_api.cpp
@@ -0,0 +1,857 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/e2e/e2e_api.h"
+
+#include "td/e2e/Blockchain.h"
+#include "td/e2e/Call.h"
+#include "td/e2e/Container.h"
+#include "td/e2e/DecryptedKey.h"
+#include "td/e2e/EncryptedKey.h"
+#include "td/e2e/EncryptedStorage.h"
+#include "td/e2e/MessageEncryption.h"
+#include "td/e2e/Mnemonic.h"
+#include "td/e2e/QRHandshake.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/base64.h"
+#include "td/utils/common.h"
+#include "td/utils/int_types.h"
+#include "td/utils/logging.h"
+#include "td/utils/overloaded.h"
+#include "td/utils/Random.h"
+#include "td/utils/SharedSlice.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Span.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_parsers.h"
+#include "td/utils/UInt.h"
+
+#include <memory>
+
+namespace tde2e_core {
+
+namespace api = tde2e_api;
+
+using SecretRef = SharedRef<td::SecureString>;
+using HandshakeBobRef = UniqueRef<QRHandshakeBob>;
+using HandshakeAliceRef = UniqueRef<QRHandshakeAlice>;
+using StorageRef = UniqueRef<EncryptedStorage>;
+using CallRef = UniqueRef<Call>;
+
+td::UInt256 to_hash(td::Slice tag, td::Slice serialization) {
+ auto res = MessageEncryption::hmac_sha512(tag, serialization);
+ td::UInt256 hash;
+ hash.as_mutable_slice().copy_from(res.as_slice().substr(0, 32));
+ return hash;
+}
+
+class KeyChain {
+ public:
+ KeyChain() {
+ set_log_verbosity_level(1).ignore();
+ }
+
+ td::Result<api::Ok> set_log_verbosity_level(td::int32 new_verbosity_level) {
+ if (0 <= new_verbosity_level && new_verbosity_level <= VERBOSITY_NAME(NEVER)) {
+ SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + new_verbosity_level);
+ return api::Ok{};
+ }
+ return td::Status::Error("Wrong new verbosity level specified");
+ }
+ td::Result<api::PrivateKeyId> generate_private_key() {
+ TRY_RESULT(mnemonic, Mnemonic::create_new({}));
+ return from_words(mnemonic.get_words_string());
+ }
+ td::Result<api::PrivateKeyId> generate_dummy_key() {
+ auto hash = to_hash("dummy key", "...");
+ return container_.try_build<Key>(hash, [&]() -> td::Result<PrivateKeyWithMnemonic> {
+ td::SecureString key(32, 1);
+ return PrivateKeyWithMnemonic::from_private_key(PrivateKey::from_slice(key).move_as_ok(), {});
+ });
+ }
+ td::Result<api::PrivateKeyId> generate_temporary_private_key() {
+ TRY_RESULT(private_key, PrivateKey::generate());
+ auto hash = to_hash("temporary private key", private_key.to_public_key().to_u256().as_slice());
+ return container_.try_build<Key>(hash, [&]() -> td::Result<PrivateKeyWithMnemonic> {
+ return PrivateKeyWithMnemonic::from_private_key(private_key, {});
+ });
+ }
+
+ td::Result<api::SymmetricKeyId> derive_secret(api::PrivateKeyId key_id, td::Slice tag) {
+ TRY_RESULT(pk, to_private_key_with_mnemonic(key_id));
+ auto hash = to_hash(PSLICE() << "derive secret with tag: " << td::base64_encode(tag),
+ pk.to_public_key().to_u256().as_slice());
+ return container_.try_build<Key>(hash, [&]() -> td::Result<td::SecureString> {
+ // TODO: this is probably wrong and should be changed
+ return MessageEncryption::hmac_sha512(pk.to_private_key().to_secure_string(), tag);
+ });
+ }
+
+ td::Result<api::PrivateKeyId> from_words(td::Slice words) {
+ auto hash = to_hash("private ed25519 key from menemonic", words);
+ return container_.try_build<Key>(hash, [&]() -> td::Result<PrivateKeyWithMnemonic> {
+ TRY_RESULT(mnemonic, Mnemonic::create(td::SecureString(words), td::SecureString("")));
+ TRY_RESULT(private_key, mnemonic_to_private_key(mnemonic));
+ return private_key;
+ });
+ }
+
+ td::Result<api::Bytes> to_encrypted_private_key(api::PrivateKeyId key_id, api::SymmetricKeyId secret_id) {
+ TRY_RESULT(pk, to_private_key_with_mnemonic(key_id));
+ TRY_RESULT(secret, to_secret_ref(secret_id));
+ auto decrypted_key =
+ DecryptedKey(td::transform(pk.words(), [](const auto &m) { return m.copy(); }), pk.to_private_key());
+ auto encrypted = decrypted_key.encrypt("tde2e private key", *secret);
+ return encrypted.encrypted_data.as_slice().str();
+ }
+
+ td::Result<api::PrivateKeyId> from_encrypted_private_key(td::Slice encrypted_private_key,
+ api::SymmetricKeyId secret_id) {
+ TRY_RESULT(secret, to_secret_ref(secret_id));
+ auto hash = to_hash(PSLICE() << "encrypted private ed25519 key " << encrypted_private_key.str(), *secret);
+ return container_.try_build<Key>(hash, [&]() -> td::Result<PrivateKeyWithMnemonic> {
+ // WOW. empty public key. is it good?
+ auto encrypted_key = EncryptedKey{td::SecureString(encrypted_private_key), {}, secret->copy()};
+ TRY_RESULT(decrypted_key, encrypted_key.decrypt("tde2e private key", false));
+ return PrivateKeyWithMnemonic::from_private_key(decrypted_key.private_key,
+ std::move(decrypted_key.mnemonic_words));
+ });
+ }
+
+ td::Result<api::Bytes> to_encrypted_private_key_internal(api::PrivateKeyId key_id, api::SymmetricKeyId secret_id) {
+ TRY_RESULT(pk, to_private_key_with_mnemonic(key_id));
+ TRY_RESULT(secret, to_secret_ref(secret_id));
+ return MessageEncryption::encrypt_data(pk.to_private_key().to_secure_string(), *secret).as_slice().str();
+ }
+
+ td::Result<api::PrivateKeyId> from_encrypted_private_key_internal(td::Slice encrypted_private_key,
+ api::SymmetricKeyId secret_id) {
+ TRY_RESULT(secret, to_secret_ref(secret_id));
+ auto hash = to_hash(PSLICE() << "encrypted private ed25519 key internal " << encrypted_private_key.str(), *secret);
+ return container_.try_build<Key>(hash, [&]() -> td::Result<PrivateKeyWithMnemonic> {
+ TRY_RESULT(raw_pk, MessageEncryption::decrypt_data(encrypted_private_key, *secret));
+ TRY_RESULT(pk, PrivateKey::from_slice(raw_pk));
+ return PrivateKeyWithMnemonic::from_private_key(pk, {});
+ });
+ }
+
+ td::Result<api::PublicKeyId> from_public_key(td::Slice public_key) {
+ TRY_RESULT(key, PublicKey::from_slice(public_key));
+ auto hash = to_hash("public ed25519 key", public_key);
+ return container_.try_build<Key>(hash, [&]() -> td::Result<PublicKey> { return std::move(key); });
+ }
+
+ td::Result<api::SymmetricKeyId> from_ecdh(api::PrivateKeyId private_key_id, api::PublicKeyId public_key_id) {
+ TRY_RESULT(public_key, to_public_key(public_key_id));
+ TRY_RESULT(private_key, to_private_key_with_mnemonic(private_key_id));
+ auto hash = to_hash("x25519 shared secret",
+ public_key.to_u256().as_slice().str() + private_key.to_public_key().to_u256().as_slice().str());
+ return container_.try_build<Key>(hash, [&]() -> td::Result<td::SecureString> {
+ TRY_RESULT(shared_secret, private_key.to_private_key().compute_shared_secret(public_key));
+ return std::move(shared_secret);
+ });
+ }
+
+ td::Result<api::SymmetricKeyId> from_bytes(td::Slice secret) {
+ auto hash = to_hash("raw secret", secret);
+ return container_.try_build<Key>(hash, [&]() -> td::Result<td::SecureString> { return td::SecureString(secret); });
+ }
+ td::Result<api::SecureBytes> to_words(api::PrivateKeyId private_key_id) {
+ TRY_RESULT(private_key, to_private_key_with_mnemonic(private_key_id));
+ api::SecureBytes res;
+ auto words = private_key.words();
+ for (size_t i = 0; i < words.size(); ++i) {
+ if (i != 0) {
+ res += ' ';
+ }
+ res.append(words[i].data(), words[i].size());
+ }
+ return res;
+ }
+
+ td::Result<api::Int512> sign(api::PrivateKeyId key, td::Slice data) {
+ TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(key));
+ TRY_RESULT(signature, private_key_ref.sign(td::Slice(data.data(), data.size())));
+ CHECK(signature.to_slice().size() == 64);
+ api::Int512 result;
+ td::MutableSlice(result.data(), result.size()).copy_from(signature.to_slice());
+ return result;
+ }
+
+ td::Status destroy(std::optional<api::AnyKeyId> o_key_id) {
+ return container_.destroy<Key>(o_key_id);
+ }
+
+ td::Result<api::EncryptedMessageForMany> encrypt_message_for_many(const std::vector<api::SymmetricKeyId> &key_ids,
+ td::Slice message) {
+ std::vector<SecretRef> secrets;
+ for (auto &key_id : key_ids) {
+ TRY_RESULT(secret, to_secret_ref(key_id));
+ secrets.emplace_back(std::move(secret));
+ }
+
+ td::SecureString one_time_secret(32);
+ td::Random::secure_bytes(one_time_secret.as_mutable_slice());
+ api::EncryptedMessageForMany res;
+ res.encrypted_message = MessageEncryption::encrypt_data(message, one_time_secret).as_slice().str();
+ for (auto &secret : secrets) {
+ TRY_RESULT(encrypted_header,
+ MessageEncryption::encrypt_header(one_time_secret, res.encrypted_message, secret->as_slice()));
+ res.encrypted_headers.emplace_back(encrypted_header.as_slice().str());
+ }
+ return res;
+ }
+ td::Result<api::EncryptedMessageForMany> re_encrypt_message_for_many(api::SymmetricKeyId decrypt_key,
+ const std::vector<api::SymmetricKeyId> &key_ids,
+ td::Slice encrypted_header,
+ td::Slice encrypted_message) {
+ std::vector<SecretRef> secrets;
+ for (auto &key_id : key_ids) {
+ TRY_RESULT(secret, to_secret_ref(key_id));
+ secrets.emplace_back(std::move(secret));
+ }
+ TRY_RESULT(secret_ref, to_secret_ref(decrypt_key));
+ TRY_RESULT(header, MessageEncryption::decrypt_header(encrypted_header, encrypted_message, secret_ref->as_slice()));
+
+ api::EncryptedMessageForMany res;
+ for (auto &secret : secrets) {
+ TRY_RESULT(new_encrypted_header,
+ MessageEncryption::encrypt_header(header, secret->as_slice(), encrypted_message));
+ res.encrypted_headers.emplace_back(new_encrypted_header.as_slice().str());
+ }
+ return res;
+ }
+
+ td::Result<api::SecureBytes> decrypt_message_for_many(api::SymmetricKeyId key_id, td::Slice encrypted_header,
+ td::Slice encrypted_message) {
+ TRY_RESULT(secret, to_secret_ref(key_id));
+ TRY_RESULT(header, MessageEncryption::decrypt_header(encrypted_header, encrypted_message, secret->as_slice()));
+ TRY_RESULT(message, MessageEncryption::decrypt_data(encrypted_message, header));
+ return message.as_slice().str();
+ }
+
+ td::Result<api::SecureBytes> encrypt_message_for_one(api::SymmetricKeyId key_id, td::Slice message) {
+ TRY_RESULT(secret, to_secret_ref(key_id));
+ auto encrypted_message = MessageEncryption::encrypt_data(message, secret->as_slice());
+ return encrypted_message.as_slice().str();
+ }
+
+ td::Result<api::SecureBytes> decrypt_message_for_one(api::SymmetricKeyId key_id, td::Slice encrypted_message) {
+ TRY_RESULT(secret, to_secret_ref(key_id));
+ TRY_RESULT(message, MessageEncryption::decrypt_data(encrypted_message, secret->as_slice()));
+ return message.as_slice().str();
+ }
+
+ td::Result<api::HandshakeId> handshake_create_for_bob(api::UserId bob_user_id, api::PrivateKeyId bob_private_key_id) {
+ TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(bob_private_key_id));
+ return container_.try_build<Handshake>({}, [&]() -> td::Result<QRHandshakeBob> {
+ return QRHandshakeBob::create(bob_user_id, private_key_ref.to_private_key());
+ });
+ }
+ td::Result<api::Bytes> handshake_bob_send_start(api::HandshakeId bob_handshake_id) {
+ TRY_RESULT(bob_handshake, to_handshake_bob_ref(bob_handshake_id));
+ return bob_handshake->generate_start();
+ }
+ td::Result<api::HandshakeId> handshake_create_for_alice(api::UserId alice_user_id,
+ api::PrivateKeyId alice_private_key_id,
+ api::UserId bob_user_id, td::Slice bob_public_key,
+ td::Slice start) {
+ TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(alice_private_key_id));
+ TRY_RESULT(bob_public_key_internal, PublicKey::from_slice(bob_public_key));
+ return container_.try_build<Handshake>({}, [&] {
+ return QRHandshakeAlice::create(alice_user_id, private_key_ref.to_private_key(), bob_user_id,
+ bob_public_key_internal, start.str());
+ });
+ }
+ td::Result<api::Bytes> handshake_alice_send_accept(api::HandshakeId alice_handshake_id) {
+ TRY_RESULT(alice_handshake, to_handshake_alice_ref(alice_handshake_id));
+ return alice_handshake->generate_accept().as_slice().str();
+ }
+
+ td::Result<api::Bytes> handshake_bob_receive_accept_send_finish(api::HandshakeId bob_handshake_id,
+ api::UserId alice_id, td::Slice alice_public_key,
+ td::Slice accept) {
+ TRY_RESULT(bob_handshake, to_handshake_bob_ref(bob_handshake_id));
+ TRY_RESULT(alice_public_key_internal, PublicKey::from_slice(alice_public_key));
+ TRY_RESULT(msg, bob_handshake->receive_accept(alice_id, alice_public_key_internal, accept.str()));
+ return msg.as_slice().str();
+ }
+
+ td::Result<api::Ok> handshake_alice_receive_finish(api::HandshakeId alice_handshake_id, td::Slice finish) {
+ TRY_RESULT(alice_handshake, to_handshake_alice_ref(alice_handshake_id));
+ TRY_STATUS(alice_handshake->receive_finish(finish));
+ return api::Ok();
+ }
+
+ td::Result<api::SymmetricKeyId> handshake_get_shared_key_id(api::HandshakeId handshake_id) {
+ TRY_RESULT(handshake, container_.get_unique<Handshake>(handshake_id));
+ TRY_RESULT(shared_secret, std::visit([&](auto &&v) { return v.shared_secret(); }, *handshake));
+ auto hash = to_hash("handshake shared_secret", shared_secret.as_slice());
+ return container_.try_build<Key>(hash, [&]() -> td::Result<td::SecureString> { return std::move(shared_secret); });
+ }
+
+ td::Result<api::Ok> handshake_destroy(std::optional<api::HandshakeId> o_handshake_id) {
+ TRY_STATUS(container_.destroy<Handshake>(o_handshake_id));
+ return api::Ok();
+ }
+
+ td::Result<api::Bytes> handshake_get_start_id(td::Slice start) {
+ auto hash = to_hash("handshake start id", start);
+ return hash.as_slice().str();
+ }
+ td::Result<api::LoginId> login_create_for_bob() {
+ auto bob_fake_id = 0;
+ auto bob_fake_pk = generate_dummy_key().move_as_ok();
+ return handshake_create_for_bob(bob_fake_id, bob_fake_pk);
+ }
+ td::Result<api::Bytes> login_bob_send_start(api::LoginId bob_login_id) {
+ TRY_RESULT(bob_handshake, to_handshake_bob_ref(bob_login_id));
+ return bob_handshake->generate_start();
+ }
+ td::Result<api::Bytes> login_create_for_alice(api::UserId alice_user_id, api::PrivateKeyId alice_private_key_id,
+ td::Slice start) {
+ auto bob_fake_id = 0;
+ auto bob_fake_pk = generate_dummy_key().move_as_ok();
+ TRY_RESULT(handshake_id,
+ handshake_create_for_alice(alice_user_id, alice_private_key_id, bob_fake_id,
+ to_public_key(bob_fake_pk).move_as_ok().to_secure_string(), start));
+ TRY_RESULT(shared_key_id, handshake_get_shared_key_id(handshake_id));
+ TRY_RESULT(encrypted_alice_pk, to_encrypted_private_key(alice_private_key_id, shared_key_id));
+ TRY_RESULT(accept, handshake_alice_send_accept(handshake_id));
+ return QRHandshakeAlice::serialize_login_import(accept, encrypted_alice_pk);
+ }
+
+ td::Result<api::PrivateKeyId> login_finish_for_bob(api::LoginId bob_login_id, api::UserId alice_user_id,
+ const api::PublicKey &alice_public_key, td::Slice data) {
+ std::pair<std::string, std::string> accept_and_key;
+ {
+ TRY_RESULT(bob_handshake, to_handshake_bob_ref(bob_login_id));
+ TRY_RESULT_ASSIGN(accept_and_key, QRHandshakeAlice::deserialize_login_import(data));
+ TRY_RESULT(alice_public_key_internal, PublicKey::from_slice(alice_public_key));
+ TRY_RESULT(finish, bob_handshake->receive_accept(alice_user_id, alice_public_key_internal, accept_and_key.first));
+ }
+ TRY_RESULT(shared_key_id, handshake_get_shared_key_id(bob_login_id));
+ return from_encrypted_private_key(accept_and_key.second, shared_key_id);
+ }
+
+ api::Result<api::Ok> login_destroy(api::LoginId login_id) {
+ return handshake_destroy(login_id);
+ }
+ td::Result<api::Ok> login_destroy_all() {
+ return handshake_destroy({});
+ }
+ td::Result<api::StorageId> storage_create(api::PrivateKeyId key_id, td::Slice last_block) {
+ TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(key_id));
+
+ TRY_RESULT(storage, EncryptedStorage::create(last_block, private_key_ref.to_private_key()));
+ return container_.emplace<EncryptedStorage>(std::move(storage));
+ }
+
+ td::Result<api::Ok> storage_destroy(std::optional<api::StorageId> o_storage_id) {
+ TRY_STATUS(container_.destroy<EncryptedStorage>(o_storage_id));
+ return api::Ok();
+ }
+
+ td::Result<api::Ok> call_destroy(std::optional<api::CallId> o_call_id) {
+ TRY_STATUS(container_.destroy<Call>(o_call_id));
+ return api::Ok();
+ }
+
+ template <class T>
+ td::Result<api::UpdateId> storage_update_contact(api::StorageId storage_id, api::PublicKeyId key,
+ api::SignedEntry<T> signed_entry) {
+ TRY_RESULT(storage_ref, to_storage_ref(storage_id));
+ TRY_RESULT(public_key_ref, to_public_key(key));
+ return storage_ref->update(KeyContactByPublicKey{public_key_ref.to_u256()}, std::move(signed_entry));
+ }
+ template <class T>
+ td::Result<api::SignedEntry<T>> storage_sign_entry(api::PrivateKeyId key, api::Entry<T> entry) {
+ TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(key));
+ return EncryptedStorage::sign_entry(private_key_ref.to_private_key(), std::move(entry));
+ }
+ td::Result<std::optional<api::Contact>> storage_get_contact(api::StorageId storage_id, api::PublicKeyId key) {
+ TRY_RESULT(storage_ref, to_storage_ref(storage_id));
+ TRY_RESULT(public_key_ref, to_public_key(key));
+ return storage_ref->get(KeyContactByPublicKey{public_key_ref.to_u256()}, false);
+ }
+ td::Result<std::optional<api::Contact>> storage_get_contact_optimistic(api::StorageId storage_id,
+ api::PublicKeyId key) {
+ TRY_RESULT(storage_ref, to_storage_ref(storage_id));
+ TRY_RESULT(public_key_ref, to_public_key(key));
+ return storage_ref->get(KeyContactByPublicKey{public_key_ref.to_u256()}, true);
+ }
+ td::Result<std::int64_t> storage_blockchain_height(api::StorageId storage_id) {
+ TRY_RESULT(storage_ref, to_storage_ref(storage_id));
+ return storage_ref->get_height();
+ }
+ td::Result<api::StorageUpdates> storage_blockchain_apply_block(api::StorageId storage_id, td::Slice block) {
+ TRY_RESULT(storage_ref, to_storage_ref(storage_id));
+ TRY_RESULT(updates, storage_ref->apply_block(block));
+ auto fixed_updates = td::transform(updates.updates, [&](auto update) {
+ auto public_key_id = from_public_key(update.first.public_key.as_slice()).move_as_ok();
+ return std::make_pair(public_key_id, std::move(update.second));
+ });
+ return api::StorageUpdates{std::move(fixed_updates)};
+ }
+ td::Result<api::Ok> storage_blockchain_add_proof(api::StorageId storage_id, td::Slice proof,
+ td::Span<std::string> keys) {
+ TRY_RESULT(storage_ref, to_storage_ref(storage_id));
+ TRY_STATUS(storage_ref->add_proof(proof, keys));
+ return api::Ok();
+ }
+ td::Result<api::StorageBlockchainState> storage_get_blockchain_state(api::StorageId storage_id) {
+ TRY_RESULT(storage_ref, to_storage_ref(storage_id));
+ auto state = storage_ref->get_blockchain_state();
+ return api::StorageBlockchainState{state.next_block, state.need_proofs};
+ }
+
+ td::Result<GroupStateRef> to_group_state(const api::CallState &call_state) {
+ GroupState group_state;
+ group_state.external_permissions = GroupParticipantFlags::AddUsers | GroupParticipantFlags::RemoveUsers;
+ for (auto &participant : call_state.participants) {
+ TRY_RESULT(public_key, to_public_key(participant.public_key_id));
+ group_state.participants.push_back(
+ GroupParticipant{participant.user_id, participant.permissions & 3, public_key, 0});
+ }
+ return std::make_shared<GroupState>(std::move(group_state));
+ }
+ td::Result<api::CallState> to_call_state(const GroupState &group_state) {
+ api::CallState call_state;
+ for (auto &participant : group_state.participants) {
+ auto public_key_id = from_public_key(participant.public_key.to_secure_string()).move_as_ok();
+ call_state.participants.push_back(
+ api::CallParticipant{participant.user_id, public_key_id, participant.flags & 3});
+ }
+ return call_state;
+ }
+
+ td::Result<api::Bytes> call_create_zero_block(api::PrivateKeyId private_key_id, const api::CallState &initial_state) {
+ TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(private_key_id));
+ TRY_RESULT(group_state, to_group_state(initial_state));
+ return Call::create_zero_block(private_key_ref.to_private_key(), group_state);
+ }
+ tde2e_api::Result<std::string> call_create_self_add_block(api::PrivateKeyId private_key_id, td::Slice previous_block,
+ const tde2e_api::CallParticipant &self) {
+ TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(private_key_id));
+ TRY_RESULT(public_key, to_public_key(self.public_key_id));
+ return Call::create_self_add_block(private_key_ref.to_private_key(), previous_block,
+ tde2e_core::GroupParticipant{self.user_id, 3, public_key, 0});
+ }
+
+ td::Result<api::CallId> call_create(api::UserId user_id, api::PrivateKeyId private_key_id, td::Slice last_block) {
+ TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(private_key_id));
+
+ TRY_RESULT(call, Call::create(user_id, private_key_ref.to_private_key(), last_block));
+ return container_.emplace<Call>(std::move(call));
+ }
+ td::Result<api::Bytes> call_describe(api::CallId call_id) {
+ TRY_RESULT(call_ref, to_call_ref(call_id));
+ td::StringBuilder sb;
+ sb << *call_ref;
+ return sb.as_cslice().str();
+ }
+
+ td::Result<api::Bytes> call_create_change_state_block(api::CallId call_id, const api::CallState &new_state) {
+ TRY_RESULT(call_ref, to_call_ref(call_id));
+ TRY_RESULT(group_state, to_group_state(new_state));
+ return call_ref->build_change_state(group_state);
+ }
+ td::Result<api::SecureBytes> call_export_shared_key(api::CallId call_id) {
+ TRY_RESULT(call_ref, to_call_ref(call_id));
+ TRY_RESULT(shared_key, call_ref->shared_key());
+ return shared_key.as_slice().str();
+ }
+ td::Result<api::Bytes> call_encrypt(api::CallId call_id, api::CallChannelId channel_id, td::Slice message) {
+ TRY_RESULT(call_ref, to_call_ref(call_id));
+ return call_ref->encrypt(channel_id, message);
+ }
+ td::Result<api::SecureBytes> call_decrypt(api::CallId call_id, api::UserId user_id, api::CallChannelId channel_id,
+ td::Slice message) {
+ TRY_RESULT(call_ref, to_call_ref(call_id));
+ return call_ref->decrypt(user_id, channel_id, message);
+ }
+
+ td::Result<int> call_get_height(api::CallId call_id) {
+ TRY_RESULT(call_ref, to_call_ref(call_id));
+ return call_ref->get_height();
+ }
+ td::Result<api::CallState> call_apply_block(api::CallId call_id, td::Slice block) {
+ TRY_RESULT(call_ref, to_call_ref(call_id));
+ TRY_STATUS(call_ref->apply_block(block));
+ TRY_RESULT(group_state, call_ref->get_group_state());
+ return to_call_state(*group_state);
+ }
+
+ td::Result<api::CallState> call_get_state(api::CallId call_id) {
+ TRY_RESULT(call_ref, to_call_ref(call_id));
+ TRY_RESULT(group_state, call_ref->get_group_state());
+ return to_call_state(*group_state);
+ }
+
+ td::Result<api::CallVerificationState> call_get_verification_state(api::CallId call_id) {
+ TRY_RESULT(call_ref, to_call_ref(call_id));
+ return call_ref->get_verification_state();
+ }
+ td::Result<api::CallVerificationState> call_receive_inbound_message(api::CallId call_id, td::Slice message) {
+ TRY_RESULT(call_ref, to_call_ref(call_id));
+ return call_ref->receive_inbound_message(message);
+ }
+ td::Result<std::vector<std::string>> call_pull_outbound_messages(api::CallId call_id) {
+ TRY_RESULT(call_ref, to_call_ref(call_id));
+ return call_ref->pull_outbound_messages();
+ }
+
+ td::Result<api::CallVerificationWords> call_get_verification_words(api::CallId call_id) {
+ TRY_RESULT(call_ref, to_call_ref(call_id));
+ return call_ref->get_verification_words();
+ }
+ td::Result<api::PublicKey> to_public_key_api(api::AnyKeyId key_id) const {
+ TRY_RESULT(public_key, to_public_key(key_id));
+ return public_key.to_secure_string().as_slice().str();
+ }
+
+ private:
+ using Key = std::variant<td::SecureString, PublicKey, PrivateKeyWithMnemonic>;
+ using Handshake = std::variant<QRHandshakeAlice, QRHandshakeBob>;
+ Container<TypeInfo<Key, false, true>, TypeInfo<Handshake, true, true>, TypeInfo<EncryptedStorage, true, false>,
+ TypeInfo<Call, true, true>>
+ container_;
+
+ td::Result<PrivateKeyWithMnemonic> mnemonic_to_private_key(const Mnemonic &mnemonic) {
+ auto decrypted_key = DecryptedKey(mnemonic);
+ auto private_key = PrivateKeyWithMnemonic::from_private_key(mnemonic.to_private_key(), mnemonic.get_words());
+ return private_key;
+ }
+
+ td::Result<PublicKey> to_public_key(api::AnyKeyId key_id) const {
+ TRY_RESULT(key, container_.get_shared<Key>(key_id));
+ return std::visit(
+ td::overloaded([&](const PrivateKeyWithMnemonic &pk) -> td::Result<PublicKey> { return pk.to_public_key(); },
+ [&](const PublicKey &pk) -> td::Result<PublicKey> { return pk; },
+ [](const auto &) -> td::Result<PublicKey> {
+ return td::Status::Error(static_cast<int>(api::ErrorCode::InvalidInput),
+ "key_id doesn't contain public key");
+ }),
+ *key);
+ }
+
+ td::Result<PrivateKeyWithMnemonic> to_private_key_with_mnemonic(api::AnyKeyId key_id) const {
+ TRY_RESULT(key, container_.get_shared<Key>(key_id));
+ TRY_RESULT(ref, convert<PrivateKeyWithMnemonic>(std::move(key)));
+ return *ref;
+ }
+
+ td::Result<SecretRef> to_secret_ref(api::AnyKeyId key_id) const {
+ TRY_RESULT(key, container_.get_shared<Key>(key_id));
+ return convert<td::SecureString>(std::move(key));
+ }
+
+ td::Result<HandshakeAliceRef> to_handshake_alice_ref(api::HandshakeId alice_handshake_id) {
+ TRY_RESULT(handshake, container_.get_unique<Handshake>(alice_handshake_id));
+ return convert<QRHandshakeAlice>(std::move(handshake));
+ }
+ td::Result<HandshakeBobRef> to_handshake_bob_ref(api::HandshakeId bob_handshake_id) {
+ TRY_RESULT(handshake, container_.get_unique<Handshake>(bob_handshake_id));
+ return convert<QRHandshakeBob>(std::move(handshake));
+ }
+ td::Result<StorageRef> to_storage_ref(api::StorageId storage_id) {
+ return container_.get_unique<EncryptedStorage>(storage_id);
+ }
+ td::Result<CallRef> to_call_ref(api::CallId call_id) {
+ return container_.get_unique<Call>(call_id);
+ }
+};
+
+} // namespace tde2e_core
+namespace tde2e_api {
+tde2e_core::KeyChain &get_default_keychain() {
+ static tde2e_core::KeyChain keychain;
+ return keychain;
+}
+td::Slice to_slice(std::string_view s) {
+ if (s.empty()) {
+ return td::Slice();
+ }
+ return td::Slice(s.data(), s.size());
+}
+Result<Ok> set_log_verbosity_level(int new_verbosity_level) {
+ return get_default_keychain().set_log_verbosity_level(new_verbosity_level);
+}
+Result<PrivateKeyId> key_generate_private_key() {
+ return get_default_keychain().generate_private_key();
+}
+Result<PrivateKeyId> key_generate_temporary_private_key() {
+ return get_default_keychain().generate_temporary_private_key();
+}
+Result<PrivateKeyId> key_derive_secret(PrivateKeyId key_id, Slice tag) {
+ return get_default_keychain().derive_secret(key_id, to_slice(tag));
+}
+Result<Bytes> key_to_encrypted_private_key(PrivateKeyId key_id, SymmetricKeyId secret_id) {
+ return get_default_keychain().to_encrypted_private_key(key_id, secret_id);
+}
+Result<PrivateKeyId> key_from_encrypted_private_key(Slice encrypted_key, SymmetricKeyId secret_id) {
+ return get_default_keychain().from_encrypted_private_key(to_slice(encrypted_key), secret_id);
+}
+Result<SymmetricKeyId> key_from_bytes(SecureSlice secret) {
+ return get_default_keychain().from_bytes(to_slice(secret));
+}
+Result<Bytes> key_to_encrypted_private_key_internal(PrivateKeyId key_id, SymmetricKeyId secret_id) {
+ return get_default_keychain().to_encrypted_private_key_internal(key_id, secret_id);
+}
+Result<PrivateKeyId> key_from_encrypted_private_key_internal(Slice encrypted_key, SymmetricKeyId secret_id) {
+ return get_default_keychain().from_encrypted_private_key_internal(to_slice(encrypted_key), secret_id);
+}
+
+Result<PublicKeyId> key_from_public_key(Slice public_key) {
+ return get_default_keychain().from_public_key(to_slice(public_key));
+}
+
+Result<PrivateKeyId> key_from_ecdh(PrivateKeyId key_id, PublicKeyId other_public_key_id) {
+ return get_default_keychain().from_ecdh(key_id, other_public_key_id);
+}
+
+Result<PublicKey> key_to_public_key(PrivateKeyId key_id) {
+ return get_default_keychain().to_public_key_api(key_id);
+}
+
+Result<SecureBytes> key_to_words(PrivateKeyId key_id) {
+ return get_default_keychain().to_words(key_id);
+}
+Result<PrivateKeyId> key_from_words(SecureSlice words) {
+ return get_default_keychain().from_words(to_slice(words));
+}
+Result<Int512> key_sign(PrivateKeyId key, Slice data) {
+ return get_default_keychain().sign(key, to_slice(data));
+}
+Result<Ok> key_destroy(AnyKeyId key_id) {
+ TRY_STATUS(get_default_keychain().destroy(key_id));
+ return Ok();
+}
+Result<Ok> key_destroy_all() {
+ TRY_STATUS(get_default_keychain().destroy({}));
+ return Ok();
+}
+
+Result<EncryptedMessageForMany> encrypt_message_for_many(const std::vector<SymmetricKeyId> &key_ids,
+ SecureSlice message) {
+ return get_default_keychain().encrypt_message_for_many(std::move(key_ids), to_slice(message));
+}
+Result<SecureBytes> decrypt_message_for_many(SymmetricKeyId key_id, Slice encrypted_header, Slice encrypted_message) {
+ return get_default_keychain().decrypt_message_for_many(key_id, to_slice(encrypted_header),
+ to_slice(encrypted_message));
+}
+Result<Bytes> encrypt_message_for_one(SymmetricKeyId key_id, SecureSlice message) {
+ return get_default_keychain().encrypt_message_for_one(key_id, to_slice(message));
+}
+Result<SecureBytes> decrypt_message_for_one(SymmetricKeyId key_id, Slice encrypted_message) {
+ return get_default_keychain().decrypt_message_for_one(key_id, to_slice(encrypted_message));
+}
+Result<EncryptedMessageForMany> re_encrypt_message_for_many(SymmetricKeyId decrypt_key_id,
+ const std::vector<SymmetricKeyId> &encrypt_key_ids,
+ Slice encrypted_header, Slice encrypted_message) {
+ return get_default_keychain().re_encrypt_message_for_many(decrypt_key_id, std::move(encrypt_key_ids),
+ to_slice(encrypted_header), to_slice(encrypted_message));
+}
+
+Result<HandshakeId> handshake_create_for_bob(UserId bob_user_id, PrivateKeyId bob_private_key_id) {
+ return get_default_keychain().handshake_create_for_bob(bob_user_id, bob_private_key_id);
+}
+Result<Bytes> handshake_bob_send_start(HandshakeId bob_handshake_id) {
+ return get_default_keychain().handshake_bob_send_start(bob_handshake_id);
+}
+Result<HandshakeId> handshake_create_for_alice(UserId alice_user_id, PrivateKeyId alice_private_key_id,
+ UserId bob_user_id, const PublicKey &bob_public_key, Slice start) {
+ return get_default_keychain().handshake_create_for_alice(alice_user_id, alice_private_key_id, bob_user_id,
+ to_slice(bob_public_key), to_slice(start));
+}
+Result<Bytes> handshake_alice_send_accept(HandshakeId alice_handshake_id) {
+ return get_default_keychain().handshake_alice_send_accept(alice_handshake_id);
+}
+Result<Bytes> handshake_bob_receive_accept_send_finish(HandshakeId bob_handshake_id, UserId alice_id,
+ const PublicKey &alice_public_key, Slice accept) {
+ return get_default_keychain().handshake_bob_receive_accept_send_finish(bob_handshake_id, alice_id,
+ to_slice(alice_public_key), to_slice(accept));
+}
+Result<Bytes> handshake_start_id(Slice start) {
+ return get_default_keychain().handshake_get_start_id(to_slice(start));
+}
+Result<Ok> handshake_alice_receive_finish(HandshakeId alice_handshake_id, Slice finish) {
+ return get_default_keychain().handshake_alice_receive_finish(alice_handshake_id, to_slice(finish));
+}
+Result<SymmetricKeyId> handshake_get_shared_key_id(HandshakeId handshake_id) {
+ return get_default_keychain().handshake_get_shared_key_id(handshake_id);
+}
+Result<Ok> handshake_destroy(HandshakeId handshake_id) {
+ return get_default_keychain().handshake_destroy(handshake_id);
+}
+Result<Ok> handshake_destroy_all() {
+ return get_default_keychain().handshake_destroy({});
+}
+
+Result<LoginId> login_create_for_bob() {
+ return get_default_keychain().login_create_for_bob();
+}
+Result<Bytes> login_bob_send_start(LoginId bob_login_id) {
+ return get_default_keychain().login_bob_send_start(bob_login_id);
+}
+Result<Bytes> login_create_for_alice(UserId alice_user_id, PrivateKeyId alice_private_key_id, Slice start) {
+ return get_default_keychain().login_create_for_alice(alice_user_id, alice_private_key_id, to_slice(start));
+}
+Result<PrivateKeyId> login_finish_for_bob(LoginId bob_login_id, UserId alice_user_id, const PublicKey &alice_public_key,
+ Slice data) {
+ return get_default_keychain().login_finish_for_bob(bob_login_id, alice_user_id, alice_public_key, to_slice(data));
+}
+Result<Ok> login_destroy(LoginId login_id) {
+ return get_default_keychain().login_destroy(login_id);
+}
+Result<Ok> login_destroy_all() {
+ return get_default_keychain().login_destroy_all();
+}
+
+Result<StorageId> storage_create(PrivateKeyId key_id, Slice last_block) {
+ return get_default_keychain().storage_create(key_id, to_slice(last_block));
+}
+Result<Ok> storage_destroy(StorageId storage_id) {
+ return get_default_keychain().storage_destroy(storage_id);
+}
+Result<Ok> storage_destroy_all() {
+ return get_default_keychain().storage_destroy({});
+}
+template <class T>
+Result<UpdateId> storage_update_contact(StorageId storage_id, PublicKeyId key, SignedEntry<T> signed_entry) {
+ return get_default_keychain().storage_update_contact(storage_id, key, std::move(signed_entry));
+}
+template <class T>
+Result<SignedEntry<T>> storage_sign_entry(PrivateKeyId key, Entry<T> entry) {
+ return get_default_keychain().storage_sign_entry(key, std::move(entry));
+}
+Result<std::optional<Contact>> storage_get_contact(StorageId storage_id, PublicKeyId key) {
+ return get_default_keychain().storage_get_contact(storage_id, key);
+}
+Result<std::optional<Contact>> storage_get_contact_optimistic(StorageId storage_id, PublicKeyId key) {
+ return get_default_keychain().storage_get_contact_optimistic(storage_id, key);
+}
+Result<std::int64_t> storage_blockchain_height(StorageId storage_id) {
+ return get_default_keychain().storage_blockchain_height(storage_id);
+}
+Result<StorageUpdates> storage_blockchain_apply_block(StorageId storage_id, Slice block) {
+ return get_default_keychain().storage_blockchain_apply_block(storage_id, to_slice(block));
+}
+Result<Ok> storage_blockchain_add_proof(StorageId storage_id, Slice proof, const std::vector<std::string> &keys) {
+ return get_default_keychain().storage_blockchain_add_proof(storage_id, to_slice(proof), keys);
+}
+Result<StorageBlockchainState> storage_get_blockchain_state(StorageId storage_id) {
+ return get_default_keychain().storage_get_blockchain_state(storage_id);
+}
+
+Result<Bytes> call_create_zero_block(PrivateKeyId private_key_id, const CallState &initial_state) {
+ return get_default_keychain().call_create_zero_block(private_key_id, initial_state);
+}
+Result<Bytes> call_create_self_add_block(PrivateKeyId private_key_id, Slice previous_block,
+ const CallParticipant &self) {
+ return get_default_keychain().call_create_self_add_block(private_key_id, to_slice(previous_block), self);
+}
+Result<CallId> call_create(UserId user_id, PrivateKeyId private_key_id, Slice last_block) {
+ return get_default_keychain().call_create(user_id, private_key_id, to_slice(last_block));
+}
+Result<std::string> call_describe(CallId call_id) {
+ return get_default_keychain().call_describe(call_id);
+}
+Result<std::string> call_describe_block(Slice block_slice) {
+ bool is_server = tde2e_core::Blockchain::is_from_server(to_slice(block_slice));
+ TRY_RESULT(block_str, tde2e_core::Blockchain::from_any_to_local(std::string(block_slice)));
+ td::TlParser parser(block_str);
+ auto magic = parser.fetch_int();
+ if (magic != td::e2e_api::e2e_chain_block::ID) {
+ return td::Status::Error("Wrong magic");
+ }
+ auto block = td::e2e_api::e2e_chain_block::fetch(parser);
+ parser.fetch_end();
+ TRY_STATUS(parser.get_status());
+ return PSTRING() << (is_server ? "Server:" : "Local:") << to_string(block);
+}
+Result<std::string> call_describe_message(Slice broadcast_slice) {
+ bool is_server = tde2e_core::Blockchain::is_from_server(to_slice(broadcast_slice));
+ TRY_RESULT(broadcast_str, tde2e_core::Blockchain::from_any_to_local(std::string(broadcast_slice)));
+
+ td::TlParser parser(broadcast_str);
+ auto broadcast = td::e2e_api::e2e_chain_GroupBroadcast::fetch(parser);
+ parser.fetch_end();
+ TRY_STATUS(parser.get_status());
+ return PSTRING() << (is_server ? "Server:" : "Local:") << to_string(broadcast);
+}
+Result<Bytes> call_create_change_state_block(CallId call_id, const CallState &new_state) {
+ return get_default_keychain().call_create_change_state_block(call_id, new_state);
+}
+Result<SecureBytes> call_export_shared_key(CallId call_id) {
+ return get_default_keychain().call_export_shared_key(call_id);
+}
+Result<Bytes> call_encrypt(CallId call_id, CallChannelId channel_id, SecureSlice message) {
+ return get_default_keychain().call_encrypt(call_id, channel_id, to_slice(message));
+}
+Result<SecureBytes> call_decrypt(CallId call_id, UserId user_id, CallChannelId channel_id, Slice message) {
+ return get_default_keychain().call_decrypt(call_id, user_id, channel_id, to_slice(message));
+}
+Result<int> call_get_height(CallId call_id) {
+ return get_default_keychain().call_get_height(call_id);
+}
+Result<CallState> call_apply_block(CallId call_id, Slice block) {
+ return get_default_keychain().call_apply_block(call_id, to_slice(block));
+}
+Result<CallState> call_get_state(CallId call_id) {
+ return get_default_keychain().call_get_state(call_id);
+}
+Result<CallVerificationState> call_get_verification_state(CallId call_id) {
+ return get_default_keychain().call_get_verification_state(call_id);
+}
+Result<CallVerificationState> call_receive_inbound_message(CallId call_id, Slice message) {
+ return get_default_keychain().call_receive_inbound_message(call_id, to_slice(message));
+}
+Result<std::vector<Bytes>> call_pull_outbound_messages(CallId call_id) {
+ return get_default_keychain().call_pull_outbound_messages(call_id);
+}
+
+Result<CallVerificationWords> call_get_verification_words(CallId call_id) {
+ return get_default_keychain().call_get_verification_words(call_id);
+}
+Result<Ok> call_destroy(CallId call_id) {
+ return get_default_keychain().call_destroy(call_id);
+}
+Result<Ok> call_destroy_all() {
+ return get_default_keychain().call_destroy({});
+}
+
+// instantiations of templates
+template Result<UpdateId> storage_update_contact<UserId>(StorageId storage_id, PublicKeyId key,
+ SignedEntry<UserId> signed_entry);
+
+template Result<SignedEntry<UserId>> storage_sign_entry<UserId>(PrivateKeyId key, Entry<UserId> entry);
+
+template Result<UpdateId> storage_update_contact<Name>(StorageId storage_id, PublicKeyId key,
+ SignedEntry<Name> signed_entry);
+
+template Result<SignedEntry<Name>> storage_sign_entry<Name>(PrivateKeyId key, Entry<Name> entry);
+
+template Result<UpdateId> storage_update_contact<PhoneNumber>(StorageId storage_id, PublicKeyId key,
+ SignedEntry<PhoneNumber> signed_entry);
+
+template Result<SignedEntry<PhoneNumber>> storage_sign_entry<PhoneNumber>(PrivateKeyId key, Entry<PhoneNumber> entry);
+
+template Result<UpdateId> storage_update_contact<EmojiNonces>(StorageId storage_id, PublicKeyId key,
+ SignedEntry<EmojiNonces> signed_entry);
+
+template Result<SignedEntry<EmojiNonces>> storage_sign_entry<EmojiNonces>(PrivateKeyId key, Entry<EmojiNonces> entry);
+
+template Result<UpdateId> storage_update_contact<ContactState>(StorageId storage_id, PublicKeyId key,
+ SignedEntry<ContactState> signed_entry);
+
+template Result<SignedEntry<ContactState>> storage_sign_entry<ContactState>(PrivateKeyId key,
+ Entry<ContactState> entry);
+
+} // namespace tde2e_api