SHA256
Миграция PDA на client.key
This commit is contained in:
@@ -12,8 +12,9 @@
|
||||
|
||||
- логин пользователя;
|
||||
- неизменяемые параметры создания записи;
|
||||
- публичный recovery-ключ пользователя;
|
||||
- корневой публичный ключ пользователя;
|
||||
- ключ устройства;
|
||||
- клиентский публичный ключ пользователя;
|
||||
- данные одного или нескольких пользовательских блокчейнов SHiNE;
|
||||
- серверные данные пользователя, если пользователь выступает сервером;
|
||||
- серверы доступа пользователя;
|
||||
@@ -34,9 +35,9 @@
|
||||
|
||||
## 2.1. Кто оплачивает create/update PDA
|
||||
|
||||
- Инструкции `create_user_pda` и `update_user_pda` оплачиваются с `device_key`.
|
||||
- Инструкции `create_user_pda` и `update_user_pda` оплачиваются с `client_key`.
|
||||
- `root_key` используется для подписи unsigned части записи через Ed25519 instruction и не является fee payer.
|
||||
- Для server PDA это правило то же самое: пополнять SOL нужно на адрес `device_key`.
|
||||
- Для server PDA это правило то же самое: пополнять SOL нужно на адрес `client_key`.
|
||||
|
||||
## 3. Общие правила кодирования
|
||||
|
||||
@@ -85,8 +86,9 @@ UserPdaRecordV1
|
||||
|
||||
| block_type | Блок | Назначение |
|
||||
|------------|------|------------|
|
||||
| `0` | `RecoveryKeyBlock` | Ключ восстановления пользователя. |
|
||||
| `1` | `RootKeyBlock` | Корневой ключ пользователя. |
|
||||
| `2` | `DeviceKeyBlock` | Ключ устройства пользователя. |
|
||||
| `2` | `ClientKeyBlock` | Клиентский ключ пользователя. |
|
||||
| `3` | `BlockchainRegistryBlock` | Один или несколько блокчейнов пользователя. |
|
||||
| `30` | `ServerProfileBlock` | Серверные данные пользователя. |
|
||||
| `40` | `AccessServersBlock` | Серверы доступа/relay. |
|
||||
@@ -97,13 +99,31 @@ UserPdaRecordV1
|
||||
Правила:
|
||||
|
||||
- неизвестный `block_type` в `format_major = 1` считается ошибкой;
|
||||
- обязательные блоки: `RootKeyBlock`, `DeviceKeyBlock`, `BlockchainRegistryBlock`;
|
||||
- обязательные блоки: `RecoveryKeyBlock`, `RootKeyBlock`, `ClientKeyBlock`, `BlockchainRegistryBlock`;
|
||||
- необязательные блоки: `ServerProfileBlock`, `AccessServersBlock`, `SessionsBlock`, `TrustedStateBlock`;
|
||||
- каждый обязательный блок должен встречаться ровно один раз;
|
||||
- порядок блоков в записи фиксируется для простоты проверки:
|
||||
`RootKey`, `DeviceKey`, `BlockchainRegistry`, `ServerProfile`, `AccessServers`, `Sessions`, `TrustedState`.
|
||||
`RecoveryKey`, `RootKey`, `ClientKey`, `BlockchainRegistry`, `ServerProfile`, `AccessServers`, `Sessions`, `TrustedState`.
|
||||
|
||||
## 6. RootKeyBlock
|
||||
## 6. RecoveryKeyBlock
|
||||
|
||||
Recovery-ключ нужен для будущих сценариев восстановления и ротации остальных ключей. В текущей версии он только публикуется в записи и не меняется через обычный `update_user_pda`.
|
||||
|
||||
```text
|
||||
RecoveryKeyBlock
|
||||
- block_type: u8 = 0
|
||||
- block_version: u8 = 0
|
||||
- recovery_key: [u8; 32]
|
||||
```
|
||||
|
||||
Правила:
|
||||
|
||||
- при создании задается публичный recovery-ключ пользователя;
|
||||
- при обновлении `recovery_key` должен совпадать с предыдущей записью;
|
||||
- приватный `recovery.key` в PDA не хранится;
|
||||
- отдельная ротация recovery-ключа будет отдельным форматом/сценарием в будущем.
|
||||
|
||||
## 7. RootKeyBlock
|
||||
|
||||
Смена `root_key` пока не проектируется и не реализуется. Блок фиксирует только стадию `0`.
|
||||
|
||||
@@ -120,24 +140,24 @@ RootKeyBlock
|
||||
- при обновлении `root_key` должен совпадать с предыдущей записью;
|
||||
- ротация root-key будет отдельным форматом/сценарием в будущем.
|
||||
|
||||
## 7. DeviceKeyBlock
|
||||
## 8. ClientKeyBlock
|
||||
|
||||
Смена `device_key` пока также не проектируется как отдельная ротация. В версии `0` хранится один ключ устройства.
|
||||
Смена `client_key` пока также не проектируется как отдельная ротация. В версии `0` хранится один клиентский ключ пользователя.
|
||||
|
||||
```text
|
||||
DeviceKeyBlock
|
||||
ClientKeyBlock
|
||||
- block_type: u8 = 2
|
||||
- block_version: u8 = 0
|
||||
- device_key: [u8; 32]
|
||||
- client_key: [u8; 32]
|
||||
```
|
||||
|
||||
Правила:
|
||||
|
||||
- при создании задается текущий публичный ключ устройства;
|
||||
- при обновлении ключ устройства может быть обновлен только если это отдельно разрешено бизнес-логикой инструкции;
|
||||
- история устройств и несколько устройств в этом формате не хранятся.
|
||||
- при создании задается текущий клиентский публичный ключ пользователя;
|
||||
- при обновлении `client_key` должен совпадать с предыдущей записью;
|
||||
- история устройств и несколько клиентских ключей в этом формате не хранятся.
|
||||
|
||||
## 8. BlockchainRegistryBlock
|
||||
## 9. BlockchainRegistryBlock
|
||||
|
||||
Блок хранит данные пользовательских блокчейнов SHiNE. Сейчас используется один блокчейн, но структура сразу сделана как список.
|
||||
|
||||
@@ -155,7 +175,7 @@ BlockchainRegistryBlock
|
||||
- в будущем можно увеличить количество записей без изменения смысла `BlockchainRecord`;
|
||||
- каждый `BlockchainRecord` описывает один пользовательский SHiNE-блокчейн.
|
||||
|
||||
## 9. BlockchainRecord
|
||||
## 10. BlockchainRecord
|
||||
|
||||
```text
|
||||
BlockchainRecord
|
||||
@@ -191,7 +211,7 @@ BlockchainRecord
|
||||
|
||||
Arweave `tx_id` - обычное поле внутри записи конкретного блокчейна. Solana-программа не проверяет, что такой Arweave transaction действительно существует и содержит корректные данные; это ответственность клиента/сервера/пользователя.
|
||||
|
||||
## 10. Правила обновления BlockchainRecord
|
||||
## 11. Правила обновления BlockchainRecord
|
||||
|
||||
При обновлении записи:
|
||||
|
||||
@@ -229,7 +249,7 @@ last_block_signature = Ed25519(blockchain_public_key, message)
|
||||
|
||||
Причина проверки подписи `LastBlockState`: `root_key` управляет Solana-записью пользователя, а `blockchain_public_key` подтверждает состояние конкретного пользовательского блокчейна. Подписывается не голый хэш, а связка логина, имени блокчейна, номера последнего блока, хэша последнего блока и занятого размера.
|
||||
|
||||
## 11. ServerProfileBlock
|
||||
## 12. ServerProfileBlock
|
||||
|
||||
Блок присутствует, если пользователь выступает сервером.
|
||||
|
||||
@@ -255,7 +275,7 @@ ServerProfileBlock
|
||||
- `server_address` - строковый адрес сервера в соответствии с `address_format_type`;
|
||||
- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует блокчейн и личные сообщения. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы.
|
||||
|
||||
## 12. AccessServersBlock
|
||||
## 13. AccessServersBlock
|
||||
|
||||
Блок хранит серверы доступа/relay для пользователя.
|
||||
|
||||
@@ -274,7 +294,7 @@ AccessServersBlock
|
||||
- `access_servers` - логины пользователей системы, используемых как серверы доступа/relay. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы;
|
||||
- точная семантика выбора сервера доступа определяется клиентской/серверной логикой SHiNE.
|
||||
|
||||
## 13. SessionsBlock
|
||||
## 14. SessionsBlock
|
||||
|
||||
Блок хранит опубликованные пользовательские сессии. На текущем этапе регистрация пользователя не добавляет туда записи автоматически, поэтому стандартный create/update продолжает работать с пустым списком.
|
||||
|
||||
@@ -321,7 +341,7 @@ SessionRecord
|
||||
- внутри одного блока должны быть уникальны и `session_name`, и `session_pub_key`;
|
||||
- на текущем этапе UI и регистрация не обязаны добавлять туда записи автоматически.
|
||||
|
||||
## 14. TrustedStateBlock
|
||||
## 15. TrustedStateBlock
|
||||
|
||||
Пока trusted-логика не реализована полностью, поэтому блок хранит только счетчик.
|
||||
|
||||
@@ -334,7 +354,7 @@ TrustedStateBlock
|
||||
|
||||
Пока блок с доверенными лицами не реализуется, потому что полный формат trusted-логики еще не составлен. В будущем trusted-связи, очереди, таймеры и подтверждения должны быть вынесены в отдельный формат.
|
||||
|
||||
## 15. Подпись user_pda
|
||||
## 16. Подпись user_pda
|
||||
|
||||
Подписывается не вся PDA целиком, а unsigned-часть записи:
|
||||
|
||||
@@ -355,7 +375,7 @@ Solana-программа проверяет подпись через встр
|
||||
|
||||
Смену формата подписи сейчас не трогаем.
|
||||
|
||||
## 16. Регистрация пользователя
|
||||
## 17. Регистрация пользователя
|
||||
|
||||
При регистрации:
|
||||
|
||||
@@ -373,12 +393,12 @@ Solana-программа проверяет подпись через встр
|
||||
- если покупается дополнительный лимит, пользователь платит комиссию за этот лимит;
|
||||
- вся unsigned-часть записи подписана `root_key`.
|
||||
|
||||
## 17. Обновление пользователя
|
||||
## 18. Обновление пользователя
|
||||
|
||||
При обновлении:
|
||||
|
||||
- PDA должна существовать;
|
||||
- `login`, `created_at_ms`, `root_key` не меняются;
|
||||
- `login`, `created_at_ms`, `recovery_key`, `root_key`, `client_key` не меняются;
|
||||
- `record_number = previous_record_number + 1`;
|
||||
- `prev_record_hash` равен хэшу unsigned-части предыдущей записи;
|
||||
- `updated_at_ms` обновляется;
|
||||
@@ -388,7 +408,7 @@ Solana-программа проверяет подпись через встр
|
||||
- при увеличении оплаченного лимита пользователь доплачивает комиссию;
|
||||
- Arweave `tx_id` может быть пустым или обновленным, но его содержимое Solana не валидирует.
|
||||
|
||||
## 18. Отличия от старого линейного формата
|
||||
## 19. Отличия от старого линейного формата
|
||||
|
||||
Старый формат после `login` хранил поля линейно:
|
||||
|
||||
@@ -396,8 +416,8 @@ Solana-программа проверяет подпись через встр
|
||||
- `root_key`;
|
||||
- `blockchain_key_status`;
|
||||
- `blockchain_key`;
|
||||
- `device_key_status`;
|
||||
- `device_key`;
|
||||
- `client_key_status`;
|
||||
- `client_key`;
|
||||
- `chain_number`;
|
||||
- `balance`;
|
||||
- серверные поля;
|
||||
@@ -408,17 +428,54 @@ Solana-программа проверяет подпись через встр
|
||||
|
||||
Новый целевой формат сохраняет первые 9 фиксированных полей как заголовок, но дальше переходит на типизированные блоки:
|
||||
|
||||
- recovery-ключ становится отдельным обязательным блоком;
|
||||
- ключи становятся отдельными блоками;
|
||||
- данные блокчейна становятся расширенным блоком со своим публичным ключом, лимитом, занятым размером, вершиной цепочки и Arweave `tx_id`;
|
||||
- серверные данные и access-серверы отделяются от данных блокчейна;
|
||||
- расширение формата делается добавлением новых версий блоков или новых `block_type`, а не вставкой полей в середину линейной записи.
|
||||
|
||||
## 18. Что пока не входит в формат
|
||||
## 20. Деривация ключей из master secret
|
||||
|
||||
Сама Solana-программа не вычисляет ключи из секрета и не хранит приватные ключи. Но текущая согласованная клиентская схема деривации для публичной версии формата фиксируется здесь как reference для UI/ESP32/внешних клиентов.
|
||||
|
||||
Базовая формула:
|
||||
|
||||
```text
|
||||
seed = SHA-256("SHiNE-key" || 0x00 || master_secret32 || 0x00 || suffix_utf8)
|
||||
```
|
||||
|
||||
Где:
|
||||
|
||||
- `master_secret32` — 32-байтовый master secret пользователя;
|
||||
- `suffix_utf8` — строка назначения ключа.
|
||||
|
||||
Согласованные suffix:
|
||||
|
||||
```text
|
||||
"recovery.key"
|
||||
"root.key"
|
||||
"blockchain.key"
|
||||
"client.key"
|
||||
```
|
||||
|
||||
Соответствие:
|
||||
|
||||
```text
|
||||
recovery.seed = SHA-256("SHiNE-key" || 0x00 || master_secret32 || 0x00 || "recovery.key")
|
||||
root.seed = SHA-256("SHiNE-key" || 0x00 || master_secret32 || 0x00 || "root.key")
|
||||
blockchain.seed = SHA-256("SHiNE-key" || 0x00 || master_secret32 || 0x00 || "blockchain.key")
|
||||
client.seed = SHA-256("SHiNE-key" || 0x00 || master_secret32 || 0x00 || "client.key")
|
||||
```
|
||||
|
||||
Далее каждая строка `seed` интерпретируется off-chain как `seed32` для отдельной пары Ed25519.
|
||||
|
||||
## 21. Что пока не входит в формат
|
||||
|
||||
Пока не проектируем:
|
||||
|
||||
- ротацию `recovery_key`;
|
||||
- ротацию `root_key`;
|
||||
- сложную ротацию `device_key`;
|
||||
- сложную ротацию `client_key`;
|
||||
- ротацию `blockchain_public_key`;
|
||||
- проверку содержимого Arweave transaction;
|
||||
- хранение полной истории пользовательского блокчейна внутри Solana;
|
||||
|
||||
@@ -154,11 +154,13 @@ system-переводом. Если бы создание шло строго ч
|
||||
|
||||
## 6. Ключи и подписи
|
||||
|
||||
В записи участвуют три ключевых роли:
|
||||
В записи участвуют четыре ключевых роли:
|
||||
|
||||
- `recovery_key`
|
||||
- публичный recovery-ключ пользователя для будущих сценариев восстановления;
|
||||
- `root_key`
|
||||
- корневая подпись самой записи;
|
||||
- `device_key`
|
||||
- `client_key`
|
||||
- текущий плательщик и signer транзакции create/update;
|
||||
- `blockchain_public_key`
|
||||
- ключ подтверждения вершины пользовательского SHiNE-блокчейна.
|
||||
@@ -176,11 +178,26 @@ system-переводом. Если бы создание шло строго ч
|
||||
- PKCS#8 контейнеры;
|
||||
- PEM;
|
||||
- способ хранения приватного ключа на клиенте;
|
||||
- откуда клиент извлёк `seed32` для `device_key`.
|
||||
- откуда клиент извлёк `seed32` для `client_key`.
|
||||
|
||||
### 6.2. Практика клиентской генерации ключей
|
||||
|
||||
Off-chain клиентская логика может хранить приватные ключи в PKCS#8 и извлекать `seed32` для `device`-signer. Это допустимая клиентская реализация, но не часть on-chain формата.
|
||||
Off-chain клиентская логика может хранить приватные ключи в PKCS#8 и извлекать `seed32` для `client`-signer. Это допустимая клиентская реализация, но не часть on-chain формата.
|
||||
|
||||
Согласованная клиентская схема деривации для первой публичной версии:
|
||||
|
||||
```text
|
||||
seed = SHA-256("SHiNE-key" || 0x00 || master_secret32 || 0x00 || suffix_utf8)
|
||||
```
|
||||
|
||||
Согласованные suffix:
|
||||
|
||||
```text
|
||||
"recovery.key"
|
||||
"root.key"
|
||||
"blockchain.key"
|
||||
"client.key"
|
||||
```
|
||||
|
||||
On-chain инвариант только один:
|
||||
|
||||
@@ -277,16 +294,16 @@ On-chain инвариант только один:
|
||||
|
||||
Плательщик транзакции и signer инструкции:
|
||||
|
||||
- `device_key`
|
||||
- `client_key`
|
||||
|
||||
Это принципиальное правило:
|
||||
|
||||
- `root_key` только подписывает запись;
|
||||
- `device_key` оплачивает rent/fees/registration flow.
|
||||
- `client_key` оплачивает rent/fees/registration flow.
|
||||
|
||||
### Аккаунты
|
||||
|
||||
- signer = `device_key`
|
||||
- signer = `client_key`
|
||||
- `user_pda`
|
||||
- system program
|
||||
- inflow vault PDA из `shine_payments`
|
||||
@@ -297,6 +314,7 @@ On-chain инвариант только один:
|
||||
### Входные данные
|
||||
|
||||
- логин
|
||||
- `recovery_key`
|
||||
- `root_key`
|
||||
- `created_at_ms`
|
||||
- `additional_limit`
|
||||
@@ -308,6 +326,7 @@ On-chain инвариант только один:
|
||||
```text
|
||||
- tag: u8 = 3
|
||||
- login: string_u8
|
||||
- recovery_key: [u8; 32]
|
||||
- root_key: [u8; 32]
|
||||
- created_at_ms: u64 LE
|
||||
- additional_limit: u64 LE
|
||||
@@ -368,13 +387,13 @@ limit_fee(additional_limit) = (additional_limit / LIMIT_STEP) * lamports_per_lim
|
||||
|
||||
Те же роли:
|
||||
|
||||
- signer/fee payer = `device_key`
|
||||
- signer/fee payer = `client_key`
|
||||
- подпись записи = `root_key`
|
||||
- подпись вершины блокчейна = `blockchain_public_key`
|
||||
|
||||
### Аккаунты
|
||||
|
||||
- signer = `device_key`
|
||||
- signer = `client_key`
|
||||
- `user_pda`
|
||||
- system program
|
||||
- inflow vault PDA из `shine_payments`
|
||||
@@ -386,17 +405,19 @@ limit_fee(additional_limit) = (additional_limit / LIMIT_STEP) * lamports_per_lim
|
||||
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.
|
||||
4. `recovery_key` совпадает со старым.
|
||||
5. `root_key` совпадает со старым.
|
||||
6. `client_key` совпадает со старым.
|
||||
7. `version = old.record_number + 1`.
|
||||
8. `prev_hash = hash(unsigned_old_record)`.
|
||||
9. `additional_limit % LIMIT_STEP == 0`.
|
||||
10. `blockchain_name` и `blockchain_public_key` не меняются.
|
||||
11. `paid_limit_bytes` не уменьшается.
|
||||
12. `used_bytes` не уменьшается.
|
||||
13. `last_block_number` не уменьшается.
|
||||
14. Если состояние блокчейна изменилось, `last_block_signature` заново проверяется через Ed25519.
|
||||
15. Новая unsigned часть записи подписана `root_key`.
|
||||
16. При необходимости PDA может быть расширена через realloc.
|
||||
|
||||
### Экономика
|
||||
|
||||
@@ -413,6 +434,7 @@ topup_fee = limit_fee(additional_limit)
|
||||
```text
|
||||
- tag: u8 = 4
|
||||
- login: string_u8
|
||||
- recovery_key: [u8; 32]
|
||||
- root_key: [u8; 32]
|
||||
- created_at_ms: u64 LE
|
||||
- updated_at_ms: u64 LE
|
||||
@@ -461,7 +483,6 @@ signature = Ed25519(blockchain_private_key, message_hash)
|
||||
|
||||
Программа допускает обновление:
|
||||
|
||||
- `device_key`
|
||||
- `used_bytes`
|
||||
- `last_block_number`
|
||||
- `last_block_hash`
|
||||
@@ -479,7 +500,9 @@ signature = Ed25519(blockchain_private_key, message_hash)
|
||||
|
||||
- `login`
|
||||
- `created_at_ms`
|
||||
- `recovery_key`
|
||||
- `root_key`
|
||||
- `client_key`
|
||||
- `blockchain_name`
|
||||
- `blockchain_public_key`
|
||||
- `blockchain_type`
|
||||
@@ -489,7 +512,7 @@ signature = Ed25519(blockchain_private_key, message_hash)
|
||||
### UserMutableFieldsV1
|
||||
|
||||
```text
|
||||
- device_key: [u8; 32]
|
||||
- client_key: [u8; 32]
|
||||
- blockchain_public_key: [u8; 32]
|
||||
- blockchain_name: string_u8
|
||||
- used_bytes: u64 LE
|
||||
|
||||
@@ -28,8 +28,9 @@ const MAX_SESSIONS: usize = 64;
|
||||
const MAX_SESSION_NAME_LEN: usize = 64;
|
||||
const MAX_AUTO_REALLOC_INCREASE: usize = 10_000;
|
||||
const ZERO_HASH: [u8; 32] = [0; 32];
|
||||
const BLOCK_TYPE_RECOVERY_KEY: u8 = 0;
|
||||
const BLOCK_TYPE_ROOT_KEY: u8 = 1;
|
||||
const BLOCK_TYPE_DEVICE_KEY: u8 = 2;
|
||||
const BLOCK_TYPE_CLIENT_KEY: u8 = 2;
|
||||
const BLOCK_TYPE_BLOCKCHAIN_REGISTRY: u8 = 3;
|
||||
const BLOCK_TYPE_SERVER_PROFILE: u8 = 30;
|
||||
const BLOCK_TYPE_ACCESS_SERVERS: u8 = 40;
|
||||
@@ -113,7 +114,7 @@ pub struct SessionRecord {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UserMutableFields {
|
||||
pub device_key: Pubkey,
|
||||
pub client_key: Pubkey,
|
||||
pub blockchain_public_key: Pubkey,
|
||||
pub blockchain_name: String,
|
||||
pub used_bytes: u64,
|
||||
@@ -135,6 +136,7 @@ pub struct UserMutableFields {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CreateUserPdaArgs {
|
||||
pub login: String,
|
||||
pub recovery_key: Pubkey,
|
||||
pub root_key: Pubkey,
|
||||
pub created_at_ms: u64,
|
||||
pub additional_limit: u64,
|
||||
@@ -145,6 +147,7 @@ pub struct CreateUserPdaArgs {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UpdateUserPdaArgs {
|
||||
pub login: String,
|
||||
pub recovery_key: Pubkey,
|
||||
pub root_key: Pubkey,
|
||||
pub created_at_ms: u64,
|
||||
pub updated_at_ms: u64,
|
||||
@@ -190,8 +193,9 @@ pub struct UserRecord {
|
||||
pub record_number: u32,
|
||||
pub prev_record_hash: [u8; 32],
|
||||
pub login: String,
|
||||
pub recovery_key: Pubkey,
|
||||
pub root_key: Pubkey,
|
||||
pub device_key: Pubkey,
|
||||
pub client_key: Pubkey,
|
||||
pub blockchain: BlockchainRecord,
|
||||
pub is_server: bool,
|
||||
pub address_format_type: u8,
|
||||
@@ -291,6 +295,7 @@ fn process_instruction(program_id: &Pubkey, accounts: &[AccountInfo], instructio
|
||||
fn parse_create_args(r: &mut Reader<'_>) -> Result<CreateUserPdaArgs, ProgramError> {
|
||||
Ok(CreateUserPdaArgs {
|
||||
login: r.read_string_u8()?,
|
||||
recovery_key: r.read_pubkey()?,
|
||||
root_key: r.read_pubkey()?,
|
||||
created_at_ms: r.read_u64()?,
|
||||
additional_limit: r.read_u64()?,
|
||||
@@ -302,6 +307,7 @@ fn parse_create_args(r: &mut Reader<'_>) -> Result<CreateUserPdaArgs, ProgramErr
|
||||
fn parse_update_args(r: &mut Reader<'_>) -> Result<UpdateUserPdaArgs, ProgramError> {
|
||||
Ok(UpdateUserPdaArgs {
|
||||
login: r.read_string_u8()?,
|
||||
recovery_key: r.read_pubkey()?,
|
||||
root_key: r.read_pubkey()?,
|
||||
created_at_ms: r.read_u64()?,
|
||||
updated_at_ms: r.read_u64()?,
|
||||
@@ -314,7 +320,7 @@ fn parse_update_args(r: &mut Reader<'_>) -> Result<UpdateUserPdaArgs, ProgramErr
|
||||
}
|
||||
|
||||
fn parse_fields(r: &mut Reader<'_>) -> Result<UserMutableFields, ProgramError> {
|
||||
let device_key = r.read_pubkey()?;
|
||||
let client_key = r.read_pubkey()?;
|
||||
let blockchain_public_key = r.read_pubkey()?;
|
||||
let blockchain_name = r.read_string_u8()?;
|
||||
let used_bytes = r.read_u64()?;
|
||||
@@ -350,7 +356,7 @@ fn parse_fields(r: &mut Reader<'_>) -> Result<UserMutableFields, ProgramError> {
|
||||
}
|
||||
let trusted_count = r.read_u8()?;
|
||||
Ok(UserMutableFields {
|
||||
device_key,
|
||||
client_key,
|
||||
blockchain_public_key,
|
||||
blockchain_name,
|
||||
used_bytes,
|
||||
@@ -438,7 +444,7 @@ fn process_create_user_pda(program_id: &Pubkey, accounts: &[AccountInfo], args:
|
||||
require!(it.next().is_none(), ShineUsersError::InvalidInstruction);
|
||||
|
||||
require!(signer.is_signer, ShineUsersError::InvalidSigner);
|
||||
require_keys_eq!(*signer.key, args.fields.device_key, ShineUsersError::InvalidSigner);
|
||||
require_keys_eq!(*signer.key, args.fields.client_key, ShineUsersError::InvalidSigner);
|
||||
require_keys_eq!(*system_program_ai.key, system_program::id(), ShineUsersError::InvalidSystemProgram);
|
||||
|
||||
validate_login(&args.login)?;
|
||||
@@ -463,8 +469,9 @@ fn process_create_user_pda(program_id: &Pubkey, accounts: &[AccountInfo], args:
|
||||
record_number: 0,
|
||||
prev_record_hash: ZERO_HASH,
|
||||
login: args.login,
|
||||
recovery_key: args.recovery_key,
|
||||
root_key: args.root_key,
|
||||
device_key: args.fields.device_key,
|
||||
client_key: args.fields.client_key,
|
||||
blockchain: BlockchainRecord {
|
||||
blockchain_type: BLOCKCHAIN_TYPE_MAIN_USER,
|
||||
blockchain_name: args.fields.blockchain_name,
|
||||
@@ -520,7 +527,7 @@ fn process_update_user_pda(program_id: &Pubkey, accounts: &[AccountInfo], args:
|
||||
require!(it.next().is_none(), ShineUsersError::InvalidInstruction);
|
||||
|
||||
require!(signer.is_signer, ShineUsersError::InvalidSigner);
|
||||
require_keys_eq!(*signer.key, args.fields.device_key, ShineUsersError::InvalidSigner);
|
||||
require_keys_eq!(*signer.key, args.fields.client_key, ShineUsersError::InvalidSigner);
|
||||
require_keys_eq!(*system_program_ai.key, system_program::id(), ShineUsersError::InvalidSystemProgram);
|
||||
|
||||
validate_login(&args.login)?;
|
||||
@@ -537,7 +544,9 @@ fn process_update_user_pda(program_id: &Pubkey, accounts: &[AccountInfo], args:
|
||||
let old_record = deserialize_record_from_pda(&read_pda_all(user_pda)?)?;
|
||||
require!(old_record.login == args.login, ShineUsersError::ImmutableFieldChanged);
|
||||
require!(old_record.created_at_ms == args.created_at_ms, ShineUsersError::ImmutableFieldChanged);
|
||||
require_keys_eq!(old_record.recovery_key, args.recovery_key, ShineUsersError::ImmutableFieldChanged);
|
||||
require_keys_eq!(old_record.root_key, args.root_key, ShineUsersError::ImmutableFieldChanged);
|
||||
require_keys_eq!(old_record.client_key, args.fields.client_key, ShineUsersError::ImmutableFieldChanged);
|
||||
require!(args.version == old_record.record_number.saturating_add(1), ShineUsersError::InvalidVersion);
|
||||
require!(hash_unsigned_record(&old_record)? == args.prev_hash, ShineUsersError::InvalidPrevHash);
|
||||
require!(args.fields.blockchain_name == old_record.blockchain.blockchain_name, ShineUsersError::ImmutableFieldChanged);
|
||||
@@ -579,8 +588,9 @@ fn build_update_record(old_record: &UserRecord, args: &UpdateUserPdaArgs, new_ba
|
||||
record_number: args.version,
|
||||
prev_record_hash: args.prev_hash,
|
||||
login: old_record.login.clone(),
|
||||
recovery_key: old_record.recovery_key,
|
||||
root_key: old_record.root_key,
|
||||
device_key: args.fields.device_key,
|
||||
client_key: args.fields.client_key,
|
||||
blockchain: BlockchainRecord {
|
||||
blockchain_type: old_record.blockchain.blockchain_type,
|
||||
blockchain_name: args.fields.blockchain_name.clone(),
|
||||
@@ -672,8 +682,9 @@ fn deserialize_record_from_pda(raw: &[u8]) -> Result<UserRecord, ProgramError> {
|
||||
let login = read_len_prefixed_string_from(useful, &mut cursor)?;
|
||||
|
||||
let blocks_count = read_u8_from(useful, &mut cursor)? as usize;
|
||||
let mut recovery_key = None;
|
||||
let mut root_key = None;
|
||||
let mut device_key = None;
|
||||
let mut client_key = None;
|
||||
let mut blockchain = None;
|
||||
let mut is_server = false;
|
||||
let mut address_format_type = 0u8;
|
||||
@@ -690,13 +701,17 @@ fn deserialize_record_from_pda(raw: &[u8]) -> Result<UserRecord, ProgramError> {
|
||||
let block_version = read_u8_from(useful, &mut cursor)?;
|
||||
require!(block_version == BLOCK_VERSION_0, ShineUsersError::InvalidRecordFormat);
|
||||
match block_type {
|
||||
BLOCK_TYPE_RECOVERY_KEY => {
|
||||
require!(recovery_key.is_none(), ShineUsersError::InvalidRecordData);
|
||||
recovery_key = Some(Pubkey::new_from_array(read_fixed_32_from(useful, &mut cursor)?));
|
||||
}
|
||||
BLOCK_TYPE_ROOT_KEY => {
|
||||
require!(root_key.is_none(), ShineUsersError::InvalidRecordData);
|
||||
root_key = Some(Pubkey::new_from_array(read_fixed_32_from(useful, &mut cursor)?));
|
||||
}
|
||||
BLOCK_TYPE_DEVICE_KEY => {
|
||||
require!(device_key.is_none(), ShineUsersError::InvalidRecordData);
|
||||
device_key = Some(Pubkey::new_from_array(read_fixed_32_from(useful, &mut cursor)?));
|
||||
BLOCK_TYPE_CLIENT_KEY => {
|
||||
require!(client_key.is_none(), ShineUsersError::InvalidRecordData);
|
||||
client_key = Some(Pubkey::new_from_array(read_fixed_32_from(useful, &mut cursor)?));
|
||||
}
|
||||
BLOCK_TYPE_BLOCKCHAIN_REGISTRY => {
|
||||
require!(blockchain.is_none(), ShineUsersError::InvalidRecordData);
|
||||
@@ -743,8 +758,9 @@ fn deserialize_record_from_pda(raw: &[u8]) -> Result<UserRecord, ProgramError> {
|
||||
record_number,
|
||||
prev_record_hash,
|
||||
login,
|
||||
recovery_key: recovery_key.ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?,
|
||||
root_key: root_key.ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?,
|
||||
device_key: device_key.ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?,
|
||||
client_key: client_key.ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?,
|
||||
blockchain: blockchain.ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?,
|
||||
is_server,
|
||||
address_format_type,
|
||||
@@ -808,10 +824,11 @@ fn serialize_unsigned_record(record: &UserRecord) -> Result<Vec<u8>, ProgramErro
|
||||
out.push(login_bytes.len() as u8);
|
||||
out.extend_from_slice(login_bytes);
|
||||
|
||||
let blocks_count = if record.is_server { 7 } else { 6 };
|
||||
let blocks_count = if record.is_server { 8 } else { 7 };
|
||||
out.push(blocks_count);
|
||||
write_recovery_key_block(&mut out, record);
|
||||
write_root_key_block(&mut out, record);
|
||||
write_device_key_block(&mut out, record);
|
||||
write_client_key_block(&mut out, record);
|
||||
write_blockchain_registry_block(&mut out, &record.blockchain)?;
|
||||
if record.is_server { write_server_profile_block(&mut out, record)?; }
|
||||
write_access_servers_block(&mut out, record)?;
|
||||
@@ -840,8 +857,9 @@ fn hash_unsigned_record(record: &UserRecord) -> Result<[u8; 32], ProgramError> {
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn write_recovery_key_block(out: &mut Vec<u8>, record: &UserRecord) { out.push(BLOCK_TYPE_RECOVERY_KEY); out.push(BLOCK_VERSION_0); out.extend_from_slice(record.recovery_key.as_ref()); }
|
||||
fn write_root_key_block(out: &mut Vec<u8>, record: &UserRecord) { out.push(BLOCK_TYPE_ROOT_KEY); out.push(BLOCK_VERSION_0); out.extend_from_slice(record.root_key.as_ref()); }
|
||||
fn write_device_key_block(out: &mut Vec<u8>, record: &UserRecord) { out.push(BLOCK_TYPE_DEVICE_KEY); out.push(BLOCK_VERSION_0); out.extend_from_slice(record.device_key.as_ref()); }
|
||||
fn write_client_key_block(out: &mut Vec<u8>, record: &UserRecord) { out.push(BLOCK_TYPE_CLIENT_KEY); out.push(BLOCK_VERSION_0); out.extend_from_slice(record.client_key.as_ref()); }
|
||||
fn write_blockchain_registry_block(out: &mut Vec<u8>, blockchain: &BlockchainRecord) -> Result<(), ProgramError> { out.push(BLOCK_TYPE_BLOCKCHAIN_REGISTRY); out.push(BLOCK_VERSION_0); out.push(1); write_blockchain_record(out, blockchain) }
|
||||
fn write_blockchain_record(out: &mut Vec<u8>, blockchain: &BlockchainRecord) -> Result<(), ProgramError> {
|
||||
out.push(blockchain.blockchain_type);
|
||||
|
||||
@@ -43,7 +43,7 @@ type SessionRecord = {
|
||||
};
|
||||
|
||||
type MutableFields = {
|
||||
deviceKey: PublicKey;
|
||||
clientKey: PublicKey;
|
||||
blockchainPublicKey: PublicKey;
|
||||
blockchainName: string;
|
||||
usedBytes: bigint;
|
||||
@@ -69,7 +69,7 @@ type UnsignedRecord = {
|
||||
prevRecordHash: Buffer;
|
||||
login: string;
|
||||
rootKey: PublicKey;
|
||||
deviceKey: PublicKey;
|
||||
clientKey: PublicKey;
|
||||
blockchain: {
|
||||
blockchainType: number;
|
||||
blockchainName: string;
|
||||
@@ -126,7 +126,7 @@ function serializeUnsignedRecord(r: UnsignedRecord): Buffer {
|
||||
out.push(Buffer.from([BLOCK_TYPE_ROOT_KEY, BLOCK_VERSION_0]));
|
||||
out.push(r.rootKey.toBuffer());
|
||||
out.push(Buffer.from([BLOCK_TYPE_DEVICE_KEY, BLOCK_VERSION_0]));
|
||||
out.push(r.deviceKey.toBuffer());
|
||||
out.push(r.clientKey.toBuffer());
|
||||
out.push(Buffer.from([BLOCK_TYPE_BLOCKCHAIN_REGISTRY, BLOCK_VERSION_0, 1]));
|
||||
out.push(Buffer.from([r.blockchain.blockchainType]));
|
||||
out.push(strBytes(r.blockchain.blockchainName));
|
||||
@@ -222,11 +222,11 @@ describe("shine_users e2e", () => {
|
||||
|
||||
const root = anchor.web3.Keypair.generate();
|
||||
const blockchain = anchor.web3.Keypair.generate();
|
||||
const deviceKey = anchor.web3.Keypair.generate().publicKey;
|
||||
const clientKey = anchor.web3.Keypair.generate().publicKey;
|
||||
const blockchainName = `${login}-001`;
|
||||
|
||||
const createFields: MutableFields = {
|
||||
deviceKey,
|
||||
clientKey,
|
||||
blockchainPublicKey: blockchain.publicKey,
|
||||
blockchainName,
|
||||
usedBytes: 0n,
|
||||
@@ -256,7 +256,7 @@ describe("shine_users e2e", () => {
|
||||
prevRecordHash: ZERO_HASH,
|
||||
login,
|
||||
rootKey: root.publicKey,
|
||||
deviceKey,
|
||||
clientKey,
|
||||
blockchain: {
|
||||
blockchainType: BLOCKCHAIN_TYPE_MAIN_USER,
|
||||
blockchainName,
|
||||
@@ -301,7 +301,7 @@ describe("shine_users e2e", () => {
|
||||
createdAtMs: new anchor.BN(createdAtMs.toString()),
|
||||
additionalLimit: new anchor.BN(additionalLimitCreate.toString()),
|
||||
fields: {
|
||||
deviceKey: createFields.deviceKey,
|
||||
clientKey: createFields.clientKey,
|
||||
blockchainPublicKey: createFields.blockchainPublicKey,
|
||||
blockchainName: createFields.blockchainName,
|
||||
usedBytes: new anchor.BN(createFields.usedBytes.toString()),
|
||||
@@ -343,9 +343,9 @@ describe("shine_users e2e", () => {
|
||||
|
||||
const additionalLimitUpdate = 30_000n;
|
||||
expect(additionalLimitUpdate % LIMIT_STEP).eq(0n);
|
||||
const updatedDeviceKey = anchor.web3.Keypair.generate().publicKey;
|
||||
const updatedClientKey = anchor.web3.Keypair.generate().publicKey;
|
||||
const updateFields: MutableFields = {
|
||||
deviceKey: updatedDeviceKey,
|
||||
clientKey: updatedClientKey,
|
||||
blockchainPublicKey: blockchain.publicKey,
|
||||
blockchainName,
|
||||
usedBytes: 512n,
|
||||
@@ -371,7 +371,7 @@ describe("shine_users e2e", () => {
|
||||
prevRecordHash: sha256(createUnsigned),
|
||||
login,
|
||||
rootKey: root.publicKey,
|
||||
deviceKey: updatedDeviceKey,
|
||||
clientKey: updatedClientKey,
|
||||
blockchain: {
|
||||
blockchainType: BLOCKCHAIN_TYPE_MAIN_USER,
|
||||
blockchainName,
|
||||
@@ -419,7 +419,7 @@ describe("shine_users e2e", () => {
|
||||
prevHash: sha256(createUnsigned),
|
||||
additionalLimit: new anchor.BN(additionalLimitUpdate.toString()),
|
||||
fields: {
|
||||
deviceKey: updateFields.deviceKey,
|
||||
clientKey: updateFields.clientKey,
|
||||
blockchainPublicKey: updateFields.blockchainPublicKey,
|
||||
blockchainName: updateFields.blockchainName,
|
||||
usedBytes: new anchor.BN(updateFields.usedBytes.toString()),
|
||||
|
||||
Reference in New Issue
Block a user