# Tencent Hunyuan 3D API Reverse Engineering Document ## Project Overview This project reverse-engineers the frontend signing algorithm of Tencent Hunyuan 3D (https://3d.hunyuan.tencent.com/), enabling **pure Python HTTP calls** without a browser environment for features like image-to-3D generation and quota queries. --- ## Core Achievements ### 1. Signing Algorithm Cracked **Algorithm Location**: webpack Chunk 3057, Module 47436 **Signing Flow**: ``` 1. nonce generation: 16 iterations of Math.random(), selecting from 62-char set (A-Za-z0-9) 2. timestamp: Math.floor(Date.now() / 1000) (second-level timestamp) 3. key derivation: hardcoded byte array → XOR → circular left shift → permutation → truncation 4. signature: HMAC-SHA256(sorted query param string, derived key) → Hex ``` **Key Constants**: ```python C = bytes([122, 59, 92, 165, 30, 79, 166, 139, 142, 129, 139, 89, 219, 131, 101, 204]) D = bytes([122, 59, 92, 45, 30, 79, 106, 139, 156, 13, 46, 63, 74, 91, 108, 125]) U = [3, 5, 2, 7, 1, 4, 6, 2, 5, 3, 1, 4, 2, 6, 3, 5] # left shift bits M = [14, 11, 13, 9, 15, 10, 12, 8, 6, 3, 5, 1, 7, 2, 4, 0] # permutation table ``` **Derived Key**: `Hf6d6KFB3D` (10 characters) ### 2. Signing Scope (Important Finding) ⚠️ **The signature only covers URL query parameters, NOT the request body** Actual browser request: ``` POST /api/3d/creations/generations?timestamp=xxx&nonce=yyy&sign=zzz Body: {"sceneType":"playGround3D-2.0",...} ``` Signature computation: ```python # Only sign query params (timestamp + nonce) param_str = "nonce=yyy×tamp=xxx" sign = HMAC-SHA256(param_str, key="Hf6d6KFB3D") ``` ### 3. Image Upload Evolution #### Method 1: Browser Automation Upload (Original) Upload via `generator.py` inside the browser to obtain a Tencent internal `resourceId`. #### Method 2: Pure Python COS Direct Upload (New) Upload directly via `cos_upload.py` without a browser: 1. Call `/resource/genUploadInfo` to obtain temporary STS credentials (`encryptTmpSecretId` / `encryptTmpSecretKey` / `encryptToken`) 2. Generate the `Authorization` header using the COS V1 signing algorithm 3. `PUT` directly to `*.cos.accelerate.myqcloud.com` 4. Obtain `resourceUrl` for subsequent image-to-3D API calls ```python from hunyuan3dweb.cos_upload import upload_image resource_url = upload_image("/path/to/image.png") ``` --- ## File Reference | File | Description | |------|-------------| | `hunyuan3dweb/sign.py` | Signing algorithm in pure Python | | `hunyuan3dweb/api.py` | Basic API client (image2model, text2model) | | `hunyuan3dweb/api_complete.py` | Full API client (all generation modes) | | `hunyuan3dweb/cos_upload.py` | Pure Python COS upload helper (no browser needed) | | `hunyuan3dweb/config.py` | User config path management (cookies, profile) | | `hunyuan3dweb/cli.py` | CLI entry point | | `hunyuan3dweb/browser/login.py` | Browser automation login tool | | `hunyuan3dweb/browser/generator.py` | Browser automation image-to-3D | | `hunyuan3dweb/browser/sniffer.py` | API request sniffer | --- ## Usage Flow ### 1. Login to Obtain Cookie ```bash hunyuan3dweb-login # Follow prompts to enter email and verification code # Login state is automatically saved to ~/.config/hunyuan3dweb/profile # Cookies are also exported to ~/.config/hunyuan3dweb/cookies.txt ``` **Login State Detection Logic** (dual verification): - Check if the current page URL contains `"login"` - Check if a "Login" button exists on the page - Only considered logged in when URL does NOT contain login **AND** no login button is present ### 2. Pure Python 3D Generation ```python from hunyuan3dweb import Hunyuan3DAPI # Automatically load cookie from ~/.config/hunyuan3dweb/cookies.txt api = Hunyuan3DAPI() # Check quota quota = api.get_quota_info() # Text-to-3D result = api.generate_text("a cute panda", title="Panda Model") # Query status status = api.get_generation_status(result["creationsId"]) ``` ### 3. Local Image Upload + Image-to-3D (Pure Python) ```python from hunyuan3dweb.cos_upload import upload_image from hunyuan3dweb import Hunyuan3DAPIComplete # Upload local image to COS resource_url = upload_image("/path/to/image.png") # Call image-to-3D API api = Hunyuan3DAPIComplete() result = api.generate_from_image(resource_url, title="My Model") ``` ### 4. CLI Download ```bash # List available formats for a creation hunyuan3dweb formats # Download a specific format (default: glb) hunyuan3dweb download --format glb -o model.glb ``` --- ## API Endpoints | Feature | Method | Endpoint | Signature Scope | |---------|--------|----------|-----------------| | User info | GET | `/getuserinfo` | Query params | | Quota query | POST | `/quotainfo` | Query params | | Generate 3D | POST | `/creations/generations` | Query params | | Query status | GET | `/creations/detail` | Query params | | Creation list | POST | `/creations/list` | Query params | | Creation count | POST | `/creations/count` | Query params | | Cancel generation | POST | `/creations/cancel` | Query params | | Upload credentials | POST | `/resource/genUploadInfo` | Query params | | Resource review | POST | `/resource/review` | Query params | | Create share | POST | `/share` | Query params | | Global config | GET | `/config` | Query params | | Animation templates | GET | `/workflow/action/templates` | Query params | ### Model Download Formats The `urlResult` field in `/creations/detail` contains up to 14 format keys: | Key | Description | |-----|-------------| | `glb` | GLB binary model with PBR textures | | `obj` | OBJ model (usually zipped) | | `mtl` | MTL material file | | `obj_url` | Standalone OBJ file URL | | `geometryGlb` | Geometry-only GLB (no materials) | | `textureGlb` | GLB texture pack | | `textureObj` | OBJ texture pack | | `image_url` | Preview / thumbnail image | | `pbrImage` | Combined PBR texture map | | `pbrMetallicImage` | PBR metallic map | | `pbrRoughnessImage` | PBR roughness map | | `pbrNormalImage` | PBR normal map | | `invisible_wall` | Invisible collision wall | | `air_wall` | Air wall (collision body) | --- ## Technical Details ### Signing Algorithm Implementation ```python def derive_key(c: bytes) -> str: """Derive signing key from hardcoded constants""" # 1. XOR with D t = bytearray(16) for i in range(16): t[i] = c[i] ^ D[i] # 2. Circular left shift o = bytearray(16) for i in range(16): n = U[i] val = t[i] o[i] = (val << n | val >> (8 - n)) & 0xFF # 3. Permutation n = bytearray(16) for i in range(16): n[i] = o[M[i]] # 4. Truncate (find first zero byte) try: r = n.index(0) except ValueError: r = 16 return n[:r].decode('utf-8') ``` ### COS V1 Signature (for Direct Upload) ```python def _cos_v1_auth(method, pathname, query_params, headers, secret_id, secret_key, key_time): sign_key = hmac.new(secret_key.encode("utf-8"), key_time.encode("utf-8"), hashlib.sha1).hexdigest() query_str = _obj2str(query_params, True) headers_str = _obj2str(headers, True) format_string = f"{method}\n{pathname}\n{query_str}\n{headers_str}\n" format_string_sha1 = hashlib.sha1(format_string.encode("utf-8")).hexdigest() string_to_sign = f"sha1\n{key_time}\n{format_string_sha1}\n" signature = hmac.new(sign_key.encode("utf-8"), string_to_sign.encode("utf-8"), hashlib.sha1).hexdigest() return "&".join([ "q-sign-algorithm=sha1", f"q-ak={secret_id}", f"q-sign-time={key_time}", f"q-key-time={key_time}", f"q-header-list={';'.join(sorted(headers.keys())).lower()}", f"q-url-param-list={';'.join(sorted(query_params.keys())).lower()}", f"q-signature={signature}", ]) ``` ### Request Signing ```python def sign(params: dict) -> dict: result = dict(params) result["timestamp"] = int(time.time()) result["nonce"] = generate_nonce(16) # Sort and join (JSON format for lists/dicts) sorted_items = sort_params(result) param_str = join_params(sorted_items) # HMAC-SHA256 key = derive_key(C) signature = hmac.new( key.encode('utf-8'), param_str.encode('utf-8'), hashlib.sha256 ).hexdigest() result["sign"] = signature return result ``` --- ## Verification Results | Test Item | Browser Signature | Python Signature | Match | |-----------|-------------------|------------------|-------| | Fixed params | `2214e55a...` | `2214e55a...` | ✅ | | Quota query | Works | Works | ✅ | | Generate request | Works | Works | ✅ | | Status query | Works | Works | ✅ | | COS direct upload | Browser upload | Python upload | ✅ | --- ## Limitations and Notes 1. **Cookie Expiration**: Login sessions expire and need periodic re-login 2. **Quota Limit**: Each account has a generation quota limit (default 20/day) 3. **Rate Limiting**: Frequent calls may trigger anti-bot measures 4. **Concurrency Safety**: When multiple tools run simultaneously, `login.py` exclusively uses the standard profile directory, while `sniffer.py` / `generator.py` automatically copy the profile to a temporary directory to avoid Chromium `SingletonLock` conflicts --- ## Dependencies ``` requests cloakbrowser (for login and browser automation) ``` --- ## Legal Notice This project is for educational and research purposes only. Use of this code is subject to Tencent Hunyuan 3D's Terms of Service. Do not use for commercial purposes or large-scale automated calling.