Добавить SendSignal и remote AddBlock через homeserver

This commit is contained in:
AidarKC
2026-06-28 11:20:51 +04:00
parent c397c28acb
commit aa02e92e4d
19 changed files with 1329 additions and 224 deletions
@@ -285,6 +285,17 @@ struct ActiveWalletSignRequest {
Screen returnScreen = SCREEN_HOME;
};
struct PendingRemoteAddBlockRequest {
String fromLogin;
String fromSessionId;
String signalRequestId;
String signalType;
String data;
String sessionSignatureB64;
String clientSignatureB64;
uint64_t timeMs = 0;
};
static lv_disp_draw_buf_t gDrawBuf;
static lv_color_t *gBuf1 = nullptr;
static lv_color_t *gBuf2 = nullptr;
@@ -415,6 +426,7 @@ static String gCustomWalletName;
static String gCustomWalletPubB58;
static String gCustomWalletPrivB58;
static std::vector<PendingWalletRpcRequest> gPendingWalletRpcRequests;
static std::vector<PendingRemoteAddBlockRequest> gPendingRemoteAddBlockRequests;
static const int kWalletRpcSignalTypeRequest = 9100;
static const int kWalletRpcSignalTypeResponse = 9101;
static ActiveWalletSignRequest gActiveWalletSignRequest;
@@ -477,7 +489,9 @@ static void refreshSelectedWalletBalanceState();
static bool loadSelectedWalletBalanceLamports(uint64_t &lamportsOut, String &messageOut);
static bool loadBalanceLamportsForAddress(const String &address, uint64_t &lamportsOut, String &messageOut);
static bool queueIncomingWalletRpcRequest(const String &frame);
static bool queueIncomingRemoteAddBlockRequest(const String &frame);
static void processPendingWalletRpcRequests();
static void processPendingRemoteAddBlockRequests();
static void pumpShineIncomingFrames(uint32_t maxFrames = 3);
static String loginDisplayValue();
static String homeserverDisplayValue();
@@ -617,6 +631,22 @@ static bool sendWalletRpcResponse(const String &toLogin,
const String &targetSessionId,
const String &callId,
const String &responseData);
static bool sendSignalResponse(const String &toLogin,
const String &targetSessionId,
const String &signalType,
const String &signalRequestId,
const String &responseData);
static bool buildSendSignalSignatures(const String &toLogin,
const String &targetMode,
const String &targetSessionId,
const String &signalType,
const String &signalRequestId,
const String &data,
uint64_t timeMs,
bool includeClientSignature,
String &sessionSignatureB64Out,
String &clientSignatureB64Out,
String &errorOut);
static void queueWalletSignRequest(const PendingWalletRpcRequest &item,
const String &requestId,
const String &publicKeyBase58,
@@ -2695,6 +2725,126 @@ static bool sendWalletRpcResponse(const String &toLogin,
return shineWsRequest(gShineWs, "CallSignalToSession", req, response, SHINE_RPC_TIMEOUT_MS);
}
static bool buildSendSignalSignatures(const String &toLogin,
const String &targetMode,
const String &targetSessionId,
const String &signalType,
const String &signalRequestId,
const String &data,
uint64_t timeMs,
bool includeClientSignature,
String &sessionSignatureB64Out,
String &clientSignatureB64Out,
String &errorOut) {
sessionSignatureB64Out = "";
clientSignatureB64Out = "";
errorOut = "";
if (gLoginValue.isEmpty() || gShineSessionId.isEmpty()) {
errorOut = "session_context_missing";
return false;
}
uint8_t dataHash32[32] = {};
sha256calc(reinterpret_cast<const uint8_t *>(data.c_str()), data.length(), dataHash32);
String dataSha256B64 = bytesToBase64String(dataHash32, sizeof(dataHash32));
uint8_t subSeed[32] = {};
uint8_t subPub[32] = {};
uint8_t subSec[64] = {};
if (!deriveSeedKeypairFromBase58(gHomeserverPrivB58, subSeed, subPub, subSec)) {
errorOut = "homeserver_session_key_unavailable";
return false;
}
String sessionPreimage = String("SEND_SIGNAL_SESSION:")
+ gLoginValue + ":"
+ gShineSessionId + ":"
+ toLogin + ":"
+ targetMode + ":"
+ targetSessionId + ":"
+ signalType + ":"
+ signalRequestId + ":"
+ String((unsigned long long)timeMs) + ":"
+ dataSha256B64;
uint8_t sessionSignature[64] = {};
std::vector<uint8_t> sessionMessage(reinterpret_cast<const uint8_t *>(sessionPreimage.c_str()),
reinterpret_cast<const uint8_t *>(sessionPreimage.c_str()) + sessionPreimage.length());
if (!signMessageEd25519(sessionMessage, subSec, sessionSignature)) {
errorOut = "session_signal_sign_failed";
return false;
}
sessionSignatureB64Out = bytesToBase64String(sessionSignature, sizeof(sessionSignature));
if (!includeClientSignature) {
return true;
}
uint8_t clientSeed[32] = {};
uint8_t clientPub[32] = {};
uint8_t clientSec[64] = {};
if (!deriveSeedKeypairFromBase58(gDevicePrivB58, clientSeed, clientPub, clientSec)) {
errorOut = "client_key_unavailable";
return false;
}
String clientPreimage = String("SEND_SIGNAL_CLIENT:")
+ gLoginValue + ":"
+ gShineSessionId + ":"
+ toLogin + ":"
+ targetMode + ":"
+ targetSessionId + ":"
+ signalType + ":"
+ signalRequestId + ":"
+ String((unsigned long long)timeMs) + ":"
+ dataSha256B64;
uint8_t clientSignature[64] = {};
std::vector<uint8_t> clientMessage(reinterpret_cast<const uint8_t *>(clientPreimage.c_str()),
reinterpret_cast<const uint8_t *>(clientPreimage.c_str()) + clientPreimage.length());
if (!signMessageEd25519(clientMessage, clientSec, clientSignature)) {
errorOut = "client_signal_sign_failed";
return false;
}
clientSignatureB64Out = bytesToBase64String(clientSignature, sizeof(clientSignature));
return true;
}
static bool sendSignalResponse(const String &toLogin,
const String &targetSessionId,
const String &signalType,
const String &signalRequestId,
const String &responseData) {
String response;
uint64_t timeMs = shineNowMs();
String sessionSignatureB64;
String clientSignatureB64;
String error;
if (!buildSendSignalSignatures(toLogin,
"single_session",
targetSessionId,
signalType,
signalRequestId,
responseData,
timeMs,
false,
sessionSignatureB64,
clientSignatureB64,
error)) {
return false;
}
String req = String("{\"toLogin\":\"") + jsonEscape(toLogin)
+ "\",\"targetMode\":\"single_session\""
+ ",\"targetSessionId\":\"" + jsonEscape(targetSessionId)
+ "\",\"signalType\":\"" + jsonEscape(signalType)
+ "\",\"signalRequestId\":\"" + jsonEscape(signalRequestId)
+ "\",\"data\":\"" + jsonEscape(responseData)
+ "\",\"timeMs\":" + String((unsigned long long)timeMs)
+ ",\"sessionSignatureB64\":\"" + jsonEscape(sessionSignatureB64)
+ "\",\"clientSignatureB64\":\"" + jsonEscape(clientSignatureB64) + "\"}";
return shineWsRequest(gShineWs, "SendSignal", req, response, SHINE_RPC_TIMEOUT_MS);
}
static void queueWalletSignRequest(const PendingWalletRpcRequest &item,
const String &requestId,
const String &publicKeyBase58,
@@ -4075,6 +4225,7 @@ static bool shineWsRequest(SimpleWebSocketClient &ws, const String &op, const St
return true;
}
queueIncomingWalletRpcRequest(frame);
queueIncomingRemoteAddBlockRequest(frame);
}
return false;
}
@@ -4121,6 +4272,50 @@ static bool queueIncomingWalletRpcRequest(const String &frame) {
return true;
}
static bool queueIncomingRemoteAddBlockRequest(const String &frame) {
if (frame.indexOf("\"op\":\"IncomingSignal\"") < 0 || frame.indexOf("\"event\":true") < 0) {
return false;
}
int payloadPos = frame.indexOf("\"payload\":");
if (payloadPos < 0) {
return false;
}
int objectStart = frame.indexOf('{', payloadPos);
if (objectStart < 0) {
return false;
}
int objectEnd = -1;
String payloadJson;
if (!extractJsonObjectAt(frame, objectStart, objectEnd, payloadJson)) {
return false;
}
String signalType;
if (!jsonStringField(payloadJson, "signalType", signalType)) {
return false;
}
if (signalType != "remote_addblock_request") {
return false;
}
PendingRemoteAddBlockRequest item;
jsonStringField(payloadJson, "fromLogin", item.fromLogin);
jsonStringField(payloadJson, "fromSessionId", item.fromSessionId);
jsonStringField(payloadJson, "signalRequestId", item.signalRequestId);
jsonStringField(payloadJson, "data", item.data);
jsonStringField(payloadJson, "sessionSignatureB64", item.sessionSignatureB64);
jsonStringField(payloadJson, "clientSignatureB64", item.clientSignatureB64);
uint64_t timeMs = 0;
jsonInt64Field(payloadJson, "timeMs", timeMs);
item.timeMs = timeMs;
item.signalType = signalType;
if (item.fromLogin.isEmpty() || item.fromSessionId.isEmpty() || item.signalRequestId.isEmpty()) {
return false;
}
gPendingRemoteAddBlockRequests.push_back(item);
return true;
}
static void processPendingWalletRpcRequests() {
if (gPendingWalletRpcRequests.empty() || !gShineAuthenticated || !gShineWs.connected || !gShineWs.client.connected()) {
return;
@@ -4180,6 +4375,119 @@ static void processPendingWalletRpcRequests() {
}
}
static void processPendingRemoteAddBlockRequests() {
if (gPendingRemoteAddBlockRequests.empty() || !gShineAuthenticated || !gShineWs.connected || !gShineWs.client.connected()) {
return;
}
while (!gPendingRemoteAddBlockRequests.empty()) {
PendingRemoteAddBlockRequest item = gPendingRemoteAddBlockRequests.front();
gPendingRemoteAddBlockRequests.erase(gPendingRemoteAddBlockRequests.begin());
String responseData;
String requestLogin;
String blockchainName;
String prevBlockHash;
String blockPreimageB64;
uint64_t blockNumberU64 = 0;
if (item.fromLogin != gLoginValue) {
responseData = String("{\"ok\":false,\"error\":\"forbidden_login\",\"errorMessage\":\"Signal login mismatch\",\"requestId\":\"")
+ jsonEscape(item.signalRequestId) + "\"}";
sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData);
continue;
}
if (item.sessionSignatureB64.isEmpty() || item.clientSignatureB64.isEmpty()) {
responseData = String("{\"ok\":false,\"error\":\"missing_required_signatures\",\"errorMessage\":\"Client and session signatures are required\",\"requestId\":\"")
+ jsonEscape(item.signalRequestId) + "\"}";
sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData);
continue;
}
jsonStringField(item.data, "login", requestLogin);
jsonStringField(item.data, "blockchainName", blockchainName);
jsonStringField(item.data, "prevBlockHash", prevBlockHash);
jsonStringField(item.data, "blockPreimageB64", blockPreimageB64);
jsonInt64Field(item.data, "blockNumber", blockNumberU64);
if (requestLogin != gLoginValue || blockchainName.isEmpty() || blockPreimageB64.isEmpty() || prevBlockHash.isEmpty()) {
responseData = String("{\"ok\":false,\"error\":\"bad_request\",\"errorMessage\":\"Missing required AddBlock fields\",\"requestId\":\"")
+ jsonEscape(item.signalRequestId) + "\"}";
sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData);
continue;
}
std::vector<uint8_t> preimage;
if (!base64DecodeStd(blockPreimageB64, preimage) || preimage.empty()) {
responseData = String("{\"ok\":false,\"error\":\"bad_preimage_base64\",\"errorMessage\":\"Invalid AddBlock preimage base64\",\"requestId\":\"")
+ jsonEscape(item.signalRequestId) + "\"}";
sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData);
continue;
}
uint8_t blockchainSeed[32] = {};
uint8_t blockchainPub[32] = {};
uint8_t blockchainSec[64] = {};
if (!deriveSeedKeypairFromBase58(gBlockchainPrivB58, blockchainSeed, blockchainPub, blockchainSec)) {
responseData = String("{\"ok\":false,\"error\":\"blockchain_key_unavailable\",\"errorMessage\":\"Blockchain key is unavailable on homeserver\",\"requestId\":\"")
+ jsonEscape(item.signalRequestId) + "\"}";
sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData);
continue;
}
uint8_t hash32[32] = {};
sha256calc(preimage.data(), preimage.size(), hash32);
uint8_t signature[64] = {};
if (!signMessageEd25519(std::vector<uint8_t>(hash32, hash32 + 32), blockchainSec, signature)) {
responseData = String("{\"ok\":false,\"error\":\"sign_failed\",\"errorMessage\":\"Failed to sign AddBlock hash\",\"requestId\":\"")
+ jsonEscape(item.signalRequestId) + "\"}";
sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData);
continue;
}
std::vector<uint8_t> fullBlock = preimage;
fullBlock.push_back(0x01);
fullBlock.push_back(0x00);
fullBlock.insert(fullBlock.end(), signature, signature + 64);
String addBlockReq = String("{\"blockchainName\":\"") + jsonEscape(blockchainName)
+ "\",\"blockNumber\":" + String((unsigned long long)blockNumberU64)
+ ",\"prevBlockHash\":\"" + jsonEscape(prevBlockHash)
+ "\",\"blockBytesB64\":\"" + jsonEscape(bytesToBase64String(fullBlock.data(), fullBlock.size())) + "\"}";
String addBlockResp;
bool addBlockOk = shineWsRequest(gShineWs, "AddBlock", addBlockReq, addBlockResp, SHINE_RPC_TIMEOUT_MS);
uint64_t statusCode = 0;
jsonInt64Field(addBlockResp, "status", statusCode);
String serverLastHash;
String errorCode;
String errorMessage;
uint64_t serverLastNumber = 0;
jsonStringField(addBlockResp, "serverLastGlobalHash", serverLastHash);
jsonStringField(addBlockResp, "code", errorCode);
jsonStringField(addBlockResp, "message", errorMessage);
jsonInt64Field(addBlockResp, "serverLastGlobalNumber", serverLastNumber);
if (!addBlockOk || statusCode != 200) {
if (errorCode.isEmpty()) {
errorCode = addBlockOk ? "addblock_rejected" : "addblock_request_failed";
}
if (errorMessage.isEmpty()) {
errorMessage = addBlockOk ? "AddBlock rejected by server" : "AddBlock request failed";
}
responseData = String("{\"ok\":false,\"error\":\"") + jsonEscape(errorCode)
+ "\",\"errorMessage\":\"" + jsonEscape(errorMessage)
+ "\",\"requestId\":\"" + jsonEscape(item.signalRequestId)
+ "\",\"serverLastGlobalNumber\":" + String((unsigned long long)serverLastNumber)
+ ",\"serverLastGlobalHash\":\"" + jsonEscape(serverLastHash) + "\"}";
sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData);
continue;
}
responseData = String("{\"ok\":true,\"requestId\":\"") + jsonEscape(item.signalRequestId)
+ "\",\"serverLastGlobalNumber\":" + String((unsigned long long)serverLastNumber)
+ ",\"serverLastGlobalHash\":\"" + jsonEscape(serverLastHash) + "\"}";
sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData);
}
}
static void pumpShineIncomingFrames(uint32_t maxFrames) {
if (!gShineAuthenticated || !gShineWs.connected || !gShineWs.client.connected()) {
return;
@@ -4193,6 +4501,7 @@ static void pumpShineIncomingFrames(uint32_t maxFrames) {
break;
}
queueIncomingWalletRpcRequest(frame);
queueIncomingRemoteAddBlockRequest(frame);
}
}
@@ -4748,6 +5057,7 @@ static void manageShineConnection() {
pumpShineIncomingFrames();
processPendingWalletRpcRequests();
processPendingRemoteAddBlockRequests();
}
static void upsertKnownWifi(const String &ssid, const String &password) {