Pairing Service
Файл:
backend/src/services/pairing.rs
Сервис P2P-паринга устройств. Реализует два протокола обнаружения и спаривания: mDNS-based (для LAN) и PIN-based (для мобильных устройств). Позволяет обнаруживать устройства в локальной сети и безопасно связывать их в одну identity.
Эндпоинты
| Метод | Путь | Функция | Описание |
|---|---|---|---|
| POST | /api/pairing/start | start_pairing_svc | Запустить режим ожидания паринга |
| POST | /api/pairing/stop | stop_pairing_svc | Остановить паринг |
| POST | /api/pairing/scan | scan_pairing_services_svc | Сканировать доступные сервисы паринга |
| POST | /api/pairing/request | submit_pairing_request_svc | Отправить запрос на паринг |
| POST | /api/pairing/approve | approve_pairing_svc | Одобрить запрос паринга |
| POST | /api/pairing/reject | reject_pairing_svc | Отклонить запрос паринга |
| POST | /api/pairing/discovery/start | start_discovery_svc | Запустить mDNS discovery |
| POST | /api/pairing/discovery/stop | stop_discovery_svc | Остановить mDNS discovery |
| GET | /api/pairing/devices | get_discovered_devices_svc | Список обнаруженных устройств |
| POST | /api/pairing/initiate | initiate_pairing_svc | Инициировать PIN-паринг (legacy) |
| POST | /api/pairing/confirm | confirm_pairing_svc | Подтвердить PIN-паринг (legacy) |
| POST | /api/pairing/apply-identity | apply_paired_identity_svc | Применить identity от другого устройства (legacy) |
| POST | /api/pairing/join/initiate | initiate_join_request_svc | Cross-sign join: joiner шлёт device-pubkey'и хосту |
Cross-sign flow vs legacy
| Свойство | Legacy (initiate_pairing) | Cross-sign (initiate_join_request) |
|---|---|---|
| Что передаётся по проводу | Полный signing_key (Ed25519 приватный) | Только pubkey'и: device_sign_pubkey, device_box_pubkey, device_transport_pubkey |
| Как хост авторизует joiner | Принимает signing_key как «адрес» новой identity | Cross-sign'ит joiner-cert через свой DeviceSignKey (Stronghold) |
| Источник cryptographic identity | Vault на обоих устройствах | Cert-chain anchor'ом на account_root_pubkey |
| Frontend-events | pairing_request, pairing_confirmation | join_response_sent, join_response_applied, join_response_error |
| Состояние после паринга | Оба устройства имеют один и тот же signing_key | Оба имеют один account_root_pubkey, но разные per-device sign+box keys в Stronghold |
Зависимости
PairingManager— логика протоколов парингаDeviceManager— информация о текущем устройствеIdentityManager— применение identity после парингаVaultManager— хранение ключей при парингеStrongholdManager— DeviceSignKey/DeviceBoxKey для join-flowEventBus— уведомления о событиях паринга в UI
start_pairing_svc
POST /api/pairing/start
Запускает режим ожидания паринга — устройство становится видимым для других и может принимать запросы.
Параметры
| Имя | Тип | Описание |
|---|---|---|
pairing_manager | Arc<Mutex<PairingManager>> | Менеджер паринга |
event_bus | EventBus | Шина событий |
Возвращает
Result<StartPairingResult, String>
Логика
- Захватывает lock на
PairingManager - Вызывает
start_pairing(event_bus)— регистрирует mDNS-сервис - Записывает метрику попытки паринга
stop_pairing_svc
POST /api/pairing/stop
Останавливает режим ожидания паринга.
Логика
- Захватывает lock на
PairingManager - Вызывает
stop_pairing()— снимает mDNS-регистрацию
scan_pairing_services_svc
POST /api/pairing/scan
Сканирует локальную сеть на наличие устройств в режиме паринга (mDNS browse).
Параметры
| Имя | Тип | Описание |
|---|---|---|
timeout_secs | u64 | Время сканирования (секунды) |
device_manager | Arc<Mutex<DeviceManager>> | Менеджер устройств |
Возвращает
Result<Vec<DiscoveredPairing>, String>
submit_pairing_request_svc
POST /api/pairing/request
Отправляет запрос на паринг к обнаруженному устройству. Собирает информацию о текущем устройстве (public key, peer ID, OS, storage) и передаёт удалённому устройству.
Параметры
| Имя | Тип | Описание |
|---|---|---|
discovered | DiscoveredPairing | Обнаруженный сервис |
device_name | String | Имя устройства |
device_type | String | Тип устройства |
device_manager | Arc<Mutex<DeviceManager>> | Менеджер устройств |
Возвращает
Result<PairingResponse, String>
Логика
- Читает данные текущего устройства (public_key, peer_id, OS, storage)
- Формирует
PairingDeviceInfo - Отправляет асинхронный запрос на паринг
- Записывает метрики (попытка + длительность)
approve_pairing_svc / reject_pairing_svc
POST /api/pairing/approve / POST /api/pairing/reject
Одобрение или отклонение входящего запроса паринга по токену.
Параметры
| Имя | Тип | Описание |
|---|---|---|
token | String | Токен запроса паринга |
start_discovery_svc / stop_discovery_svc
POST /api/pairing/discovery/start / POST /api/pairing/discovery/stop
Управление mDNS discovery — обнаружение устройств в сети с той же identity.
get_discovered_devices_svc
GET /api/pairing/devices
Возвращает список устройств, обнаруженных через mDNS discovery.
Возвращает
Result<Vec<DiscoveredDevice>, String>
initiate_pairing_svc
POST /api/pairing/initiate
Инициирует PIN-based паринг с указанным peer. Возвращает PIN-код для подтверждения.
Параметры
| Имя | Тип | Описание |
|---|---|---|
peer_id | String | ID пира |
identity_id | String | ID identity |
Возвращает
Result<String, String> — PIN-код
confirm_pairing_svc
POST /api/pairing/confirm
Подтверждение PIN-based паринга. Принимает peer_id, флаг одобрения и PIN.
Параметры
| Имя | Тип | Описание |
|---|---|---|
peer_id | String | ID пира |
approved | bool | Одобрен ли паринг |
pin | String | PIN-код для верификации |
apply_paired_identity_svc
POST /api/pairing/apply-identity
Применяет identity, полученную от другого устройства в процессе паринга. Обновляет локальные IdentityManager, DeviceManager и VaultManager.
Параметры
| Имя | Тип | Описание |
|---|---|---|
identity_data | PairingIdentityData | Данные identity от другого устройства |
identity_manager | Arc<Mutex<IdentityManager>> | Менеджер идентичности |
device_manager | Arc<Mutex<DeviceManager>> | Менеджер устройств |
vault_manager | Arc<Mutex<VaultManager>> | Менеджер хранилища |
Логика
- Делегирует
pairing::protocol::apply_paired_identity()с менеджерами
initiate_join_request_svc
POST /api/pairing/join/initiate
Joiner-side entrypoint cross-sign flow. Генерирует Stronghold-ключи (DeviceSignKey + DeviceBoxKey), собирает PairingJoinRequest с pubkey'ями и отправляет хосту через libp2p req-resp. Ответ обрабатывается асинхронно в background-задаче (PairingManager::start_discovery'шной); фронт слушает event-bus.
Параметры
| Имя | Тип | Описание |
|---|---|---|
target_peer_id | String | libp2p PeerId хоста (из discovery) |
pairing_manager | Arc<Mutex<PairingManager>> | Менеджер паринга |
vault_manager | Arc<Mutex<VaultManager>> | Vault должен быть unlock'нут |
shared_state | SharedState | Глобальное состояние (для Stronghold) |
Возвращает
Result<PairingJoinRequest, String> — сам отправленный request (фронт может показать «отправлено», ждать event).
Логика
- Sync scope: lock vault →
derive_stronghold_password(), drop guard. - Async tokio mutex: snapshot
Arc<StrongholdManager>изshared_state. - Sync PairingManager: lock →
pm.initiate_join_request(...)возвращаетimpl Future + Send + 'static(не борровит self/guard). - Await future (guard'ы все дропнуты до этого момента) — внутри:
stronghold.generate_device_keypair(DeviceSignKey)→ joiner_device_sign_pubkeystronghold.generate_device_keypair(DeviceBoxKey)→ joiner_device_box_pubkey- Чтение
transport_pubkeyиз current device row - Build
PairingJoinRequest, stash вpending_join_requestskeyed by peer_id - Send
DiscoveryCommand::InitiateJoinв bg-task — она пошлёт по libp2p
- После прихода
PairingJoinResponse(отдельный event в bg-task) joiner-сторона применит cert и эмититjoin_response_applied/_errorв EventBus.
Lock discipline
Метод
pm.initiate_join_request(...)возвращаетimpl Future + Send + 'staticименно потому, чтоstd::sync::MutexGuardнельзя пронести через.await. Будущее не борровит ниself, ни guard — клонирует Arc-поля внутрьasync move. Это критично для безопасной работы из commands-layer.
Event-bus после join
| Event name | Эмитит | Когда | Payload |
|---|---|---|---|
join_response_sent | Host bg-task | Cross-sign'нул joiner-cert и поставил в очередь отправку | { peer_id } |
join_response_applied | Joiner bg-task | Применил response: account_root + cert + pubkey'и persisted | { peer_id } |
join_response_error | Joiner bg-task | Ошибка unseal/verify | { peer_id, error } |