Дорабатываю добавление блоков! Вроде всё.  (но ещё не проверял и тестов нету)
This commit is contained in:
AidarKC 2025-12-24 11:05:25 +03:00
parent bba4b7fb41
commit 33635886e0

View File

@ -1,75 +1,205 @@
# 📦 Модуль `blockchain` формат добавления блоков и из чего реально состоит блок, плюс как именно считаются хэши и подписи.
Модуль отвечает за хранение, чтение, проверку и создание бинарных блоков формата `.bch` — базовых элементов цепочек в системе SHiNE Blockchain. 1) Формат добавления блоков в файл .bch
Каждый блок содержит заголовок, тело (body), подпись Ed25519 и хэш SHA-256.
--- Файл — это просто конкатенация блоков один за другим, без разделителей:
## 🔹 `BchBlockEntry` BLOCK_0 | BLOCK_1 | BLOCK_2 | ...
Главный класс блока.
**Публичные методы:**
- `BchBlockEntry(int num, long time, short type, short ver, byte[] body)` — создаёт новый блок без подписи.
- `BchBlockEntry(byte[] full)` — распаковывает блок из байтов (`RAW + SIG + HASH`).
- `addSignatureAndHash(byte[] sig, byte[] hash)` — добавляет подпись и хэш.
- `getBodyAsText()` — возвращает тело как строку.
- `getSignature64()`, `getHash32()`, `getRawBytesWithSignatureAndHash()` — доступ к данным блока.
- `toString()` — краткое описание блока.
--- Чтобы читать файл, ты идёшь по нему последовательно:
## 🔹 `BchBlockValidator` // сделан на будущее, для сетевых запросов не используется Читаешь первые 4 байта = recordSize
Проверяет, можно ли добавить блок в цепочку.
**Публичный метод:** Понимаешь, сколько всего байт занимает блок:
- `validate(BchBlockEntry block, BchInfoEntry chain, long chainId)` — сверяет номер блока, подпись и хэш.
--- fullBlockLen = recordSize + 64 + 32
## 🔹 `BodyRecordParser`
Определяет, какой тип тела (`HeaderBody`, `TextBody` и т.п.) нужно создать.
**Публичный метод:** Считываешь оставшиеся байты блока и парсишь:
- `parse(short type, short version, byte[] body)` — возвращает объект, реализующий `BodyRecord`.
--- RAW часть длиной recordSize
## 🔹 `BodyRecord` (интерфейс) затем signature64
Общее поведение для всех тел блоков.
**Методы:** затем hash32
- `check()` — проверить корректность данных.
- `toBytes()` — сериализация обратно в байты (по умолчанию не реализована).
--- Это удобно тем, что файл можно дописывать (APPEND) хоть по сети, хоть локально: блоки самодостаточные.
## 🔹 `HeaderBody` 2) Из чего состоит блок (формат блока)
Тело первого блока цепочки (тип 0). 2.1. RAW (участвует в preimage и верификации; подпись/хэш к нему «пришиты»)
Содержит логин, ID цепочки и публичный ключ пользователя.
**Публичные методы:** BigEndian, фиксированный заголовок + body:
- `HeaderBody(byte[] body)` — парсинг из байтов.
- `HeaderBody(long id, String login, …)` — создание нового заголовка.
- `check()` — валидация логина и ключа.
- `toBytes()` — сериализация в байты.
- `toString()` — краткое описание полей.
--- RAW:
## 🔹 `TextBody` [4] recordSize (int) — размер RAW включая эти 4 байта, но без signature+hash
Простое текстовое сообщение (тип 1).
**Публичные методы:** [4] recordNumber (int) — глобальный номер блока (цепочка)
- `TextBody(byte[] body)` — парсинг из байтов.
- `TextBody(String msg)` — создание нового текстового блока.
- `check()` — проверка, что текст не пуст.
- `toBytes()` — вернуть текст как UTF-8-массив.
- `toString()` — короткое текстовое описание.
--- [8] timestamp (long) — unix seconds
💡 Вся логика создания, подписи и проверки блоков построена вокруг этих классов. [2] line (short) — линия (у вас пока по сути одна)
`BchBlockEntry` — контейнер блока,
`HeaderBody` / `TextBody` — содержимое, [4] lineNumber (int) — номер в линии (у вас пока обычно равен global)
`BodyRecordParser` — выбор нужного типа,
`BchBlockValidator` — контроль целостности. [N] bodyBytes — тело, начинается с [2] type + [2] version, дальше payload
RAW_HEADER_SIZE = 4 + 4 + 8 + 2 + 4 = 22 байта
2.2. TAIL (не входит в recordSize)
[64] signature64 — Ed25519 подпись
[32] hash32 — SHA-256 хэш
Итого полный блок:
FULL = RAW(recordSize bytes) + signature64 + hash32
3) Формат body (тела блока)
У тебя правило железное: каждый body начинается одинаково:
[2] type
[2] version
дальше payload по типу/версии
Парсер делает ключ:
key = (type<<16) | version
3.1 HeaderBody (type=0, ver=1)
Полный bodyBytes:
[2] type=0
[2] ver=1
[8] tag "SHiNE001" (ASCII)
[1] loginLength (uint8)
[N] login UTF-8
Это «генезис-смысл» для идентичности: в теле блока явно хранится логин.
3.2 TextBody (type=1, ver=1)
[2] type=1
[2] ver=1
[N] message UTF-8
4) Как считается хэш (hash32)
Важно: hash32 в блоке — это не hash(rawBytes).
Ты считаешь:
4.1 Preimage
preimage =
"SHiNE" +
[1] loginLen + loginBytes(UTF-8) +
prevGlobalHash32 +
prevLineHash32 +
rawBytes
Где:
"SHiNE" — доменная метка (защита от «подпиши этим же ключом что-то другое»)
loginLen + loginBytes — привязка блока к пользователю/цепочке
prevGlobalHash32 — сцепление по глобальной цепи
prevLineHash32 — сцепление по линии
rawBytes — содержимое текущего блока без подписи/хэша
4.2 Сам хэш
hash32 = SHA-256(preimage)
И именно этот hash32 должен лежать в конце блока.
5) Как считается подпись (signature64)
Подписывается не rawBytes, а hash32:
signature64 = Ed25519.sign(hash32, privateKey)
Проверка:
Пересобираем preimage
Считаем hash32 = sha256(preimage)
Сверяем hash32 с тем, что лежит в блоке
Проверяем подпись:
Ed25519.verify(hash32, signature64, publicKey32)
Это хорошая схема: подпись всегда фиксированного размера, а хэш — единая точка правды.
6) Что является «цепочкой» и чем блоки сцеплены
Сцепление идёт через prevGlobalHash32 и prevLineHash32, которые ты передаёшь в verifyAll(...).
То есть логика добавления нового блока (в терминах данных) такая:
берём последний блок
достаём его hash32
это и будет prevGlobalHash32 для нового блока
prevLineHash32 — по той линии, куда добавляем (у вас пока это то же самое, потому что одна линия/нет разветвления)
У тебя даже отдельно отмечено: пока нет отдельных линий, lastLineHash == lastGlobalHash — значит сейчас обе цепочки фактически совпадают.
7) Мини-схема “как добавить новый блок” (пошагово)
Собрать BodyRecord → получить bodyBytes = body.toBytes()
Собрать RAW:
recordNumber = lastRecordNumber + 1
timestamp = nowSeconds
line (скорее всего 0 или 1 — как вы договорились)
lineNumber = lastLineNumber + 1 (сейчас равно global)
recordSize = RAW_HEADER_SIZE + bodyBytes.length
rawBytes = new BchBlockEntry(...).getRawBytes() (или собрать вручную)
preimage = buildPreimage(login, prevGlobalHash32, prevLineHash32, rawBytes)
hash32 = sha256(preimage)
signature64 = Ed25519.sign(hash32, privateKey)
Собрать финальный BchBlockEntry(..., signature64, hash32)
Дописать toBytes() в файл .bch
8) Важные нюансы (чтобы не словить «тихий баг»)
recordSize включает себя (первые 4 байта) — это ок, просто помнить.
bodyLen = recordSize - RAW_HEADER_SIZE — значит RAW_HEADER_SIZE обязан быть всегда синхронен с реальным RAW.
login в preimage ограничен 255 байт (у тебя [1] loginLen) — это правильно и предсказуемо.
В HeaderBody tag фиксирован "SHiNE001" — фактически версия протокола body-headerа.