Дорабатываю добавление блоков! Вроде всё.Осталось ещё размер уточнить что без хэш и пподписи  2
This commit is contained in:
AidarKC 2025-12-23 16:12:36 +03:00
parent 9633e3528d
commit 26afcb892a
7 changed files with 118 additions and 492 deletions

View File

@ -6,151 +6,146 @@ import java.util.Arrays;
import java.util.Objects;
/**
* ============================================================================
* BchBlockEntry запись блокчейна SHiNE (.bch)
* ============================================================================
* BchBlockEntry_new универсальный блок нового формата.
*
* RAW (BigEndian):
* [4] recordSize = RAW_HEADER_SIZE + body.length
* [4] recordNumber
* [8] timestamp UNIX time (секунды)
* [M] bodyBytes (внутри body первые 4 байта = [type][ver])
* [4] recordSize (int) = RAW + signature + hash
* [4] recordNumber (int) глобальный номер блока
* [8] timestamp (long) unix seconds
* [2] line (short)
* [4] lineNumber (int)
* [N] bodyBytes (body, начинается с [type][version])
*
* FULL:
* RAW + signature64 + hash32
*
* ============================================================================
* TAIL:
* [64] signature64 (Ed25519)
* [32] hash32 (SHA-256)
*/
public class BchBlockEntry {
public final class BchBlockEntry {
// ---- Константы размеров ----
public static final int SIGNATURE_LEN = 64;
public static final int HASH_LEN = 32;
public static final int HASH_LEN = 32;
/** RAW заголовок теперь: size + number + timestamp */
public static final int RAW_HEADER_SIZE = 4 + 4 + 8; // 16
/** Размер фиксированного RAW-заголовка без body */
public static final int RAW_HEADER_SIZE = 4 + 4 + 8 + 2 + 4;
// ---- Поля RAW-заголовка ----
public final int recordSize; // [4] M + 16
public final int recordNumber; // [4]
public final long timestamp; // [8]
public final byte[] body; // [M] тело записи (начинается с type/ver)
// --- RAW ---
public final int recordSize;
public final int recordNumber;
public final long timestamp;
public final short line;
public final int lineNumber;
public final byte[] bodyBytes;
// ---- Поля подписи и хэша ----
private byte[] signature64; // [64]
private byte[] hash32; // [32]
// --- TAIL ---
private final byte[] signature64;
private final byte[] hash32;
// ---- Кэшированные представления ----
public final byte[] rawBytes; // RAW без подписи/хэша
private byte[] rawBytesWithSignatureAndHash; // FULL (может быть null)
// --- cached ---
private final byte[] fullBytes;
// ========================================================================
// КОНСТРУКТОР 1 из полей (RAW only)
// ========================================================================
public BchBlockEntry(int recordNumber,
long timestamp,
byte[] body) {
Objects.requireNonNull(body, "body == null");
/* ===================================================================== */
/* ====================== Конструктор из байт ========================== */
/* ===================================================================== */
this.recordNumber = recordNumber;
this.timestamp = timestamp;
this.body = Arrays.copyOf(body, body.length);
this.recordSize = body.length + RAW_HEADER_SIZE;
public BchBlockEntry(byte[] fullBytes) {
Objects.requireNonNull(fullBytes, "fullBytes == null");
if (fullBytes.length < RAW_HEADER_SIZE + SIGNATURE_LEN + HASH_LEN)
throw new IllegalArgumentException("Block too short");
ByteBuffer buf = ByteBuffer
.allocate(RAW_HEADER_SIZE + body.length)
.order(ByteOrder.BIG_ENDIAN);
ByteBuffer bb = ByteBuffer.wrap(fullBytes).order(ByteOrder.BIG_ENDIAN);
buf.putInt(recordSize);
buf.putInt(recordNumber);
buf.putLong(timestamp);
buf.put(body);
this.recordSize = bb.getInt();
if (recordSize != fullBytes.length)
throw new IllegalArgumentException("recordSize mismatch");
this.rawBytes = buf.array();
}
this.recordNumber = bb.getInt();
this.timestamp = bb.getLong();
this.line = bb.getShort();
this.lineNumber = bb.getInt();
// ========================================================================
// КОНСТРУКТОР 2 из полного массива (RAW + SIG + HASH)
// ========================================================================
public BchBlockEntry(byte[] rawWithSigAndHash) {
Objects.requireNonNull(rawWithSigAndHash, "rawWithSigAndHash == null");
if (rawWithSigAndHash.length < RAW_HEADER_SIZE + SIGNATURE_LEN + HASH_LEN)
throw new IllegalArgumentException("Слишком мало данных для полного блока");
int bodyLen = recordSize - RAW_HEADER_SIZE - SIGNATURE_LEN - HASH_LEN;
if (bodyLen <= 0)
throw new IllegalArgumentException("Invalid body length");
ByteBuffer probe = ByteBuffer.wrap(rawWithSigAndHash).order(ByteOrder.BIG_ENDIAN);
int rs = probe.getInt(); // recordSize
if (rs < RAW_HEADER_SIZE)
throw new IllegalArgumentException("Некорректный recordSize: " + rs);
if (rawWithSigAndHash.length < rs + SIGNATURE_LEN + HASH_LEN)
throw new IllegalArgumentException("Данные короче, чем raw+sig+hash");
this.rawBytes = Arrays.copyOfRange(rawWithSigAndHash, 0, rs);
ByteBuffer buf = ByteBuffer.wrap(this.rawBytes).order(ByteOrder.BIG_ENDIAN);
this.recordSize = buf.getInt();
this.recordNumber = buf.getInt();
this.timestamp = buf.getLong();
int bodyLen = this.recordSize - RAW_HEADER_SIZE;
if (bodyLen < 0 || bodyLen != this.rawBytes.length - RAW_HEADER_SIZE)
throw new IllegalArgumentException("Неконсистентная длина тела блока");
this.body = new byte[bodyLen];
buf.get(this.body);
// подпись + хэш
ByteBuffer tail = ByteBuffer
.wrap(rawWithSigAndHash, rs, SIGNATURE_LEN + HASH_LEN)
.order(ByteOrder.BIG_ENDIAN);
this.bodyBytes = new byte[bodyLen];
bb.get(this.bodyBytes);
this.signature64 = new byte[SIGNATURE_LEN];
tail.get(this.signature64);
this.hash32 = new byte[HASH_LEN];
tail.get(this.hash32);
bb.get(this.signature64);
this.rawBytesWithSignatureAndHash =
Arrays.copyOf(rawWithSigAndHash, rs + SIGNATURE_LEN + HASH_LEN);
this.hash32 = new byte[HASH_LEN];
bb.get(this.hash32);
this.fullBytes = Arrays.copyOf(fullBytes, fullBytes.length);
}
// ========================================================================
// Добавить подпись и хэш
// ========================================================================
public BchBlockEntry addSignatureAndHash(byte[] signature64, byte[] hash32) {
/* ===================================================================== */
/* ====================== Конструктор сборки ============================ */
/* ===================================================================== */
public BchBlockEntry(int recordNumber,
long timestamp,
short line,
int lineNumber,
byte[] bodyBytes,
byte[] signature64,
byte[] hash32) {
Objects.requireNonNull(bodyBytes, "bodyBytes == null");
Objects.requireNonNull(signature64, "signature64 == null");
Objects.requireNonNull(hash32, "hash32 == null");
if (signature64.length != SIGNATURE_LEN) throw new IllegalArgumentException("signature64 != 64");
if (hash32.length != HASH_LEN) throw new IllegalArgumentException("hash32 != 32");
if (signature64.length != SIGNATURE_LEN)
throw new IllegalArgumentException("signature64 != 64");
if (hash32.length != HASH_LEN)
throw new IllegalArgumentException("hash32 != 32");
this.recordNumber = recordNumber;
this.timestamp = timestamp;
this.line = line;
this.lineNumber = lineNumber;
this.bodyBytes = Arrays.copyOf(bodyBytes, bodyBytes.length);
this.signature64 = Arrays.copyOf(signature64, SIGNATURE_LEN);
this.hash32 = Arrays.copyOf(hash32, HASH_LEN);
byte[] full = new byte[this.rawBytes.length + SIGNATURE_LEN + HASH_LEN];
System.arraycopy(this.rawBytes, 0, full, 0, this.rawBytes.length);
System.arraycopy(this.signature64, 0, full, this.rawBytes.length, SIGNATURE_LEN);
System.arraycopy(this.hash32, 0, full, this.rawBytes.length + SIGNATURE_LEN, HASH_LEN);
this.rawBytesWithSignatureAndHash = full;
return this;
this.recordSize =
RAW_HEADER_SIZE +
bodyBytes.length +
SIGNATURE_LEN +
HASH_LEN;
ByteBuffer bb = ByteBuffer.allocate(recordSize).order(ByteOrder.BIG_ENDIAN);
bb.putInt(recordSize);
bb.putInt(recordNumber);
bb.putLong(timestamp);
bb.putShort(line);
bb.putInt(lineNumber);
bb.put(bodyBytes);
bb.put(this.signature64);
bb.put(this.hash32);
this.fullBytes = bb.array();
}
// ========================================================================
// Геттеры
// ========================================================================
public byte[] getSignature64() { return signature64 == null ? null : Arrays.copyOf(signature64, SIGNATURE_LEN); }
public byte[] getHash32() { return hash32 == null ? null : Arrays.copyOf(hash32, HASH_LEN); }
public byte[] getRawBytesWithSignatureAndHash() {
return rawBytesWithSignatureAndHash == null ? null : Arrays.copyOf(rawBytesWithSignatureAndHash, rawBytesWithSignatureAndHash.length);
public byte[] getRawBytes() {
int rawLen = recordSize - SIGNATURE_LEN - HASH_LEN;
byte[] raw = new byte[rawLen];
System.arraycopy(fullBytes, 0, raw, 0, rawLen);
return raw;
}
// ========================================================================
// Отладка
// ========================================================================
@Override
public String toString() {
return String.format(
"BchBlock[num=%d, time=%d, raw=%d, full=%s]",
recordNumber, timestamp,
rawBytes.length,
rawBytesWithSignatureAndHash == null ? "null" : String.valueOf(rawBytesWithSignatureAndHash.length)
);
/* ===================================================================== */
public byte[] getSignature64() {
return Arrays.copyOf(signature64, SIGNATURE_LEN);
}
public byte[] getHash32() {
return Arrays.copyOf(hash32, HASH_LEN);
}
public byte[] toBytes() {
return Arrays.copyOf(fullBytes, fullBytes.length);
}
}

View File

@ -1,110 +0,0 @@
package blockchain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import utils.blockchain.BchInfoEntry;
import utils.crypto.BchCryptoVerifier;
/**
* BchBlockValidator проверяет корректность блока:
* 1) последовательность номеров блоков в цепочке;
* 2) криптографическую целостность (подпись и хэш).
*.
* Не проверяет:
* - структуру и содержимое body;
* - поля HEADER и логин (этим занимаются другие проверки).
*/
public final class BchBlockValidator {
private static final Logger log = LoggerFactory.getLogger(BchBlockValidator.class);
private BchBlockValidator() {}
/**
* Проверяет, что блок может быть корректно добавлен к цепочке.
*
* @param block блок (распарсенный из байт)
* @param chain информация о цепочке (BchInfoEntry)
* @param chainId идентификатор цепочки
* @return true если порядок и криптография корректны, иначе false
*/
public static boolean validate(BchBlockEntry block, BchInfoEntry chain, long chainId) {
if (block == null || chain == null) {
log.warn("❌ Ошибка: блок или данные о цепочке не переданы");
return false;
}
// 1 Проверка последовательности номера
int expectedNumber = chain.lastBlockNumber + 1;
if (block.recordNumber < expectedNumber) {
log.warn("❌ Блок с номером {} уже существует (ожидался {})", block.recordNumber, expectedNumber);
return false;
}
if (block.recordNumber > expectedNumber) {
log.warn("❌ Нарушена последовательность: получен блок {}, ожидался {}", block.recordNumber, expectedNumber);
return false;
}
// // 1.5 Проверим, что body хотя бы содержит type/ver (первые 4 байта)
// try {
// short bodyType = block.getBodyType();
// short bodyVer = block.getBodyVer();
// // тут специально не валидируем смысл (это делает парсер/логика выше),
// // но оставим для диагностики
// log.debug("Body type/ver from bodyBytes: type={} ver={} (blockNum={}, chainId={})",
// bodyType, bodyVer, block.recordNumber, chainId);
// } catch (Exception e) {
// log.warn("❌ Некорректное тело блока: {}", e.getMessage());
// return false;
// }
// 2 Проверка публичного ключа
byte[] publicKey = chain.getPublicKey32();
if (publicKey == null || publicKey.length != 32) {
log.warn("В цепочке отсутствует корректный публичный ключ (chainId={})", chainId);
return false;
}
// 3 Получаем предыдущий хэш
byte[] prevHash32 = hexToBytes(chain.lastBlockHash);
// 4 Проверка подписи и хэша
try {
boolean ok = BchCryptoVerifier.verifyAll(
chain.userLogin,
chainId,
prevHash32,
block.rawBytes,
block.getSignature64(),
block.getHash32(),
publicKey
);
if (!ok) {
log.warn("❌ Криптографическая проверка не пройдена: хэш или подпись не совпадают (chainId={}, blockNum={})",
chainId, block.recordNumber);
return false;
}
log.info("✅ Блок {} успешно прошёл проверку подписи и хэша (chainId={})",
block.recordNumber, chainId);
return true;
} catch (Exception e) {
log.error("❌ Исключение при проверке блока (chainId={}): {}", chainId, e.getMessage());
return false;
}
}
// -------------------- HEX байты --------------------
private static byte[] hexToBytes(String hex) {
if (hex == null || hex.isEmpty()) return new byte[32]; // пустой хэш = 32 нуля
int len = hex.length();
byte[] out = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
out[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16);
}
return out;
}
}

View File

@ -1,4 +1,4 @@
package blockchain_new;
package blockchain;
import utils.crypto.Ed25519Util;
@ -8,11 +8,11 @@ import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Objects;
public final class BchCryptoVerifier_new {
public final class BchCryptoVerifier {
private static final byte[] DOMAIN = "SHiNE".getBytes(StandardCharsets.US_ASCII);
private BchCryptoVerifier_new() {}
private BchCryptoVerifier() {}
/**
* preimage =

View File

@ -1,106 +0,0 @@
//package blockchain;
//
//import blockchain.body.BodyRecord;
//import blockchain.body.HeaderBody;
//import blockchain.body.TextBody;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//
///**
// * ============================================================================
// * BodyRecordParser универсальный парсер тел (body) блоков .bch
// * ============================================================================
// *.
// * 🧩 Назначение:
// * Преобразует пару (recordType, recordTypeVersion, bodyBytes)
// * в конкретный объект, реализующий интерфейс {@link BodyRecord}.
// *.
// * 🔹 Особенность:
// * Используется объединённый 4-байтовый код:
// *.
// * fullCode = (recordType << 16) | (recordTypeVersion & 0xFFFF)
// *.
// * Это позволяет различать версии одного типа блока,
// * например: TextBody v1, TextBody v2 и т.д.
// *.
// * 🔹 Пример:
// * BodyRecord body = BodyRecordParser.parse(block.recordType, block.recordTypeVersion, block.body);
// *.
// * ============================================================================
// */
//public final class BodyRecordParser {
//
// private static final Logger log = LoggerFactory.getLogger(BodyRecordParser.class);
//
// private BodyRecordParser() {}
//
// /**
// * Распарсить тело блока по типу и версии записи.
// *
// * @param recordType код типа записи (0 = Header, 1 = Text, ...)
// * @param recordTypeVersion версия формата записи
// * @param body массив байт тела записи
// * @return объект, реализующий BodyRecord
// */
// public static BodyRecord parse(short recordType, short recordTypeVersion, byte[] body) {
// if (body == null)
// throw new IllegalArgumentException("body == null");
//
// // Объединяем тип и версию в 4-байтовый ключ
// int fullCode = ((recordType & 0xFFFF) << 16) | (recordTypeVersion & 0xFFFF);
//
// switch (fullCode) {
//
// // ---------------------------------------------------------
// // TYPE 0, VERSION 1 HeaderBody v1
// // ---------------------------------------------------------
// // Заголовок цепочки пользователя (первый блок).
// //
// // Формат body (без общих 20 байт заголовка блока):
// // [8] ASCII tag = "SHiNE001"
// // [8] blockchainId (long, BE)
// // [4] blockchainType (int, BE)
// // [4] blockchainNumber (int, BE)
// // [1] userLoginLength = N (unsigned byte)
// // [N] userLogin (UTF-8)
// // [2] versionUserBch (short, BE)
// // [8] prevUserBchId (long, BE)
// // [32] publicKey32
// //
// // Назначение:
// // Создаёт новую пользовательскую цепочку (блок 0).
// case (0x0000_0001):
// return new HeaderBody(body);
//
// // ---------------------------------------------------------
// // TYPE 1, VERSION 1 TextBody v1
// // ---------------------------------------------------------
// // Простое текстовое сообщение UTF-8.
// //
// // Формат body (без общих 20 байт заголовка блока):
// // [N] message (UTF-8)
// //
// // Назначение:
// // Текстовые и системные сообщения, описания, комментарии.
// case (0x0001_0001):
// return new TextBody(body);
//
// // ---------------------------------------------------------
// // РЕЗЕРВ будущие типы и версии
// // ---------------------------------------------------------
// // Пример: (0x0001_0002) TextBody v2 (например, JSON-структура)
// // (0x0002_0001) FileBody v1
// //
// // case (0x0001_0002):
// // return new TextBodyV2(body);
// //
// // case (0x0002_0001):
// // return new FileBody(body);
//
// default:
// throw new IllegalArgumentException(String.format(
// "Неизвестный тип блока: type=%d version=%d (fullCode=0x%08X)",
// recordType, recordTypeVersion, fullCode));
// }
// }
//}

View File

@ -1,151 +0,0 @@
package blockchain_new;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Objects;
/**
* BchBlockEntry_new универсальный блок нового формата.
*
* RAW (BigEndian):
* [4] recordSize (int) = RAW + signature + hash
* [4] recordNumber (int) глобальный номер блока
* [8] timestamp (long) unix seconds
* [2] line (short)
* [4] lineNumber (int)
* [N] bodyBytes (body, начинается с [type][version])
*
* TAIL:
* [64] signature64 (Ed25519)
* [32] hash32 (SHA-256)
*/
public final class BchBlockEntry_new {
public static final int SIGNATURE_LEN = 64;
public static final int HASH_LEN = 32;
/** Размер фиксированного RAW-заголовка без body */
public static final int RAW_HEADER_SIZE = 4 + 4 + 8 + 2 + 4;
// --- RAW ---
public final int recordSize;
public final int recordNumber;
public final long timestamp;
public final short line;
public final int lineNumber;
public final byte[] bodyBytes;
// --- TAIL ---
private final byte[] signature64;
private final byte[] hash32;
// --- cached ---
private final byte[] fullBytes;
/* ===================================================================== */
/* ====================== Конструктор из байт ========================== */
/* ===================================================================== */
public BchBlockEntry_new(byte[] fullBytes) {
Objects.requireNonNull(fullBytes, "fullBytes == null");
if (fullBytes.length < RAW_HEADER_SIZE + SIGNATURE_LEN + HASH_LEN)
throw new IllegalArgumentException("Block too short");
ByteBuffer bb = ByteBuffer.wrap(fullBytes).order(ByteOrder.BIG_ENDIAN);
this.recordSize = bb.getInt();
if (recordSize != fullBytes.length)
throw new IllegalArgumentException("recordSize mismatch");
this.recordNumber = bb.getInt();
this.timestamp = bb.getLong();
this.line = bb.getShort();
this.lineNumber = bb.getInt();
int bodyLen = recordSize - RAW_HEADER_SIZE - SIGNATURE_LEN - HASH_LEN;
if (bodyLen <= 0)
throw new IllegalArgumentException("Invalid body length");
this.bodyBytes = new byte[bodyLen];
bb.get(this.bodyBytes);
this.signature64 = new byte[SIGNATURE_LEN];
bb.get(this.signature64);
this.hash32 = new byte[HASH_LEN];
bb.get(this.hash32);
this.fullBytes = Arrays.copyOf(fullBytes, fullBytes.length);
}
/* ===================================================================== */
/* ====================== Конструктор сборки ============================ */
/* ===================================================================== */
public BchBlockEntry_new(int recordNumber,
long timestamp,
short line,
int lineNumber,
byte[] bodyBytes,
byte[] signature64,
byte[] hash32) {
Objects.requireNonNull(bodyBytes, "bodyBytes == null");
Objects.requireNonNull(signature64, "signature64 == null");
Objects.requireNonNull(hash32, "hash32 == null");
if (signature64.length != SIGNATURE_LEN)
throw new IllegalArgumentException("signature64 != 64");
if (hash32.length != HASH_LEN)
throw new IllegalArgumentException("hash32 != 32");
this.recordNumber = recordNumber;
this.timestamp = timestamp;
this.line = line;
this.lineNumber = lineNumber;
this.bodyBytes = Arrays.copyOf(bodyBytes, bodyBytes.length);
this.signature64 = Arrays.copyOf(signature64, SIGNATURE_LEN);
this.hash32 = Arrays.copyOf(hash32, HASH_LEN);
this.recordSize =
RAW_HEADER_SIZE +
bodyBytes.length +
SIGNATURE_LEN +
HASH_LEN;
ByteBuffer bb = ByteBuffer.allocate(recordSize).order(ByteOrder.BIG_ENDIAN);
bb.putInt(recordSize);
bb.putInt(recordNumber);
bb.putLong(timestamp);
bb.putShort(line);
bb.putInt(lineNumber);
bb.put(bodyBytes);
bb.put(this.signature64);
bb.put(this.hash32);
this.fullBytes = bb.array();
}
public byte[] getRawBytes() {
int rawLen = recordSize - SIGNATURE_LEN - HASH_LEN;
byte[] raw = new byte[rawLen];
System.arraycopy(fullBytes, 0, raw, 0, rawLen);
return raw;
}
/* ===================================================================== */
public byte[] getSignature64() {
return Arrays.copyOf(signature64, SIGNATURE_LEN);
}
public byte[] getHash32() {
return Arrays.copyOf(hash32, HASH_LEN);
}
public byte[] toBytes() {
return Arrays.copyOf(fullBytes, fullBytes.length);
}
}

View File

@ -1,7 +1,7 @@
package server.logic.ws_protocol.JSON.handlers.blockchain;
import blockchain_new.BchBlockEntry_new;
import blockchain_new.BchCryptoVerifier_new;
import blockchain.BchBlockEntry;
import blockchain.BchCryptoVerifier;
import server.logic.ws_protocol.WireCodes;
import shine.db.SqliteDbController;
import shine.db.dao.BlockchainStateDAO;
@ -102,9 +102,9 @@ public final class BlockchainStateService_new {
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_block_base64", 0, "");
}
final BchBlockEntry_new block;
final BchBlockEntry block;
try {
block = new BchBlockEntry_new(blockBytes);
block = new BchBlockEntry(blockBytes);
} catch (Exception e) {
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_block_format", 0, "");
}
@ -158,7 +158,7 @@ public final class BlockchainStateService_new {
byte[] prevLineHash32 = prevGlobalHash32; // пока линии не используем
// 5) крипто-проверка
boolean ok = BchCryptoVerifier_new.verifyAll(
boolean ok = BchCryptoVerifier.verifyAll(
login,
prevGlobalHash32,
prevLineHash32,

View File

@ -35,9 +35,7 @@ public final class Net_AddBlock_new_Handler implements JsonMessageHandler {
}
// Даже при ошибке (например bad_global_sequence) можно вернуть что сервер считает последним
if (r.serverLastGlobalNumber != null) {
resp.setServerLastGlobalNumber(r.serverLastGlobalNumber);
}
resp.setServerLastGlobalNumber(r.serverLastGlobalNumber);
if (r.serverLastGlobalHash != null) {
resp.setServerLastGlobalHash(r.serverLastGlobalHash);
}