17 12 25
Промежуточный комит верии которая может быть работает :)
This commit is contained in:
parent
eaf1affb27
commit
eb37b43de4
@ -127,6 +127,14 @@ public final class BchBlockEntry_new {
|
|||||||
this.fullBytes = bb.array();
|
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() {
|
public byte[] getSignature64() {
|
||||||
|
|||||||
@ -33,11 +33,11 @@ dependencies {
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
вапва
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
|
пвпва
|
||||||
|
|
||||||
|
|||||||
@ -2,37 +2,49 @@ package server.logic.ws_protocol.JSON.entyties.Blockchain;
|
|||||||
|
|
||||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AddBlock_new request.
|
||||||
|
*
|
||||||
|
* payload:
|
||||||
|
* - userLogin
|
||||||
|
* - blockchainId
|
||||||
|
* - globalBlockNumber
|
||||||
|
* - prevGlobalHashHex (может быть "" для нулевого)
|
||||||
|
* - line (0..7)
|
||||||
|
* - lineBlockNumber
|
||||||
|
* - blockBase64 (FULL bytes блока)
|
||||||
|
*/
|
||||||
public class Net_AddBlock_new_Request extends Net_Request {
|
public class Net_AddBlock_new_Request extends Net_Request {
|
||||||
|
|
||||||
|
private String userLogin;
|
||||||
|
|
||||||
private long blockchainId;
|
private long blockchainId;
|
||||||
|
private int globalBlockNumber;
|
||||||
|
private String prevGlobalHashHex;
|
||||||
|
|
||||||
private int globalNumber;
|
private short line;
|
||||||
private String prevGlobalHash; // HEX(64) or ""
|
|
||||||
|
|
||||||
private int lineNumber; // 0..7
|
|
||||||
private int lineBlockNumber;
|
private int lineBlockNumber;
|
||||||
private String prevLineHash; // HEX(64) or ""
|
|
||||||
|
|
||||||
private String blockBase64; // base64url of raw .bch bytes
|
private String blockBase64;
|
||||||
|
|
||||||
|
public String getUserLogin() { return userLogin; }
|
||||||
|
public void setUserLogin(String userLogin) { this.userLogin = userLogin; }
|
||||||
|
|
||||||
public long getBlockchainId() { return blockchainId; }
|
public long getBlockchainId() { return blockchainId; }
|
||||||
public void setBlockchainId(long blockchainId) { this.blockchainId = blockchainId; }
|
public void setBlockchainId(long blockchainId) { this.blockchainId = blockchainId; }
|
||||||
|
|
||||||
public int getGlobalNumber() { return globalNumber; }
|
public int getGlobalBlockNumber() { return globalBlockNumber; }
|
||||||
public void setGlobalNumber(int globalNumber) { this.globalNumber = globalNumber; }
|
public void setGlobalBlockNumber(int globalBlockNumber) { this.globalBlockNumber = globalBlockNumber; }
|
||||||
|
|
||||||
public String getPrevGlobalHash() { return prevGlobalHash; }
|
public String getPrevGlobalHashHex() { return prevGlobalHashHex; }
|
||||||
public void setPrevGlobalHash(String prevGlobalHash) { this.prevGlobalHash = prevGlobalHash; }
|
public void setPrevGlobalHashHex(String prevGlobalHashHex) { this.prevGlobalHashHex = prevGlobalHashHex; }
|
||||||
|
|
||||||
public int getLineNumber() { return lineNumber; }
|
public short getLine() { return line; }
|
||||||
public void setLineNumber(int lineNumber) { this.lineNumber = lineNumber; }
|
public void setLine(short line) { this.line = line; }
|
||||||
|
|
||||||
public int getLineBlockNumber() { return lineBlockNumber; }
|
public int getLineBlockNumber() { return lineBlockNumber; }
|
||||||
public void setLineBlockNumber(int lineBlockNumber) { this.lineBlockNumber = lineBlockNumber; }
|
public void setLineBlockNumber(int lineBlockNumber) { this.lineBlockNumber = lineBlockNumber; }
|
||||||
|
|
||||||
public String getPrevLineHash() { return prevLineHash; }
|
|
||||||
public void setPrevLineHash(String prevLineHash) { this.prevLineHash = prevLineHash; }
|
|
||||||
|
|
||||||
public String getBlockBase64() { return blockBase64; }
|
public String getBlockBase64() { return blockBase64; }
|
||||||
public void setBlockBase64(String blockBase64) { this.blockBase64 = blockBase64; }
|
public void setBlockBase64(String blockBase64) { this.blockBase64 = blockBase64; }
|
||||||
}
|
}
|
||||||
@ -2,28 +2,44 @@ package server.logic.ws_protocol.JSON.entyties.Blockchain;
|
|||||||
|
|
||||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AddBlock_new response.
|
||||||
|
*
|
||||||
|
* payload:
|
||||||
|
* - accepted (true/false)
|
||||||
|
* - newGlobalNumber
|
||||||
|
* - newGlobalHashHex
|
||||||
|
* - newLineNumber
|
||||||
|
* - newLineHashHex
|
||||||
|
* - sizeBytes
|
||||||
|
*/
|
||||||
public class Net_AddBlock_new_Response extends Net_Response {
|
public class Net_AddBlock_new_Response extends Net_Response {
|
||||||
|
|
||||||
private int serverLastGlobalNumber;
|
private boolean accepted;
|
||||||
private String serverLastGlobalHash;
|
|
||||||
|
|
||||||
private int serverLastLineNumber;
|
private int newGlobalNumber;
|
||||||
private String serverLastLineHash;
|
private String newGlobalHashHex;
|
||||||
|
|
||||||
private String reasonCode; // "OUT_OF_SEQUENCE", "HASH_MISMATCH", ...
|
private int newLineNumber;
|
||||||
|
private String newLineHashHex;
|
||||||
|
|
||||||
public int getServerLastGlobalNumber() { return serverLastGlobalNumber; }
|
private int sizeBytes;
|
||||||
public void setServerLastGlobalNumber(int v) { this.serverLastGlobalNumber = v; }
|
|
||||||
|
|
||||||
public String getServerLastGlobalHash() { return serverLastGlobalHash; }
|
public boolean isAccepted() { return accepted; }
|
||||||
public void setServerLastGlobalHash(String v) { this.serverLastGlobalHash = v; }
|
public void setAccepted(boolean accepted) { this.accepted = accepted; }
|
||||||
|
|
||||||
public int getServerLastLineNumber() { return serverLastLineNumber; }
|
public int getNewGlobalNumber() { return newGlobalNumber; }
|
||||||
public void setServerLastLineNumber(int v) { this.serverLastLineNumber = v; }
|
public void setNewGlobalNumber(int newGlobalNumber) { this.newGlobalNumber = newGlobalNumber; }
|
||||||
|
|
||||||
public String getServerLastLineHash() { return serverLastLineHash; }
|
public String getNewGlobalHashHex() { return newGlobalHashHex; }
|
||||||
public void setServerLastLineHash(String v) { this.serverLastLineHash = v; }
|
public void setNewGlobalHashHex(String newGlobalHashHex) { this.newGlobalHashHex = newGlobalHashHex; }
|
||||||
|
|
||||||
public String getReasonCode() { return reasonCode; }
|
public int getNewLineNumber() { return newLineNumber; }
|
||||||
public void setReasonCode(String reasonCode) { this.reasonCode = reasonCode; }
|
public void setNewLineNumber(int newLineNumber) { this.newLineNumber = newLineNumber; }
|
||||||
|
|
||||||
|
public String getNewLineHashHex() { return newLineHashHex; }
|
||||||
|
public void setNewLineHashHex(String newLineHashHex) { this.newLineHashHex = newLineHashHex; }
|
||||||
|
|
||||||
|
public int getSizeBytes() { return sizeBytes; }
|
||||||
|
public void setSizeBytes(int sizeBytes) { this.sizeBytes = sizeBytes; }
|
||||||
}
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package server.logic.ws_protocol.JSON.handlers.blockchain;
|
||||||
|
|
||||||
|
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||||
|
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||||
|
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||||
|
import server.logic.ws_protocol.JSON.entyties.Blockchain.Net_AddBlock_new_Request;
|
||||||
|
import server.logic.ws_protocol.JSON.entyties.Blockchain.Net_AddBlock_new_Response;
|
||||||
|
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||||
|
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||||
|
import server.logic.ws_protocol.WireCodes;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
public class AddBlock_new_Handler implements JsonMessageHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Net_Response handle(Net_Request baseReq, ConnectionContext ctx) throws Exception {
|
||||||
|
|
||||||
|
Net_AddBlock_new_Request req = (Net_AddBlock_new_Request) baseReq;
|
||||||
|
|
||||||
|
// 1) простая валидация запроса
|
||||||
|
if (req.getLogin() == null || req.getLogin().isBlank())
|
||||||
|
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "EMPTY_LOGIN", "Пустой login");
|
||||||
|
|
||||||
|
if (req.getBlockchainId() <= 0)
|
||||||
|
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_CHAIN_ID", "Некорректный blockchainId");
|
||||||
|
|
||||||
|
if (req.getGlobalBlockNumber() < 0)
|
||||||
|
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_NUMBER", "Некорректный globalBlockNumber");
|
||||||
|
|
||||||
|
if (req.getBlockBase64() == null || req.getBlockBase64().isBlank())
|
||||||
|
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "EMPTY_BLOCK", "Пустой blockBase64");
|
||||||
|
|
||||||
|
byte[] blockBytes;
|
||||||
|
try {
|
||||||
|
blockBytes = Base64.getDecoder().decode(req.getBlockBase64());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_BASE64", "blockBase64 не декодируется");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) основная логика — в сервис
|
||||||
|
var r = BlockchainStateService_new.getInstance().addBlock(
|
||||||
|
req.getLogin(),
|
||||||
|
req.getBlockchainId(),
|
||||||
|
req.getGlobalBlockNumber(),
|
||||||
|
req.getPrevGlobalHashHex(),
|
||||||
|
blockBytes
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3) собрать ответ
|
||||||
|
Net_AddBlock_new_Response resp = new Net_AddBlock_new_Response();
|
||||||
|
resp.setOp(req.getOp());
|
||||||
|
resp.setRequestId(req.getRequestId());
|
||||||
|
resp.setStatus(r.status);
|
||||||
|
|
||||||
|
resp.setLastGlobalNumber(r.lastGlobalNumber);
|
||||||
|
resp.setLastGlobalHashHex(r.lastGlobalHashHex);
|
||||||
|
|
||||||
|
resp.setExpectedGlobalNumber(r.expectedGlobalNumber);
|
||||||
|
resp.setExpectedPrevGlobalHashHex(r.expectedPrevGlobalPrevHashHex);
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,245 @@
|
|||||||
|
package server.logic.blockchain_new;
|
||||||
|
|
||||||
|
import blockchain_new.BchBlockEntry_new;
|
||||||
|
import blockchain_new.BchCryptoVerifier_new;
|
||||||
|
import shine.db.SqliteDbController;
|
||||||
|
import shine.db.dao.BlockchainStateDAO;
|
||||||
|
import shine.db.entities.BlockchainStateEntry;
|
||||||
|
import utils.files.FileStoreUtil;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public final class BlockchainStateService_new {
|
||||||
|
|
||||||
|
private static final BlockchainStateService_new INSTANCE = new BlockchainStateService_new();
|
||||||
|
|
||||||
|
public static BlockchainStateService_new getInstance() { return INSTANCE; }
|
||||||
|
|
||||||
|
private final SqliteDbController db = SqliteDbController.getInstance();
|
||||||
|
private final BlockchainStateDAO stateDao = BlockchainStateDAO.getInstance();
|
||||||
|
private final FileStoreUtil fileStore = FileStoreUtil.getInstance();
|
||||||
|
|
||||||
|
/** JVM-level locks per blockchainId */
|
||||||
|
private final ConcurrentHashMap<Long, Object> locks = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private BlockchainStateService_new() {}
|
||||||
|
|
||||||
|
public static final class ApplyResult {
|
||||||
|
public final int newGlobalNumber;
|
||||||
|
public final String newGlobalHashHex;
|
||||||
|
public final int newLineNumber;
|
||||||
|
public final String newLineHashHex;
|
||||||
|
public final int sizeBytes;
|
||||||
|
|
||||||
|
public ApplyResult(int newGlobalNumber, String newGlobalHashHex,
|
||||||
|
int newLineNumber, String newLineHashHex,
|
||||||
|
int sizeBytes) {
|
||||||
|
this.newGlobalNumber = newGlobalNumber;
|
||||||
|
this.newGlobalHashHex = newGlobalHashHex;
|
||||||
|
this.newLineNumber = newLineNumber;
|
||||||
|
this.newLineHashHex = newLineHashHex;
|
||||||
|
this.sizeBytes = sizeBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApplyResult applyAddBlock(
|
||||||
|
String userLogin,
|
||||||
|
long blockchainId,
|
||||||
|
int globalBlockNumber,
|
||||||
|
String prevGlobalHashHexFromClient,
|
||||||
|
short lineIndex,
|
||||||
|
int lineBlockNumber,
|
||||||
|
String blockBase64
|
||||||
|
) throws Exception {
|
||||||
|
|
||||||
|
Objects.requireNonNull(userLogin, "userLogin == null");
|
||||||
|
Objects.requireNonNull(blockBase64, "blockBase64 == null");
|
||||||
|
|
||||||
|
if (blockchainId <= 0) throw new IllegalArgumentException("blockchainId <= 0");
|
||||||
|
if (globalBlockNumber < 0) throw new IllegalArgumentException("globalBlockNumber < 0");
|
||||||
|
if (lineIndex < 0 || lineIndex > 7) throw new IllegalArgumentException("lineIndex must be 0..7");
|
||||||
|
if (lineBlockNumber < 0) throw new IllegalArgumentException("lineBlockNumber < 0");
|
||||||
|
|
||||||
|
byte[] fullBytes;
|
||||||
|
try {
|
||||||
|
fullBytes = Base64.getDecoder().decode(blockBase64);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new IllegalArgumentException("blockBase64 is not valid Base64", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
BchBlockEntry_new block = new BchBlockEntry_new(fullBytes);
|
||||||
|
|
||||||
|
// Быстрая проверка: что клиентские “в шапке запроса” совпадают с тем, что внутри блока.
|
||||||
|
if (block.recordNumber != globalBlockNumber)
|
||||||
|
throw new IllegalArgumentException("Global number mismatch: req=" + globalBlockNumber + " block=" + block.recordNumber);
|
||||||
|
if (block.line != lineIndex)
|
||||||
|
throw new IllegalArgumentException("Line mismatch: req=" + lineIndex + " block=" + block.line);
|
||||||
|
if (block.lineNumber != lineBlockNumber)
|
||||||
|
throw new IllegalArgumentException("LineBlockNumber mismatch: req=" + lineBlockNumber + " block=" + block.lineNumber);
|
||||||
|
|
||||||
|
Object lock = locks.computeIfAbsent(blockchainId, k -> new Object());
|
||||||
|
|
||||||
|
synchronized (lock) {
|
||||||
|
Connection conn = db.getConnection();
|
||||||
|
boolean prevAutoCommit = conn.getAutoCommit();
|
||||||
|
|
||||||
|
try {
|
||||||
|
conn.setAutoCommit(false);
|
||||||
|
|
||||||
|
// SQLite writer-lock
|
||||||
|
try (Statement st = conn.createStatement()) {
|
||||||
|
st.execute("BEGIN IMMEDIATE");
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockchainStateEntry state = stateDao.getByBlockchainId(blockchainId);
|
||||||
|
if (state == null)
|
||||||
|
throw new IllegalStateException("BLOCKCHAIN_NOT_FOUND: id=" + blockchainId);
|
||||||
|
|
||||||
|
// 1) логин должен совпадать с тем, что хранится в state (иначе легко подделывать)
|
||||||
|
if (!userLogin.equals(state.getUserLogin()))
|
||||||
|
throw new IllegalStateException("LOGIN_MISMATCH: requestLogin=" + userLogin + " dbLogin=" + state.getUserLogin());
|
||||||
|
|
||||||
|
// 2) глобальная последовательность
|
||||||
|
int expectedGlobal = state.getLastGlobalNumber() + 1;
|
||||||
|
if (globalBlockNumber != expectedGlobal)
|
||||||
|
throw new IllegalStateException("BAD_GLOBAL_NUMBER: expected=" + expectedGlobal + " got=" + globalBlockNumber);
|
||||||
|
|
||||||
|
String prevGlobalHashHexDb = nn(state.getLastGlobalHash());
|
||||||
|
String prevGlobalHashHexClient = nn(prevGlobalHashHexFromClient);
|
||||||
|
|
||||||
|
// 3) prev global hash должен совпасть с db
|
||||||
|
if (!eqHash(prevGlobalHashHexDb, prevGlobalHashHexClient))
|
||||||
|
throw new IllegalStateException("BAD_PREV_GLOBAL_HASH");
|
||||||
|
|
||||||
|
// 4) line последовательность
|
||||||
|
int expectedLine = state.getLastLineNumber(lineIndex) + 1;
|
||||||
|
if (lineBlockNumber != expectedLine)
|
||||||
|
throw new IllegalStateException("BAD_LINE_NUMBER: expected=" + expectedLine + " got=" + lineBlockNumber);
|
||||||
|
|
||||||
|
String prevLineHashHexDb = nn(state.getLastLineHash(lineIndex));
|
||||||
|
|
||||||
|
// 5) криптография: проверка хэша и подписи
|
||||||
|
byte[] publicKey32 = decodeBase64_32(state.getPublicKeyBase64());
|
||||||
|
if (publicKey32 == null)
|
||||||
|
throw new IllegalStateException("BAD_PUBLIC_KEY_BASE64 in db");
|
||||||
|
|
||||||
|
byte[] prevGlobalHash32 = hexTo32(prevGlobalHashHexDb);
|
||||||
|
byte[] prevLineHash32 = hexTo32(prevLineHashHexDb);
|
||||||
|
|
||||||
|
byte[] rawBytes = block.getRawBytes(); // нужно добавить метод в BchBlockEntry_new
|
||||||
|
byte[] preimage = BchCryptoVerifier_new.buildPreimage(
|
||||||
|
userLogin,
|
||||||
|
prevGlobalHash32,
|
||||||
|
prevLineHash32,
|
||||||
|
rawBytes
|
||||||
|
);
|
||||||
|
|
||||||
|
byte[] expectedHash32 = BchCryptoVerifier_new.sha256(preimage);
|
||||||
|
|
||||||
|
if (!constTimeEq32(expectedHash32, block.getHash32()))
|
||||||
|
throw new IllegalStateException("HASH_MISMATCH");
|
||||||
|
|
||||||
|
// Подпись — тут подключишь свой Ed25519 util (сейчас у тебя в new-верификаторе TODO)
|
||||||
|
boolean sigOk = BchCryptoVerifier_new.verifySignature(
|
||||||
|
expectedHash32,
|
||||||
|
block.getSignature64(),
|
||||||
|
publicKey32
|
||||||
|
);
|
||||||
|
if (!sigOk)
|
||||||
|
throw new IllegalStateException("SIGNATURE_MISMATCH");
|
||||||
|
|
||||||
|
// 6) лимит / размер
|
||||||
|
int newSizeBytes = state.getSizeBytes() + block.recordSize;
|
||||||
|
if (newSizeBytes > state.getSizeLimit())
|
||||||
|
throw new IllegalStateException("SIZE_LIMIT_EXCEEDED");
|
||||||
|
|
||||||
|
// 7) Сначала дописываем файл (если упадёт — транзакция откатится)
|
||||||
|
fileStore.addDataToBlockchain(blockchainId, block.toBytes());
|
||||||
|
|
||||||
|
// 8) Апдейт state в памяти
|
||||||
|
state.setSizeBytes(newSizeBytes);
|
||||||
|
state.setLastGlobalNumber(globalBlockNumber);
|
||||||
|
String newGlobalHashHex = toHex(expectedHash32);
|
||||||
|
state.setLastGlobalHash(newGlobalHashHex);
|
||||||
|
|
||||||
|
state.setLastLineNumber(lineIndex, lineBlockNumber);
|
||||||
|
String newLineHashHex = newGlobalHashHex; // если глобальный hash = hash блока (обычно да)
|
||||||
|
state.setLastLineHash(lineIndex, newLineHashHex);
|
||||||
|
|
||||||
|
state.setUpdatedAtMs(System.currentTimeMillis());
|
||||||
|
|
||||||
|
// 9) UPSERT в БД
|
||||||
|
stateDao.upsert(state);
|
||||||
|
|
||||||
|
// 10) commit
|
||||||
|
conn.commit();
|
||||||
|
|
||||||
|
return new ApplyResult(
|
||||||
|
globalBlockNumber,
|
||||||
|
newGlobalHashHex,
|
||||||
|
lineBlockNumber,
|
||||||
|
newLineHashHex,
|
||||||
|
newSizeBytes
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
try { conn.rollback(); } catch (SQLException ignore) {}
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
try { conn.setAutoCommit(prevAutoCommit); } catch (SQLException ignore) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- helpers ----------------
|
||||||
|
|
||||||
|
private static String nn(String s) { return s == null ? "" : s; }
|
||||||
|
|
||||||
|
/** сравнение хэшей: пустой == "0"*? — упростим: пустой = пустой. */
|
||||||
|
private static boolean eqHash(String a, String b) {
|
||||||
|
return nn(a).equalsIgnoreCase(nn(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] decodeBase64_32(String b64) {
|
||||||
|
try {
|
||||||
|
byte[] x = Base64.getDecoder().decode(b64);
|
||||||
|
return (x != null && x.length == 32) ? x : null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] hexTo32(String hex) {
|
||||||
|
if (hex == null || hex.isBlank()) return new byte[32];
|
||||||
|
String h = hex.trim();
|
||||||
|
if (h.length() != 64) throw new IllegalArgumentException("hex must be 64 chars (or empty)");
|
||||||
|
byte[] out = new byte[32];
|
||||||
|
for (int i = 0; i < 32; i++) {
|
||||||
|
int hi = Character.digit(h.charAt(i * 2), 16);
|
||||||
|
int lo = Character.digit(h.charAt(i * 2 + 1), 16);
|
||||||
|
if (hi < 0 || lo < 0) throw new IllegalArgumentException("bad hex");
|
||||||
|
out[i] = (byte) ((hi << 4) | lo);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean constTimeEq32(byte[] a, byte[] b) {
|
||||||
|
if (a == null || b == null || a.length != 32 || b.length != 32) return false;
|
||||||
|
int r = 0;
|
||||||
|
for (int i = 0; i < 32; i++) r |= (a[i] ^ b[i]);
|
||||||
|
return r == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toHex(byte[] b) {
|
||||||
|
StringBuilder sb = new StringBuilder(b.length * 2);
|
||||||
|
for (byte v : b) sb.append(String.format("%02x", v));
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user