| .. | ||
| README.md | ||
Solana user_pda: итоговый целевой формат пользовательской записи
Документ описывает целевой формат пользовательской PDA-записи user_pda для Solana-программы shine_users.
Это не формат основного блокчейна SHiNE и не документация по AddBlock. Основной блокчейн SHiNE описан отдельно в Dev_Docs/Blockchain/.
Статус документа: итоговый согласованный целевой формат. Текущий код Solana-модуля пока использует старый линейный формат записи; этот документ должен стать основой для изменения кода, тестов и интеграции с сервером.
1. Назначение user_pda
user_pda хранит публичное состояние пользователя в Solana:
- логин пользователя;
- неизменяемые параметры создания записи;
- корневой публичный ключ пользователя;
- ключ устройства;
- данные одного или нескольких пользовательских блокчейнов SHiNE;
- серверные данные пользователя, если пользователь выступает сервером;
- серверы доступа пользователя;
- счетчики/лимиты;
- подпись записи.
На первом этапе поддерживается один пользовательский блокчейн SHiNE, но формат блока блокчейна сразу допускает повторение таких блоков в будущем.
2. Адрес PDA
Адрес пользовательской PDA вычисляется по логину:
- seed prefix:
login=; - второй seed: нормализованный логин в нижнем регистре;
- program id: программа
shine_users.
Один логин соответствует одной user_pda.
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 | Для нового формата: 2. |
| 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 полей идет набор типизированных блоков:
UserPdaRecordV2
- 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 = 2считается ошибкой; - обязательные блоки:
RootKeyBlock,DeviceKeyBlock,BlockchainRegistryBlock; - необязательные блоки:
ServerProfileBlock,AccessServersBlock,TrustedStateBlock; - каждый обязательный блок должен встречаться ровно один раз;
- порядок блоков в записи фиксируется для простоты проверки:
RootKey,DeviceKey,BlockchainRegistry,ServerProfile,AccessServers,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;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
- server_key: [u8; 32], только если 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означает, что пользователь публикует серверный профиль;sync_servers_countмаксимум32;server_address- строковый адрес сервера в формате, который будет отдельно закреплен на уровне приложения;sync_servers- логины пользователей системы, через которых этот сервер пытается синхронизироваться. 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. TrustedStateBlock
Пока trusted-логика не реализована полностью, поэтому блок хранит только счетчик.
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.
Алгоритм:
message = hash(unsigned_record_bytes)
signature = Ed25519(root_key, message)
Solana-программа проверяет подпись через встроенную Ed25519-инструкцию. Подписантом должен быть root_key из RootKeyBlock.
Смену формата подписи сейчас не трогаем.
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.