SHA256
ESP32: регистрация по кнопке и зазор между кнопками
This commit is contained in:
+594
-5
@@ -52,6 +52,15 @@
|
||||
|
||||
static const char *kShineUsersProgramId = "FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm";
|
||||
static const char *kShineUsersUserPdaSeedPrefix = "user_login=";
|
||||
static const char *kShineLoginGuardProgramId = "3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo";
|
||||
static const char *kShinePaymentsProgramId = "c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW";
|
||||
static const char *kSystemProgramId = "11111111111111111111111111111111";
|
||||
static const char *kEd25519ProgramId = "Ed25519SigVerify111111111111111111111111111";
|
||||
static const char *kSysvarInstructionsId = "Sysvar1nstructions1111111111111111111111111";
|
||||
static const char *kUsersSeedPrefix = "user_login=";
|
||||
static const char *kUsersEconomyConfigSeed = "shine_users_economy_config";
|
||||
static const char *kPaymentsInflowSeed = "shine_payments_inflow_vault";
|
||||
static const char *kLastBlockPrefix = "SHiNE_LAST_BLOCK";
|
||||
static const char *kProgramDerivedAddressMarker = "ProgramDerivedAddress";
|
||||
static const uint8_t kBlockTypeRootKey = 1;
|
||||
static const uint8_t kBlockTypeDeviceKey = 2;
|
||||
@@ -234,6 +243,8 @@ static String gAccountPdaStatusMessage = "Account not checked";
|
||||
static bool gAccountCheckPending = true;
|
||||
static unsigned long gLastAccountCheckMs = 0;
|
||||
static bool gShowRegisterAccountButton = false;
|
||||
static String gUserPdaAddress;
|
||||
static String gRegistrationSignature;
|
||||
static String gShineStatusLine = "SHiNE: account not configured";
|
||||
static String gShineSessionId;
|
||||
static String gShineSessionKey;
|
||||
@@ -329,6 +340,51 @@ static String shineWsUrl();
|
||||
static String shineHomeLine();
|
||||
static String balanceHomeLine();
|
||||
static uint64_t shineNowMs();
|
||||
static void shortVecEncode(size_t value, std::vector<uint8_t> &out);
|
||||
static void pushU32LE(std::vector<uint8_t> &out, uint32_t value);
|
||||
static void pushU64LE(std::vector<uint8_t> &out, uint64_t value);
|
||||
static void pushStrU8(std::vector<uint8_t> &out, const String &value);
|
||||
static void pushFixed(std::vector<uint8_t> &out, const uint8_t *data, size_t len);
|
||||
static String bytesToBase58(const uint8_t *data, size_t len);
|
||||
static String buildBaseRpcRequest(const char *method, const String ¶msJson);
|
||||
static bool rpcCallSolana(const char *method, const String ¶msJson, String &payloadOut);
|
||||
static bool rpcResponseHasError(const String &payload);
|
||||
static bool getLatestBlockhashBytes(uint8_t out[32], String &blockhashB58, String &messageOut);
|
||||
static bool pdaAlreadyExists(const String &login, String &pdaAddress, String &messageOut);
|
||||
static std::vector<uint8_t> buildLastBlockStateBytes(const String &login, const String &blockchainName);
|
||||
static std::vector<uint8_t> buildUnsignedCreateRecord(
|
||||
const String &login,
|
||||
const String &blockchainName,
|
||||
const String &serverAddress,
|
||||
const uint8_t rootPub[32],
|
||||
const uint8_t devicePub[32],
|
||||
const uint8_t blockchainPub[32],
|
||||
const uint8_t lastBlockSignature[64],
|
||||
uint64_t createdAtMs);
|
||||
static std::vector<uint8_t> buildCreateInstructionData(
|
||||
const String &login,
|
||||
const String &blockchainName,
|
||||
const String &serverAddress,
|
||||
const uint8_t rootPub[32],
|
||||
const uint8_t devicePub[32],
|
||||
const uint8_t blockchainPub[32],
|
||||
const uint8_t lastBlockSignature[64],
|
||||
const uint8_t rootSignature[64],
|
||||
uint64_t createdAtMs);
|
||||
static std::vector<uint8_t> buildEd25519InstructionData(const uint8_t signature[64], const uint8_t publicKey[32], const uint8_t messageHash[32]);
|
||||
static std::vector<uint8_t> buildLegacyMessage(
|
||||
const uint8_t recentBlockhash[32],
|
||||
const uint8_t devicePub[32],
|
||||
const uint8_t userPda[32],
|
||||
const uint8_t inflowVault[32],
|
||||
const uint8_t economyConfig[32],
|
||||
const std::vector<uint8_t> &edRootData,
|
||||
const std::vector<uint8_t> &edBchData,
|
||||
const std::vector<uint8_t> &createData);
|
||||
static bool signMessageEd25519(const std::vector<uint8_t> &message, const uint8_t secretKey[64], uint8_t signature[64]);
|
||||
static String encodeTransactionBase64(const uint8_t signature[64], const std::vector<uint8_t> &message);
|
||||
static bool awaitTransactionConfirmation(const String &signatureB58, String &messageOut);
|
||||
static bool registerHomeserverOnSolana(String &messageOut);
|
||||
static String buildSessionKeyStringFromPublicBase64(const String &pubB64);
|
||||
static bool deriveKeypairFromSeed32(const uint8_t seed32[32], uint8_t pub32[32], uint8_t sec64[64]);
|
||||
static bool deriveSeedKeypairFromBase58(const String &seedB58, uint8_t seed32[32], uint8_t pub32[32], uint8_t sec64[64]);
|
||||
@@ -569,6 +625,13 @@ static String normalizeLoginValue(const String &value) {
|
||||
return out;
|
||||
}
|
||||
|
||||
static String abbreviateValue(const String &value, size_t head = 8, size_t tail = 6) {
|
||||
if (value.length() <= head + tail + 3) {
|
||||
return value;
|
||||
}
|
||||
return value.substring(0, head) + "..." + value.substring(value.length() - tail);
|
||||
}
|
||||
|
||||
static String jsonEscape(const String &value) {
|
||||
String out;
|
||||
out.reserve(value.length() + 8);
|
||||
@@ -781,6 +844,8 @@ static void markAccountStateDirty() {
|
||||
gAccountPdaStatus = ACCOUNT_PDA_UNKNOWN;
|
||||
gAccountPdaStatusMessage = "Account not checked";
|
||||
gShowRegisterAccountButton = false;
|
||||
gUserPdaAddress = "";
|
||||
gRegistrationSignature = "";
|
||||
clearShineSessionState(true);
|
||||
gShineStatusLine = "SHiNE: account not configured";
|
||||
}
|
||||
@@ -965,6 +1030,501 @@ static uint64_t shineNowMs() {
|
||||
return value > 0 ? (uint64_t)value : (uint64_t)millis();
|
||||
}
|
||||
|
||||
static void shortVecEncode(size_t value, std::vector<uint8_t> &out) {
|
||||
do {
|
||||
uint8_t byte = value & 0x7F;
|
||||
value >>= 7;
|
||||
if (value) {
|
||||
byte |= 0x80;
|
||||
}
|
||||
out.push_back(byte);
|
||||
} while (value);
|
||||
}
|
||||
|
||||
static void pushU32LE(std::vector<uint8_t> &out, uint32_t value) {
|
||||
out.push_back((uint8_t)(value & 0xFF));
|
||||
out.push_back((uint8_t)((value >> 8) & 0xFF));
|
||||
out.push_back((uint8_t)((value >> 16) & 0xFF));
|
||||
out.push_back((uint8_t)((value >> 24) & 0xFF));
|
||||
}
|
||||
|
||||
static void pushU64LE(std::vector<uint8_t> &out, uint64_t value) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
out.push_back((uint8_t)((value >> (8 * i)) & 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
static void pushStrU8(std::vector<uint8_t> &out, const String &value) {
|
||||
shortVecEncode(value.length(), out);
|
||||
for (size_t i = 0; i < value.length(); ++i) {
|
||||
out.push_back((uint8_t)value.charAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
static void pushFixed(std::vector<uint8_t> &out, const uint8_t *data, size_t len) {
|
||||
out.insert(out.end(), data, data + len);
|
||||
}
|
||||
|
||||
static String bytesToBase58(const uint8_t *data, size_t len) {
|
||||
char out[160] = {};
|
||||
shineSecretBase58Encode(data, len, out, sizeof(out));
|
||||
return String(out);
|
||||
}
|
||||
|
||||
static String buildBaseRpcRequest(const char *method, const String ¶msJson) {
|
||||
return "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"" + String(method) + "\",\"params\":" + paramsJson + "}";
|
||||
}
|
||||
|
||||
static bool rpcCallSolana(const char *method, const String ¶msJson, String &payloadOut) {
|
||||
int code = -1;
|
||||
if (!httpPostJson(gSolanaRpcUrl, buildBaseRpcRequest(method, paramsJson), code, payloadOut)) {
|
||||
return false;
|
||||
}
|
||||
return code >= 200 && code < 300;
|
||||
}
|
||||
|
||||
static bool rpcResponseHasError(const String &payload) {
|
||||
return payload.indexOf("\"error\"") >= 0;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> buildLastBlockStateBytes(const String &login, const String &blockchainName) {
|
||||
std::vector<uint8_t> out;
|
||||
out.reserve(80);
|
||||
pushFixed(out, (const uint8_t *)kLastBlockPrefix, strlen(kLastBlockPrefix));
|
||||
pushStrU8(out, login);
|
||||
pushStrU8(out, blockchainName);
|
||||
pushU32LE(out, 0);
|
||||
out.insert(out.end(), 32, 0);
|
||||
pushU64LE(out, 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> buildUnsignedCreateRecord(
|
||||
const String &login,
|
||||
const String &blockchainName,
|
||||
const String &serverAddress,
|
||||
const uint8_t rootPub[32],
|
||||
const uint8_t devicePub[32],
|
||||
const uint8_t blockchainPub[32],
|
||||
const uint8_t lastBlockSignature[64],
|
||||
uint64_t createdAtMs) {
|
||||
std::vector<uint8_t> out;
|
||||
out.reserve(512);
|
||||
pushFixed(out, (const uint8_t *)"SHiNE", 5);
|
||||
out.push_back(1);
|
||||
out.push_back(0);
|
||||
out.push_back(0);
|
||||
out.push_back(0);
|
||||
pushU64LE(out, createdAtMs);
|
||||
pushU64LE(out, createdAtMs);
|
||||
pushU32LE(out, 0);
|
||||
out.insert(out.end(), 32, 0);
|
||||
pushStrU8(out, login);
|
||||
out.push_back(7);
|
||||
|
||||
out.push_back(kBlockTypeRootKey);
|
||||
out.push_back(0);
|
||||
pushFixed(out, rootPub, 32);
|
||||
|
||||
out.push_back(kBlockTypeDeviceKey);
|
||||
out.push_back(0);
|
||||
pushFixed(out, devicePub, 32);
|
||||
|
||||
out.push_back(kBlockTypeBlockchainRegistry);
|
||||
out.push_back(0);
|
||||
out.push_back(1);
|
||||
out.push_back(1);
|
||||
pushStrU8(out, blockchainName);
|
||||
pushFixed(out, blockchainPub, 32);
|
||||
pushU64LE(out, 100000);
|
||||
pushU64LE(out, 0);
|
||||
pushU32LE(out, 0);
|
||||
out.insert(out.end(), 32, 0);
|
||||
pushFixed(out, lastBlockSignature, 64);
|
||||
out.push_back(0);
|
||||
|
||||
out.push_back(kBlockTypeServerProfile);
|
||||
out.push_back(0);
|
||||
out.push_back(1);
|
||||
out.push_back(1);
|
||||
out.push_back(0);
|
||||
pushStrU8(out, serverAddress);
|
||||
out.push_back(0);
|
||||
|
||||
out.push_back(kBlockTypeAccessServers);
|
||||
out.push_back(0);
|
||||
out.push_back(0);
|
||||
|
||||
out.push_back(kBlockTypeSessions);
|
||||
out.push_back(0);
|
||||
out.push_back(1);
|
||||
out.push_back(0);
|
||||
|
||||
out.push_back(kBlockTypeTrustedState);
|
||||
out.push_back(0);
|
||||
out.push_back(0);
|
||||
|
||||
uint16_t recordLen = (uint16_t)(out.size() + 64);
|
||||
out[7] = (uint8_t)(recordLen & 0xFF);
|
||||
out[8] = (uint8_t)((recordLen >> 8) & 0xFF);
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> buildCreateInstructionData(
|
||||
const String &login,
|
||||
const String &blockchainName,
|
||||
const String &serverAddress,
|
||||
const uint8_t rootPub[32],
|
||||
const uint8_t devicePub[32],
|
||||
const uint8_t blockchainPub[32],
|
||||
const uint8_t lastBlockSignature[64],
|
||||
const uint8_t rootSignature[64],
|
||||
uint64_t createdAtMs) {
|
||||
std::vector<uint8_t> out;
|
||||
out.reserve(512);
|
||||
out.push_back(3);
|
||||
pushStrU8(out, login);
|
||||
pushFixed(out, rootPub, 32);
|
||||
pushU64LE(out, createdAtMs);
|
||||
pushU64LE(out, 0);
|
||||
pushFixed(out, devicePub, 32);
|
||||
pushFixed(out, blockchainPub, 32);
|
||||
pushStrU8(out, blockchainName);
|
||||
pushU64LE(out, 0);
|
||||
pushU32LE(out, 0);
|
||||
out.insert(out.end(), 32, 0);
|
||||
pushFixed(out, lastBlockSignature, 64);
|
||||
out.push_back(0);
|
||||
out.push_back(1);
|
||||
out.push_back(0);
|
||||
pushStrU8(out, serverAddress);
|
||||
out.push_back(0);
|
||||
out.push_back(0);
|
||||
out.push_back(1);
|
||||
out.push_back(0);
|
||||
out.push_back(0);
|
||||
pushFixed(out, rootSignature, 64);
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> buildEd25519InstructionData(const uint8_t signature[64], const uint8_t publicKey[32], const uint8_t messageHash[32]) {
|
||||
const uint16_t sigOff = 16;
|
||||
const uint16_t pkOff = sigOff + 64;
|
||||
const uint16_t msgOff = pkOff + 32;
|
||||
std::vector<uint8_t> out(msgOff + 32, 0);
|
||||
out[0] = 1;
|
||||
out[1] = 0;
|
||||
out[2] = (uint8_t)(sigOff & 0xFF);
|
||||
out[3] = (uint8_t)((sigOff >> 8) & 0xFF);
|
||||
out[6] = (uint8_t)(pkOff & 0xFF);
|
||||
out[7] = (uint8_t)((pkOff >> 8) & 0xFF);
|
||||
out[10] = (uint8_t)(msgOff & 0xFF);
|
||||
out[11] = (uint8_t)((msgOff >> 8) & 0xFF);
|
||||
out[12] = 32;
|
||||
out[4] = out[8] = out[14] = 0xFF;
|
||||
out[5] = out[9] = out[15] = 0xFF;
|
||||
memcpy(out.data() + sigOff, signature, 64);
|
||||
memcpy(out.data() + pkOff, publicKey, 32);
|
||||
memcpy(out.data() + msgOff, messageHash, 32);
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool getLatestBlockhashBytes(uint8_t out[32], String &blockhashB58, String &messageOut) {
|
||||
String payload;
|
||||
if (!rpcCallSolana("getLatestBlockhash", "[{\"commitment\":\"confirmed\"}]", payload)) {
|
||||
messageOut = "RPC не вернул blockhash";
|
||||
return false;
|
||||
}
|
||||
if (!jsonStringField(payload, "blockhash", blockhashB58) || blockhashB58.isEmpty()) {
|
||||
messageOut = "В ответе нет blockhash";
|
||||
return false;
|
||||
}
|
||||
if (!base58ToFixed32(blockhashB58, out)) {
|
||||
messageOut = "Некорректный blockhash";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool pdaAlreadyExists(const String &login, String &pdaAddress, String &messageOut) {
|
||||
uint8_t userPda[32];
|
||||
std::vector<std::vector<uint8_t>> seeds = {
|
||||
std::vector<uint8_t>((const uint8_t *)kUsersSeedPrefix, (const uint8_t *)kUsersSeedPrefix + strlen(kUsersSeedPrefix)),
|
||||
std::vector<uint8_t>((const uint8_t *)login.c_str(), (const uint8_t *)login.c_str() + login.length())};
|
||||
if (!findProgramAddress(seeds, kShineUsersProgramId, userPda)) {
|
||||
messageOut = "Не удалось вычислить user PDA";
|
||||
return false;
|
||||
}
|
||||
pdaAddress = bytesToBase58(userPda, 32);
|
||||
String payload;
|
||||
if (!rpcCallSolana("getAccountInfo", "[\"" + pdaAddress + "\",{\"encoding\":\"base64\",\"commitment\":\"confirmed\"}]", payload)) {
|
||||
messageOut = "Не удалось проверить PDA";
|
||||
return false;
|
||||
}
|
||||
if (payload.indexOf("\"value\":null") >= 0) {
|
||||
return false;
|
||||
}
|
||||
if (payload.indexOf("\"value\"") >= 0) {
|
||||
messageOut = "Такой логин уже зарегистрирован";
|
||||
return true;
|
||||
}
|
||||
messageOut = "Непонятный ответ getAccountInfo";
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> buildLegacyMessage(
|
||||
const uint8_t recentBlockhash[32],
|
||||
const uint8_t devicePub[32],
|
||||
const uint8_t userPda[32],
|
||||
const uint8_t inflowVault[32],
|
||||
const uint8_t economyConfig[32],
|
||||
const std::vector<uint8_t> &edRootData,
|
||||
const std::vector<uint8_t> &edBchData,
|
||||
const std::vector<uint8_t> &createData) {
|
||||
uint8_t systemProgram[32];
|
||||
uint8_t ed25519Program[32];
|
||||
uint8_t sysvarInstructions[32];
|
||||
uint8_t usersProgram[32];
|
||||
uint8_t loginGuardProgram[32];
|
||||
base58ToFixed32(kSystemProgramId, systemProgram);
|
||||
base58ToFixed32(kEd25519ProgramId, ed25519Program);
|
||||
base58ToFixed32(kSysvarInstructionsId, sysvarInstructions);
|
||||
base58ToFixed32(kShineUsersProgramId, usersProgram);
|
||||
base58ToFixed32(kShineLoginGuardProgramId, loginGuardProgram);
|
||||
|
||||
std::vector<std::vector<uint8_t>> accountKeys;
|
||||
accountKeys.emplace_back(devicePub, devicePub + 32);
|
||||
accountKeys.emplace_back(userPda, userPda + 32);
|
||||
accountKeys.emplace_back(inflowVault, inflowVault + 32);
|
||||
accountKeys.emplace_back(systemProgram, systemProgram + 32);
|
||||
accountKeys.emplace_back(sysvarInstructions, sysvarInstructions + 32);
|
||||
accountKeys.emplace_back(economyConfig, economyConfig + 32);
|
||||
accountKeys.emplace_back(loginGuardProgram, loginGuardProgram + 32);
|
||||
accountKeys.emplace_back(ed25519Program, ed25519Program + 32);
|
||||
accountKeys.emplace_back(usersProgram, usersProgram + 32);
|
||||
|
||||
std::vector<uint8_t> msg;
|
||||
msg.reserve(512);
|
||||
msg.push_back(1);
|
||||
msg.push_back(0);
|
||||
msg.push_back(6);
|
||||
shortVecEncode(accountKeys.size(), msg);
|
||||
for (const auto &key : accountKeys) {
|
||||
msg.insert(msg.end(), key.begin(), key.end());
|
||||
}
|
||||
msg.insert(msg.end(), recentBlockhash, recentBlockhash + 32);
|
||||
shortVecEncode(3, msg);
|
||||
|
||||
msg.push_back(7);
|
||||
msg.push_back(0);
|
||||
shortVecEncode(edRootData.size(), msg);
|
||||
msg.insert(msg.end(), edRootData.begin(), edRootData.end());
|
||||
|
||||
msg.push_back(7);
|
||||
msg.push_back(0);
|
||||
shortVecEncode(edBchData.size(), msg);
|
||||
msg.insert(msg.end(), edBchData.begin(), edBchData.end());
|
||||
|
||||
msg.push_back(8);
|
||||
msg.push_back(7);
|
||||
msg.push_back(0);
|
||||
msg.push_back(1);
|
||||
msg.push_back(3);
|
||||
msg.push_back(2);
|
||||
msg.push_back(4);
|
||||
msg.push_back(5);
|
||||
msg.push_back(6);
|
||||
shortVecEncode(createData.size(), msg);
|
||||
msg.insert(msg.end(), createData.begin(), createData.end());
|
||||
return msg;
|
||||
}
|
||||
|
||||
static bool signMessageEd25519(const std::vector<uint8_t> &message, const uint8_t secretKey[64], uint8_t signature[64]) {
|
||||
return crypto_sign_ed25519_detached(signature, nullptr, message.data(), (unsigned long long)message.size(), secretKey) == 0;
|
||||
}
|
||||
|
||||
static String encodeTransactionBase64(const uint8_t signature[64], const std::vector<uint8_t> &message) {
|
||||
std::vector<uint8_t> tx;
|
||||
tx.reserve(1 + 64 + message.size());
|
||||
shortVecEncode(1, tx);
|
||||
pushFixed(tx, signature, 64);
|
||||
tx.insert(tx.end(), message.begin(), message.end());
|
||||
return bytesToBase64String(tx.data(), tx.size());
|
||||
}
|
||||
|
||||
static bool awaitTransactionConfirmation(const String &signatureB58, String &messageOut) {
|
||||
for (int attempt = 0; attempt < 15; attempt++) {
|
||||
String payload;
|
||||
if (!rpcCallSolana("getSignatureStatuses", "[[\"" + signatureB58 + "\"],{\"searchTransactionHistory\":true}]", payload)) {
|
||||
delay(1000);
|
||||
continue;
|
||||
}
|
||||
if (payload.indexOf("\"err\":null") >= 0 &&
|
||||
(payload.indexOf("\"confirmationStatus\":\"confirmed\"") >= 0 ||
|
||||
payload.indexOf("\"confirmationStatus\":\"finalized\"") >= 0)) {
|
||||
return true;
|
||||
}
|
||||
if (payload.indexOf("\"err\":{") >= 0 || payload.indexOf("\"err\":\"") >= 0) {
|
||||
messageOut = "Транзакция отклонена сетью";
|
||||
return false;
|
||||
}
|
||||
delay(1000);
|
||||
}
|
||||
messageOut = "RPC не подтвердил транзакцию вовремя";
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool registerHomeserverOnSolana(String &messageOut) {
|
||||
messageOut = "";
|
||||
String cleanLogin = normalizeLoginValue(gLoginValue);
|
||||
if (cleanLogin.isEmpty()) {
|
||||
messageOut = "Логин не задан";
|
||||
return false;
|
||||
}
|
||||
if (!gSecretConfigured || gRootPrivB58.isEmpty() || gBlockchainPrivB58.isEmpty() || gDevicePrivB58.isEmpty()) {
|
||||
messageOut = "Секрет не готов";
|
||||
return false;
|
||||
}
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
messageOut = "Сначала подключите Wi-Fi";
|
||||
return false;
|
||||
}
|
||||
if (gSolanaRpcUrl.isEmpty()) {
|
||||
messageOut = "Сначала задайте Solana RPC";
|
||||
return false;
|
||||
}
|
||||
|
||||
String existingPda;
|
||||
String pdaCheckMessage;
|
||||
if (pdaAlreadyExists(cleanLogin, existingPda, pdaCheckMessage)) {
|
||||
gUserPdaAddress = existingPda;
|
||||
gRegistrationSignature = "";
|
||||
saveAccountPrefs();
|
||||
gAccountPdaStatus = ACCOUNT_PDA_OK;
|
||||
gAccountPdaStatusMessage = "Пользователь уже зарегистрирован";
|
||||
gShowRegisterAccountButton = false;
|
||||
gAccountStatusMessage = "Пользователь уже зарегистрирован";
|
||||
gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " registered";
|
||||
refreshAccountPdaStatus();
|
||||
return true;
|
||||
}
|
||||
if (pdaCheckMessage == "Не удалось вычислить user PDA" || pdaCheckMessage == "Не удалось проверить PDA" || pdaCheckMessage == "Непонятный ответ getAccountInfo") {
|
||||
messageOut = pdaCheckMessage;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t userPda[32];
|
||||
uint8_t economyConfig[32];
|
||||
uint8_t inflowVault[32];
|
||||
if (!findProgramAddress({
|
||||
std::vector<uint8_t>((const uint8_t *)kUsersSeedPrefix, (const uint8_t *)kUsersSeedPrefix + strlen(kUsersSeedPrefix)),
|
||||
std::vector<uint8_t>((const uint8_t *)cleanLogin.c_str(), (const uint8_t *)cleanLogin.c_str() + cleanLogin.length())
|
||||
}, kShineUsersProgramId, userPda) ||
|
||||
!findProgramAddress({
|
||||
std::vector<uint8_t>((const uint8_t *)kUsersEconomyConfigSeed, (const uint8_t *)kUsersEconomyConfigSeed + strlen(kUsersEconomyConfigSeed))
|
||||
}, kShineUsersProgramId, economyConfig) ||
|
||||
!findProgramAddress({
|
||||
std::vector<uint8_t>((const uint8_t *)kPaymentsInflowSeed, (const uint8_t *)kPaymentsInflowSeed + strlen(kPaymentsInflowSeed))
|
||||
}, kShinePaymentsProgramId, inflowVault)) {
|
||||
messageOut = "Не удалось вычислить обязательные PDA";
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t rootSeed[32] = {};
|
||||
uint8_t rootPub[32] = {};
|
||||
uint8_t rootSec[64] = {};
|
||||
uint8_t blockchainSeed[32] = {};
|
||||
uint8_t blockchainPub[32] = {};
|
||||
uint8_t blockchainSec[64] = {};
|
||||
uint8_t deviceSeed[32] = {};
|
||||
uint8_t devicePub[32] = {};
|
||||
uint8_t deviceSec[64] = {};
|
||||
if (!deriveSeedKeypairFromBase58(gRootPrivB58, rootSeed, rootPub, rootSec) ||
|
||||
!deriveSeedKeypairFromBase58(gBlockchainPrivB58, blockchainSeed, blockchainPub, blockchainSec) ||
|
||||
!deriveSeedKeypairFromBase58(gDevicePrivB58, deviceSeed, devicePub, deviceSec)) {
|
||||
messageOut = "Не удалось восстановить ключи";
|
||||
return false;
|
||||
}
|
||||
|
||||
String blockchainName = cleanLogin + "-001";
|
||||
std::vector<uint8_t> lastBlockState = buildLastBlockStateBytes(cleanLogin, blockchainName);
|
||||
uint8_t lastBlockHash[32];
|
||||
uint8_t lastBlockSignature[64];
|
||||
sha256calc(lastBlockState.data(), lastBlockState.size(), lastBlockHash);
|
||||
if (!signMessageEd25519(std::vector<uint8_t>(lastBlockHash, lastBlockHash + 32), blockchainSec, lastBlockSignature)) {
|
||||
messageOut = "Не удалось подписать LastBlockState";
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t createdAtMs = shineNowMs();
|
||||
std::vector<uint8_t> unsignedRecord = buildUnsignedCreateRecord(
|
||||
cleanLogin, blockchainName, gShineServerUrl,
|
||||
rootPub, devicePub, blockchainPub,
|
||||
lastBlockSignature, createdAtMs);
|
||||
uint8_t unsignedHash[32];
|
||||
uint8_t rootSignature[64];
|
||||
sha256calc(unsignedRecord.data(), unsignedRecord.size(), unsignedHash);
|
||||
if (!signMessageEd25519(std::vector<uint8_t>(unsignedHash, unsignedHash + 32), rootSec, rootSignature)) {
|
||||
messageOut = "Не удалось подписать PDA-запись";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> createData = buildCreateInstructionData(
|
||||
cleanLogin, blockchainName, gShineServerUrl,
|
||||
rootPub, devicePub, blockchainPub,
|
||||
lastBlockSignature, rootSignature, createdAtMs);
|
||||
std::vector<uint8_t> edRootData = buildEd25519InstructionData(rootSignature, rootPub, unsignedHash);
|
||||
std::vector<uint8_t> edBchData = buildEd25519InstructionData(lastBlockSignature, blockchainPub, lastBlockHash);
|
||||
|
||||
uint8_t recentBlockhash[32];
|
||||
String recentBlockhash58;
|
||||
if (!getLatestBlockhashBytes(recentBlockhash, recentBlockhash58, messageOut)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> message = buildLegacyMessage(
|
||||
recentBlockhash,
|
||||
devicePub,
|
||||
userPda,
|
||||
inflowVault,
|
||||
economyConfig,
|
||||
edRootData,
|
||||
edBchData,
|
||||
createData);
|
||||
uint8_t txSignature[64];
|
||||
if (!signMessageEd25519(message, deviceSec, txSignature)) {
|
||||
messageOut = "Не удалось подписать Solana-транзакцию";
|
||||
return false;
|
||||
}
|
||||
String txBase64 = encodeTransactionBase64(txSignature, message);
|
||||
String signatureB58 = bytesToBase58(txSignature, 64);
|
||||
|
||||
String payload;
|
||||
if (!rpcCallSolana("sendTransaction", "[\"" + txBase64 + "\",{\"encoding\":\"base64\",\"preflightCommitment\":\"confirmed\"}]", payload)) {
|
||||
messageOut = "RPC не принял транзакцию";
|
||||
return false;
|
||||
}
|
||||
if (rpcResponseHasError(payload)) {
|
||||
messageOut = "RPC вернул ошибку sendTransaction";
|
||||
return false;
|
||||
}
|
||||
if (!awaitTransactionConfirmation(signatureB58, messageOut)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
gUserPdaAddress = bytesToBase58(userPda, 32);
|
||||
gRegistrationSignature = signatureB58;
|
||||
gAccountStatusMessage = "Solana-регистрация завершена";
|
||||
gAccountPdaStatus = ACCOUNT_PDA_OK;
|
||||
gAccountPdaStatusMessage = "Пользователь зарегистрирован";
|
||||
gShowRegisterAccountButton = false;
|
||||
gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " registered";
|
||||
saveAccountPrefs();
|
||||
refreshAccountPdaStatus();
|
||||
messageOut = "Solana-регистрация подтверждена";
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseShineUserPdaBytes(const std::vector<uint8_t> &bytes, ShinePdaUserState &outState, String &errorOut) {
|
||||
outState = ShinePdaUserState{};
|
||||
errorOut = "";
|
||||
@@ -1817,6 +2377,8 @@ static void loadPrefs() {
|
||||
gShineSessionId = gPrefs.getString("shine_sess_id", "");
|
||||
gShineSessionKey = gPrefs.getString("shine_sess_key", "");
|
||||
gShineStoragePwd = gPrefs.getString("shine_store_pwd", "");
|
||||
gUserPdaAddress = gPrefs.getString("user_pda", "");
|
||||
gRegistrationSignature = gPrefs.getString("registration_sig", "");
|
||||
gBalanceStatusMessage = gDevicePubB58.isEmpty() ? "Balance: secret not set" : "Balance: tap to load";
|
||||
gAccountCheckPending = true;
|
||||
gLastAccountCheckMs = 0;
|
||||
@@ -1853,6 +2415,16 @@ static void saveAccountPrefs() {
|
||||
gPrefs.putString("homeserver", gHomeserverValue);
|
||||
gPrefs.putBool("secret_set", gSecretConfigured);
|
||||
gPrefs.putString("secret_b58", gSecretBase58);
|
||||
if (gUserPdaAddress.isEmpty()) {
|
||||
gPrefs.remove("user_pda");
|
||||
} else {
|
||||
gPrefs.putString("user_pda", gUserPdaAddress);
|
||||
}
|
||||
if (gRegistrationSignature.isEmpty()) {
|
||||
gPrefs.remove("registration_sig");
|
||||
} else {
|
||||
gPrefs.putString("registration_sig", gRegistrationSignature);
|
||||
}
|
||||
if (gSecretConfigured) {
|
||||
gPrefs.putBytes("secret_bytes", gSecretBytes, 32);
|
||||
} else {
|
||||
@@ -2410,7 +2982,18 @@ static void actionButtonCb(lv_event_t *event) {
|
||||
showScreen(SCREEN_SETTINGS_MENU);
|
||||
break;
|
||||
case ACTION_REGISTER_ACCOUNT:
|
||||
showScreen(SCREEN_REGISTER_ACCOUNT_PLACEHOLDER);
|
||||
gAccountStatusMessage = "Регистрация запущена...";
|
||||
gShineStatusLine = "SHiNE: регистрация запущена";
|
||||
{
|
||||
String registerMessage;
|
||||
if (registerHomeserverOnSolana(registerMessage)) {
|
||||
gAccountStatusMessage = registerMessage;
|
||||
} else {
|
||||
gAccountStatusMessage = registerMessage;
|
||||
gShineStatusLine = String("SHiNE: ") + registerMessage;
|
||||
}
|
||||
rebuildScreen();
|
||||
}
|
||||
break;
|
||||
case ACTION_OPEN_WIFI:
|
||||
gWifiViewMode = WIFI_VIEW_OVERVIEW;
|
||||
@@ -2677,9 +3260,9 @@ static void drawHome() {
|
||||
makeButton(balanceHomeLine().c_str(), 22, 254, 436, 56, 0x355C7D, ACTION_REFRESH_BALANCE, &lv_font_montserrat_18);
|
||||
showMessageAt(shineHomeLine(), 322);
|
||||
if (gShowRegisterAccountButton) {
|
||||
makeButton("REGISTER ACCOUNT", 22, 360, 220, 78, 0x2A9D8F, ACTION_REGISTER_ACCOUNT, &lv_font_montserrat_20);
|
||||
makeButton("REGISTER ACCOUNT", 20, 360, 210, 78, 0x2A9D8F, ACTION_REGISTER_ACCOUNT, &lv_font_montserrat_20);
|
||||
}
|
||||
makeButton("SETTINGS", 238, 360, 220, 78, 0x2A6F97, ACTION_OPEN_SETTINGS, &lv_font_montserrat_24);
|
||||
makeButton("SETTINGS", 250, 360, 210, 78, 0x2A6F97, ACTION_OPEN_SETTINGS, &lv_font_montserrat_24);
|
||||
makeVersionTag();
|
||||
}
|
||||
|
||||
@@ -2791,6 +3374,12 @@ static void drawAccountScreen() {
|
||||
|
||||
makeTitle("ACCOUNT", 18, &lv_font_montserrat_24);
|
||||
showMessageAt(gAccountStatusMessage, 56);
|
||||
if (!gUserPdaAddress.isEmpty()) {
|
||||
showMessageAt(String("PDA: ") + abbreviateValue(gUserPdaAddress, 10, 6), 80);
|
||||
}
|
||||
if (!gRegistrationSignature.isEmpty()) {
|
||||
showMessageAt(String("TX: ") + abbreviateValue(gRegistrationSignature, 10, 6), 100);
|
||||
}
|
||||
|
||||
String loginButton = String("Login (") + (gLoginValue.isEmpty() ? "not set" : gLoginValue) + ")";
|
||||
String homeserverButton = String("Homeserver (") + (gHomeserverValue.isEmpty() ? "not set" : gHomeserverValue) + ")";
|
||||
@@ -2939,8 +3528,8 @@ static void drawSecretGenerateCancelConfirmScreen() {
|
||||
static void drawRegisterAccountPlaceholderScreen() {
|
||||
setRootStyle();
|
||||
makeTitle("REGISTER ACCOUNT", 22, &lv_font_montserrat_24);
|
||||
makeBody("Registration flow is not implemented yet.", 112, 420);
|
||||
makeBody("This button is shown when login is not found in Solana PDA.", 156, 420);
|
||||
makeBody("Registration now starts directly from the home screen button.", 112, 420);
|
||||
makeBody("This screen is kept only as a fallback status page.", 156, 420);
|
||||
makeButton("BACK", 140, 360, 200, 72, 0x5A6570, ACTION_BACK_HOME, &lv_font_montserrat_22);
|
||||
makeVersionTag();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user