package blockchain; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; import java.util.Objects; /** * ============================================================================ * BchBlockEntry — запись блокчейна SHiNE (.bch) * ============================================================================ * * RAW (BigEndian): * [4] recordSize = RAW_HEADER_SIZE + body.length * [4] recordNumber * [8] timestamp UNIX time (секунды) * [M] bodyBytes (внутри body первые 4 байта = [type][ver]) * * FULL: * RAW + signature64 + hash32 * * ============================================================================ */ public class BchBlockEntry { // ---- Константы размеров ---- public static final int SIGNATURE_LEN = 64; public static final int HASH_LEN = 32; /** RAW заголовок теперь: size + number + timestamp */ public static final int RAW_HEADER_SIZE = 4 + 4 + 8; // 16 // ---- Поля 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) // ---- Поля подписи и хэша ---- private byte[] signature64; // [64] private byte[] hash32; // [32] // ---- Кэшированные представления ---- public final byte[] rawBytes; // RAW без подписи/хэша private byte[] rawBytesWithSignatureAndHash; // FULL (может быть null) // ======================================================================== // КОНСТРУКТОР №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; ByteBuffer buf = ByteBuffer .allocate(RAW_HEADER_SIZE + body.length) .order(ByteOrder.BIG_ENDIAN); buf.putInt(recordSize); buf.putInt(recordNumber); buf.putLong(timestamp); buf.put(body); this.rawBytes = buf.array(); } // ======================================================================== // КОНСТРУКТОР №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("Слишком мало данных для полного блока"); 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.signature64 = new byte[SIGNATURE_LEN]; tail.get(this.signature64); this.hash32 = new byte[HASH_LEN]; tail.get(this.hash32); this.rawBytesWithSignatureAndHash = Arrays.copyOf(rawWithSigAndHash, rs + SIGNATURE_LEN + HASH_LEN); } // ======================================================================== // Добавить подпись и хэш // ======================================================================== public BchBlockEntry addSignatureAndHash(byte[] signature64, byte[] hash32) { 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.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; } // ======================================================================== // Геттеры // ======================================================================== 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); } // ======================================================================== // Отладка // ======================================================================== @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) ); } }