diff --git a/Dev_Docs/Pending_Features/2026-06-04_1347_fix_native_ed25519_update_server_pda.md b/Dev_Docs/Pending_Features/2026-06-04_1347_fix_native_ed25519_update_server_pda.md new file mode 100644 index 0000000..c6ce5f5 --- /dev/null +++ b/Dev_Docs/Pending_Features/2026-06-04_1347_fix_native_ed25519_update_server_pda.md @@ -0,0 +1,16 @@ +# Фикс native Ed25519 для update server PDA + +- Краткое описание: + В `shine_users` восстановлена нативная проверка подписи через встроенные Solana Ed25519-инструкции без прямой Rust-верификации. Для `create_user_pda` и `update_user_pda` зафиксирован порядок инструкций в транзакции: сначала подпись `root_key`, затем подпись `blockchain_public_key`, затем вызов `shine_users`. +- Что проверять: + 1. В `shine-UI/server-ui/update-server-pda.html` загрузить существующий server PDA. + 2. Ввести правильный пароль, сгенерировать ключи и выполнить `Обновить PDA`. + 3. Убедиться, что транзакция проходит без `memory allocation failed, out of memory`. + 4. Отдельно проверить создание server PDA из `shine-UI/server-ui/create-server-pda.html`. + 5. Отдельно проверить обычную пользовательскую регистрацию через клиентский UI. +- Ожидаемый результат: + 1. `update server PDA` проходит успешно. + 2. `create server PDA` проходит успешно. + 3. Регистрация обычного пользователя через тот же JS-модуль работы с PDA тоже проходит успешно. + 4. Одинаковый общий JS-модуль используется и клиентским UI, и server UI. +- Статус: `pending` diff --git a/VERSION.properties b/VERSION.properties index 60c3ce7..3094aed 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.121 -server.version=1.2.113 +client.version=1.2.122 +server.version=1.2.114 diff --git a/shine-solana/shine/AGENTS.md b/shine-solana/shine/AGENTS.md index 2b2b851..9fd62bb 100644 --- a/shine-solana/shine/AGENTS.md +++ b/shine-solana/shine/AGENTS.md @@ -48,6 +48,20 @@ Push выполнять через `http.extraHeader` (Authorization) без в - комментарии в `build.gradle` (в корне `shine/`). +## Известное предупреждение сборки + +При `cargo build` / `anchor build` для Solana-программ может регулярно появляться предупреждение вида: + +- `A function call in method ... driftsort_main ... overwrites values in the frame` + +Для текущего проекта это известное предупреждение toolchain/stdlib. Если: + +1. сборка завершается успешно; +2. `anchor deploy` проходит успешно; +3. целевой сценарий реально работает в devnet/localnet, + +то это предупреждение считать допустимым и не блокирующим само изменение. + ## Rule: Dictionary Growth Reporting Если пользователь просит увеличить количество слов в словарях `shine_login_guard`: diff --git a/shine-solana/shine/doc/SHiNE-user-format-v.1.0.md b/shine-solana/shine/doc/SHiNE-user-format-v.1.0.md index 1efee8c..a430fac 100644 --- a/shine-solana/shine/doc/SHiNE-user-format-v.1.0.md +++ b/shine-solana/shine/doc/SHiNE-user-format-v.1.0.md @@ -203,6 +203,7 @@ Arweave `tx_id` - обычное поле внутри записи конкре - `used_bytes <= paid_limit_bytes`; - если `last_block_number` увеличился, то должны быть переданы новый `last_block_hash` и новая `last_block_signature`; - `last_block_signature` проверяется через Ed25519-инструкцию Solana: подпись должна соответствовать хэшу сообщения `LastBlockState` и `blockchain_public_key`; +- в транзакции `create_user_pda` / `update_user_pda` две Ed25519-инструкции должны идти непосредственно перед вызовом `shine_users`: сначала подпись `root_key`, затем подпись `blockchain_public_key`; - `arweave_tx_id` можно добавить или заменить на новый, если пользователь выгрузил более актуальное состояние в Arweave; - уменьшать лимит, число блоков или занятый размер нельзя. @@ -302,6 +303,7 @@ signature = Ed25519(root_key, message) ``` Solana-программа проверяет подпись через встроенную Ed25519-инструкцию. Подписантом должен быть `root_key` из `RootKeyBlock`. +Для `shine_users` эта инструкция должна стоять в транзакции сразу перед Ed25519-инструкцией `last_block_signature` и непосредственно перед самой `create/update`-инструкцией программы. Смену формата подписи сейчас не трогаем. diff --git a/shine-solana/shine/programs/shine_users/src/users.rs b/shine-solana/shine/programs/shine_users/src/users.rs index 8efa6c8..543d4be 100644 --- a/shine-solana/shine/programs/shine_users/src/users.rs +++ b/shine-solana/shine/programs/shine_users/src/users.rs @@ -3,10 +3,9 @@ use anchor_lang::prelude::*; use anchor_lang::solana_program::{ ed25519_program, hash::hashv, - instruction::Instruction, program::{get_return_data, invoke}, system_instruction, - sysvar::instructions::{load_current_index_checked, load_instruction_at_checked}, + sysvar::instructions::get_instruction_relative, }; use common::utils::{create_pda, safe_read_pda, write_to_pda, ErrCode}; use std::str::FromStr; @@ -411,7 +410,8 @@ pub fn update_user_pda(ctx: Context, args: UpdateUserPdaArgs) -> let raw = safe_read_pda(&ctx.accounts.user_pda); require!(!raw.is_empty(), ErrCode::EmptyPdaData); - let old_record = deserialize_record_from_pda(&raw)?; + let old_record = deserialize_record_from_pda(raw.as_slice())?; + drop(raw); require!( old_record.login == args.login, @@ -447,6 +447,14 @@ pub fn update_user_pda(ctx: Context, args: UpdateUserPdaArgs) -> new_balance >= old_record.blockchain.paid_limit_bytes, ErrCode::BalanceDecrease ); + let old_used_bytes = old_record.blockchain.used_bytes; + let old_last_block_number = old_record.blockchain.last_block_number; + let blockchain_state_unchanged = old_record.blockchain.used_bytes == args.fields.used_bytes + && old_record.blockchain.last_block_number == args.fields.last_block_number + && old_record.blockchain.last_block_hash.as_slice() == args.fields.last_block_hash.as_slice() + && old_record.blockchain.last_block_signature.as_slice() + == args.fields.last_block_signature.as_slice() + && old_record.blockchain.arweave_tx_id == args.fields.arweave_tx_id; let mut new_record = UserRecord { created_at_ms: old_record.created_at_ms, @@ -485,11 +493,14 @@ pub fn update_user_pda(ctx: Context, args: UpdateUserPdaArgs) -> ); validate_blockchain_limits( &new_record.blockchain, - old_record.blockchain.used_bytes, - old_record.blockchain.last_block_number, + old_used_bytes, + old_last_block_number, false, )?; - verify_last_block_state_signature(&ctx.accounts.instructions, &new_record)?; + drop(old_record); + if !blockchain_state_unchanged { + verify_last_block_state_signature(&ctx.accounts.instructions, &new_record)?; + } let unsigned = serialize_unsigned_record(&new_record)?; new_record.signature = verify_record_signature( @@ -498,6 +509,7 @@ pub fn update_user_pda(ctx: Context, args: UpdateUserPdaArgs) -> &args.signature, &unsigned, )?; + drop(unsigned); let serialized = serialize_full_record(&new_record)?; ensure_pda_size_and_rent( @@ -807,6 +819,7 @@ fn verify_record_signature( let msg_hash = hashv(&[unsigned]); verify_ed25519_signature_instruction( instructions_sysvar, + -2, root_key, &provided_sig, msg_hash.as_ref(), @@ -822,6 +835,7 @@ fn verify_last_block_state_signature( let msg_hash = hashv(&[&message]); verify_ed25519_signature_instruction( instructions_sysvar, + -1, &record.blockchain.blockchain_public_key, &record.blockchain.last_block_signature, msg_hash.as_ref(), @@ -830,6 +844,7 @@ fn verify_last_block_state_signature( fn verify_ed25519_signature_instruction( instructions_sysvar: &AccountInfo, + index_relative_to_current: i64, expected_pubkey: &Pubkey, expected_signature: &[u8; 64], expected_message: &[u8], @@ -839,24 +854,14 @@ fn verify_ed25519_signature_instruction( anchor_lang::solana_program::sysvar::instructions::id(), ErrCode::InvalidSignature ); - let current_ix_index = load_current_index_checked(instructions_sysvar) + let ed_ix = get_instruction_relative(index_relative_to_current, instructions_sysvar) .map_err(|_| error!(ErrCode::InvalidSignature))?; - require!(current_ix_index > 0, ErrCode::InvalidSignature); - for ix_index in 0..current_ix_index { - let ed_ix = load_instruction_at_checked(ix_index as usize, instructions_sysvar) - .map_err(|_| error!(ErrCode::InvalidSignature))?; - if ed_ix.program_id != ed25519_program::id() { - continue; - } - let parsed = parse_ed25519_ix(&ed_ix)?; - if parsed.pubkey == *expected_pubkey - && parsed.signature == *expected_signature - && parsed.message == expected_message - { - return Ok(()); - } - } - Err(error!(ErrCode::InvalidSignature)) + require_keys_eq!(ed_ix.program_id, ed25519_program::id(), ErrCode::InvalidSignature); + let parsed = parse_ed25519_ix(ed_ix.data.as_slice())?; + require!(parsed.pubkey == *expected_pubkey, ErrCode::InvalidSignature); + require!(parsed.signature == *expected_signature, ErrCode::InvalidSignature); + require!(parsed.message == expected_message, ErrCode::InvalidSignature); + Ok(()) } fn serialize_last_block_state(record: &UserRecord) -> Result> { @@ -870,22 +875,15 @@ fn serialize_last_block_state(record: &UserRecord) -> Result> { Ok(out) } -struct ParsedEd25519 { - pub pubkey: Pubkey, - pub signature: [u8; 64], - pub message: Vec, +struct ParsedEd25519Ref<'a> { + pubkey: Pubkey, + signature: [u8; 64], + message: &'a [u8], } -fn parse_ed25519_ix(ix: &Instruction) -> Result { - require_keys_eq!( - ix.program_id, - ed25519_program::id(), - ErrCode::InvalidSignature - ); - - let data = &ix.data; +fn parse_ed25519_ix<'a>(data: &'a [u8]) -> Result> { require!(data.len() >= 16, ErrCode::InvalidSignature); - require!(data[0] == 1, ErrCode::InvalidSignature); // одна подпись + require!(data[0] == 1, ErrCode::InvalidSignature); let signature_offset = le_u16(data, 2)? as usize; let signature_ix_index = le_u16(data, 4)?; @@ -917,8 +915,7 @@ fn parse_ed25519_ix(ix: &Instruction) -> Result { .ok_or(error!(ErrCode::InvalidSignature))?; let message = data .get(message_offset..message_end) - .ok_or(error!(ErrCode::InvalidSignature))? - .to_vec(); + .ok_or(error!(ErrCode::InvalidSignature))?; let mut signature = [0u8; 64]; signature.copy_from_slice(signature_slice); @@ -926,7 +923,7 @@ fn parse_ed25519_ix(ix: &Instruction) -> Result { <[u8; 32]>::try_from(pubkey_slice).map_err(|_| error!(ErrCode::InvalidSignature))?, ); - Ok(ParsedEd25519 { + Ok(ParsedEd25519Ref { pubkey, signature, message, diff --git a/shine-solana/shine/tests/shine.ts b/shine-solana/shine/tests/shine.ts index 6b3ea82..4aee55d 100644 --- a/shine-solana/shine/tests/shine.ts +++ b/shine-solana/shine/tests/shine.ts @@ -294,7 +294,7 @@ describe("shine_users e2e", () => { .instruction(); await provider.sendAndConfirm( - new Transaction().add(createLastBlockEdIx, createEdIx, createIx), + new Transaction().add(createEdIx, createLastBlockEdIx, createIx), [] ); @@ -388,7 +388,7 @@ describe("shine_users e2e", () => { .instruction(); await provider.sendAndConfirm( - new Transaction().add(updateLastBlockEdIx, updateEdIx, updateIx), + new Transaction().add(updateEdIx, updateLastBlockEdIx, updateIx), [] );