Skip to content

Pairing Service

Файл: backend/src/services/pairing.rs

Сервис P2P-паринга устройств. Реализует два протокола обнаружения и спаривания: mDNS-based (для LAN) и PIN-based (для мобильных устройств). Позволяет обнаруживать устройства в локальной сети и безопасно связывать их в одну identity.

Эндпоинты

МетодПутьФункцияОписание
POST/api/pairing/startstart_pairing_svcЗапустить режим ожидания паринга
POST/api/pairing/stopstop_pairing_svcОстановить паринг
POST/api/pairing/scanscan_pairing_services_svcСканировать доступные сервисы паринга
POST/api/pairing/requestsubmit_pairing_request_svcОтправить запрос на паринг
POST/api/pairing/approveapprove_pairing_svcОдобрить запрос паринга
POST/api/pairing/rejectreject_pairing_svcОтклонить запрос паринга
POST/api/pairing/discovery/startstart_discovery_svcЗапустить mDNS discovery
POST/api/pairing/discovery/stopstop_discovery_svcОстановить mDNS discovery
GET/api/pairing/devicesget_discovered_devices_svcСписок обнаруженных устройств
POST/api/pairing/initiateinitiate_pairing_svcИнициировать PIN-паринг (legacy)
POST/api/pairing/confirmconfirm_pairing_svcПодтвердить PIN-паринг (legacy)
POST/api/pairing/apply-identityapply_paired_identity_svcПрименить identity от другого устройства (legacy)
POST/api/pairing/join/initiateinitiate_join_request_svcCross-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 как «адрес» новой identityCross-sign'ит joiner-cert через свой DeviceSignKey (Stronghold)
Источник cryptographic identityVault на обоих устройствахCert-chain anchor'ом на account_root_pubkey
Frontend-eventspairing_request, pairing_confirmationjoin_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-flow
  • EventBus — уведомления о событиях паринга в UI

start_pairing_svc

POST /api/pairing/start

Запускает режим ожидания паринга — устройство становится видимым для других и может принимать запросы.

Параметры

ИмяТипОписание
pairing_managerArc<Mutex<PairingManager>>Менеджер паринга
event_busEventBusШина событий

Возвращает

Result<StartPairingResult, String>

Логика

  1. Захватывает lock на PairingManager
  2. Вызывает start_pairing(event_bus) — регистрирует mDNS-сервис
  3. Записывает метрику попытки паринга
plantuml Diagram

stop_pairing_svc

POST /api/pairing/stop

Останавливает режим ожидания паринга.

Логика

  1. Захватывает lock на PairingManager
  2. Вызывает stop_pairing() — снимает mDNS-регистрацию
plantuml Diagram

scan_pairing_services_svc

POST /api/pairing/scan

Сканирует локальную сеть на наличие устройств в режиме паринга (mDNS browse).

Параметры

ИмяТипОписание
timeout_secsu64Время сканирования (секунды)
device_managerArc<Mutex<DeviceManager>>Менеджер устройств

Возвращает

Result<Vec<DiscoveredPairing>, String>

plantuml Diagram

submit_pairing_request_svc

POST /api/pairing/request

Отправляет запрос на паринг к обнаруженному устройству. Собирает информацию о текущем устройстве (public key, peer ID, OS, storage) и передаёт удалённому устройству.

Параметры

ИмяТипОписание
discoveredDiscoveredPairingОбнаруженный сервис
device_nameStringИмя устройства
device_typeStringТип устройства
device_managerArc<Mutex<DeviceManager>>Менеджер устройств

Возвращает

Result<PairingResponse, String>

Логика

  1. Читает данные текущего устройства (public_key, peer_id, OS, storage)
  2. Формирует PairingDeviceInfo
  3. Отправляет асинхронный запрос на паринг
  4. Записывает метрики (попытка + длительность)
plantuml Diagram

approve_pairing_svc / reject_pairing_svc

POST /api/pairing/approve / POST /api/pairing/reject

Одобрение или отклонение входящего запроса паринга по токену.

Параметры

ИмяТипОписание
tokenStringТокен запроса паринга
plantuml Diagram

start_discovery_svc / stop_discovery_svc

POST /api/pairing/discovery/start / POST /api/pairing/discovery/stop

Управление mDNS discovery — обнаружение устройств в сети с той же identity.

plantuml Diagram

get_discovered_devices_svc

GET /api/pairing/devices

Возвращает список устройств, обнаруженных через mDNS discovery.

Возвращает

Result<Vec<DiscoveredDevice>, String>

plantuml Diagram

initiate_pairing_svc

POST /api/pairing/initiate

Инициирует PIN-based паринг с указанным peer. Возвращает PIN-код для подтверждения.

Параметры

ИмяТипОписание
peer_idStringID пира
identity_idStringID identity

Возвращает

Result<String, String> — PIN-код

plantuml Diagram

confirm_pairing_svc

POST /api/pairing/confirm

Подтверждение PIN-based паринга. Принимает peer_id, флаг одобрения и PIN.

Параметры

ИмяТипОписание
peer_idStringID пира
approvedboolОдобрен ли паринг
pinStringPIN-код для верификации
plantuml Diagram

apply_paired_identity_svc

POST /api/pairing/apply-identity

Применяет identity, полученную от другого устройства в процессе паринга. Обновляет локальные IdentityManager, DeviceManager и VaultManager.

Параметры

ИмяТипОписание
identity_dataPairingIdentityDataДанные identity от другого устройства
identity_managerArc<Mutex<IdentityManager>>Менеджер идентичности
device_managerArc<Mutex<DeviceManager>>Менеджер устройств
vault_managerArc<Mutex<VaultManager>>Менеджер хранилища

Логика

  1. Делегирует pairing::protocol::apply_paired_identity() с менеджерами
plantuml Diagram

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_idStringlibp2p PeerId хоста (из discovery)
pairing_managerArc<Mutex<PairingManager>>Менеджер паринга
vault_managerArc<Mutex<VaultManager>>Vault должен быть unlock'нут
shared_stateSharedStateГлобальное состояние (для Stronghold)

Возвращает

Result<PairingJoinRequest, String> — сам отправленный request (фронт может показать «отправлено», ждать event).

Логика

  1. Sync scope: lock vault → derive_stronghold_password(), drop guard.
  2. Async tokio mutex: snapshot Arc<StrongholdManager> из shared_state.
  3. Sync PairingManager: lock → pm.initiate_join_request(...) возвращает impl Future + Send + 'static (не борровит self/guard).
  4. Await future (guard'ы все дропнуты до этого момента) — внутри:
    • stronghold.generate_device_keypair(DeviceSignKey) → joiner_device_sign_pubkey
    • stronghold.generate_device_keypair(DeviceBoxKey) → joiner_device_box_pubkey
    • Чтение transport_pubkey из current device row
    • Build PairingJoinRequest, stash в pending_join_requests keyed by peer_id
    • Send DiscoveryCommand::InitiateJoin в bg-task — она пошлёт по libp2p
  5. После прихода 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_sentHost bg-taskCross-sign'нул joiner-cert и поставил в очередь отправку{ peer_id }
join_response_appliedJoiner bg-taskПрименил response: account_root + cert + pubkey'и persisted{ peer_id }
join_response_errorJoiner bg-taskОшибка unseal/verify{ peer_id, error }