23 01 25
Сделал ещё более два поля в общем формате блоков блокчейна (перед самим блоком данных) и перед его цп (все тесты проходят)
This commit is contained in:
parent
9f1ca37977
commit
580695b486
@ -9,36 +9,65 @@ import java.util.Arrays;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BchBlockEntry — универсальный блок нового формата.
|
* BchBlockEntry — универсальный блок формата SHiNE (Frame v0).
|
||||||
*
|
*
|
||||||
* RAW (BigEndian) = preimage:
|
* =========================================================================
|
||||||
* [32] prevHash32 (SHA-256) hash предыдущего блока (цепочка)
|
* FRAME v0 — ФИКСИРОВАННЫЙ ФОРМАТ БЛОКА (ДОКУМЕНТ ПРОТОКОЛА)
|
||||||
* [4] blockSize (int) = размер preimage (в байтах), БЕЗ signature64
|
* =========================================================================
|
||||||
* [4] blockNumber (int) глобальный номер блока (>=0)
|
|
||||||
* [8] timestamp (long) unix seconds
|
|
||||||
*
|
*
|
||||||
* [2] type (short) тип сообщения
|
* Все числа BigEndian.
|
||||||
* [2] subType (short) подтип сообщения
|
|
||||||
* [2] version (short) версия формата сообщения
|
|
||||||
*
|
*
|
||||||
|
* PREIMAGE (входит в blockSize, подписывается):
|
||||||
|
* [2] frameCode (uint16) код/версия рамки:
|
||||||
|
* - 0x0000 = Frame v0 (текущий)
|
||||||
|
* [32] prevHash32 (bytes) SHA-256(preimage) предыдущего блока (цепочка)
|
||||||
|
* [4] blockSize (int32) размер preimage (в байтах), ВКЛЮЧАЯ frameCode,
|
||||||
|
* НО БЕЗ sigMarker и БЕЗ signature64
|
||||||
|
* [4] blockNumber (int32) глобальный номер блока (>=0)
|
||||||
|
* [8] timestamp (int64) unix seconds
|
||||||
|
* [2] type (uint16) тип сообщения
|
||||||
|
* [2] subType (uint16) подтип сообщения
|
||||||
|
* [2] version (uint16) версия формата сообщения
|
||||||
* [N] bodyBytes (bytes) тело сообщения (БЕЗ type/subType/version)
|
* [N] bodyBytes (bytes) тело сообщения (БЕЗ type/subType/version)
|
||||||
*
|
*
|
||||||
* TAIL (НЕ входит в blockSize):
|
* TAIL (НЕ входит в blockSize, НЕ подписывается в Frame v0):
|
||||||
* [64] signature64 (Ed25519) подпись над hash32
|
* [2] sigMarker (uint16) маркер подписи:
|
||||||
|
* - 0x0100 (256) = далее подпись Ed25519 64 байта
|
||||||
|
* [64] signature64 (bytes) Ed25519 signature над hash32
|
||||||
*
|
*
|
||||||
* hash32 ВНУТРИ БЛОКА НЕ ХРАНИМ.
|
* hash32 НЕ хранится в блоке.
|
||||||
* hash32 вычисляется при парсинге:
|
* hash32 вычисляется при парсинге:
|
||||||
* preimage = первые blockSize байт
|
* preimage = первые blockSize байт
|
||||||
* hash32 = SHA-256(preimage)
|
* hash32 = SHA-256(preimage)
|
||||||
|
*
|
||||||
|
* Правила MVP-парсера (Frame v0):
|
||||||
|
* - frameCode должен быть строго 0x0000, иначе REJECT.
|
||||||
|
* - sigMarker должен быть строго 0x0100, иначе REJECT.
|
||||||
|
* - подпись обязана присутствовать всегда (sigMarker+signature64).
|
||||||
|
* - НИКАКИХ fallback-веток “если маркер другой, то подписи нет/другой хвост”.
|
||||||
|
*
|
||||||
|
* Важно по безопасности:
|
||||||
|
* - sigMarker в v0 не входит в подписываемые байты → его можно подменить,
|
||||||
|
* поэтому единственная безопасная логика: "если не 0x0100 — reject".
|
||||||
|
* =========================================================================
|
||||||
*/
|
*/
|
||||||
public final class BchBlockEntry {
|
public final class BchBlockEntry {
|
||||||
|
|
||||||
public static final int SIGNATURE_LEN = 64;
|
public static final int SIGNATURE_LEN = 64;
|
||||||
public static final int HASH_LEN = 32;
|
public static final int HASH_LEN = 32;
|
||||||
|
|
||||||
|
public static final int FRAME_CODE_LEN = 2;
|
||||||
|
public static final int SIG_MARKER_LEN = 2;
|
||||||
|
|
||||||
|
/** Frame v0 */
|
||||||
|
public static final int FRAME_CODE_V0 = 0x0000;
|
||||||
|
|
||||||
|
/** sigMarker: 256 = 0x0100 */
|
||||||
|
public static final int SIG_MARKER_ED25519 = 0x0100;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Максимальный допустимый размер блока (preimage+signature), чтобы не уложить сервер по памяти/диску.
|
* Максимальный допустимый размер блока (fullBytes = preimage + sigMarker + signature),
|
||||||
* 4 МБ — нормальный “потолок” под тексты/метаданные, и при этом защищает от мусора/атаки.
|
* чтобы не уложить сервер по памяти/диску.
|
||||||
*/
|
*/
|
||||||
public static final int MAX_BLOCK_FULL_BYTES = 4 * 1024 * 1024;
|
public static final int MAX_BLOCK_FULL_BYTES = 4 * 1024 * 1024;
|
||||||
|
|
||||||
@ -48,9 +77,16 @@ public final class BchBlockEntry {
|
|||||||
*/
|
*/
|
||||||
public static final long MAX_FUTURE_SECONDS = 60;
|
public static final long MAX_FUTURE_SECONDS = 60;
|
||||||
|
|
||||||
/** Размер фиксированного RAW-заголовка без body */
|
/**
|
||||||
public static final int RAW_HEADER_SIZE =
|
* Размер фиксированной части PREIMAGE (без bodyBytes).
|
||||||
32 // prevHash32
|
*
|
||||||
|
* PREIMAGE header:
|
||||||
|
* frameCode(2) + prevHash32(32) + blockSize(4) + blockNumber(4) + timestamp(8)
|
||||||
|
* + type(2) + subType(2) + version(2)
|
||||||
|
*/
|
||||||
|
public static final int PREIMAGE_HEADER_SIZE =
|
||||||
|
2 // frameCode
|
||||||
|
+ 32 // prevHash32
|
||||||
+ 4 // blockSize
|
+ 4 // blockSize
|
||||||
+ 4 // blockNumber
|
+ 4 // blockNumber
|
||||||
+ 8 // timestamp
|
+ 8 // timestamp
|
||||||
@ -58,28 +94,34 @@ public final class BchBlockEntry {
|
|||||||
+ 2 // subType
|
+ 2 // subType
|
||||||
+ 2; // version
|
+ 2; // version
|
||||||
|
|
||||||
// --- HEADER (RAW) ---
|
/** Минимальный полный размер блока (без bodyBytes). */
|
||||||
|
public static final int MIN_FULL_BYTES =
|
||||||
|
PREIMAGE_HEADER_SIZE + SIG_MARKER_LEN + SIGNATURE_LEN;
|
||||||
|
|
||||||
|
// --- HEADER (PREIMAGE) ---
|
||||||
|
public final int frameCode; // uint16 (v0=0)
|
||||||
public final byte[] prevHash32; // 32
|
public final byte[] prevHash32; // 32
|
||||||
public final int blockSize; // preimage size
|
public final int blockSize; // preimage size (включая frameCode)
|
||||||
public final int blockNumber; // >=0
|
public final int blockNumber; // >=0
|
||||||
public final long timestamp;
|
public final long timestamp;
|
||||||
public final short type;
|
public final short type;
|
||||||
public final short subType;
|
public final short subType;
|
||||||
public final short version;
|
public final short version;
|
||||||
|
|
||||||
// --- BODY (RAW) ---
|
// --- BODY (PREIMAGE) ---
|
||||||
public final byte[] bodyBytes;
|
public final byte[] bodyBytes;
|
||||||
|
|
||||||
/** Распарсенное тело (создаётся сразу при парсинге блока). */
|
/** Распарсенное тело (создаётся сразу при парсинге блока). */
|
||||||
public final BodyRecord body;
|
public final BodyRecord body;
|
||||||
|
|
||||||
// --- TAIL ---
|
// --- TAIL ---
|
||||||
|
public final int sigMarker; // uint16 (v0: 0x0100)
|
||||||
private final byte[] signature64; // 64
|
private final byte[] signature64; // 64
|
||||||
|
|
||||||
// --- derived ---
|
// --- derived ---
|
||||||
private final byte[] hash32; // 32, computed
|
private final byte[] hash32; // 32, computed
|
||||||
private final byte[] preimage; // blockSize bytes
|
private final byte[] preimage; // blockSize bytes
|
||||||
private final byte[] fullBytes; // preimage + signature
|
private final byte[] fullBytes; // preimage + sigMarker + signature
|
||||||
|
|
||||||
/* ===================================================================== */
|
/* ===================================================================== */
|
||||||
/* ====================== Конструктор из байт ========================== */
|
/* ====================== Конструктор из байт ========================== */
|
||||||
@ -88,8 +130,8 @@ public final class BchBlockEntry {
|
|||||||
public BchBlockEntry(byte[] fullBytes) {
|
public BchBlockEntry(byte[] fullBytes) {
|
||||||
Objects.requireNonNull(fullBytes, "fullBytes == null");
|
Objects.requireNonNull(fullBytes, "fullBytes == null");
|
||||||
|
|
||||||
if (fullBytes.length < RAW_HEADER_SIZE + SIGNATURE_LEN) {
|
if (fullBytes.length < MIN_FULL_BYTES) {
|
||||||
throw new IllegalArgumentException("Block too short");
|
throw new IllegalArgumentException("Block too short: " + fullBytes.length + " < " + MIN_FULL_BYTES);
|
||||||
}
|
}
|
||||||
if (fullBytes.length > MAX_BLOCK_FULL_BYTES) {
|
if (fullBytes.length > MAX_BLOCK_FULL_BYTES) {
|
||||||
throw new IllegalArgumentException("Block too large: " + fullBytes.length + " > " + MAX_BLOCK_FULL_BYTES);
|
throw new IllegalArgumentException("Block too large: " + fullBytes.length + " > " + MAX_BLOCK_FULL_BYTES);
|
||||||
@ -97,47 +139,77 @@ public final class BchBlockEntry {
|
|||||||
|
|
||||||
ByteBuffer bb = ByteBuffer.wrap(fullBytes).order(ByteOrder.BIG_ENDIAN);
|
ByteBuffer bb = ByteBuffer.wrap(fullBytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
// [2] frameCode
|
||||||
|
this.frameCode = Short.toUnsignedInt(bb.getShort());
|
||||||
|
if (this.frameCode != FRAME_CODE_V0) {
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
"Bad frameCode: 0x%04X (expected 0x%04X)", this.frameCode, FRAME_CODE_V0
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// [32] prevHash32
|
||||||
this.prevHash32 = new byte[32];
|
this.prevHash32 = new byte[32];
|
||||||
bb.get(this.prevHash32);
|
bb.get(this.prevHash32);
|
||||||
|
|
||||||
|
// [4] blockSize
|
||||||
this.blockSize = bb.getInt();
|
this.blockSize = bb.getInt();
|
||||||
if (blockSize < RAW_HEADER_SIZE) {
|
if (blockSize < PREIMAGE_HEADER_SIZE) {
|
||||||
throw new IllegalArgumentException("blockSize too small: " + blockSize);
|
throw new IllegalArgumentException("blockSize too small: " + blockSize + " < " + PREIMAGE_HEADER_SIZE);
|
||||||
}
|
|
||||||
if (blockSize + SIGNATURE_LEN != fullBytes.length) {
|
|
||||||
throw new IllegalArgumentException("blockSize mismatch: blockSize=" + blockSize + " fullLen=" + fullBytes.length);
|
|
||||||
}
|
|
||||||
if (blockSize + SIGNATURE_LEN > MAX_BLOCK_FULL_BYTES) {
|
|
||||||
throw new IllegalArgumentException("Block too large by blockSize: " + (blockSize + SIGNATURE_LEN) + " > " + MAX_BLOCK_FULL_BYTES);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fullLen must match exactly: blockSize + sigMarker(2) + signature(64)
|
||||||
|
int expectedFullLen = blockSize + SIG_MARKER_LEN + SIGNATURE_LEN;
|
||||||
|
if (expectedFullLen != fullBytes.length) {
|
||||||
|
throw new IllegalArgumentException("blockSize mismatch: blockSize=" + blockSize
|
||||||
|
+ " expectedFullLen=" + expectedFullLen
|
||||||
|
+ " fullLen=" + fullBytes.length);
|
||||||
|
}
|
||||||
|
if (expectedFullLen > MAX_BLOCK_FULL_BYTES) {
|
||||||
|
throw new IllegalArgumentException("Block too large by blockSize: " + expectedFullLen + " > " + MAX_BLOCK_FULL_BYTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
// [4] blockNumber
|
||||||
this.blockNumber = bb.getInt();
|
this.blockNumber = bb.getInt();
|
||||||
if (this.blockNumber < 0) {
|
if (this.blockNumber < 0) {
|
||||||
throw new IllegalArgumentException("blockNumber < 0: " + this.blockNumber);
|
throw new IllegalArgumentException("blockNumber < 0: " + this.blockNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [8] timestamp
|
||||||
this.timestamp = bb.getLong();
|
this.timestamp = bb.getLong();
|
||||||
|
|
||||||
// запрет “в будущее” больше чем на 1 минуту
|
// запрет “в будущее” больше чем на 1 минуту
|
||||||
long now = Instant.now().getEpochSecond();
|
long now = Instant.now().getEpochSecond();
|
||||||
if (this.timestamp > now + MAX_FUTURE_SECONDS) {
|
if (this.timestamp > now + MAX_FUTURE_SECONDS) {
|
||||||
throw new IllegalArgumentException("timestamp is too far in future: ts=" + this.timestamp + " now=" + now + " maxFutureSec=" + MAX_FUTURE_SECONDS);
|
throw new IllegalArgumentException("timestamp is too far in future: ts=" + this.timestamp
|
||||||
|
+ " now=" + now + " maxFutureSec=" + MAX_FUTURE_SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [2][2][2] type/subType/version
|
||||||
this.type = bb.getShort();
|
this.type = bb.getShort();
|
||||||
this.subType = bb.getShort();
|
this.subType = bb.getShort();
|
||||||
this.version = bb.getShort();
|
this.version = bb.getShort();
|
||||||
|
|
||||||
int bodyLen = blockSize - RAW_HEADER_SIZE;
|
// [N] bodyBytes
|
||||||
if (bodyLen < 0) throw new IllegalArgumentException("Invalid body length: " + bodyLen);
|
int bodyLen = blockSize - PREIMAGE_HEADER_SIZE;
|
||||||
|
if (bodyLen < 0) {
|
||||||
|
throw new IllegalArgumentException("Invalid body length: " + bodyLen);
|
||||||
|
}
|
||||||
this.bodyBytes = new byte[bodyLen];
|
this.bodyBytes = new byte[bodyLen];
|
||||||
bb.get(this.bodyBytes);
|
bb.get(this.bodyBytes);
|
||||||
|
|
||||||
|
// TAIL: [2] sigMarker
|
||||||
|
this.sigMarker = Short.toUnsignedInt(bb.getShort());
|
||||||
|
if (this.sigMarker != SIG_MARKER_ED25519) {
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
"Bad sigMarker: 0x%04X (expected 0x%04X)", this.sigMarker, SIG_MARKER_ED25519
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TAIL: [64] signature64
|
||||||
this.signature64 = new byte[SIGNATURE_LEN];
|
this.signature64 = new byte[SIGNATURE_LEN];
|
||||||
bb.get(this.signature64);
|
bb.get(this.signature64);
|
||||||
|
|
||||||
// preimage = первые blockSize байт
|
// preimage = первые blockSize байт (включая frameCode)
|
||||||
this.preimage = Arrays.copyOfRange(fullBytes, 0, blockSize);
|
this.preimage = Arrays.copyOfRange(fullBytes, 0, blockSize);
|
||||||
|
|
||||||
// hash32 = sha256(preimage)
|
// hash32 = sha256(preimage)
|
||||||
@ -148,7 +220,6 @@ public final class BchBlockEntry {
|
|||||||
|
|
||||||
this.fullBytes = Arrays.copyOf(fullBytes, fullBytes.length);
|
this.fullBytes = Arrays.copyOf(fullBytes, fullBytes.length);
|
||||||
|
|
||||||
// запрет мусора
|
|
||||||
if (bb.remaining() != 0) {
|
if (bb.remaining() != 0) {
|
||||||
throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining());
|
throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining());
|
||||||
}
|
}
|
||||||
@ -181,9 +252,11 @@ public final class BchBlockEntry {
|
|||||||
// запрет “в будущее” больше чем на 1 минуту
|
// запрет “в будущее” больше чем на 1 минуту
|
||||||
long now = Instant.now().getEpochSecond();
|
long now = Instant.now().getEpochSecond();
|
||||||
if (timestamp > now + MAX_FUTURE_SECONDS) {
|
if (timestamp > now + MAX_FUTURE_SECONDS) {
|
||||||
throw new IllegalArgumentException("timestamp is too far in future: ts=" + timestamp + " now=" + now + " maxFutureSec=" + MAX_FUTURE_SECONDS);
|
throw new IllegalArgumentException("timestamp is too far in future: ts=" + timestamp
|
||||||
|
+ " now=" + now + " maxFutureSec=" + MAX_FUTURE_SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.frameCode = FRAME_CODE_V0;
|
||||||
this.prevHash32 = Arrays.copyOf(prevHash32, 32);
|
this.prevHash32 = Arrays.copyOf(prevHash32, 32);
|
||||||
this.blockNumber = blockNumber;
|
this.blockNumber = blockNumber;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
@ -191,11 +264,11 @@ public final class BchBlockEntry {
|
|||||||
this.subType = subType;
|
this.subType = subType;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.bodyBytes = Arrays.copyOf(bodyBytes, bodyBytes.length);
|
this.bodyBytes = Arrays.copyOf(bodyBytes, bodyBytes.length);
|
||||||
this.signature64 = Arrays.copyOf(signature64, SIGNATURE_LEN);
|
|
||||||
|
|
||||||
this.blockSize = RAW_HEADER_SIZE + this.bodyBytes.length;
|
// blockSize = размер preimage (включая frameCode)
|
||||||
|
this.blockSize = PREIMAGE_HEADER_SIZE + this.bodyBytes.length;
|
||||||
|
|
||||||
int fullLen = this.blockSize + SIGNATURE_LEN;
|
int fullLen = this.blockSize + SIG_MARKER_LEN + SIGNATURE_LEN;
|
||||||
if (fullLen > MAX_BLOCK_FULL_BYTES) {
|
if (fullLen > MAX_BLOCK_FULL_BYTES) {
|
||||||
throw new IllegalArgumentException("Block too large: " + fullLen + " > " + MAX_BLOCK_FULL_BYTES);
|
throw new IllegalArgumentException("Block too large: " + fullLen + " > " + MAX_BLOCK_FULL_BYTES);
|
||||||
}
|
}
|
||||||
@ -203,8 +276,13 @@ public final class BchBlockEntry {
|
|||||||
// parse body по header + ОБЯЗАТЕЛЬНЫЙ check()
|
// parse body по header + ОБЯЗАТЕЛЬНЫЙ check()
|
||||||
this.body = BodyRecordParser.parse(this.type, this.subType, this.version, this.bodyBytes);
|
this.body = BodyRecordParser.parse(this.type, this.subType, this.version, this.bodyBytes);
|
||||||
|
|
||||||
|
// tail marker фиксирован
|
||||||
|
this.sigMarker = SIG_MARKER_ED25519;
|
||||||
|
this.signature64 = Arrays.copyOf(signature64, SIGNATURE_LEN);
|
||||||
|
|
||||||
// build preimage
|
// build preimage
|
||||||
ByteBuffer pre = ByteBuffer.allocate(blockSize).order(ByteOrder.BIG_ENDIAN);
|
ByteBuffer pre = ByteBuffer.allocate(blockSize).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
pre.putShort((short) (FRAME_CODE_V0 & 0xFFFF));
|
||||||
pre.put(this.prevHash32);
|
pre.put(this.prevHash32);
|
||||||
pre.putInt(this.blockSize);
|
pre.putInt(this.blockSize);
|
||||||
pre.putInt(this.blockNumber);
|
pre.putInt(this.blockNumber);
|
||||||
@ -217,24 +295,33 @@ public final class BchBlockEntry {
|
|||||||
this.preimage = pre.array();
|
this.preimage = pre.array();
|
||||||
this.hash32 = BchCryptoVerifier.sha256(preimage);
|
this.hash32 = BchCryptoVerifier.sha256(preimage);
|
||||||
|
|
||||||
ByteBuffer full = ByteBuffer.allocate(blockSize + SIGNATURE_LEN).order(ByteOrder.BIG_ENDIAN);
|
// build fullBytes: preimage + sigMarker + signature64
|
||||||
|
ByteBuffer full = ByteBuffer.allocate(fullLen).order(ByteOrder.BIG_ENDIAN);
|
||||||
full.put(this.preimage);
|
full.put(this.preimage);
|
||||||
|
full.putShort((short) (SIG_MARKER_ED25519 & 0xFFFF));
|
||||||
full.put(this.signature64);
|
full.put(this.signature64);
|
||||||
this.fullBytes = full.array();
|
this.fullBytes = full.array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===================================================================== */
|
||||||
|
/* ============================ Getters ================================= */
|
||||||
|
/* ===================================================================== */
|
||||||
|
|
||||||
public byte[] getPreimageBytes() {
|
public byte[] getPreimageBytes() {
|
||||||
return Arrays.copyOf(preimage, preimage.length);
|
return Arrays.copyOf(preimage, preimage.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Возвращает подпись Ed25519 (64 байта). */
|
||||||
public byte[] getSignature64() {
|
public byte[] getSignature64() {
|
||||||
return Arrays.copyOf(signature64, SIGNATURE_LEN);
|
return Arrays.copyOf(signature64, SIGNATURE_LEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Возвращает hash32 = SHA-256(preimage). */
|
||||||
public byte[] getHash32() {
|
public byte[] getHash32() {
|
||||||
return Arrays.copyOf(hash32, HASH_LEN);
|
return Arrays.copyOf(hash32, HASH_LEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Возвращает полный блок: preimage + sigMarker + signature. */
|
||||||
public byte[] toBytes() {
|
public byte[] toBytes() {
|
||||||
return Arrays.copyOf(fullBytes, fullBytes.length);
|
return Arrays.copyOf(fullBytes, fullBytes.length);
|
||||||
}
|
}
|
||||||
@ -249,7 +336,8 @@ public final class BchBlockEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return "BchBlockEntry{"
|
return "BchBlockEntry{"
|
||||||
+ "HDR{"
|
+ "FRAME{frameCode=0x" + hex4(frameCode)
|
||||||
|
+ "}, HDR{"
|
||||||
+ "blockSize=" + blockSize
|
+ "blockSize=" + blockSize
|
||||||
+ ", blockNumber=" + blockNumber
|
+ ", blockNumber=" + blockNumber
|
||||||
+ ", timestamp=" + timestamp + " (" + timeIso + ")"
|
+ ", timestamp=" + timestamp + " (" + timeIso + ")"
|
||||||
@ -259,19 +347,25 @@ public final class BchBlockEntry {
|
|||||||
+ ", prevHash32(hex)=" + toHex(prevHash32)
|
+ ", prevHash32(hex)=" + toHex(prevHash32)
|
||||||
+ "}"
|
+ "}"
|
||||||
+ ", BODY{len=" + (bodyBytes == null ? -1 : bodyBytes.length) + "}"
|
+ ", BODY{len=" + (bodyBytes == null ? -1 : bodyBytes.length) + "}"
|
||||||
+ ", TAIL{signature64(hex)=" + toHex(signature64) + "}"
|
+ ", TAIL{sigMarker=0x" + hex4(sigMarker) + ", signature64(hex)=" + toHex(signature64) + "}"
|
||||||
+ ", DERIVED{hash32(hex)=" + toHex(hash32) + "}"
|
+ ", DERIVED{hash32(hex)=" + toHex(hash32) + "}"
|
||||||
+ "}";
|
+ "}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String hex4(int v) {
|
||||||
|
String s = Integer.toHexString(v & 0xFFFF);
|
||||||
|
while (s.length() < 4) s = "0" + s;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
private static String toHex(byte[] bytes) {
|
private static String toHex(byte[] bytes) {
|
||||||
if (bytes == null) return "null";
|
if (bytes == null) return "null";
|
||||||
char[] HEX = "0123456789abcdef".toCharArray();
|
char[] HEX = "0123456789abcdef".toCharArray();
|
||||||
char[] out = new char[bytes.length * 2];
|
char[] out = new char[bytes.length * 2];
|
||||||
for (int i = 0; i < bytes.length; i++) {
|
for (int i = 0; i < bytes.length; i++) {
|
||||||
int v = bytes[i] & 0xFF;
|
int vv = bytes[i] & 0xFF;
|
||||||
out[i * 2] = HEX[v >>> 4];
|
out[i * 2] = HEX[vv >>> 4];
|
||||||
out[i * 2 + 1] = HEX[v & 0x0F];
|
out[i * 2 + 1] = HEX[vv & 0x0F];
|
||||||
}
|
}
|
||||||
return new String(out);
|
return new String(out);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,9 +6,11 @@ import java.security.MessageDigest;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Новый верификатор по ТЗ:
|
* Верификатор SHiNE (Frame v0):
|
||||||
|
*
|
||||||
|
* preimage = первые blockSize байт блока (ВКЛЮЧАЯ frameCode=0x0000),
|
||||||
|
* = всё до TAIL (sigMarker+signature).
|
||||||
*
|
*
|
||||||
* preimage = все байты блока без signature64
|
|
||||||
* hash32 = SHA-256(preimage)
|
* hash32 = SHA-256(preimage)
|
||||||
* verify = Ed25519.verify(hash32, signature64, pubKey32)
|
* verify = Ed25519.verify(hash32, signature64, pubKey32)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -15,8 +15,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AddBlockSender — под новый формат BchBlockEntry:
|
* AddBlockSender — под новый формат BchBlockEntry (Frame v0):
|
||||||
* - block хранит только preimage + signature
|
* - blockBytes = preimage + sigMarker(2) + signature64
|
||||||
|
* - preimage начинается с frameCode(2) = 0x0000
|
||||||
* - hash32 вычисляется как sha256(preimage)
|
* - hash32 вычисляется как sha256(preimage)
|
||||||
* - signature = Ed25519.sign(hash32)
|
* - signature = Ed25519.sign(hash32)
|
||||||
*
|
*
|
||||||
@ -74,7 +75,9 @@ public final class AddBlockSender {
|
|||||||
|
|
||||||
byte[] bodyBytes = body.toBytes();
|
byte[] bodyBytes = body.toBytes();
|
||||||
|
|
||||||
|
// ВАЖНО: preimage должен быть БАЙТ-В-БАЙТ таким же, как в BchBlockEntry
|
||||||
byte[] preimage = buildPreimage(prevHash32, blockNumber, tsSec, type, subType, version, bodyBytes);
|
byte[] preimage = buildPreimage(prevHash32, blockNumber, tsSec, type, subType, version, bodyBytes);
|
||||||
|
|
||||||
byte[] hash32 = blockchain.BchCryptoVerifier.sha256(preimage);
|
byte[] hash32 = blockchain.BchCryptoVerifier.sha256(preimage);
|
||||||
byte[] signature64 = utils.crypto.Ed25519Util.sign(hash32, loginPrivKey);
|
byte[] signature64 = utils.crypto.Ed25519Util.sign(hash32, loginPrivKey);
|
||||||
|
|
||||||
@ -191,7 +194,7 @@ public final class AddBlockSender {
|
|||||||
throw new IllegalArgumentException("Unknown body class: " + body.getClass());
|
throw new IllegalArgumentException("Unknown body class: " + body.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- preimage builder (строго по BchBlockEntry) ----------
|
// ---------- preimage builder (строго по BchBlockEntry Frame v0) ----------
|
||||||
|
|
||||||
private static byte[] buildPreimage(byte[] prevHash32,
|
private static byte[] buildPreimage(byte[] prevHash32,
|
||||||
int blockNumber,
|
int blockNumber,
|
||||||
@ -201,17 +204,36 @@ public final class AddBlockSender {
|
|||||||
short version,
|
short version,
|
||||||
byte[] bodyBytes) {
|
byte[] bodyBytes) {
|
||||||
|
|
||||||
int blockSize = BchBlockEntry.RAW_HEADER_SIZE + (bodyBytes == null ? 0 : bodyBytes.length);
|
if (prevHash32 == null || prevHash32.length != 32) {
|
||||||
|
throw new IllegalArgumentException("prevHash32 must be 32 bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
int bodyLen = (bodyBytes == null ? 0 : bodyBytes.length);
|
||||||
|
int blockSize = BchBlockEntry.PREIMAGE_HEADER_SIZE + bodyLen;
|
||||||
|
|
||||||
java.nio.ByteBuffer bb = java.nio.ByteBuffer.allocate(blockSize).order(java.nio.ByteOrder.BIG_ENDIAN);
|
java.nio.ByteBuffer bb = java.nio.ByteBuffer.allocate(blockSize).order(java.nio.ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
// [2] frameCode (v0)
|
||||||
|
bb.putShort((short) (BchBlockEntry.FRAME_CODE_V0 & 0xFFFF));
|
||||||
|
|
||||||
|
// [32] prevHash32
|
||||||
bb.put(prevHash32);
|
bb.put(prevHash32);
|
||||||
|
|
||||||
|
// [4] blockSize (preimage size)
|
||||||
bb.putInt(blockSize);
|
bb.putInt(blockSize);
|
||||||
|
|
||||||
|
// [4] blockNumber
|
||||||
bb.putInt(blockNumber);
|
bb.putInt(blockNumber);
|
||||||
|
|
||||||
|
// [8] timestamp
|
||||||
bb.putLong(tsSec);
|
bb.putLong(tsSec);
|
||||||
|
|
||||||
|
// [2][2][2] type/subType/version
|
||||||
bb.putShort(type);
|
bb.putShort(type);
|
||||||
bb.putShort(subType);
|
bb.putShort(subType);
|
||||||
bb.putShort(version);
|
bb.putShort(version);
|
||||||
|
|
||||||
|
// [N] bodyBytes
|
||||||
if (bodyBytes != null) bb.put(bodyBytes);
|
if (bodyBytes != null) bb.put(bodyBytes);
|
||||||
|
|
||||||
return bb.array();
|
return bb.array();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user