535 lines
18 KiB
Markdown
535 lines
18 KiB
Markdown
# Montana Communications — Техническая Спецификация
|
||
|
||
**Версия:** 1.0
|
||
**Дата:** 2026-01-21
|
||
**Автор:** Alejandro Montana
|
||
|
||
---
|
||
|
||
## Обзор
|
||
|
||
Montana Communications — единая система коммуникаций протокола Montana, где **время = деньги** в буквальном смысле. Каждая секунда голосового или видеозвонка стоит 1 Jcoin.
|
||
|
||
**Принцип:** Телефон — это адрес кошелька. SMS — это транзакция. Звонок — это стриминг времени.
|
||
|
||
---
|
||
|
||
## Архитектура
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ MONTANA COMMUNICATIONS │
|
||
├─────────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||
│ │ SMS │ │ VOICE │ │ VIDEO │ │
|
||
│ │ Gateway │ │ WebRTC │ │ WebRTC │ │
|
||
│ │ │ │ │ │ │ │
|
||
│ │ 1 SMS = 1Ɉ │ │ 1 sec = 1Ɉ │ │ 1 sec = 2Ɉ │ │
|
||
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
||
│ │ │ │ │
|
||
│ └──────────────────┼──────────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌─────────────────┐ │
|
||
│ │ SIGNALING │ │
|
||
│ │ (Telegram Bot) │ │
|
||
│ └────────┬────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌─────────────────┐ │
|
||
│ │ BILLING │ │
|
||
│ │ (Time = Money) │ │
|
||
│ └────────┬────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌─────────────────┐ │
|
||
│ │ MONTANA DB │ │
|
||
│ │ (SQLite) │ │
|
||
│ └─────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 1. SMS Gateway
|
||
|
||
### Концепция
|
||
|
||
**Телефон = Адрес кошелька.** Любой человек в мире может отправить SMS и получить кошелёк Montana без регистрации.
|
||
|
||
### Формат адреса
|
||
|
||
```
|
||
+79991234567 → Международный (E.164)
|
||
телефон = wallet address
|
||
```
|
||
|
||
### Команды SMS
|
||
|
||
| Команда | Описание | Пример |
|
||
|---------|----------|--------|
|
||
| `BAL` | Проверить баланс | `BAL` |
|
||
| `SEND <тел> <сумма>` | Перевод | `SEND +79991234567 100` |
|
||
| `TX` | История транзакций | `TX` |
|
||
| `INFO` | Информация об аккаунте | `INFO` |
|
||
| `?` | Справка | `?` |
|
||
|
||
### Архитектура SMS
|
||
|
||
```
|
||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||
│ Телефон │──SMS─│ Twilio │─HTTP─│ Montana │
|
||
│ (+7...) │ │ Webhook │ │ Gateway │
|
||
└─────────────┘ └─────────────┘ └──────┬──────┘
|
||
│
|
||
┌──────────────────────┤
|
||
│ │
|
||
▼ ▼
|
||
┌─────────────┐ ┌─────────────┐
|
||
│ Montana │ │ Telegram │
|
||
│ DB │ │ Bot │
|
||
└─────────────┘ └─────────────┘
|
||
```
|
||
|
||
### Синхронизация Telegram ↔ SMS
|
||
|
||
Если пользователь привязал телефон к Telegram аккаунту Montana:
|
||
- Баланс синхронизирован
|
||
- Транзакции видны в обоих каналах
|
||
- Можно звонить через любой интерфейс
|
||
|
||
```python
|
||
def link_phone_to_telegram(phone: str, telegram_id: str) -> bool:
|
||
"""
|
||
Привязка телефона к Telegram.
|
||
Балансы объединяются.
|
||
"""
|
||
phone_balance = get_balance(phone)
|
||
tg_balance = get_balance(f"tg:{telegram_id}")
|
||
|
||
# Объединяем балансы
|
||
total = phone_balance + tg_balance
|
||
|
||
# Устанавливаем единый баланс
|
||
set_balance(phone, total)
|
||
set_balance(f"tg:{telegram_id}", total)
|
||
|
||
# Создаём связь
|
||
create_link(phone, telegram_id)
|
||
return True
|
||
```
|
||
|
||
### Тарификация SMS
|
||
|
||
| Операция | Стоимость |
|
||
|----------|-----------|
|
||
| Входящее SMS (команда) | 1 Ɉ |
|
||
| Исходящее SMS (ответ) | 0 Ɉ (включено) |
|
||
| Перевод (SEND) | 1 Ɉ + комиссия 1% |
|
||
|
||
---
|
||
|
||
## 2. Voice (WebRTC P2P)
|
||
|
||
### Концепция
|
||
|
||
Голосовые звонки через WebRTC — P2P соединение между устройствами. Montana только обеспечивает signaling и биллинг.
|
||
|
||
**Принцип:** Голос идёт напрямую между устройствами. Montana не слышит разговор.
|
||
|
||
### Архитектура WebRTC
|
||
|
||
```
|
||
┌─────────────┐ ┌─────────────┐
|
||
│ Телефон A │ │ Телефон B │
|
||
│ (Browser) │ │ (Browser) │
|
||
└──────┬──────┘ └──────┬──────┘
|
||
│ │
|
||
│ 1. Offer (SDP) │
|
||
├──────────────────► Signaling ◄────────────┤
|
||
│ (Telegram) │
|
||
│ 2. Answer (SDP) │
|
||
├◄───────────────────────────────────────────┤
|
||
│ │
|
||
│ 3. ICE Candidates │
|
||
├◄──────────────────────────────────────────►│
|
||
│ │
|
||
│ 4. P2P Media Stream (Encrypted) │
|
||
└◄══════════════════════════════════════════►┘
|
||
│
|
||
1 секунда = 1 Ɉ
|
||
```
|
||
|
||
### ICE серверы
|
||
|
||
```python
|
||
ICE_SERVERS = [
|
||
# STUN серверы (NAT traversal)
|
||
{"urls": "stun:stun.l.google.com:19302"},
|
||
{"urls": "stun:stun1.l.google.com:19302"},
|
||
{"urls": "stun:stun2.l.google.com:19302"},
|
||
{"urls": "stun:stun3.l.google.com:19302"},
|
||
{"urls": "stun:stun4.l.google.com:19302"},
|
||
|
||
# TURN сервер (для symmetric NAT) — опционально
|
||
# {"urls": "turn:turn.efir.org:3478",
|
||
# "username": "montana",
|
||
# "credential": "<from_keychain>"}
|
||
]
|
||
```
|
||
|
||
### Signaling через Telegram
|
||
|
||
Вместо отдельного WebSocket сервера используем Telegram бот как signaling:
|
||
|
||
1. **Caller** создаёт offer (SDP) в браузере
|
||
2. **Offer** передаётся через Telegram бот получателю
|
||
3. **Receiver** создаёт answer (SDP)
|
||
4. **Answer** передаётся обратно через бот
|
||
5. **ICE candidates** обмениваются через бот
|
||
6. **P2P соединение** установлено — голос идёт напрямую
|
||
|
||
### WebRTC клиент
|
||
|
||
```javascript
|
||
// Создание peer connection
|
||
const pc = new RTCPeerConnection({iceServers: ICE_SERVERS});
|
||
|
||
// Получить доступ к микрофону
|
||
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
|
||
stream.getTracks().forEach(track => pc.addTrack(track, stream));
|
||
|
||
// Создать offer
|
||
const offer = await pc.createOffer();
|
||
await pc.setLocalDescription(offer);
|
||
|
||
// Отправить offer через Telegram (signaling)
|
||
sendToTelegram({type: 'offer', sdp: offer.sdp});
|
||
|
||
// Получить answer от второй стороны
|
||
pc.ontrack = (e) => {
|
||
// Воспроизвести входящий аудиопоток
|
||
audioElement.srcObject = e.streams[0];
|
||
};
|
||
```
|
||
|
||
### Биллинг звонков
|
||
|
||
```python
|
||
def end_call(call_id: str) -> dict:
|
||
"""
|
||
Завершение звонка и списание Ɉ
|
||
"""
|
||
call = get_call(call_id)
|
||
|
||
# Расчёт времени
|
||
duration = (call.ended_at - call.started_at).total_seconds()
|
||
duration_seconds = int(duration)
|
||
|
||
# 1 секунда = 1 Ɉ
|
||
cost = duration_seconds
|
||
|
||
# Списание со звонящего
|
||
transfer(
|
||
from_address=call.from_address,
|
||
to_address="BURN", # или пул узлов
|
||
amount=cost,
|
||
memo=f"Call {call_id}: {duration_seconds}s"
|
||
)
|
||
|
||
return {
|
||
"call_id": call_id,
|
||
"duration_seconds": duration_seconds,
|
||
"cost_coins": cost
|
||
}
|
||
```
|
||
|
||
### Команды Telegram
|
||
|
||
| Команда | Описание |
|
||
|---------|----------|
|
||
| `/call @user` | Позвонить пользователю |
|
||
| `/call +79991234567` | Позвонить на телефон |
|
||
| `/answer` | Принять входящий звонок |
|
||
| `/hangup` | Завершить звонок |
|
||
| `/calls` | История звонков |
|
||
|
||
---
|
||
|
||
## 3. Video (WebRTC P2P)
|
||
|
||
### Концепция
|
||
|
||
Видеозвонки используют ту же архитектуру WebRTC, но с видеопотоком.
|
||
|
||
**Тариф:** 1 секунда = 2 Ɉ (audio + video)
|
||
|
||
### Отличия от Voice
|
||
|
||
```javascript
|
||
// Видеозвонок — включаем камеру
|
||
const stream = await navigator.mediaDevices.getUserMedia({
|
||
audio: true,
|
||
video: {
|
||
width: { ideal: 1280 },
|
||
height: { ideal: 720 },
|
||
facingMode: "user"
|
||
}
|
||
});
|
||
```
|
||
|
||
### Тарификация видео
|
||
|
||
| Тип | Стоимость |
|
||
|-----|-----------|
|
||
| Voice call | 1 Ɉ/сек |
|
||
| Video call | 2 Ɉ/сек |
|
||
| Screen share | 2 Ɉ/сек |
|
||
|
||
---
|
||
|
||
## База данных
|
||
|
||
### Таблица calls
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS calls (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
call_id TEXT UNIQUE NOT NULL,
|
||
from_address TEXT NOT NULL,
|
||
to_address TEXT NOT NULL,
|
||
started_at TEXT,
|
||
ended_at TEXT,
|
||
duration_seconds INTEGER DEFAULT 0,
|
||
cost_coins INTEGER DEFAULT 0,
|
||
status TEXT DEFAULT 'pending',
|
||
call_type TEXT DEFAULT 'voice', -- voice/video/screen
|
||
offer_sdp TEXT,
|
||
answer_sdp TEXT,
|
||
ice_candidates_from TEXT, -- JSON array
|
||
ice_candidates_to TEXT, -- JSON array
|
||
created_at TEXT NOT NULL
|
||
);
|
||
|
||
CREATE INDEX idx_calls_from ON calls(from_address);
|
||
CREATE INDEX idx_calls_to ON calls(to_address);
|
||
CREATE INDEX idx_calls_status ON calls(status);
|
||
```
|
||
|
||
### Статусы звонка
|
||
|
||
| Статус | Описание |
|
||
|--------|----------|
|
||
| `pending` | Звонок инициирован, ждёт ответа |
|
||
| `ringing` | Получатель видит входящий звонок |
|
||
| `active` | Разговор в процессе |
|
||
| `ended` | Завершён нормально |
|
||
| `missed` | Пропущен |
|
||
| `rejected` | Отклонён |
|
||
| `failed` | Ошибка соединения |
|
||
|
||
### Таблица sms_transactions
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS sms_transactions (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
phone TEXT NOT NULL,
|
||
direction TEXT NOT NULL, -- inbound/outbound
|
||
message TEXT,
|
||
command TEXT,
|
||
result TEXT,
|
||
cost_coins INTEGER DEFAULT 1,
|
||
created_at TEXT NOT NULL
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## Безопасность
|
||
|
||
### Шифрование
|
||
|
||
| Компонент | Шифрование |
|
||
|-----------|------------|
|
||
| WebRTC Media | SRTP (AES-128) |
|
||
| Signaling | Telegram TLS |
|
||
| SDP | Транспортное шифрование |
|
||
| ICE | DTLS |
|
||
|
||
### Приватность
|
||
|
||
- **P2P:** Голос/видео идут напрямую между устройствами
|
||
- **Montana не слышит:** Сервер только signaling и биллинг
|
||
- **Нет записи:** Montana не хранит медиа
|
||
- **End-to-End:** SRTP шифрование между браузерами
|
||
|
||
### Защита от атак
|
||
|
||
| Атака | Защита |
|
||
|-------|--------|
|
||
| MITM | DTLS fingerprint verification |
|
||
| DoS звонками | Минимальный баланс 60 Ɉ |
|
||
| Spam SMS | Rate limiting + стоимость 1 Ɉ |
|
||
| Fake caller ID | WebRTC peer verification |
|
||
|
||
---
|
||
|
||
## API
|
||
|
||
### CallManager
|
||
|
||
```python
|
||
from webrtc_signaling import CallManager
|
||
|
||
manager = CallManager()
|
||
|
||
# Инициировать звонок
|
||
success, msg, call_id = manager.initiate_call(
|
||
from_address="tg:123456789",
|
||
to_address="tg:987654321"
|
||
)
|
||
|
||
# Установить SDP offer
|
||
manager.set_offer(call_id, offer_sdp)
|
||
|
||
# Принять звонок
|
||
manager.answer_call(call_id, answer_sdp)
|
||
|
||
# Добавить ICE candidate
|
||
manager.add_ice_candidate(call_id, candidate, is_from=True)
|
||
|
||
# Начать тарификацию (P2P соединено)
|
||
manager.start_call(call_id)
|
||
|
||
# Завершить и списать
|
||
success, info = manager.end_call(call_id)
|
||
# info = {"duration_seconds": 120, "cost_coins": 120}
|
||
```
|
||
|
||
### SMSGateway
|
||
|
||
```python
|
||
from sms_gateway import SMSGateway
|
||
|
||
gateway = SMSGateway()
|
||
|
||
# Обработать входящее SMS
|
||
response = gateway.process_incoming(
|
||
from_phone="+79991234567",
|
||
body="BAL"
|
||
)
|
||
# response = "Баланс: 1000 Ɉ"
|
||
|
||
# Отправить SMS
|
||
gateway.send_sms(
|
||
to_phone="+79991234567",
|
||
message="Перевод получен: 100 Ɉ"
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## Интеграция с Telegram
|
||
|
||
### MiniApp для звонков
|
||
|
||
```
|
||
https://efir.org/call?id=CALL-XXXXXXXX
|
||
```
|
||
|
||
MiniApp открывается внутри Telegram и использует WebRTC для звонка.
|
||
|
||
### Webhook обработка
|
||
|
||
```python
|
||
@dp.message(Command("call"))
|
||
async def call_cmd(message: Message):
|
||
"""Инициировать WebRTC звонок"""
|
||
|
||
target = extract_target(message.text)
|
||
if not target:
|
||
await message.answer("Использование: /call @user или /call +79991234567")
|
||
return
|
||
|
||
# Проверяем баланс (минимум 60 Ɉ = 1 минута)
|
||
balance = db.get_balance(message.from_user.id)
|
||
if balance < 60:
|
||
await message.answer("Минимальный баланс для звонка: 60 Ɉ (1 минута)")
|
||
return
|
||
|
||
# Создаём звонок
|
||
success, msg, call_id = call_manager.initiate_call(
|
||
from_address=f"tg:{message.from_user.id}",
|
||
to_address=target
|
||
)
|
||
|
||
# Отправляем ссылку на WebRTC клиент
|
||
await message.answer(
|
||
f"Звонок: {call_id}\n\n"
|
||
f"Откройте для звонка:\n"
|
||
f"https://efir.org/call?id={call_id}"
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## Тарификация
|
||
|
||
### Сводная таблица
|
||
|
||
| Услуга | Стоимость | Минимум |
|
||
|--------|-----------|---------|
|
||
| SMS команда | 1 Ɉ | 1 Ɉ |
|
||
| Voice call | 1 Ɉ/сек | 60 Ɉ |
|
||
| Video call | 2 Ɉ/сек | 120 Ɉ |
|
||
| Перевод | 1% | 1 Ɉ |
|
||
|
||
### Экономика
|
||
|
||
```
|
||
1 Ɉ = 1 секунда времени
|
||
1 минута звонка = 60 Ɉ
|
||
1 час звонка = 3600 Ɉ
|
||
|
||
TIME IS THE ONLY REAL CURRENCY
|
||
```
|
||
|
||
---
|
||
|
||
## Файлы модуля
|
||
|
||
| Файл | Назначение |
|
||
|------|------------|
|
||
| `sms_gateway.py` | SMS Gateway (Twilio webhook) |
|
||
| `webrtc_signaling.py` | WebRTC signaling через Telegram |
|
||
| `static/call.html` | WebRTC клиент (браузер) |
|
||
| `montana_db.py` | База данных (таблица calls) |
|
||
|
||
---
|
||
|
||
## Roadmap
|
||
|
||
### v1.0 (текущая)
|
||
- [x] SMS Gateway (BAL, SEND, TX)
|
||
- [x] Voice calls (WebRTC P2P)
|
||
- [x] Signaling через Telegram
|
||
- [x] Биллинг по секундам
|
||
|
||
### v1.1 (планируется)
|
||
- [ ] Video calls
|
||
- [ ] Screen sharing
|
||
- [ ] Telegram MiniApp
|
||
- [ ] TURN сервер на Timeweb
|
||
|
||
### v2.0 (будущее)
|
||
- [ ] Group calls (до 12 участников)
|
||
- [ ] Voicemail (оплата за хранение)
|
||
- [ ] Call recording (opt-in, оплата)
|
||
- [ ] SIP интеграция
|
||
|
||
---
|
||
|
||
**Montana Protocol — Time is the only real currency.**
|