Починить native Ed25519 update_user_pda без OOM
This commit is contained in:
parent
eeb115584d
commit
de9606519a
@ -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`
|
||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.121
|
client.version=1.2.122
|
||||||
server.version=1.2.113
|
server.version=1.2.114
|
||||||
|
|||||||
@ -48,6 +48,20 @@ Push выполнять через `http.extraHeader` (Authorization) без в
|
|||||||
|
|
||||||
- комментарии в `build.gradle` (в корне `shine/`).
|
- комментарии в `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
|
## Rule: Dictionary Growth Reporting
|
||||||
|
|
||||||
Если пользователь просит увеличить количество слов в словарях `shine_login_guard`:
|
Если пользователь просит увеличить количество слов в словарях `shine_login_guard`:
|
||||||
|
|||||||
@ -203,6 +203,7 @@ Arweave `tx_id` - обычное поле внутри записи конкре
|
|||||||
- `used_bytes <= paid_limit_bytes`;
|
- `used_bytes <= paid_limit_bytes`;
|
||||||
- если `last_block_number` увеличился, то должны быть переданы новый `last_block_hash` и новая `last_block_signature`;
|
- если `last_block_number` увеличился, то должны быть переданы новый `last_block_hash` и новая `last_block_signature`;
|
||||||
- `last_block_signature` проверяется через Ed25519-инструкцию Solana: подпись должна соответствовать хэшу сообщения `LastBlockState` и `blockchain_public_key`;
|
- `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;
|
- `arweave_tx_id` можно добавить или заменить на новый, если пользователь выгрузил более актуальное состояние в Arweave;
|
||||||
- уменьшать лимит, число блоков или занятый размер нельзя.
|
- уменьшать лимит, число блоков или занятый размер нельзя.
|
||||||
|
|
||||||
@ -302,6 +303,7 @@ signature = Ed25519(root_key, message)
|
|||||||
```
|
```
|
||||||
|
|
||||||
Solana-программа проверяет подпись через встроенную Ed25519-инструкцию. Подписантом должен быть `root_key` из `RootKeyBlock`.
|
Solana-программа проверяет подпись через встроенную Ed25519-инструкцию. Подписантом должен быть `root_key` из `RootKeyBlock`.
|
||||||
|
Для `shine_users` эта инструкция должна стоять в транзакции сразу перед Ed25519-инструкцией `last_block_signature` и непосредственно перед самой `create/update`-инструкцией программы.
|
||||||
|
|
||||||
Смену формата подписи сейчас не трогаем.
|
Смену формата подписи сейчас не трогаем.
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,9 @@ use anchor_lang::prelude::*;
|
|||||||
use anchor_lang::solana_program::{
|
use anchor_lang::solana_program::{
|
||||||
ed25519_program,
|
ed25519_program,
|
||||||
hash::hashv,
|
hash::hashv,
|
||||||
instruction::Instruction,
|
|
||||||
program::{get_return_data, invoke},
|
program::{get_return_data, invoke},
|
||||||
system_instruction,
|
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 common::utils::{create_pda, safe_read_pda, write_to_pda, ErrCode};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@ -411,7 +410,8 @@ pub fn update_user_pda(ctx: Context<UpdateUserPda>, args: UpdateUserPdaArgs) ->
|
|||||||
|
|
||||||
let raw = safe_read_pda(&ctx.accounts.user_pda);
|
let raw = safe_read_pda(&ctx.accounts.user_pda);
|
||||||
require!(!raw.is_empty(), ErrCode::EmptyPdaData);
|
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!(
|
require!(
|
||||||
old_record.login == args.login,
|
old_record.login == args.login,
|
||||||
@ -447,6 +447,14 @@ pub fn update_user_pda(ctx: Context<UpdateUserPda>, args: UpdateUserPdaArgs) ->
|
|||||||
new_balance >= old_record.blockchain.paid_limit_bytes,
|
new_balance >= old_record.blockchain.paid_limit_bytes,
|
||||||
ErrCode::BalanceDecrease
|
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 {
|
let mut new_record = UserRecord {
|
||||||
created_at_ms: old_record.created_at_ms,
|
created_at_ms: old_record.created_at_ms,
|
||||||
@ -485,11 +493,14 @@ pub fn update_user_pda(ctx: Context<UpdateUserPda>, args: UpdateUserPdaArgs) ->
|
|||||||
);
|
);
|
||||||
validate_blockchain_limits(
|
validate_blockchain_limits(
|
||||||
&new_record.blockchain,
|
&new_record.blockchain,
|
||||||
old_record.blockchain.used_bytes,
|
old_used_bytes,
|
||||||
old_record.blockchain.last_block_number,
|
old_last_block_number,
|
||||||
false,
|
false,
|
||||||
)?;
|
)?;
|
||||||
|
drop(old_record);
|
||||||
|
if !blockchain_state_unchanged {
|
||||||
verify_last_block_state_signature(&ctx.accounts.instructions, &new_record)?;
|
verify_last_block_state_signature(&ctx.accounts.instructions, &new_record)?;
|
||||||
|
}
|
||||||
|
|
||||||
let unsigned = serialize_unsigned_record(&new_record)?;
|
let unsigned = serialize_unsigned_record(&new_record)?;
|
||||||
new_record.signature = verify_record_signature(
|
new_record.signature = verify_record_signature(
|
||||||
@ -498,6 +509,7 @@ pub fn update_user_pda(ctx: Context<UpdateUserPda>, args: UpdateUserPdaArgs) ->
|
|||||||
&args.signature,
|
&args.signature,
|
||||||
&unsigned,
|
&unsigned,
|
||||||
)?;
|
)?;
|
||||||
|
drop(unsigned);
|
||||||
|
|
||||||
let serialized = serialize_full_record(&new_record)?;
|
let serialized = serialize_full_record(&new_record)?;
|
||||||
ensure_pda_size_and_rent(
|
ensure_pda_size_and_rent(
|
||||||
@ -807,6 +819,7 @@ fn verify_record_signature(
|
|||||||
let msg_hash = hashv(&[unsigned]);
|
let msg_hash = hashv(&[unsigned]);
|
||||||
verify_ed25519_signature_instruction(
|
verify_ed25519_signature_instruction(
|
||||||
instructions_sysvar,
|
instructions_sysvar,
|
||||||
|
-2,
|
||||||
root_key,
|
root_key,
|
||||||
&provided_sig,
|
&provided_sig,
|
||||||
msg_hash.as_ref(),
|
msg_hash.as_ref(),
|
||||||
@ -822,6 +835,7 @@ fn verify_last_block_state_signature(
|
|||||||
let msg_hash = hashv(&[&message]);
|
let msg_hash = hashv(&[&message]);
|
||||||
verify_ed25519_signature_instruction(
|
verify_ed25519_signature_instruction(
|
||||||
instructions_sysvar,
|
instructions_sysvar,
|
||||||
|
-1,
|
||||||
&record.blockchain.blockchain_public_key,
|
&record.blockchain.blockchain_public_key,
|
||||||
&record.blockchain.last_block_signature,
|
&record.blockchain.last_block_signature,
|
||||||
msg_hash.as_ref(),
|
msg_hash.as_ref(),
|
||||||
@ -830,6 +844,7 @@ fn verify_last_block_state_signature(
|
|||||||
|
|
||||||
fn verify_ed25519_signature_instruction(
|
fn verify_ed25519_signature_instruction(
|
||||||
instructions_sysvar: &AccountInfo,
|
instructions_sysvar: &AccountInfo,
|
||||||
|
index_relative_to_current: i64,
|
||||||
expected_pubkey: &Pubkey,
|
expected_pubkey: &Pubkey,
|
||||||
expected_signature: &[u8; 64],
|
expected_signature: &[u8; 64],
|
||||||
expected_message: &[u8],
|
expected_message: &[u8],
|
||||||
@ -839,24 +854,14 @@ fn verify_ed25519_signature_instruction(
|
|||||||
anchor_lang::solana_program::sysvar::instructions::id(),
|
anchor_lang::solana_program::sysvar::instructions::id(),
|
||||||
ErrCode::InvalidSignature
|
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))?;
|
.map_err(|_| error!(ErrCode::InvalidSignature))?;
|
||||||
require!(current_ix_index > 0, ErrCode::InvalidSignature);
|
require_keys_eq!(ed_ix.program_id, ed25519_program::id(), ErrCode::InvalidSignature);
|
||||||
for ix_index in 0..current_ix_index {
|
let parsed = parse_ed25519_ix(ed_ix.data.as_slice())?;
|
||||||
let ed_ix = load_instruction_at_checked(ix_index as usize, instructions_sysvar)
|
require!(parsed.pubkey == *expected_pubkey, ErrCode::InvalidSignature);
|
||||||
.map_err(|_| error!(ErrCode::InvalidSignature))?;
|
require!(parsed.signature == *expected_signature, ErrCode::InvalidSignature);
|
||||||
if ed_ix.program_id != ed25519_program::id() {
|
require!(parsed.message == expected_message, ErrCode::InvalidSignature);
|
||||||
continue;
|
Ok(())
|
||||||
}
|
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_last_block_state(record: &UserRecord) -> Result<Vec<u8>> {
|
fn serialize_last_block_state(record: &UserRecord) -> Result<Vec<u8>> {
|
||||||
@ -870,22 +875,15 @@ fn serialize_last_block_state(record: &UserRecord) -> Result<Vec<u8>> {
|
|||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ParsedEd25519 {
|
struct ParsedEd25519Ref<'a> {
|
||||||
pub pubkey: Pubkey,
|
pubkey: Pubkey,
|
||||||
pub signature: [u8; 64],
|
signature: [u8; 64],
|
||||||
pub message: Vec<u8>,
|
message: &'a [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_ed25519_ix(ix: &Instruction) -> Result<ParsedEd25519> {
|
fn parse_ed25519_ix<'a>(data: &'a [u8]) -> Result<ParsedEd25519Ref<'a>> {
|
||||||
require_keys_eq!(
|
|
||||||
ix.program_id,
|
|
||||||
ed25519_program::id(),
|
|
||||||
ErrCode::InvalidSignature
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = &ix.data;
|
|
||||||
require!(data.len() >= 16, ErrCode::InvalidSignature);
|
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_offset = le_u16(data, 2)? as usize;
|
||||||
let signature_ix_index = le_u16(data, 4)?;
|
let signature_ix_index = le_u16(data, 4)?;
|
||||||
@ -917,8 +915,7 @@ fn parse_ed25519_ix(ix: &Instruction) -> Result<ParsedEd25519> {
|
|||||||
.ok_or(error!(ErrCode::InvalidSignature))?;
|
.ok_or(error!(ErrCode::InvalidSignature))?;
|
||||||
let message = data
|
let message = data
|
||||||
.get(message_offset..message_end)
|
.get(message_offset..message_end)
|
||||||
.ok_or(error!(ErrCode::InvalidSignature))?
|
.ok_or(error!(ErrCode::InvalidSignature))?;
|
||||||
.to_vec();
|
|
||||||
|
|
||||||
let mut signature = [0u8; 64];
|
let mut signature = [0u8; 64];
|
||||||
signature.copy_from_slice(signature_slice);
|
signature.copy_from_slice(signature_slice);
|
||||||
@ -926,7 +923,7 @@ fn parse_ed25519_ix(ix: &Instruction) -> Result<ParsedEd25519> {
|
|||||||
<[u8; 32]>::try_from(pubkey_slice).map_err(|_| error!(ErrCode::InvalidSignature))?,
|
<[u8; 32]>::try_from(pubkey_slice).map_err(|_| error!(ErrCode::InvalidSignature))?,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(ParsedEd25519 {
|
Ok(ParsedEd25519Ref {
|
||||||
pubkey,
|
pubkey,
|
||||||
signature,
|
signature,
|
||||||
message,
|
message,
|
||||||
|
|||||||
@ -294,7 +294,7 @@ describe("shine_users e2e", () => {
|
|||||||
.instruction();
|
.instruction();
|
||||||
|
|
||||||
await provider.sendAndConfirm(
|
await provider.sendAndConfirm(
|
||||||
new Transaction().add(createLastBlockEdIx, createEdIx, createIx),
|
new Transaction().add(createEdIx, createLastBlockEdIx, createIx),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -388,7 +388,7 @@ describe("shine_users e2e", () => {
|
|||||||
.instruction();
|
.instruction();
|
||||||
|
|
||||||
await provider.sendAndConfirm(
|
await provider.sendAndConfirm(
|
||||||
new Transaction().add(updateLastBlockEdIx, updateEdIx, updateIx),
|
new Transaction().add(updateEdIx, updateLastBlockEdIx, updateIx),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user