diff --git a/VERSION.properties b/VERSION.properties index 7af343b..e4f760b 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.125 -server.version=1.2.117 +client.version=1.2.126 +server.version=1.2.118 diff --git a/shine-solana/shine/doc/SHiNE-user-format-v.1.0.md b/shine-solana/shine/doc/SHiNE-user-format-v.1.0.md deleted file mode 100644 index a430fac..0000000 --- a/shine-solana/shine/doc/SHiNE-user-format-v.1.0.md +++ /dev/null @@ -1,376 +0,0 @@ -# Solana user_pda: итоговый целевой формат пользовательской записи - -Документ описывает целевой формат пользовательской PDA-записи `user_pda` для Solana-программы `shine_users`. - -Это не формат основного блокчейна SHiNE и не документация по `AddBlock`. Основной блокчейн SHiNE описан отдельно в `Dev_Docs/Blockchain/`. - -Статус документа: итоговый согласованный формат, к которому приведены `create_user_pda`, `update_user_pda` и тестовый сериализатор Solana-модуля. - -## 1. Назначение user_pda - -`user_pda` хранит публичное состояние пользователя в Solana: - -- логин пользователя; -- неизменяемые параметры создания записи; -- корневой публичный ключ пользователя; -- ключ устройства; -- данные одного или нескольких пользовательских блокчейнов SHiNE; -- серверные данные пользователя, если пользователь выступает сервером; -- серверы доступа пользователя; -- счетчики/лимиты; -- подпись записи. - -На первом этапе поддерживается один пользовательский блокчейн SHiNE, но формат блока блокчейна сразу допускает повторение таких блоков в будущем. - -## 2. Адрес PDA - -Адрес пользовательской PDA вычисляется по логину: - -- seed prefix: `login=`; -- второй seed: нормализованный логин в нижнем регистре; -- program id: программа `shine_users`. - -Один логин соответствует одной `user_pda`. - -## 2.1. Кто оплачивает create/update PDA - -- Инструкции `create_user_pda` и `update_user_pda` оплачиваются с `device_key`. -- `root_key` используется для подписи unsigned части записи через Ed25519 instruction и не является fee payer. -- Для server PDA это правило то же самое: пополнять SOL нужно на адрес `device_key`. - -## 3. Общие правила кодирования - -- Числа кодируются в Little Endian. -- `u8`, `u16`, `u32`, `u64` имеют обычный фиксированный размер. -- Публичный ключ Solana/Ed25519: 32 байта. -- Ed25519-подпись: 64 байта. -- SHA-256/Solana hash: 32 байта. -- Строка переменной длины: `len: u8` + `bytes[len]` в UTF-8. -- Arweave `tx_id`: строка переменной длины. Ожидаемая практическая длина base64url tx id - 43 байта, но формат хранит длину явно. -- Все типизированные блоки после фиксированного заголовка начинаются с `block_type: u8` и `block_version: u8`. -- Отдельный `block_len` у типизированных блоков не хранится: блоки парсятся по известным полям, счетчикам и строкам с `len: u8`. - -## 4. Верхний формат записи - -Первые 9 полей фиксированы и идут строго в указанном порядке. Это общий заголовок записи. - -| N | Поле | Тип | Размер | Правило | -|---|------|-----|--------|---------| -| 1 | `magic` | bytes | 5 | Всегда `SHiNE`. | -| 2 | `format_major` | `u8` | 1 | Для первого формата: `1`. | -| 3 | `format_minor` | `u8` | 1 | Для первой версии нового формата: `0`. | -| 4 | `record_len` | `u16` | 2 | Длина полезной записи от `magic` до `signature` включительно, без padding. | -| 5 | `created_at_ms` | `u64` | 8 | Время создания записи, Unix time в миллисекундах. Не меняется. | -| 6 | `updated_at_ms` | `u64` | 8 | Время последнего обновления записи. | -| 7 | `record_number` | `u32` | 4 | Номер версии записи пользователя. При создании `0`, при обновлении +1. | -| 8 | `prev_record_hash` | bytes | 32 | Хэш unsigned-части предыдущей записи. При создании 32 нулевых байта. | -| 9 | `login` | string | `1 + len` | Логин пользователя. Не меняется. | - -После первых 9 полей идет набор типизированных блоков: - -```text -UserPdaRecordV1 -- fixed_header: поля 1..9 -- blocks_count: u8 -- blocks: TypedBlock[blocks_count] -- signature: [u8; 64] -- padding: bytes до размера PDA, если нужен -``` - -`blocks_count` входит в unsigned-часть записи и подписывается. - -## 5. Типы блоков - -Зарезервированные значения `block_type`: - -| block_type | Блок | Назначение | -|------------|------|------------| -| `1` | `RootKeyBlock` | Корневой ключ пользователя. | -| `2` | `DeviceKeyBlock` | Ключ устройства пользователя. | -| `3` | `BlockchainRegistryBlock` | Один или несколько блокчейнов пользователя. | -| `30` | `ServerProfileBlock` | Серверные данные пользователя. | -| `40` | `AccessServersBlock` | Серверы доступа/relay. | -| `50` | `TrustedStateBlock` | Счетчик trusted-связей. | -| `255` | `ReservedBlock` | Зарезервировано, пока не используется. | - -Правила: - -- неизвестный `block_type` в `format_major = 1` считается ошибкой; -- обязательные блоки: `RootKeyBlock`, `DeviceKeyBlock`, `BlockchainRegistryBlock`; -- необязательные блоки: `ServerProfileBlock`, `AccessServersBlock`, `TrustedStateBlock`; -- каждый обязательный блок должен встречаться ровно один раз; -- порядок блоков в записи фиксируется для простоты проверки: - `RootKey`, `DeviceKey`, `BlockchainRegistry`, `ServerProfile`, `AccessServers`, `TrustedState`. - -## 6. RootKeyBlock - -Смена `root_key` пока не проектируется и не реализуется. Блок фиксирует только стадию `0`. - -```text -RootKeyBlock -- block_type: u8 = 1 -- block_version: u8 = 0 -- root_key: [u8; 32] -``` - -Правила: - -- при создании задается корневой публичный ключ пользователя; -- при обновлении `root_key` должен совпадать с предыдущей записью; -- ротация root-key будет отдельным форматом/сценарием в будущем. - -## 7. DeviceKeyBlock - -Смена `device_key` пока также не проектируется как отдельная ротация. В версии `0` хранится один ключ устройства. - -```text -DeviceKeyBlock -- block_type: u8 = 2 -- block_version: u8 = 0 -- device_key: [u8; 32] -``` - -Правила: - -- при создании задается текущий публичный ключ устройства; -- при обновлении ключ устройства может быть обновлен только если это отдельно разрешено бизнес-логикой инструкции; -- история устройств и несколько устройств в этом формате не хранятся. - -## 8. BlockchainRegistryBlock - -Блок хранит данные пользовательских блокчейнов SHiNE. Сейчас используется один блокчейн, но структура сразу сделана как список. - -```text -BlockchainRegistryBlock -- block_type: u8 = 3 -- block_version: u8 = 0 -- blockchain_count: u8 -- blockchain_records: BlockchainRecord[blockchain_count] -``` - -Правила: - -- на первом этапе `blockchain_count = 1`; -- в будущем можно увеличить количество записей без изменения смысла `BlockchainRecord`; -- каждый `BlockchainRecord` описывает один пользовательский SHiNE-блокчейн. - -## 9. BlockchainRecord - -```text -BlockchainRecord -- blockchain_type: u8 -- blockchain_name: string -- blockchain_public_key: [u8; 32] -- paid_limit_bytes: u64 -- used_bytes: u64 -- last_block_number: u32 -- last_block_hash: [u8; 32] -- last_block_signature: [u8; 64] -- arweave_present: u8 -- arweave_tx_id: string, только если arweave_present = 1 -``` - -`blockchain_type`: - -| Значение | Смысл | -|----------|-------| -| `1` | Основной пользовательский SHiNE-блокчейн. | - -Поля: - -- `blockchain_name` - строковое имя пользовательского блокчейна, например `login-001`. На первом этапе для основного блокчейна пользователя используется имя вида `-001`, потому что это первый блокчейн этого пользователя. -- `blockchain_public_key` - публичный ключ блокчейна пользователя. -- `paid_limit_bytes` - оплаченный лимит хранения/записей в байтах. -- `used_bytes` - сколько байт уже занято в пользовательском SHiNE-блокчейне. -- `last_block_number` - номер последнего известного блока пользовательского блокчейна. -- `last_block_hash` - хэш последнего известного блока. -- `last_block_signature` - подпись хэша специального сообщения о вершине блокчейна ключом `blockchain_public_key`. -- `arweave_present` - `0`, если ссылки нет; `1`, если ссылка есть. -- `arweave_tx_id` - Arweave transaction id, где лежит выгруженный пользовательский канал/состояние. - -Arweave `tx_id` - обычное поле внутри записи конкретного блокчейна. Solana-программа не проверяет, что такой Arweave transaction действительно существует и содержит корректные данные; это ответственность клиента/сервера/пользователя. - -## 10. Правила обновления BlockchainRecord - -При обновлении записи: - -- `blockchain_type` для существующей записи не меняется; -- `blockchain_public_key` пока не ротируется автоматически; смена ключа требует отдельного согласованного сценария; -- `paid_limit_bytes` может только увеличиваться или оставаться прежним; -- при увеличении `paid_limit_bytes` пользователь платит комиссию в Solana по тарифам программы; -- `used_bytes` может только увеличиваться или оставаться прежним; -- `last_block_number` может только увеличиваться или оставаться прежним; -- `used_bytes <= paid_limit_bytes`; -- если `last_block_number` увеличился, то должны быть переданы новый `last_block_hash` и новая `last_block_signature`; -- `last_block_signature` проверяется через Ed25519-инструкцию Solana: подпись должна соответствовать хэшу сообщения `LastBlockState` и `blockchain_public_key`; -- в транзакции `create_user_pda` / `update_user_pda` две Ed25519-инструкции должны идти непосредственно перед вызовом `shine_users`: сначала подпись `root_key`, затем подпись `blockchain_public_key`; -- `arweave_tx_id` можно добавить или заменить на новый, если пользователь выгрузил более актуальное состояние в Arweave; -- уменьшать лимит, число блоков или занятый размер нельзя. - -Сообщение `LastBlockState`, которое хэшируется и подписывается ключом `blockchain_public_key`: - -```text -LastBlockState -- constant: bytes = "SHiNE_LAST_BLOCK" -- login: string -- blockchain_name: string -- last_block_number: u32 -- last_block_hash: [u8; 32] -- used_bytes: u64 -``` - -Алгоритм: - -```text -message = SHA-256(LastBlockState bytes) -last_block_signature = Ed25519(blockchain_public_key, message) -``` - -Причина проверки подписи `LastBlockState`: `root_key` управляет Solana-записью пользователя, а `blockchain_public_key` подтверждает состояние конкретного пользовательского блокчейна. Подписывается не голый хэш, а связка логина, имени блокчейна, номера последнего блока, хэша последнего блока и занятого размера. - -## 11. ServerProfileBlock - -Блок присутствует, если пользователь выступает сервером. - -```text -ServerProfileBlock -- block_type: u8 = 30 -- block_version: u8 = 0 -- is_server: u8 -- address_format_type: u8, только если is_server = 1 -- address_format_version: u8, только если is_server = 1 -- server_address: string, только если is_server = 1 -- sync_servers_count: u8, только если is_server = 1 -- sync_servers: string[sync_servers_count], только если is_server = 1 -``` - -Правила: - -- `is_server = 0` означает, что серверных данных нет; -- `is_server = 1` означает, что пользователь публикует серверный профиль; -- `address_format_type` — тип формата адреса сервера: `1` = URL-строка (например `https://shineup.me/ws`); -- `address_format_version` — версия формата адреса, сейчас `0`; -- `sync_servers_count` максимум `32`; -- `server_address` - строковый адрес сервера в соответствии с `address_format_type`; -- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует блокчейн и личные сообщения. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы. - -## 12. AccessServersBlock - -Блок хранит серверы доступа/relay для пользователя. - -```text -AccessServersBlock -- block_type: u8 = 40 -- block_version: u8 = 0 -- access_servers_count: u8 -- access_servers: string[access_servers_count] -``` - -Правила: - -- блок может отсутствовать, если серверы доступа не заданы; -- список может обновляться при изменении маршрутизации пользователя; -- `access_servers` - логины пользователей системы, используемых как серверы доступа/relay. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы; -- точная семантика выбора сервера доступа определяется клиентской/серверной логикой SHiNE. - -## 13. TrustedStateBlock - -Пока trusted-логика не реализована полностью, поэтому блок хранит только счетчик. - -```text -TrustedStateBlock -- block_type: u8 = 50 -- block_version: u8 = 0 -- trusted_count: u8 = 0 -``` - -Пока блок с доверенными лицами не реализуется, потому что полный формат trusted-логики еще не составлен. В будущем trusted-связи, очереди, таймеры и подтверждения должны быть вынесены в отдельный формат. - -## 14. Подпись user_pda - -Подписывается не вся PDA целиком, а unsigned-часть записи: - -- от `magic` до последнего байта последнего типизированного блока включительно; -- включая `record_len`, `blocks_count`, все заголовки блоков и тела блоков; -- без поля `signature`; -- без padding. - -Алгоритм: - -```text -message = hash(unsigned_record_bytes) -signature = Ed25519(root_key, message) -``` - -Solana-программа проверяет подпись через встроенную Ed25519-инструкцию. Подписантом должен быть `root_key` из `RootKeyBlock`. -Для `shine_users` эта инструкция должна стоять в транзакции сразу перед Ed25519-инструкцией `last_block_signature` и непосредственно перед самой `create/update`-инструкцией программы. - -Смену формата подписи сейчас не трогаем. - -## 15. Регистрация пользователя - -При регистрации: - -- PDA еще не должна существовать; -- логин проходит проверку формата и login guard; -- `record_number = 0`; -- `prev_record_hash = 0x00...00`; -- `created_at_ms = updated_at_ms`; -- обязательные блоки присутствуют; -- создается минимум один `BlockchainRecord`; -- стартовый `paid_limit_bytes` равен стартовому бонусу плюс оплаченный дополнительный лимит; -- `used_bytes <= paid_limit_bytes`; -- пользователь платит регистрационную комиссию; -- если покупается дополнительный лимит, пользователь платит комиссию за этот лимит; -- вся unsigned-часть записи подписана `root_key`. - -## 16. Обновление пользователя - -При обновлении: - -- PDA должна существовать; -- `login`, `created_at_ms`, `root_key` не меняются; -- `record_number = previous_record_number + 1`; -- `prev_record_hash` равен хэшу unsigned-части предыдущей записи; -- `updated_at_ms` обновляется; -- unsigned-часть новой записи подписана `root_key`; -- лимиты блокчейнов могут только увеличиваться; -- занятый размер и номер последнего блока не могут уменьшаться; -- при увеличении оплаченного лимита пользователь доплачивает комиссию; -- Arweave `tx_id` может быть пустым или обновленным, но его содержимое Solana не валидирует. - -## 17. Отличия от старого линейного формата - -Старый формат после `login` хранил поля линейно: - -- `root_key_status`; -- `root_key`; -- `blockchain_key_status`; -- `blockchain_key`; -- `device_key_status`; -- `device_key`; -- `chain_number`; -- `balance`; -- серверные поля; -- access-серверы; -- `trusted_count`; -- `reserved`; -- `signature`. - -Новый целевой формат сохраняет первые 9 фиксированных полей как заголовок, но дальше переходит на типизированные блоки: - -- ключи становятся отдельными блоками; -- данные блокчейна становятся расширенным блоком со своим публичным ключом, лимитом, занятым размером, вершиной цепочки и Arweave `tx_id`; -- серверные данные и access-серверы отделяются от данных блокчейна; -- расширение формата делается добавлением новых версий блоков или новых `block_type`, а не вставкой полей в середину линейной записи. - -## 18. Что пока не входит в формат - -Пока не проектируем: - -- ротацию `root_key`; -- сложную ротацию `device_key`; -- ротацию `blockchain_public_key`; -- проверку содержимого Arweave transaction; -- хранение полной истории пользовательского блокчейна внутри Solana; -- подключение Solana-модуля к сборке/деплою основного сервера SHiNE.