# Программа `shine_users`

Документ описывает целевое поведение Solana-программы регистрации пользователей SHiNE.

Назначение документа:

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

Если код программы расходится с этим документом, это считается ошибкой: нужно либо исправить код, либо обновить документ в том же изменении.

## 1. Назначение программы

`shine_users` хранит публичную пользовательскую запись SHiNE в Solana PDA и управляет её экономикой.

Программа отвечает за:

- создание `user_pda` по логину;
- обновление `user_pda` без смены логина и root key;
- хранение economy-конфига регистрации;
- взимание комиссии за регистрацию и увеличение лимита;
- проверку Ed25519-подписей `root_key` и `blockchain_public_key`;
- проверку связности новой версии записи с предыдущей через `prev_record_hash`.

Программа не отвечает за:

- хранение приватных ключей;
- проверку существования Arweave tx;
- валидацию того, что логины из `sync_servers` или `access_servers` реально существуют как серверы;
- выполнение SHiNE-блокчейна пользователя;
- хранение серверных auth-сессий.

## 2. Program ID и внешние зависимости

Текущий program id devnet/localnet:

- `FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm`

Внешние зависимости по логике:

- `shine_payments`
  - используется только как источник PDA inflow-вольта;
- `shine_login_guard`
  - используется для классификации логина через CPI;
- системная программа Solana;
- sysvar `instructions`.

## 3. PDA и seed-правила

### 3.1. Пользовательская PDA

Пользовательская запись строится так:

- seed prefix: `user_login=`
- второй seed: логин в нижнем регистре
- program id: `shine_users`

Формула:

```text
user_pda = PDA(["user_login=", lower(login)], shine_users_program_id)
```

### 3.2. Economy config PDA

PDA экономических настроек:

- seed: `shine_users_economy_config`

Формула:

```text
users_economy_config_pda = PDA(["shine_users_economy_config"], shine_users_program_id)
```

### 3.3. Правило создания PDA (защита от «минирования» адреса)

Адрес пользовательской PDA выводится из логина и публично предсказуем: зная желаемый логин,
любой может заранее вычислить адрес записи и перевести на него немного лампортов обычным
system-переводом. Если бы создание шло строго через `system_instruction::create_account`,
такой «подсев» приводил бы к ошибке «account already in use» и навсегда блокировал бы
регистрацию этого логина (targeted-DoS / сквоттинг логинов), причём без оплаты комиссии.

Поэтому `create_pda_account` создаёт аккаунт устойчиво к предзаполненному балансу:

- если на адресе нет лампортов — обычный `create_account` (быстрый путь);
- если лампорты уже есть — «создание поверх предзаполненного»: добор ренты переводом,
  затем `allocate` + `assign` под подписью PDA.

Проверки повторной инициализации (`owner == System Program` и пустые данные) остаются и
не зависят от баланса аккаунта.

### 3.4. Строгий список аккаунтов (нет «лишних» аккаунтов)

Все инструкции `shine_users` читают строго фиксированный набор аккаунтов и после этого
явно требуют, чтобы в переданном списке больше ничего не было
(`require!(it.next().is_none(), InvalidInstruction)`). Если вызывающий добавит лишние
аккаунты в хвост, инструкция завершится ошибкой `InvalidInstruction (1)`.

Это не закрывает отдельной уязвимости (каждый используемый аккаунт и так строго
валидируется по signer/owner/адресу PDA), а является defense-in-depth и приводит поведение
к единому виду с `shine_payments`, где такая же проверка стоит во всех инструкциях.
Списки аккаунтов в разделах ниже надо считать исчерпывающими и точными по количеству.

## 4. Состояния программы

### 4.1. `UsersEconomyConfigState`

Хранится в `users_economy_config_pda`.

Поля:

- `version: u8`
- `registration_fee_lamports: u64`
- `lamports_per_limit_step: u64`
- `start_bonus_limit: u64`

Смысл:

- `registration_fee_lamports` — базовая плата за регистрацию;
- `lamports_per_limit_step` — стоимость одного шага лимита;
- `start_bonus_limit` — стартовый бесплатный лимит записи, который получает новый пользователь.

### 4.2. `user_pda`

Формат пользовательской записи описан отдельно:

- [shine-user-pda-format-v.1.0.md](/home/ai/work/SHiNE/SHiNE-server-sha256/shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md)

Этот документ описывает именно логику программы, а не байтовую структуру блока.

## 5. Константы и базовые правила

Базовые значения из текущей логики:

- seed `user_pda`: `user_login=`
- seed economy config: `shine_users_economy_config`
- стартовый размер `user_pda`: `768` байт
- `LIMIT_STEP = 10_000`
- `START_REGISTRATION_FEE_LAMPORTS = 10_000_000`
- `START_LAMPORTS_PER_LIMIT_STEP = 100_000`
- `START_BONUS_LIMIT = 100_000`

Правила:

- `additional_limit` всегда кратен `LIMIT_STEP`;
- `paid_limit_bytes` не может уменьшаться;
- `used_bytes` не может уменьшаться;
- `last_block_number` не может уменьшаться;
- `root_key` после создания не меняется;
- логин после создания не меняется;
- `created_at_ms` после создания не меняется.

## 6. Ключи и подписи

В записи участвуют три ключевых роли:

- `root_key`
  - корневая подпись самой записи;
- `device_key`
  - текущий плательщик и signer транзакции create/update;
- `blockchain_public_key`
  - ключ подтверждения вершины пользовательского SHiNE-блокчейна.

### 6.1. Что программа видит on-chain

Программа работает только с:

- публичными ключами `32` байта;
- подписями `64` байта;
- сообщениями для Ed25519-проверки.

Программа не знает и не должна знать:

- PKCS#8 контейнеры;
- PEM;
- способ хранения приватного ключа на клиенте;
- откуда клиент извлёк `seed32` для `device_key`.

### 6.2. Практика клиентской генерации ключей

Off-chain клиентская логика может хранить приватные ключи в PKCS#8 и извлекать `seed32` для `device`-signer. Это допустимая клиентская реализация, но не часть on-chain формата.

On-chain инвариант только один:

- публичные ключи и подписи должны соответствовать друг другу.

## 7. Логин и login guard

Перед созданием пользователя логин обязан пройти две проверки:

1. базовая syntactic validation внутри `shine_users`;
2. CPI-вызов `shine_login_guard::classify_login`.

### 7.1. Базовая проверка логина

Логин должен:

- быть не пустым;
- быть длиной не больше `20` символов;
- содержать только `A-Z`, `a-z`, `0-9`, `_`.

### 7.2. Классификация через `shine_login_guard`

`shine_users` вызывает `shine_login_guard` и читает `return_data`.

Классы:

- `0` — логин разрешён;
- `1` — premium login, регистрация запрещена автоматически;
- `2` — trademark login, требует отдельного review и не регистрируется автоматически.

Если `shine_login_guard` вернул что-то иное или return_data некорректны, это ошибка.

## 8. Инструкция `init_users_economy_config`

### Назначение

Создать `users_economy_config_pda` со стартовыми параметрами экономики.

### Аккаунты

- signer/payer
- `users_economy_config_pda`
- system program

### Правила

- PDA должна ещё не существовать;
- адрес PDA обязан совпадать с seed `shine_users_economy_config`;
- в PDA записывается стартовый `UsersEconomyConfigState`.

### Бинарный ABI

```text
- tag: u8 = 1
```

## 9. Инструкция `update_users_economy_config`

### Назначение

Изменить экономические параметры регистрации.

### Авторизация

Только `DAO_AUTHORITY`.

### Аккаунты

- signer
- `users_economy_config_pda`

### Правила

- signer должен совпадать с `DAO_AUTHORITY`;
- PDA должна существовать и принадлежать программе;
- `lamports_per_limit_step > 0`.

### Бинарный ABI

```text
- tag: u8 = 2
- registration_fee_lamports: u64 LE
- lamports_per_limit_step: u64 LE
- start_bonus_limit: u64 LE
```

## 10. Инструкция `create_user_pda`

### Назначение

Создать новую пользовательскую запись по логину.

### Кто платит

Плательщик транзакции и signer инструкции:

- `device_key`

Это принципиальное правило:

- `root_key` только подписывает запись;
- `device_key` оплачивает rent/fees/registration flow.

### Аккаунты

- signer = `device_key`
- `user_pda`
- system program
- inflow vault PDA из `shine_payments`
- sysvar `instructions`
- `users_economy_config_pda`
- `shine_login_guard_program`

### Входные данные

- логин
- `root_key`
- `created_at_ms`
- `additional_limit`
- mutable fields записи
- `root` signature по unsigned части

### Бинарный ABI

```text
- tag: u8 = 3
- login: string_u8
- root_key: [u8; 32]
- created_at_ms: u64 LE
- additional_limit: u64 LE
- fields: UserMutableFieldsV1
- root_signature: [u8; 64]
```

### Обязательные проверки

1. Логин валиден по синтаксису.
2. Логин разрешён `shine_login_guard`.
3. `additional_limit % LIMIT_STEP == 0`.
4. inflow vault совпадает с PDA программы `shine_payments`.
5. `user_pda` вычислена правильно и ещё не существует.
6. Поля блокчейна валидны.
7. Поля server/session/trusted валидны по формату.
8. `last_block_signature` соответствует `LastBlockState`.
9. `root signature` соответствует unsigned части записи.
10. Размер сериализованной записи не превышает допустимый стартовый размер PDA или иные ограничения реализации.

### Экономика

При создании:

- пользователь получает `start_bonus_limit`;
- дополнительно может купить `additional_limit`;
- итоговый оплаченный лимит:

```text
paid_limit_bytes = start_bonus_limit + additional_limit
```

Комиссия:

```text
total_fee = registration_fee_lamports + limit_fee(additional_limit)
```

Где:

```text
limit_fee(additional_limit) = (additional_limit / LIMIT_STEP) * lamports_per_limit_step
```

### Результат

- создаётся PDA;
- в неё записывается полная запись `user_pda`;
- средства переводятся в inflow vault `shine_payments`.

## 11. Инструкция `update_user_pda`

### Назначение

Создать новую версию той же пользовательской записи.

### Авторизация

Те же роли:

- signer/fee payer = `device_key`
- подпись записи = `root_key`
- подпись вершины блокчейна = `blockchain_public_key`

### Аккаунты

- signer = `device_key`
- `user_pda`
- system program
- inflow vault PDA из `shine_payments`
- sysvar `instructions`
- `users_economy_config_pda`

### Обязательные проверки

1. PDA существует и принадлежит `shine_users`.
2. Новый логин совпадает со старым.
3. `created_at_ms` совпадает со старым.
4. `root_key` совпадает со старым.
5. `version = old.record_number + 1`.
6. `prev_hash = hash(unsigned_old_record)`.
7. `additional_limit % LIMIT_STEP == 0`.
8. `blockchain_name` и `blockchain_public_key` не меняются.
9. `paid_limit_bytes` не уменьшается.
10. `used_bytes` не уменьшается.
11. `last_block_number` не уменьшается.
12. Если состояние блокчейна изменилось, `last_block_signature` заново проверяется через Ed25519.
13. Новая unsigned часть записи подписана `root_key`.
14. При необходимости PDA может быть расширена через realloc.

### Экономика

При update оплачивается только докупаемый лимит:

```text
topup_fee = limit_fee(additional_limit)
```

Если `additional_limit = 0`, доплата не требуется.

### Бинарный ABI

```text
- tag: u8 = 4
- login: string_u8
- root_key: [u8; 32]
- created_at_ms: u64 LE
- updated_at_ms: u64 LE
- version: u32 LE
- prev_hash: [u8; 32]
- additional_limit: u64 LE
- fields: UserMutableFieldsV1
- root_signature: [u8; 64]
```

## 12. Ed25519-проверки и порядок инструкций

В транзакции должны стоять две встроенные Ed25519-инструкции прямо перед вызовом `shine_users`:

1. подпись `root_key` по unsigned записи;
2. подпись `blockchain_public_key` по `LastBlockState`.

Текущая логика `shine_users` читает их через sysvar `instructions` относительно текущего индекса:

- `-2` — `root_key`
- `-1` — `blockchain_public_key`

Это правило порядка является частью контракта между off-chain клиентом и программой.

## 13. LastBlockState

Сообщение для подписи `blockchain_public_key`:

```text
- constant: "SHiNE_LAST_BLOCK"
- login
- blockchain_name
- last_block_number
- last_block_hash[32]
- used_bytes
```

Алгоритм:

```text
message_hash = SHA-256(LastBlockState bytes)
signature = Ed25519(blockchain_private_key, message_hash)
```

## 14. Валидируемые mutable-поля записи

Программа допускает обновление:

- `device_key`
- `used_bytes`
- `last_block_number`
- `last_block_hash`
- `last_block_signature`
- `arweave_tx_id`
- `is_server`
- `server profile`
- `access_servers`
- `sessions_mode`
- `sessions`
- `trusted_count`
- `additional_limit`

Программа не допускает update:

- `login`
- `created_at_ms`
- `root_key`
- `blockchain_name`
- `blockchain_public_key`
- `blockchain_type`

## 15. Правила серверных и сессионных полей

### UserMutableFieldsV1

```text
- device_key: [u8; 32]
- blockchain_public_key: [u8; 32]
- blockchain_name: string_u8
- used_bytes: u64 LE
- last_block_number: u32 LE
- last_block_hash: [u8; 32]
- last_block_signature: [u8; 64]
- arweave_tx_id: string_u8
- is_server: u8
- if is_server = 1:
  - address_format_type: u8
  - address_format_version: u8
  - server_address: string_u8
  - sync_servers_count: u8
  - sync_servers[sync_servers_count]: string_u8[]
- access_servers_count: u8
- access_servers[access_servers_count]: string_u8[]
- sessions_mode: u8
- sessions_count: u8
- sessions[sessions_count]:
  - session_type: u8
  - session_version: u8
  - session_name: string_u8
  - session_pub_key: [u8; 32]
- trusted_count: u8
```

### Server profile

Если `is_server = false`:

- `server_address` должен быть пустой;
- `sync_servers` должен быть пустой.

Если `is_server = true`:

- `server_address` обязателен;
- `sync_servers.len() <= 32`.

### Sessions block

Формат сессий описан в PDA-формате, но логика такая:

- максимум `64` записей;
- `sessions_mode` допускает только `1` и `10`;
- `session_type` допускает `1` и `100`;
- `session_version` сейчас только `1`;
- `session_name` должен содержать только `[A-Za-z0-9_]`;
- `session_name` и `session_pub_key` уникальны внутри списка.

На текущем этапе обычная регистрация пользователя должна продолжать работать с:

- `sessions_mode = 1`
- `sessions = []`

## 16. Realloc поведения PDA

Запись может расти. Если новая сериализованная запись длиннее текущего размера PDA:

- PDA разрешено увеличить через realloc;
- нельзя делать чрезмерный рост одним шагом выше внутреннего лимита реализации;
- перед realloc нужно обеспечить rent для нового размера.

## 17. Ошибки и классы отказа

Программа должна различать как минимум такие классы ошибок:

- неверный логин;
- premium/trademark login;
- неверный PDA адрес;
- PDA уже существует / PDA пустая / PDA не принадлежит программе;
- неверный формат записи;
- неверная подпись root;
- неверная подпись last block state;
- попытка изменить immutable поля;
- неверная версия;
- неверный `prev_hash`;
- попытка уменьшить лимит/used_bytes/block number;
- overflow;
- неверный inflow vault;
- неверный DAO authority для economy config.

## 18. Что должно сохраниться при переписи без Anchor

В текущей чисто-rust реализации уже сохранены:

- те же PDA seed-правила;
- тот же формат `user_pda`;
- ту же экономику регистрации и topup;
- тот же порядок Ed25519-инструкций;
- те же immutable/mutable правила;
- ту же валидацию логина и CPI в `shine_login_guard`;
- ту же зависимость от inflow vault программы `shine_payments`.

Сознательно не сохранялись:

- структура Anchor `Context`;
- Anchor discriminator'ы и Anchor-ABI инструкций;
- старые seed'ы, которые конфликтовали с уже существующим Anchor-состоянием в devnet;
- внутренние helper-функции старой реализации.
