ESP32 wallet RPC, browser wallet provider, and side panel

This commit is contained in:
AidarKC
2026-06-22 01:30:08 +04:00
parent 475db28095
commit ce2d310e8c
23 changed files with 2407 additions and 300 deletions
@@ -110,3 +110,9 @@
```text
sha256$<hex( SHA-256("shine-pairing|" + lower(login.trim()) + "|" + password) )>
```
## 8. Связанный документ по внешнему кошельку
Для отдельного RPC-взаимодействия между браузерным wallet-расширением и ESP32 см. документ:
- [Формат_взаимодействия_внешнего_кошелька_и_ESP32.md](/home/ai/work/SHiNE/SHiNE-server-sha256/Dev_Docs/Протоколы/Формат_взаимодействия_внешнего_кошелька_и_ESP32.md)
@@ -0,0 +1,311 @@
# Формат взаимодействия внешнего кошелька и ESP32
Этот документ фиксирует первый этап формата взаимодействия между внешним браузерным wallet-расширением SHiNE и устройством `ESP32-S3-Touch-AMOLED-2.16`.
Документ описывает:
- как расширение получает текущий активный публичный ключ кошелька с ESP32;
- как расширение отправляет на ESP32 запрос подписи транзакции;
- что именно считается активным кошельком на ESP32;
- какие проверки и UI-реакции ожидаются в браузерном расширении и на устройстве;
- какие ограничения действуют в текущей версии протокола.
## 1. Общая идея
Устройство ESP32 хранит `master secret` пользователя и локально умеет выводить несколько кошельков из одного секрета.
На устройстве в UI пользователь выбирает текущий активный кошелёк:
- `dev.key`
- `root.key`
- `custom`
Для `custom` используется derivation:
```text
sha256(base64(secret32) + "|wallet." + customName)
```
Браузерное расширение не указывает ESP32, какой кошелёк нужно вернуть в первом запросе. Оно просто спрашивает:
```text
какой кошелёк сейчас активен на устройстве
```
ESP32 возвращает:
- тип текущего активного кошелька;
- его публичный ключ `Base58`.
## 2. Транспорт и маршрут
Первая версия формата использует уже существующую `wallet-session` браузерного расширения.
Схема маршрута:
`browser extension -> SHiNE server -> homeserver session on ESP32 -> SHiNE server -> browser extension`
В первой версии:
- отдельная цифровая подпись payload не добавляется;
- отдельное E2E-шифрование для wallet RPC не добавляется;
- используется существующая авторизованная `wallet-session`, транспорт `WSS` и server-side маршрут через уже существующую операцию `CallSignalToSession`.
## 3. Запрос текущего публичного ключа кошелька
### 3.1. Назначение
Операция нужна, чтобы браузерное расширение могло узнать, какой кошелёк сейчас выбран на ESP32, и показать его пользователю перед дальнейшими действиями.
### 3.2. Формат запроса
```json
{
"v": 1,
"operation": "get_wallet_public_key",
"requestId": "1718998123456-482193",
"timeMs": 1718998123456
}
```
### 3.3. Поля запроса
- `v` — версия формата wallet RPC. Для текущего варианта: `1`.
- `operation` — строка операции. Для текущего запроса: `get_wallet_public_key`.
- `requestId` — идентификатор запроса, уникальный в пределах сеанса расширения. Рекомендуемый формат:
`timeMs-random`.
- `timeMs` — локальное время отправителя в миллисекундах.
### 3.4. Поведение ESP32
При получении такого запроса ESP32:
1. смотрит, какой кошелёк сейчас выбран в локальном UI;
2. вычисляет или берёт уже подготовленный публичный ключ именно этого активного кошелька;
3. возвращает тип кошелька и его `publicKeyBase58`.
Запрос не содержит:
- `walletSelector`;
- `customName`;
- `targetSessionName`.
Они намеренно не входят в первую версию этого запроса.
## 4. Формат ответа
```json
{
"v": 1,
"op": "get_wallet_public_key_result",
"requestId": "1718998123456-482193",
"ok": true,
"wallet": {
"type": "custom",
"publicKeyBase58": "...."
},
"timeMs": 1718998123999
}
```
## 5. Поля ответа
- `v` — версия формата ответа. Сейчас `1`.
- `op` — строка результата операции. Сейчас `get_wallet_public_key_result`.
- `requestId` — должен совпадать с `requestId` исходного запроса.
- `ok` — признак успешного результата.
- `wallet.type` — тип активного кошелька:
- `dev.key`
- `root.key`
- `custom`
- `wallet.publicKeyBase58` — публичный ключ активного кошелька в `Base58`.
- `timeMs` — время формирования ответа на стороне ESP32 в миллисекундах.
## 6. Ошибки первой версии
Минимальный формат ошибки в первой версии допускается таким:
```json
{
"v": 1,
"op": "get_wallet_public_key_result",
"requestId": "1718998123456-482193",
"ok": false,
"error": "wallet_unavailable",
"timeMs": 1718998123999
}
```
Рекомендуемые коды ошибок первой версии:
- `wallet_unavailable` — на устройстве нельзя получить текущий кошелёк;
- `secret_not_configured` — на устройстве ещё нет корректно сохранённого секрета;
- `wallet_type_unknown` — выбранный локальный тип кошелька не распознан;
- `internal_error` — прочая локальная ошибка устройства.
## 7. Правила для браузерного расширения
После ответа `ok=true` расширение должно:
1. показать пользователю тип кошелька;
2. показать полный `publicKeyBase58`;
3. дать кнопку копирования ключа в буфер;
4. сохранить этот ключ как текущий ключ устройства для следующей операции подписи.
### 7.1. Проверка через PDA Solana
Расширение уже знает публичные ключи пользователя из Solana PDA. Поэтому оно может дополнительно проверить ответ ESP32:
- если `wallet.type = dev.key`, то `publicKeyBase58` должен совпасть с `deviceKey`, прочитанным из PDA;
- если `wallet.type = root.key`, то `publicKeyBase58` должен совпасть с `rootKey`, прочитанным из PDA;
- если `wallet.type = custom`, такой проверки по PDA в первой версии нет.
При несовпадении для `dev.key` или `root.key` расширение должно показать пользователю предупреждение, что возвращённый ключ не совпал с ожидаемым ключом из PDA.
## 8. Ожидаемое поведение UI расширения
### 8.1. Общий вид popup
Popup браузерного расширения должен быть узким и вытянутым по вертикали.
### 8.2. Состояние без подключения
Если `wallet-session` ещё не подключена:
- показывается кнопка `Подключить`;
- по нажатию открывается экран подключения, близкий по смыслу к сценарию `Войти через другое устройство`;
- пользователь вводит логин устройства и получает код подключения.
### 8.3. Состояние после подключения
Если `wallet-session` уже подключена:
- показывается статус `Подключено`;
- остаётся выбор homeserver;
- появляется кнопка запроса текущего кошелька;
- появляется кнопка `Отключить`.
### 8.4. Подключение кошелька с сайта
Когда сайт просит подключить кошелёк через расширение, расширение должно вести себя как обычный wallet extension:
1. показать пользователю подтверждение подключения;
2. показать, какой именно кошелёк будет подключён;
3. после подтверждения пользователя завершить подключение;
4. если пользователь отказался, не подключать кошелёк к сайту.
## 9. Запрос подписи транзакции
### 9.1. Назначение
Операция нужна, чтобы браузерное расширение могло запросить у ESP32 подпись Solana-транзакции текущим активным кошельком.
Расширение передаёт:
- публичный ключ, которым ожидается подпись;
- сериализованную транзакцию;
- комментарий, который должен быть показан на экране ESP32.
ESP32:
1. показывает пользователю запрос подтверждения;
2. показывает комментарий к подписи;
3. после нажатия `APPROVE` или `REJECT` возвращает ответ в расширение.
### 9.2. Формат запроса
```json
{
"v": 1,
"operation": "sign_transaction",
"requestId": "1718998123456-482193",
"timeMs": 1718998123456,
"publicKeyBase58": "....",
"transactionBase64": "....",
"comment": "Site https://example.com requested transaction signature"
}
```
### 9.3. Поля запроса
- `v` — версия wallet RPC. Сейчас `1`.
- `operation` — строка операции: `sign_transaction`.
- `requestId` — идентификатор запроса.
- `timeMs` — время отправки на стороне расширения.
- `publicKeyBase58` — публичный ключ, от которого ожидается подпись.
- `transactionBase64` — сериализованная Solana transaction в `base64`.
- `comment` — короткое текстовое описание, которое ESP32 показывает пользователю при запросе подписи.
### 9.4. Поведение ESP32
При получении такого запроса ESP32:
1. сравнивает `publicKeyBase58` с публичным ключом текущего активного выбранного кошелька;
2. если ключ не совпадает, сразу возвращает ошибку `wallet_mismatch`;
3. если ключ совпадает, показывает отдельный экран подтверждения подписи;
4. на экране показывает:
- каким кошельком будет выполнена подпись;
- комментарий `comment`;
- кнопки `APPROVE` и `REJECT`;
5. если пользователь подтверждает подпись, ESP32 подписывает транзакцию и возвращает результат;
6. если пользователь отклоняет, ESP32 возвращает `rejected_by_user`.
## 10. Формат ответа на подпись
```json
{
"v": 1,
"op": "sign_transaction_result",
"requestId": "1718998123456-482193",
"ok": true,
"publicKeyBase58": "....",
"signatureBase58": "....",
"signedTransactionBase64": "....",
"timeMs": 1718998123999
}
```
Если пользователь отклонил запрос:
```json
{
"v": 1,
"op": "sign_transaction_result",
"requestId": "1718998123456-482193",
"ok": false,
"error": "rejected_by_user",
"timeMs": 1718998123999
}
```
Рекомендуемые ошибки для `sign_transaction`:
- `rejected_by_user`
- `wallet_unavailable`
- `wallet_mismatch`
- `transaction_base64_invalid`
- `transaction_sign_failed`
- `bad_request`
## 11. Подключение кошелька с сайта
При вызове сайта `connect wallet` расширение должно вести себя как обычный wallet extension:
1. запросить подтверждение у пользователя в браузере;
2. получить текущий публичный ключ с ESP32;
3. вернуть сайту `publicKey` текущего активного кошелька.
Для `signTransaction` расширение:
1. получает транзакцию от сайта;
2. пересылает её на ESP32 через `sign_transaction`;
3. ждёт решение пользователя на устройстве;
4. возвращает браузеру уже подписанную транзакцию.
## 12. Ограничения текущей версии
- запрос возвращает только текущий активный кошелёк, а не список всех кошельков;
- выбор типа кошелька делается только на самом ESP32;
- отдельная цифровая подпись ответа пока не используется;
- отдельное E2E-шифрование wallet RPC пока не используется;
- `custom`-кошельки в первой версии не сверяются с PDA.