SHA256
186 lines
8.9 KiB
Markdown
186 lines
8.9 KiB
Markdown
# Solana `user_pda`: формат 1.2
|
||
|
||
Актуальный бинарный формат пользовательской PDA программы `shine_users`.
|
||
Формат пользовательских SHiNE-блоков и Arweave этим изменением не меняется.
|
||
|
||
## Назначение
|
||
|
||
PDA 1.2 хранит идентичность и authority пользователя, append-only историю blockchain keys, оплаченный лимит и маршрутизацию.
|
||
Состояние вершины пользовательского blockchain в Solana больше не хранится.
|
||
|
||
Из PDA 1.2 удалены `RecoveryKeyBlock`, `SessionsBlock`, `TrustedStateBlock`, `ArchiveHeadBlock`, `sync_servers[]`, а также старые `used_bytes`, `last_block_*`, `arweave_tx_id`, `blockchain_name/type`.
|
||
|
||
## Общие правила кодирования
|
||
|
||
- Little Endian;
|
||
- `Pubkey` — 32 байта;
|
||
- Ed25519 signature — 64 байта;
|
||
- hash — 32 байта;
|
||
- строка — `len:u8 + UTF-8 bytes`;
|
||
- `block_type:u8 + block_version:u8` есть у каждого typed block;
|
||
- фиксированные `RootKeyBlock` и `ClientKeyBlock` не содержат длину;
|
||
- любой variable block: `block_type:u8 + block_version:u8 + payload_len:u16 + payload`;
|
||
- неизвестный variable block можно пропустить по `payload_len`.
|
||
|
||
Все известные блоки PDA 1.2 имеют `block_version = 0`.
|
||
|
||
## Header
|
||
|
||
```text
|
||
magic: [u8;5] = "SHiNE"
|
||
format_major: u8 = 1
|
||
format_minor: u8 = 2
|
||
record_len: u16
|
||
created_at_ms: u64
|
||
updated_at_ms: u64
|
||
record_number: u32
|
||
prev_record_hash: [u8;32]
|
||
login_len: u8
|
||
login: [u8;login_len]
|
||
blocks_count: u8
|
||
blocks: TypedBlock[blocks_count]
|
||
signature: [u8;64]
|
||
```
|
||
|
||
`record_len` включает подпись и не включает padding Solana account.
|
||
`record_number=0` на create и увеличивается программой на 1 после каждого успешного update.
|
||
`prev_record_hash=0` на create; при update это hash unsigned-части предыдущей PDA.
|
||
|
||
## Типы блоков
|
||
|
||
| type | блок | статус |
|
||
|---:|---|---|
|
||
| 1 | `RootKeyBlock` | обязательный |
|
||
| 2 | `ClientKeyBlock` | обязательный |
|
||
| 3 | `BlockchainRegistryBlock` | обязательный |
|
||
| 30 | `ServerProfileBlock` | опциональный |
|
||
| 40 | `AccessServersBlock` | опциональный |
|
||
|
||
## RootKeyBlock
|
||
|
||
```text
|
||
block_type: u8 = 1
|
||
block_version: u8 = 0
|
||
root_key: [u8;32]
|
||
```
|
||
|
||
`root_key` — холодный recovery authority. Обычные update не используют root. Root участвует только в полной ротации ключей: старый root разрешает переход на одновременно новые `root_key`, `client_key` и blockchain fork.
|
||
|
||
## ClientKeyBlock
|
||
|
||
```text
|
||
block_type: u8 = 2
|
||
block_version: u8 = 0
|
||
client_key: [u8;32]
|
||
```
|
||
|
||
Client key не является authority PDA. Он может использоваться клиентским уровнем и как fee payer Solana-транзакции. В текущем протоколе смена `client_key` разрешена только как часть полной ротации вместе с root и новым blockchain fork.
|
||
|
||
## BlockchainRegistryBlock
|
||
|
||
```text
|
||
block_type: u8 = 3
|
||
block_version: u8 = 0
|
||
payload_len: u16
|
||
|
||
fork_count: u16
|
||
forks[fork_count]:
|
||
blockchain_key: [u8;32]
|
||
created_at_ms: u64
|
||
paid_limit_bytes: u32
|
||
```
|
||
|
||
Одна fork-запись занимает 44 байта.
|
||
|
||
Правила:
|
||
|
||
- `fork_count >= 1`;
|
||
- последний fork — активный;
|
||
- старые fork-записи append-only: их нельзя удалить, переставить или изменить;
|
||
- один blockchain key нельзя добавить повторно;
|
||
- `paid_limit_bytes` старых fork неизменяем;
|
||
- лимит можно пополнять только у последнего fork;
|
||
- новый fork наследует текущий лимит плюс оплаченный top-up;
|
||
- обычный blockchain authority может создать новый fork не раньше чем через 72 часа после `created_at_ms` активного fork;
|
||
- root recovery может создать fork без 72-часового ожидания.
|
||
|
||
`created_at_ms` передаёт клиент и включает в подписываемую PDA. Программа принимает его только если он отличается от Solana Clock не более чем на ±5 минут.
|
||
|
||
## ServerProfileBlock
|
||
|
||
Блок отсутствует у обычного пользователя. Наличие блока означает, что PDA публикует SHiNE-server endpoint.
|
||
|
||
```text
|
||
block_type: u8 = 30
|
||
block_version: u8 = 0
|
||
payload_len: u16
|
||
|
||
address_count: u8
|
||
addresses[address_count]:
|
||
address_format_type: u8
|
||
address_format_version: u8
|
||
address_len: u8
|
||
address: [u8;address_len]
|
||
```
|
||
|
||
PDA 1.2 разрешает **ровно один адрес**, если `ServerProfileBlock` присутствует. Массив/count сохранён для будущего увеличения лимита без изменения бинарной структуры.
|
||
|
||
`address_format_type + address_format_version` позволяют позже стандартизовать URL, IPv4, IPv6, Tor/I2P и другие адресные форматы.
|
||
|
||
## AccessServersBlock
|
||
|
||
```text
|
||
block_type: u8 = 40
|
||
block_version: u8 = 0
|
||
payload_len: u16
|
||
|
||
server_count: u8
|
||
servers[server_count]:
|
||
login_len: u8
|
||
login: [u8;login_len]
|
||
```
|
||
|
||
PDA 1.2 разрешает `server_count = 0` или `1`. Count оставлен для будущего расширения.
|
||
|
||
## Authority и подпись
|
||
|
||
Новая PDA хранит `signature[64]` authority **нового состояния** по hash unsigned PDA:
|
||
|
||
- обычный update — активный blockchain key;
|
||
- новый fork — новый blockchain key;
|
||
- полная ротация root + client + blockchain fork — новый blockchain key.
|
||
|
||
На update программа дополнительно требует Ed25519-подпись authority предыдущего состояния по тому же hash нового unsigned state. Эта transition-подпись не хранится внутри PDA.
|
||
|
||
Blockchain-mode не может изменить root. Для полной ротации используется root-mode: текущий root подписывает hash нового unsigned PDA state как разрешение на переход, а новый blockchain key подписывает тот же hash и его `signature[64]` сохраняется в PDA. Поскольку hash считается по всему unsigned state, обе подписи одновременно фиксируют новый root key, новый client key, новый blockchain fork и остальные поля записи.
|
||
|
||
Смена root считается корректной только как полная ротация: root key изменён, client key изменён и в ту же запись добавлен новый blockchain fork. Такая recovery-ротация не ограничивается обычным 72-часовым cooldown blockchain-authority.
|
||
|
||
## Create
|
||
|
||
Создаются только PDA 1.2.
|
||
|
||
```text
|
||
record_number = 0
|
||
prev_record_hash = 0x00 * 32
|
||
fork_count = 1
|
||
fork[0].blockchain_key = initial blockchain key
|
||
fork[0].created_at_ms = created_at_ms
|
||
fork[0].paid_limit_bytes = start_bonus + paid top-up
|
||
```
|
||
|
||
Root доказывает владение recovery-key отдельной Ed25519 instruction, а `signature[64]` новой PDA делает initial blockchain key.
|
||
|
||
## Legacy 1.0
|
||
|
||
Нормальные create/update/read flows нового клиента и сервера поддерживают только PDA 1.2.
|
||
|
||
Временно существует instruction `close_legacy_pda` для тестовых PDA 1.0. Она:
|
||
|
||
- принимает только PDA с `format_major=1`, `format_minor=0`;
|
||
- не может закрыть 1.2;
|
||
- закрывает старый program-owned account;
|
||
- переводит его lamports вызывающему signer.
|
||
|
||
Legacy migration в 1.2 отсутствует. После удаления тестовых PDA временную close-инструкцию можно удалить из программы.
|