SHiNE-server/shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md

24 KiB
Raw Permalink Blame History

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: user_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 полей идет набор типизированных блоков:

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 SessionsBlock Опубликованные пользовательские сессии и саб-серверы.
70 TrustedStateBlock Счетчик trusted-связей.
255 ReservedBlock Зарезервировано, пока не используется.

Правила:

  • неизвестный block_type в format_major = 1 считается ошибкой;
  • обязательные блоки: RootKeyBlock, DeviceKeyBlock, BlockchainRegistryBlock;
  • необязательные блоки: ServerProfileBlock, AccessServersBlock, SessionsBlock, TrustedStateBlock;
  • каждый обязательный блок должен встречаться ровно один раз;
  • порядок блоков в записи фиксируется для простоты проверки: RootKey, DeviceKey, BlockchainRegistry, ServerProfile, AccessServers, Sessions, TrustedState.

6. RootKeyBlock

Смена root_key пока не проектируется и не реализуется. Блок фиксирует только стадию 0.

RootKeyBlock
- block_type: u8 = 1
- block_version: u8 = 0
- root_key: [u8; 32]

Правила:

  • при создании задается корневой публичный ключ пользователя;
  • при обновлении root_key должен совпадать с предыдущей записью;
  • ротация root-key будет отдельным форматом/сценарием в будущем.

7. DeviceKeyBlock

Смена device_key пока также не проектируется как отдельная ротация. В версии 0 хранится один ключ устройства.

DeviceKeyBlock
- block_type: u8 = 2
- block_version: u8 = 0
- device_key: [u8; 32]

Правила:

  • при создании задается текущий публичный ключ устройства;
  • при обновлении ключ устройства может быть обновлен только если это отдельно разрешено бизнес-логикой инструкции;
  • история устройств и несколько устройств в этом формате не хранятся.

8. BlockchainRegistryBlock

Блок хранит данные пользовательских блокчейнов SHiNE. Сейчас используется один блокчейн, но структура сразу сделана как список.

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

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. На первом этапе для основного блокчейна пользователя используется имя вида <login>-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:

LastBlockState
- constant: bytes = "SHiNE_LAST_BLOCK"
- login: string
- blockchain_name: string
- last_block_number: u32
- last_block_hash: [u8; 32]
- used_bytes: u64

Алгоритм:

message = SHA-256(LastBlockState bytes)
last_block_signature = Ed25519(blockchain_public_key, message)

Причина проверки подписи LastBlockState: root_key управляет Solana-записью пользователя, а blockchain_public_key подтверждает состояние конкретного пользовательского блокчейна. Подписывается не голый хэш, а связка логина, имени блокчейна, номера последнего блока, хэша последнего блока и занятого размера.

11. ServerProfileBlock

Блок присутствует, если пользователь выступает сервером.

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 для пользователя.

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. SessionsBlock

Блок хранит опубликованные пользовательские сессии. На текущем этапе регистрация пользователя не добавляет туда записи автоматически, поэтому стандартный create/update продолжает работать с пустым списком.

SessionsBlock
- block_type: u8 = 50
- block_version: u8 = 0
- sessions_mode: u8
- sessions_count: u8
- sessions: SessionRecord[sessions_count]

sessions_mode:

Значение Смысл
1 Можно использовать и сессии, зарегистрированные в PDA, и сессии, созданные вне PDA.
10 Зарезервировано на будущее: можно использовать только сессии, опубликованные в PDA.

Сейчас рабочий режим по умолчанию: sessions_mode = 1. Серверная логика пока не реализует особое поведение для 10; это задел под будущее расширение.

SessionRecord
- session_type: u8
- session_version: u8
- session_name: string
- session_pub_key: [u8; 32]

session_type:

Значение Смысл
1 Обычная пользовательская сессия.
100 Саб-сервер пользователя.

Правила:

  • максимум 64 записей на пользователя;
  • session_name не пустой, максимум 64 байта;
  • session_name может содержать только символы [A-Za-z0-9_];
  • session_version сейчас должна быть равна 1;
  • внутри одного блока должны быть уникальны и session_name, и session_pub_key;
  • на текущем этапе UI и регистрация не обязаны добавлять туда записи автоматически.

14. TrustedStateBlock

Пока trusted-логика не реализована полностью, поэтому блок хранит только счетчик.

TrustedStateBlock
- block_type: u8 = 70
- block_version: u8 = 0
- trusted_count: u8 = 0

Пока блок с доверенными лицами не реализуется, потому что полный формат trusted-логики еще не составлен. В будущем trusted-связи, очереди, таймеры и подтверждения должны быть вынесены в отдельный формат.

15. Подпись user_pda

Подписывается не вся PDA целиком, а unsigned-часть записи:

  • от magic до последнего байта последнего типизированного блока включительно;
  • включая record_len, blocks_count, все заголовки блоков и тела блоков;
  • без поля signature;
  • без padding.

Алгоритм:

message = hash(unsigned_record_bytes)
signature = Ed25519(root_key, message)

Solana-программа проверяет подпись через встроенную Ed25519-инструкцию. Подписантом должен быть root_key из RootKeyBlock. Для shine_users эта инструкция должна стоять в транзакции сразу перед Ed25519-инструкцией last_block_signature и непосредственно перед самой create/update-инструкцией программы.

Смену формата подписи сейчас не трогаем.

16. Регистрация пользователя

При регистрации:

  • PDA еще не должна существовать;
  • логин проходит проверку формата и login guard;
  • record_number = 0;
  • prev_record_hash = 0x00...00;
  • created_at_ms = updated_at_ms;
  • обязательные блоки присутствуют;
  • создается минимум один BlockchainRecord;
  • новый SessionsBlock может присутствовать, но при обычной регистрации сейчас записывается пустой список с sessions_mode = 1;
  • стартовый paid_limit_bytes равен стартовому бонусу плюс оплаченный дополнительный лимит;
  • used_bytes <= paid_limit_bytes;
  • пользователь платит регистрационную комиссию;
  • если покупается дополнительный лимит, пользователь платит комиссию за этот лимит;
  • вся unsigned-часть записи подписана root_key.

17. Обновление пользователя

При обновлении:

  • 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 не валидирует.

18. Отличия от старого линейного формата

Старый формат после 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.