// // 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) // #pragma once #include namespace tde2e_api { enum class ErrorCode : int { UnknownError = 100, Any = 101, InvalidInput = 102, InvalidKeyId = 103, InvalidId = 104, InvalidBlock = 200, InvalidBlock_NoChanges = 201, InvalidBlock_InvalidSignature = 202, InvalidBlock_HashMismatch = 203, InvalidBlock_HeightMismatch = 204, InvalidBlock_InvalidStateProof_Group = 205, InvalidBlock_InvalidStateProof_Secret = 206, InvalidBlock_NoPermissions = 207, InvalidBlock_InvalidGroupState = 208, InvalidBlock_InvalidSharedSecret = 209, InvalidCallGroupState_NotParticipant = 300, InvalidCallGroupState_WrongUserId = 301, Decrypt_UnknownEpoch = 400, Encrypt_UnknownEpoch = 401, InvalidBroadcast_InFuture = 500, InvalidBroadcast_NotInCommit = 501, InvalidBroadcast_NotInReveal = 502, InvalidBroadcast_UnknownUserId = 503, InvalidBroadcast_AlreadyApplied = 504, InvalidBroadcast_InvalidReveal = 505, InvalidBroadcast_InvalidBlockHash = 506, InvalidCallChannelId = 600, CallFailed = 601, CallKeyAlreadyUsed = 602 }; inline std::string_view error_string(ErrorCode error_code) { switch (error_code) { case ErrorCode::Any: return ""; case ErrorCode::UnknownError: return "UNKNOWN_ERROR"; case ErrorCode::InvalidInput: return "INVALID_INPUT"; case ErrorCode::InvalidKeyId: return "INVALID_KEY_ID"; case ErrorCode::InvalidId: return "INVALID_ID"; case ErrorCode::InvalidBlock: return "INVALID_BLOCK"; case ErrorCode::InvalidBlock_NoChanges: return "INVALID_BLOCK__NO_CHANGES"; case ErrorCode::InvalidBlock_InvalidSignature: return "INVALID_BLOCK__INVALID_SIGNATURE"; case ErrorCode::InvalidBlock_HashMismatch: return "INVALID_BLOCK__HASH_MISMATCH"; case ErrorCode::InvalidBlock_HeightMismatch: return "INVALID_BLOCK__HEIGHT_MISMATCH"; case ErrorCode::InvalidBlock_InvalidStateProof_Group: return "INVALID_BLOCK__INVALID_STATE_PROOF__GROUP"; case ErrorCode::InvalidBlock_InvalidStateProof_Secret: return "INVALID_BLOCK__INVALID_STATE_PROOF__SECRET"; case ErrorCode::InvalidBlock_InvalidGroupState: return "INVALID_BLOCK__INVALID_GROUP_STATE"; case ErrorCode::InvalidBlock_InvalidSharedSecret: return "INVALID_BLOCK__INVALID_SHARED_SECRET"; case ErrorCode::InvalidBlock_NoPermissions: return "INVALID_BLOCK__NO_PERMISSIONS"; case ErrorCode::InvalidCallGroupState_NotParticipant: return "INVALID_CALL_GROUP_STATE__NOT_PARTICIPANT"; case ErrorCode::InvalidCallGroupState_WrongUserId: return "INVALID_CALL_GROUP_STATE__WRONG_USER_ID"; case ErrorCode::Decrypt_UnknownEpoch: return "DECRYPT__UNKNOWN_EPOCH"; case ErrorCode::Encrypt_UnknownEpoch: return "ENCRYPT__UNKNOWN_EPOCH"; case ErrorCode::InvalidBroadcast_InFuture: return "INVALID_BROADCAST__IN_FUTURE"; case ErrorCode::InvalidBroadcast_NotInCommit: return "INVALID_BROADCAST__NOT_IN_COMMIT"; case ErrorCode::InvalidBroadcast_NotInReveal: return "INVALID_BROADCAST__NOT_IN_REVEAL"; case ErrorCode::InvalidBroadcast_UnknownUserId: return "INVALID_BROADCAST__UNKNOWN_USER_ID"; case ErrorCode::InvalidBroadcast_AlreadyApplied: return "INVALID_BROADCAST__ALREADY_APPLIED"; case ErrorCode::InvalidBroadcast_InvalidReveal: return "INVALID_BROADCAST__INVALID_REVEAL"; case ErrorCode::InvalidBroadcast_InvalidBlockHash: return "INVALID_BROADCAST__INVALID_BLOCK_HASH"; case ErrorCode::CallFailed: return "CALL_FAILED"; case ErrorCode::CallKeyAlreadyUsed: return "CALL_KEY_ALREADY_USED"; case ErrorCode::InvalidCallChannelId: return "INVALID_CALL_CHANNEL_ID"; } return "UNKNOWN_ERROR"; } } // namespace tde2e_api #include #include #include #include #include #include #include #include namespace td { template class Result; class Status; } // namespace td namespace tde2e_api { // // Result and Error helper classes // struct Error { ErrorCode code; std::string message; }; template class Result { public: Result(const T &value) : data_(value) { } Result(T &&value) : data_(std::move(value)) { } Result(const Error &error) : data_(error) { } Result(Error &&error) : data_(std::move(error)) { } Result(td::Result &&value); Result(td::Status &&status); // Check if the result is a success bool is_ok() const { return std::holds_alternative(data_); } T &value() { return std::get(data_); } const T &value() const { return std::get(data_); } Error &error() { return std::get(data_); } const Error &error() const { return std::get(data_); } private: std::variant data_; }; // // Encryption // using Int256 = std::array; using Int512 = std::array; //TODO: strong typed ids using PublicKey = std::string; using HandshakeId = std::int64_t; using LoginId = std::int64_t; using UserId = std::int64_t; using AnyKeyId = std::int64_t; using PrivateKeyId = std::int64_t; using PublicKeyId = std::int64_t; using SymmetricKeyId = std::int64_t; using Bytes = std::string; using SecureBytes = std::string; using Slice = std::string_view; using SecureSlice = std::string_view; struct Ok {}; struct EncryptedMessageForMany { std::vector encrypted_headers; std::string encrypted_message; }; Result set_log_verbosity_level(int level); // Keys management // private keys will stay inside the library when it is possible // all keys are stored only in memory and should be created or imported before usage Result key_generate_private_key(); Result key_generate_temporary_private_key(); Result key_derive_secret(PrivateKeyId key_id, Slice tag); Result key_from_bytes(SecureSlice secret); Result key_to_encrypted_private_key(PrivateKeyId key_id, SymmetricKeyId secret_id); Result key_from_encrypted_private_key(Slice encrypted_key, SymmetricKeyId secret_id); Result key_from_public_key(Slice public_key); Result key_from_ecdh(PrivateKeyId key_id, PublicKeyId other_public_key_id); Result key_to_public_key(PrivateKeyId key_id); Result key_to_words(PrivateKeyId key_id); Result key_from_words(SecureSlice words); Result key_sign(PrivateKeyId key, Slice data); Result key_destroy(AnyKeyId key_id); Result key_destroy_all(); // Used to encrypt key between processes, secret_id must be generated with key_from_ecdh Result key_to_encrypted_private_key_internal(PrivateKeyId key_id, SymmetricKeyId secret_id); Result key_from_encrypted_private_key_internal(Slice encrypted_key, SymmetricKeyId secret_id); Result encrypt_message_for_many(const std::vector &key_ids, SecureSlice message); // keeps encrypted_message empty in result Result re_encrypt_message_for_many(SymmetricKeyId decrypt_key_id, const std::vector &encrypt_key_ids, Slice encrypted_header, Slice encrypted_message); Result decrypt_message_for_many(SymmetricKeyId key_id, Slice encrypted_header, Slice encrypted_message); Result encrypt_message_for_one(SymmetricKeyId key_id, SecureSlice message); Result decrypt_message_for_one(SymmetricKeyId key_id, Slice encrypted_message); // Utilities for secret key verification/transfer via qr (or any other alternative channel) // Requires: // - alternative channel to reliably transfer 'start' message from Bob to Alice (scanning QR is such channel) // // Alice: // - knows shared secret right after the handshake creation // - Bob's secret key is verified after finish is received // // Bob: // - knows shared secret right after accept is received // - Alice's secret key is verified after accept is received // // Use cases: // - Transfer of the key from old device to the new device // - Verification of other person's public key // - Contact sharing // Result handshake_create_for_bob(UserId bob_user_id, PrivateKeyId bob_private_key_id); Result handshake_create_for_alice(UserId alice_user_id, PrivateKeyId alice_private_key_id, UserId bob_user_id, const PublicKey &bob_public_key, Slice start); Result handshake_bob_send_start(HandshakeId bob_handshake_id); Result handshake_alice_send_accept(HandshakeId alice_handshake_id); Result handshake_bob_receive_accept_send_finish(HandshakeId bob_handshake_id, UserId alice_id, const PublicKey &alice_public_key, Slice accept); Result handshake_alice_receive_finish(HandshakeId alice_handshake_id, Slice finish); Result handshake_get_shared_key_id(HandshakeId handshake_id); Result handshake_destroy(HandshakeId handshake_id); Result handshake_destroy_all(); // Helper to get QR-code identifier Result handshake_start_id(Slice start); // There is wrapper for login Result login_create_for_bob(); Result login_bob_send_start(LoginId bob_login_id); Result login_create_for_alice(UserId alice_user_id, PrivateKeyId alice_private_key_id, Slice start); Result login_finish_for_bob(LoginId bob_login_id, UserId alice_user_id, const PublicKey &alice_public_key, Slice data); Result login_destroy(LoginId login_id); Result login_destroy_all(); // Personal info // 1. Each entry stored and signed separately // 2. Signature is never stored, but always is verified // 3. It should be possible to save data without signature (but it can't override data with signature) // 4. We should keep source of entry. Our, Server, Contact+Ts // 5. We must keep is_contact flag. template struct Entry { enum Source { Self, Server, Contact }; Source source; std::uint32_t timestamp; T value; Entry() : source(Self), timestamp(0), value() { } Entry(Source source, std::uint32_t timestamp, T &&value) : source(source), timestamp(timestamp), value(std::move(value)) { } }; template struct SignedEntry { Int512 signature; std::uint32_t timestamp{0}; T value; }; struct Name { std::string first_name; std::string last_name; }; struct PhoneNumber { std::string phone_number; }; struct EmojiNonces { std::optional self_nonce; std::optional contact_nonce_hash; std::optional contact_nonce; }; struct ContactState { enum State { Unknown, Contact, NotContact }; State state{Unknown}; ContactState() = default; explicit ContactState(State state) : state(state) { } }; struct Contact { // Each contact has both public_key and user_id // how it is stored internally is not clients library user's concern std::uint32_t generation{0}; // we always have public key. Essentially contact is defined by its public key PublicKeyId public_key{}; // Personal data, signed by contact. If it is not signed, it has no relations to this public key std::optional> o_user_id; std::optional> o_name; std::optional> o_phone_number; // Personal data save by user_itself // It always exists and it always given but we keep timestamp just in case Entry emoji_nonces; Entry contact_state; }; // library can't enforce persistency of changes, because it has no persistent state. // so it is client's responsibility to ensure that each change will be eventually saved to the server // NB: it is unclear how protect ourself from server ignoring our queries. using StorageId = std::int64_t; using UpdateId = std::int64_t; struct StorageBlockchainState { std::string next_suggested_block; std::vector required_proofs; }; struct StorageUpdates { std::vector>> updates; }; Result storage_create(PrivateKeyId key_id, Slice last_block); Result storage_destroy(StorageId storage_id); Result storage_destroy_all(); template Result storage_update_contact(StorageId storage_id, PublicKeyId key, SignedEntry signed_entry); template Result> storage_sign_entry(PrivateKeyId key, Entry entry); Result> storage_get_contact(StorageId storage_id, PublicKeyId key); Result> storage_get_contact_optimistic(StorageId storage_id, PublicKeyId key); Result storage_blockchain_height(StorageId storage_id); Result storage_blockchain_apply_block(StorageId storage_id, Slice block); Result storage_blockchain_add_proof(StorageId storage_id, Slice proof, const std::vector &keys); Result storage_get_blockchain_state(StorageId); using CallId = std::int64_t; using CallChannelId = std::int32_t; struct CallParticipant { UserId user_id; PublicKeyId public_key_id; int permissions{}; }; struct CallState { int height{}; std::vector participants; }; Result call_create_zero_block(PrivateKeyId private_key_id, const CallState &initial_state); Result call_create_self_add_block(PrivateKeyId private_key_id, Slice previous_block, const CallParticipant &self); Result call_create(UserId user_id, PrivateKeyId private_key_id, Slice last_block); Result call_describe(CallId call); Result call_describe_block(Slice block); Result call_describe_message(Slice message); Result call_create_change_state_block(CallId call_id, const CallState &new_state); Result call_encrypt(CallId call_id, CallChannelId channel_id, SecureSlice message, size_t unencrypted_prefix_size); Result call_decrypt(CallId call_id, UserId user_id, CallChannelId channel_id, Slice message); Result call_get_height(CallId call_id); Result call_apply_block(CallId call_id, Slice block); Result call_get_state(CallId call_id); struct CallVerificationState { int height{}; std::optional emoji_hash; }; Result call_get_verification_state(CallId call_id); Result call_receive_inbound_message(CallId call_id, Slice message); // should be called after: // - creation // - call_apply_block // - call_receive_inbound_messages Result> call_pull_outbound_messages(CallId call_id); struct CallVerificationWords { int height{}; std::vector words; }; Result call_get_verification_words(CallId call_id); Result call_destroy(CallId call_id); Result call_destroy_all(); } // namespace tde2e_api