Private AI Chat Proxy running in Trusted Execution Environment (TEE)
This project implements a Signal bot that runs inside a Dstack-powered TEE (Intel TDX) and proxies user messages to NEAR AI Cloud's private inference API. The design creates a fully verifiable, end-to-end private AI chat experience.
[User] <--Signal E2E--> [TEE: Signal CLI + Bot] <--HTTPS--> [NEAR AI GPU TEE]
|
[In-memory only]
[Intel TDX protected]
- Signal: E2E encrypted messaging between user and bot
- Dstack TEE: Verifiable proxy execution with Intel TDX attestation
- NEAR AI Cloud: Private inference with GPU TEE (NVIDIA H100/H200) attestation
- End-to-end privacy from user device to AI inference
- Dual attestation (Intel TDX + NVIDIA GPU TEE)
- Cryptographic verification with user-provided challenges
- In-memory conversation storage (no external persistence)
- Group chat support with shared conversation context
- OpenAI-compatible API integration
| Command | Description |
|---|---|
!verify <challenge> |
Get TEE attestation with your challenge embedded in TDX quote |
!clear |
Clear conversation history |
!models |
List available AI models |
!help |
Show help message |
Any other message is sent to the AI for a response.
The bot can be added to Signal group chats with shared conversation context:
| Context | Behavior |
|---|---|
| Direct Message | Personal conversation history per user |
| Group Chat | Shared history - all members see the same context |
In groups:
- All messages contribute to a shared conversation
- The AI can reference what other group members said
!clearclears the entire group's conversation history!verifyworks the same (provides TEE attestation)
Example:
Alice: "My favorite color is blue"
Bob: "What's Alice's favorite color?"
Bot: "Alice mentioned her favorite color is blue"
Users can cryptographically verify the bot runs in a genuine TEE:
- Send
!verify my-random-nonceto the bot - Bot returns a TDX quote with your nonce embedded in
report_data - Verify the quote signature at https://proof.phala.network
- Compare
compose_hashwith this repository'sdocker/docker-compose.yaml
This proves:
- The attestation was generated fresh (contains your nonce)
- The code is running in Intel TDX hardware
- The exact docker-compose configuration is as published
crates/
signal-bot/ # Main application binary
near-ai-client/ # NEAR AI Cloud API client
conversation-store/ # In-memory conversation storage with TTL
dstack-client/ # Dstack TEE attestation client
signal-client/ # Signal CLI REST API client
docker/
Dockerfile # Multi-stage build for Alpine
docker-compose.yaml # Production deployment config
See CLAUDE.md for detailed security documentation including:
- Why in-memory storage instead of Redis
- Why Signal CLI must run inside the TEE
- User verification process
- Trust assumptions and metadata leakage
- Rust 1.83+
- Docker & Docker Compose
- Signal phone number (for the bot)
- NEAR AI API key
cargo build --releasecargo testcd docker
cp ../.env.example .env
# Edit .env with your credentials
docker-compose up -dThe signal-registration-proxy provides a secure API for registering multiple Signal phone numbers. Each tenant (phone number) has isolated conversation history and can be managed independently.
Base URL: https://[your-deployment]-8081.dstack-prod5.phala.network
Initiates registration and sends SMS verification code.
curl -X POST https://[base-url]/v1/register/+1234567890 \
-H "Content-Type: application/json" \
-d '{
"captcha": "signalcaptcha://signal-hcaptcha...",
"use_voice": false,
"ownership_secret": "your-secret-for-verification"
}'Parameters:
captcha(optional): Captcha token from signalcaptchas.org - required if Signal requests ituse_voice(optional):truefor voice call instead of SMSownership_secret(optional): Secret to prove ownership for future operations
Response:
{
"phone_number": "+1234567890",
"status": "pending",
"message": "Verification code sent. Use /v1/register/{number}/verify/{code} to complete."
}Submit the SMS/voice verification code.
curl -X POST https://[base-url]/v1/register/+1234567890/verify/123456 \
-H "Content-Type: application/json" \
-d '{
"ownership_secret": "your-secret-for-verification",
"pin": "optional-signal-pin"
}'Parameters:
ownership_secret: Must match the secret used during registrationpin(optional): Signal PIN if the account has one set
curl https://[base-url]/v1/status/+1234567890Response:
{
"phone_number": "+1234567890",
"status": "verified",
"registered_at": "2025-01-15T10:30:00Z"
}curl https://[base-url]/v1/accountsResponse:
{
"accounts": [
{
"phone_number": "+1234567890",
"status": "verified",
"registered_at": "2025-01-15T10:30:00Z"
}
],
"total": 1
}curl -X DELETE https://[base-url]/v1/unregister/+1234567890 \
-H "Content-Type: application/json" \
-d '{"ownership_secret": "your-secret-for-verification"}'curl https://[base-url]/healthResponse:
{
"status": "ok",
"registry_count": 1,
"signal_api_healthy": true
}Each registered phone number is a separate tenant with:
- Isolated conversations: Each phone number has its own conversation history
- Separate storage: Registry entries encrypted with TEE-derived keys
- Rate limiting: Per-number rate limits prevent abuse
- Ownership verification: Operations require the secret used at registration
If registration fails with "Account is already registered":
- The Signal CLI may have stale data from a previous registration
- Use the debug endpoint to force unregister:
POST /v1/debug/force-unregister/+1234567890 - Retry registration with a fresh captcha
See CLAUDE.md for detailed debugging documentation.
Environment variables:
| Variable | Description | Default |
|---|---|---|
SIGNAL__PHONE_NUMBER |
Bot's Signal phone number | Required |
SIGNAL__SERVICE_URL |
Signal CLI REST API URL | http://signal-api:8080 |
NEAR_AI__API_KEY |
NEAR AI API key | Required |
NEAR_AI__MODEL |
AI model to use | llama-3.3-70b |
CONVERSATION__TTL |
Conversation expiry time | 24h |
CONVERSATION__MAX_MESSAGES |
Max messages per conversation | 50 |
MIT