Миграция PDA на client.key

This commit is contained in:
AidarKC
2026-06-22 21:57:09 +04:00
parent ba348dafb3
commit 5c92b6a734
133 changed files with 941 additions and 30531 deletions
@@ -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;
+45 -22
View File
@@ -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);
+11 -11
View File
@@ -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()),