Document Version: 1.2
Date: 2026-05-03
Scope: Embedding tdlib-obf into custom client software through tdjson, generated TDLib bindings, or native C++ integration
This document is for integrators who are building their own Telegram client software on top of tdlib-obf and need the actual integration contract:
- how to build the library correctly;
- what the client must provide at runtime;
- how stealth shaping is activated;
- what behavior changes when stealth is active;
- which parts are public TDLib API and which parts are internal-only seams.
This is not a protocol design document and not a re-explanation of all stealth internals. It is an operational guide for shipping a client that uses this fork correctly.
You do not need to start with the original TDLib documentation to integrate this fork correctly. The upstream TDLib pages are still useful as API reference and for examples of the authorization/update model, but they do not describe this fork's stealth transport behavior, DoH defaults, or other network-policy changes.
Use this guide as the primary integration contract for tdlib-obf, and use the original TDLib docs as a secondary reference for object names, authorization states, and generic TDLib behavior.
Published tdlib-obf API documentation website: https://telemt.github.io/tdlib-obf/
These points are not optional.
tdlib-obfstealth shaping is MTProto-proxy-only. It activates only when TDLib is using an MTProto proxy secret that enters TLS-emulation mode.- Direct Telegram connections are unaffected. SOCKS5 and HTTP proxies are also unaffected.
- The library must be built with
TDLIB_STEALTH_SHAPING=ON. - If your client uses a TLS-emulation MTProto proxy secret while the library was compiled with
TDLIB_STEALTH_SHAPING=OFF, TDLib fails fast withLOG(FATAL)instead of silently falling back to legacy behavior. - There is no separate public stealth API. Integrators use the normal TDLib proxy API:
addProxy,enableProxy,disableProxy,getProxies.
Operational consequence: if your client does not support MTProto proxy configuration, there is nothing to integrate on the stealth side.
The original TDLib getting-started guide is still correct about one important thing: your application is integrating an asynchronous request/update engine, not a synchronous RPC client.
These are the minimum TDLib concepts your client must implement correctly even if you never read the upstream tutorial end-to-end.
Your client sends requests and receives responses later. If you use tdjson, attach an @extra field to requests and use it to correlate responses.
Your client must handle updateAuthorizationState and drive authorization by reacting to the current state.
The first required state is authorizationStateWaitTdlibParameters. At that point the client must call setTdlibParameters with correct application and storage settings, including:
api_idapi_hash- writable database directory paths
- device and system metadata
- secret-chat and local-cache settings appropriate for the product
After that, the client must continue reacting to later authorization states such as phone number, login code, registration, and password until it reaches authorizationStateReady.
TDLib relies on the application processing incoming updates and responses in the order they are received. Do not build an integration that reorders update handling arbitrarily.
The application should maintain local caches of chats, users, groups, supergroups, and secret chats from updates such as:
updateNewChatupdateUserupdateBasicGroupupdateSupergroupupdateSecretChat
Do not assume every returned identifier should be followed by getChat or getUser. TDLib already sends the authoritative object stream through updates.
TDLib manages list ordering. The client should maintain chat lists by the (position.order, chat.id) pair and request more entries through loadChats when it needs more data.
If your client uploads or downloads files, it must handle updateFile correctly. File progress and final local/remote locations are delivered through the update stream, not through a separate polling API.
The following original pages are still useful as reference material:
https://core.telegram.org/tdlib/getting-startedfor the authorization flow and update modelhttps://core.telegram.org/tdlib/docs/td__api_8h.htmlfor object names, type shapes, and generated API conventions
Treat them as reference, not as the authoritative guide for this fork's network behavior.
At the application level, a custom client must be able to do the following:
- Accept an MTProto proxy server address, port, and secret from configuration, UI, MDM, or provisioning.
- Run the normal TDLib async request/update loop and process updates in order.
- Drive
setTdlibParametersand the authorization-state machine correctly. - Persist the proxy configuration and re-apply it before authorization when needed.
- Enable exactly one active proxy through the standard TDLib API.
- Preserve the provider-issued MTProto secret string exactly as issued.
- Expose or collect TDLib logs during rollout, because stealth activation, DoH behavior, and fallback decisions are logged.
- Configure DoH options before the first network activity if the product needs non-default DNS resolution.
- Tolerate lower parallel connection counts than upstream TDLib when stealth is active.
If your application architecture assumes many parallel raw connections to the same endpoint for download/upload throughput, you must revisit those assumptions for stealth-proxy deployments.
The repository build contract is standard CMake-based TDLib plus the stealth option enabled.
Required components:
- CMake
- A C++23-capable compiler
- OpenSSL
- zlib
- gperf
Optional binding outputs:
tdjson/ C interface for FFI-based integrations- generated C++ TDLib API bindings
- JNI bindings if
TD_ENABLE_JNI=ON - .NET bindings if
TD_ENABLE_DOTNET=ON
From repository root:
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DTD_ENABLE_BENCHMARKS=OFF \
-DTDLIB_STEALTH_SHAPING=ON
cmake --build build --parallel 4Notes:
TDLIB_STEALTH_SHAPINGdefaults toONin this fork, but integrators should still pass it explicitly in release builds so the contract is visible in CI and packaging scripts.- If you are building a distribution artifact for multiple client products, treat
TDLIB_STEALTH_SHAPING=ONas a release invariant, not a local convenience flag.
After building:
cmake --build build --target run_all_tests --parallel 4
./build/test/run_all_tests --filter TlsHelloIf you want a wider transport sanity pass:
ctest --test-dir build --output-on-failure -j 4Yes, integrators should use the repository example/ tree as a practical companion to this contract.
Start here:
example/README.mdfor the cross-language map and build overview.
Per-platform entry points:
- Python (
tdjson):example/python/README.md,example/python/tdjson_example.py - C++:
example/cpp/README.md,example/cpp/td_example.cpp,example/cpp/tdjson_example.cpp - Java/JNI and Java JSON:
example/java/README.md,example/java/org/drinkless/tdlib/example/Example.java,example/java/org/drinkless/tdlib/example/JsonExample.java - Android packaging scripts:
example/android/README.md,example/android/check-environment.sh,example/android/fetch-sdk.sh,example/android/build-openssl.sh,example/android/build-tdlib.sh - Apple/XCFramework build path:
example/ios/README.md,example/ios/build-openssl.sh,example/ios/build.sh - Swift sample app:
example/swift/README.md,example/swift/src/main.swift - .NET/C# paths:
example/csharp/README.md,example/csharp/TdExample.cs,example/uwp/README.md,example/uwp/build.ps1 - Browser/WebAssembly path:
example/web/README.md,example/web/build-openssl.sh,example/web/build-tdlib.sh,example/web/copy-tdlib.sh,example/web/build-tdweb.sh,example/web/tdweb/README.md
Important for this fork: those examples come from the upstream TDLib ecosystem and are useful for integration structure, but your production tdlib-obf artifacts should still explicitly enforce -DTDLIB_STEALTH_SHAPING=ON as described in this guide.
Stealth integration is deliberately routed through existing TDLib proxy objects.
Relevant public API surface:
proxyTypeMtproto secret:string = ProxyTypeaddProxy proxy:proxy enable:Bool = AddedProxyenableProxy proxy_id:int32 = OkdisableProxy = Ok
If you use the JSON interface, the minimal request shape is:
{
"@type": "addProxy",
"proxy": {
"@type": "proxy",
"server": "your-proxy.example.com",
"port": 443,
"type": {
"@type": "proxyTypeMtproto",
"secret": "PASTE_PROVIDER_SECRET_HERE"
}
},
"enable": true
}Important details:
- The TL schema comment says the MTProto secret is hexadecimal.
- The implementation currently accepts hexadecimal, Base64URL, and Base64 encodings through
ProxySecret::from_link. - The safest policy for integrators is: treat the secret as an opaque provider-issued string and pass it through unchanged.
Do not normalize, lowercase, split, or reconstruct the secret string in UI or middleware layers unless you fully control the server-side generation logic.
For tdjson integrations, the same setOption mechanism used for generic TDLib options is also the public entry point for the DoH configuration described later in this guide.
Stealth activation happens only for MTProto secrets that satisfy ProxySecret::emulate_tls().
At the raw byte level, that means:
- first byte
0xee; - followed by a 16-byte MTProto proxy secret;
- followed by an SNI domain string.
If you generate or validate secrets yourself, the appended TLS-emulation domain must satisfy the library's fail-closed parser:
- total appended domain length must be between
1and182bytes; - labels must be ASCII alphanumeric or
-only; - labels must not start with
-; - labels must not end with
-; - labels must not be empty;
- each label must be at most
63bytes.
This means embedded NUL bytes, control bytes, non-ASCII bytes, leading dots, trailing dots, empty labels, and overlong labels are all rejected.
These do not enter the stealth decorator path:
- plain 16-byte MTProto secrets;
0xddpadded MTProto secrets;- SOCKS5 proxies;
- HTTP proxies.
Those routes still work as supported TDLib proxy modes, but they do not get stealth shaping.
Once the library is built with stealth enabled and the client uses an ee... MTProto proxy secret, the transport stack changes in these ways.
The library wraps the MTProto obfuscated transport in StealthTransportDecorator and automatically selects a browser profile for TLS-emulation mode. Integrators do not choose a profile through public API.
Current runtime behavior is platform-aware and route-aware.
For stealth TLS MTProto proxies, the connection-count planner caps session counts to browser-like levels:
- main sessions:
1 - upload sessions:
1 - download sessions:
1 - small-download lane is merged into download
This is intentional. It reduces proxy-like connection fan-out that would otherwise be visible to DPI or telemetry.
Integrators should expect different bandwidth/concurrency behavior from upstream TDLib in this mode.
The flow controller enforces a per-destination budget and minimum reconnect interval using runtime flow behavior policy. Current default policy includes:
- maximum connects per 10 seconds per destination;
- minimum reconnect interval;
- connection lifetime and reuse constraints consumed by connection-pool policy.
Do not write client code that assumes it can force rapid repeated reconnects to the same stealth proxy endpoint without pacing.
The runtime route-policy validation keeps QUIC disabled. This fork is TCP/TLS-oriented for stealth-proxy mode. Do not design a custom client integration that depends on QUIC/HTTP3 support here.
If the active transport is not MTProto-proxy TLS emulation, tdlib-obf behaves like TDLib plus the other hardening changes in this fork, but the stealth masking subsystem itself is not active.
Integrators should route TDLib logs somewhere observable during rollout.
Important behaviors:
- Build mismatch:
ee...secret plusTDLIB_STEALTH_SHAPING=OFFcauses fatal termination with explicit diagnostics. - Bad proxy secret:
addProxy/ proxy construction fails with a400error. - Runtime config rejection: if transport stealth config validation fails during decorator construction, TDLib logs a warning and falls back to plain obfuscated MTProto transport for that connection.
- Decorator initialization failure: TDLib logs a warning and falls back to plain obfuscated transport.
- Successful activation: TDLib logs that stealth shaping is enabled for the emulate-TLS transport.
For production integrations, configure setLogStream or the equivalent binding-specific log sink before testing stealth deployment.
Stealth shaping does not remove the need for correct TLS trust handling in your client runtime environment.
Current trust-store behavior in this fork:
- On non-iOS-family platforms, OpenSSL default cert locations are probed.
- Environment overrides are supported through:
SSL_CERT_FILESSL_CERT_DIRTDLIB_SSL_CERT_FILETDLIB_SSL_CERT_DIR
- On Android, the implementation explicitly probes both:
/apex/com.android.conscrypt/cacerts/system/etc/security/cacerts
- On Apple platforms, trust anchors are loaded through
Security.framework/ keychain APIs. - iOS-family platforms intentionally avoid relying on OpenSSL default filesystem bundle probing.
What this means for integrators:
- If you ship on Linux, Windows, or Android variants with unusual CA layouts, verify trust-store discovery early.
- If your app sandbox does not expose the platform default bundle path, provide explicit cert overrides where appropriate.
- If verification is enabled and no trusted certificates are available, this fork now fails closed instead of silently proceeding with an empty trust store.
This fork ships with built-in DoH resolver support and also allows integrators to point TDLib at a custom DoH endpoint.
This is not limited to stealth MTProto proxy mode. It affects the connection-creation path that resolves hostnames before network connections are opened.
If you do nothing, hostname resolution uses a DoH resolver chain.
Current resolver order:
- default /
dns_type=google: Google first, Cloudflare fallback dns_type=cloudflare: Cloudflare first, Google fallbackdns_type=customwithout a custom URL: Google first, Cloudflare fallback
Built-in endpoints:
- Google:
https://dns.google/resolve - Cloudflare:
https://cloudflare-dns.com/dns-query
The public integration surface is setOption.
Supported option names:
dns_typewith valuesgoogle,cloudflare, orcustomcustom_dns_urlcustom_dns_headers
Important precedence rules:
- if
custom_dns_urlis non-empty, the resolver switches to custom mode regardless ofdns_type - if
dns_type=custombutcustom_dns_urlis empty, TDLib falls back to the default Google-then-Cloudflare chain
Set DNS options before first network use: before login, before enabling a proxy, and before any request that can trigger connection creation.
Reason: ConnectionCreator constructs and caches the DNS resolver actor on first use. Later option changes are not the safe integration path to rely on.
Use standard TDLib setOption requests.
Select Cloudflare-first resolution:
{
"@type": "setOption",
"name": "dns_type",
"value": {
"@type": "optionValueString",
"value": "cloudflare"
}
}Select a custom DoH endpoint:
{
"@type": "setOption",
"name": "custom_dns_url",
"value": {
"@type": "optionValueString",
"value": "https://resolver.example.com/dns-query"
}
}Pass an additional header to the custom resolver:
{
"@type": "setOption",
"name": "custom_dns_headers",
"value": {
"@type": "optionValueString",
"value": "Authorization: Bearer YOUR_TOKEN"
}
}Despite the plural name, custom_dns_headers is currently parsed as a single Header-Name: value string and converted into one header pair. TDLib also derives and adds a Host header from custom_dns_url automatically.
If your deployment requires multiple custom headers or a different custom DoH request shape, that is currently a native-fork customization task rather than a supported public tdjson feature.
For normal client integrators, the effective public contract is simple:
- stealth runtime behavior comes from the compiled default runtime params snapshot;
- the client does not need to call any stealth-specific API;
- there is currently no public
td_apiortdjsonoption to point TDLib at a stealth runtime params file or trigger reloads.
The codebase does contain an internal file-backed runtime params loader:
StealthParamsLoaderset_runtime_stealth_paramsget_runtime_stealth_params_snapshot
This is relevant only if you maintain a native fork and want to wire your own advanced embedding seam.
The loader has a strict fail-closed file contract:
- missing file means use defaults;
- config must be a regular file;
- file must be owned by the current user;
- file must not be writable by group or others;
- parent directory must be secure;
- size limit is
64 KiB; - JSON root must be an object with exact schema;
versionmust be1;- after five consecutive reload failures, reload enters a 60-second cooldown;
- failed reload keeps the last-known-good published snapshot.
There is also a stability constraint: once a successful non-default publication happens, platform_hints cannot drift across reloads.
If you are embedding at the C++ level and intentionally wiring the loader, a minimal accepted config shape looks like:
{
"version": 1,
"profile_weights": {
"chrome133": 50,
"chrome131": 20,
"chrome120": 15,
"firefox148": 15,
"safari26_3": 20,
"ios14": 70,
"android11_okhttp_advisory": 30
},
"route_policy": {
"unknown": {"ech_mode": "disabled"},
"ru": {"ech_mode": "disabled"},
"non_ru": {"ech_mode": "rfc9180_outer"}
},
"route_failure": {
"ech_failure_threshold": 4,
"ech_disable_ttl_seconds": 600.0,
"persist_across_restart": true
},
"bulk_threshold_bytes": 16384
}If you are not maintaining a native fork, ignore this section and use the compiled defaults.
Before shipping tdlib-obf in a custom client, verify the following.
- Your build scripts explicitly pass
-DTDLIB_STEALTH_SHAPING=ON. - Your app can create and enable an MTProto proxy before authorization.
- Your proxy configuration UI or provisioning path can store a provider-issued MTProto secret string without rewriting it.
- You understand that only
ee...TLS-emulation MTProto secrets activate stealth shaping. - You do not assume upstream TDLib connection parallelism when stealth proxy mode is enabled.
- You have a logging path for TDLib warnings and info messages during rollout.
- You validated certificate/trust-store discovery on each target OS.
- If the product requires non-default DNS resolution, you set
dns_type,custom_dns_url, andcustom_dns_headersbefore first network activity. - You do not rely on QUIC/HTTP3 in this fork.
For a concrete rollout with a server such as telemt:
- Obtain the MTProto proxy endpoint, port, and secret from the server operator.
- Build
tdlib-obfwithTDLIB_STEALTH_SHAPING=ON. - Integrate through
tdjsonor your binding of choice without adding any stealth-specific public API. - If needed, set DoH options through
setOptionbefore first network use. - Add the MTProto proxy using
proxyTypeMtprotoand enable it. - Confirm logs show stealth activation instead of fallback.
- Run at least the
TlsHellotest slice during CI for the library artifact you ship.
These are the primary code paths behind the integration contract described above.
CMakeLists.txttd/mtproto/IStreamTransport.cpptd/mtproto/ProxySecret.htd/mtproto/ProxySecret.cpptd/telegram/net/Proxy.cpptd/generate/scheme/td_api.tltd/telegram/OptionManager.cpptd/telegram/net/ConnectionCreator.cpptdnet/td/net/GetHostByNameActor.htdnet/td/net/GetHostByNameActor.cpptd/telegram/net/StealthConnectionCountPolicy.cpptd/telegram/net/ConnectionFlowController.cpptd/mtproto/stealth/StealthConfig.cpptd/mtproto/stealth/StealthRuntimeParams.cpptd/mtproto/stealth/StealthParamsLoader.cpptdnet/td/net/SslCtx.cpp
Status: Current and code-backed
Maintainer: telemt community
Document Version: 1.0
Date: 2026-05-02
Scope: Embedding tdlib-obf into custom client software through tdjson, generated TDLib bindings, or native C++ integration
This document is for integrators who are building their own Telegram client software on top of tdlib-obf and need the actual integration contract:
- how to build the library correctly;
- what the client must provide at runtime;
- how stealth shaping is activated;
- what behavior changes when stealth is active;
- which parts are public TDLib API and which parts are internal-only seams.
This is not a protocol design document and not a re-explanation of all stealth internals. It is an operational guide for shipping a client that uses this fork correctly.
These points are not optional.
tdlib-obfstealth shaping is MTProto-proxy-only. It activates only when TDLib is using an MTProto proxy secret that enters TLS-emulation mode.- Direct Telegram connections are unaffected. SOCKS5 and HTTP proxies are also unaffected.
- The library must be built with
TDLIB_STEALTH_SHAPING=ON. - If your client uses a TLS-emulation MTProto proxy secret while the library was compiled with
TDLIB_STEALTH_SHAPING=OFF, TDLib fails fast withLOG(FATAL)instead of silently falling back to legacy behavior. - There is no separate public stealth API. Integrators use the normal TDLib proxy API:
addProxy,enableProxy,disableProxy,getProxies.
Operational consequence: if your client does not support MTProto proxy configuration, there is nothing to integrate on the stealth side.
At the application level, a custom client must be able to do the following:
- Accept an MTProto proxy server address, port, and secret from configuration, UI, MDM, or provisioning.
- Persist the proxy configuration and re-apply it before authorization when needed.
- Enable exactly one active proxy through the standard TDLib API.
- Preserve the provider-issued MTProto secret string exactly as issued.
- Expose or collect TDLib logs during rollout, because stealth activation and fallback decisions are logged.
- Tolerate lower parallel connection counts than upstream TDLib when stealth is active.
If your application architecture assumes many parallel raw connections to the same endpoint for download/upload throughput, you must revisit those assumptions for stealth-proxy deployments.
The repository build contract is standard CMake-based TDLib plus the stealth option enabled.
Required components:
- CMake
- A C++23-capable compiler
- OpenSSL
- zlib
- gperf
Optional binding outputs:
tdjson/ C interface for FFI-based integrations- generated C++ TDLib API bindings
- JNI bindings if
TD_ENABLE_JNI=ON - .NET bindings if
TD_ENABLE_DOTNET=ON
From repository root:
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DTD_ENABLE_BENCHMARKS=OFF \
-DTDLIB_STEALTH_SHAPING=ON
cmake --build build --parallel 4Notes:
TDLIB_STEALTH_SHAPINGdefaults toONin this fork, but integrators should still pass it explicitly in release builds so the contract is visible in CI and packaging scripts.- If you are building a distribution artifact for multiple client products, treat
TDLIB_STEALTH_SHAPING=ONas a release invariant, not a local convenience flag.
After building:
cmake --build build --target run_all_tests --parallel 4
./build/test/run_all_tests --filter TlsHelloIf you want a wider transport sanity pass:
ctest --test-dir build --output-on-failure -j 4Stealth integration is deliberately routed through existing TDLib proxy objects.
Relevant public API surface:
proxyTypeMtproto secret:string = ProxyTypeaddProxy proxy:proxy enable:Bool = AddedProxyenableProxy proxy_id:int32 = OkdisableProxy = Ok
If you use the JSON interface, the minimal request shape is:
{
"@type": "addProxy",
"proxy": {
"@type": "proxy",
"server": "your-proxy.example.com",
"port": 443,
"type": {
"@type": "proxyTypeMtproto",
"secret": "PASTE_PROVIDER_SECRET_HERE"
}
},
"enable": true
}Important details:
- The TL schema comment says the MTProto secret is hexadecimal.
- The implementation currently accepts hexadecimal, Base64URL, and Base64 encodings through
ProxySecret::from_link. - The safest policy for integrators is: treat the secret as an opaque provider-issued string and pass it through unchanged.
Do not normalize, lowercase, split, or reconstruct the secret string in UI or middleware layers unless you fully control the server-side generation logic.
Stealth activation happens only for MTProto secrets that satisfy ProxySecret::emulate_tls().
At the raw byte level, that means:
- first byte
0xee; - followed by a 16-byte MTProto proxy secret;
- followed by an SNI domain string.
If you generate or validate secrets yourself, the appended TLS-emulation domain must satisfy the library's fail-closed parser:
- total appended domain length must be between
1and182bytes; - labels must be ASCII alphanumeric or
-only; - labels must not start with
-; - labels must not end with
-; - labels must not be empty;
- each label must be at most
63bytes.
This means embedded NUL bytes, control bytes, non-ASCII bytes, leading dots, trailing dots, empty labels, and overlong labels are all rejected.
These do not enter the stealth decorator path:
- plain 16-byte MTProto secrets;
0xddpadded MTProto secrets;- SOCKS5 proxies;
- HTTP proxies.
Those routes still work as supported TDLib proxy modes, but they do not get stealth shaping.
Once the library is built with stealth enabled and the client uses an ee... MTProto proxy secret, the transport stack changes in these ways.
The library wraps the MTProto obfuscated transport in StealthTransportDecorator and automatically selects a browser profile for TLS-emulation mode. Integrators do not choose a profile through public API.
Current runtime behavior is platform-aware and route-aware.
For stealth TLS MTProto proxies, the connection-count planner caps session counts to browser-like levels:
- main sessions:
1 - upload sessions:
1 - download sessions:
1 - small-download lane is merged into download
This is intentional. It reduces proxy-like connection fan-out that would otherwise be visible to DPI or telemetry.
Integrators should expect different bandwidth/concurrency behavior from upstream TDLib in this mode.
The flow controller enforces a per-destination budget and minimum reconnect interval using runtime flow behavior policy. Current default policy includes:
- maximum connects per 10 seconds per destination;
- minimum reconnect interval;
- connection lifetime and reuse constraints consumed by connection-pool policy.
Do not write client code that assumes it can force rapid repeated reconnects to the same stealth proxy endpoint without pacing.
The runtime route-policy validation keeps QUIC disabled. This fork is TCP/TLS-oriented for stealth-proxy mode. Do not design a custom client integration that depends on QUIC/HTTP3 support here.
If the active transport is not MTProto-proxy TLS emulation, tdlib-obf behaves like TDLib plus the other hardening changes in this fork, but the stealth masking subsystem itself is not active.
Integrators should route TDLib logs somewhere observable during rollout.
Important behaviors:
- Build mismatch:
ee...secret plusTDLIB_STEALTH_SHAPING=OFFcauses fatal termination with explicit diagnostics. - Bad proxy secret:
addProxy/ proxy construction fails with a400error. - Runtime config rejection: if transport stealth config validation fails during decorator construction, TDLib logs a warning and falls back to plain obfuscated MTProto transport for that connection.
- Decorator initialization failure: TDLib logs a warning and falls back to plain obfuscated transport.
- Successful activation: TDLib logs that stealth shaping is enabled for the emulate-TLS transport.
For production integrations, configure setLogStream or the equivalent binding-specific log sink before testing stealth deployment.
Stealth shaping does not remove the need for correct TLS trust handling in your client runtime environment.
Current trust-store behavior in this fork:
- On non-iOS-family platforms, OpenSSL default cert locations are probed.
- Environment overrides are supported through:
SSL_CERT_FILESSL_CERT_DIRTDLIB_SSL_CERT_FILETDLIB_SSL_CERT_DIR
- On Android, the implementation explicitly probes both:
/apex/com.android.conscrypt/cacerts/system/etc/security/cacerts
- On Apple platforms, trust anchors are loaded through
Security.framework/ keychain APIs. - iOS-family platforms intentionally avoid relying on OpenSSL default filesystem bundle probing.
What this means for integrators:
- If you ship on Linux, Windows, or Android variants with unusual CA layouts, verify trust-store discovery early.
- If your app sandbox does not expose the platform default bundle path, provide explicit cert overrides where appropriate.
- If verification is enabled and no trusted certificates are available, this fork now fails closed instead of silently proceeding with an empty trust store.
For normal client integrators, the effective public contract is simple:
- stealth runtime behavior comes from the compiled default runtime params snapshot;
- the client does not need to call any stealth-specific API;
- there is currently no public
td_apiortdjsonoption to point TDLib at a stealth runtime params file or trigger reloads.
The codebase does contain an internal file-backed runtime params loader:
StealthParamsLoaderset_runtime_stealth_paramsget_runtime_stealth_params_snapshot
This is relevant only if you maintain a native fork and want to wire your own advanced embedding seam.
The loader has a strict fail-closed file contract:
- missing file means use defaults;
- config must be a regular file;
- file must be owned by the current user;
- file must not be writable by group or others;
- parent directory must be secure;
- size limit is
64 KiB; - JSON root must be an object with exact schema;
versionmust be1;- after five consecutive reload failures, reload enters a 60-second cooldown;
- failed reload keeps the last-known-good published snapshot.
There is also a stability constraint: once a successful non-default publication happens, platform_hints cannot drift across reloads.
If you are embedding at the C++ level and intentionally wiring the loader, a minimal accepted config shape looks like:
{
"version": 1,
"profile_weights": {
"chrome133": 50,
"chrome131": 20,
"chrome120": 15,
"firefox148": 15,
"safari26_3": 20,
"ios14": 70,
"android11_okhttp_advisory": 30
},
"route_policy": {
"unknown": {"ech_mode": "disabled"},
"ru": {"ech_mode": "disabled"},
"non_ru": {"ech_mode": "rfc9180_outer"}
},
"route_failure": {
"ech_failure_threshold": 4,
"ech_disable_ttl_seconds": 600.0,
"persist_across_restart": true
},
"bulk_threshold_bytes": 16384
}If you are not maintaining a native fork, ignore this section and use the compiled defaults.
Before shipping tdlib-obf in a custom client, verify the following.
- Your build scripts explicitly pass
-DTDLIB_STEALTH_SHAPING=ON. - Your app can create and enable an MTProto proxy before authorization.
- Your proxy configuration UI or provisioning path can store a provider-issued MTProto secret string without rewriting it.
- You understand that only
ee...TLS-emulation MTProto secrets activate stealth shaping. - You do not assume upstream TDLib connection parallelism when stealth proxy mode is enabled.
- You have a logging path for TDLib warnings and info messages during rollout.
- You validated certificate/trust-store discovery on each target OS.
- You do not rely on QUIC/HTTP3 in this fork.
For a concrete rollout with a server such as telemt:
- Obtain the MTProto proxy endpoint, port, and secret from the server operator.
- Build
tdlib-obfwithTDLIB_STEALTH_SHAPING=ON. - Integrate through
tdjsonor your binding of choice without adding any stealth-specific public API. - Add the MTProto proxy using
proxyTypeMtprotoand enable it. - Confirm logs show stealth activation instead of fallback.
- Run at least the
TlsHellotest slice during CI for the library artifact you ship.
These are the primary code paths behind the integration contract described above.
CMakeLists.txttd/mtproto/IStreamTransport.cpptd/mtproto/ProxySecret.htd/mtproto/ProxySecret.cpptd/telegram/net/Proxy.cpptd/generate/scheme/td_api.tltd/telegram/net/StealthConnectionCountPolicy.cpptd/telegram/net/ConnectionFlowController.cpptd/mtproto/stealth/StealthConfig.cpptd/mtproto/stealth/StealthRuntimeParams.cpptd/mtproto/stealth/StealthParamsLoader.cpptdnet/td/net/SslCtx.cpp
Status: Current and code-backed
Maintainer: telemt community