db_sync: datahash removed, Ed25519 signature mandatory, PK=(timestamp,author_signature), chain_hash includes signature, signed message=ts||json

This commit is contained in:
Evgeny committed 2026-07-17 15:44:19 +03:00
1 parent 05b24d36bb
commit f0bb03c2cf
9 files changed
+640 -1017

No files matched your search

+500 -936
View File
File diff suppressed because it is too large. Load diff
+22 -6
View File
@@ -3,13 +3,21 @@
// Назначение: децентрализованная реплицируемая таблица JSON-записей между всеми узлами сети.
// Модуль поддерживает несколько независимых инстансов (таблиц), каждый идентифицируется парой (name, id).
//
// Каждая запись ОБЯЗАТЕЛЬНО содержит Ed25519-подпись автора.
// chain_hash[pos] = SHA256(chain_hash[pos-1] || id || timestamp || author || author_signature[64])
// Первичный ключ: (timestamp, author) — тот же автор в ту же ms = дубликат.
// Упорядочение: ORDER BY timestamp, author.
//
// Использование:
// 1. Включить в конфиге: db_sync_enabled = 1
// Опционально: db_sync_ttl = 86400 (по умолчанию)
// 2. db_sync_init() — вызывается автоматически при старте utun_instance
// 3. struct DB_SYNC_INSTANCE* si = db_sync_instance_add(inst, "chats", 1);
// Создаёт/регистрирует инстанс. При поднятии соединений автоматически запускает sync.
// 4. db_sync_insert(si, json_data) — вставить запись. Автоматически PUSH всем synced-пирам.
// 4. uint64_t ts = db_sync_next_timestamp(si);
// sig = Ed25519(ts || my_node_id || json_data)
// db_sync_insert_signed(si, json_data, len, sig, 64, ts) — вставить запись.
// sig=NULL → ошибка.
// 5. db_sync_count(si) — количество записей в локальной БД для этого инстанса
// 6. db_sync_instance_remove(si) — удалить инстанс (таблица БД не удаляется)
// 7. db_sync_destroy() — вызывается автоматически при завершении
@@ -23,9 +31,11 @@
// - Протокол: сравнение chain_hash для поиска расхождений (7 типов сообщений)
// - Новые записи немедленно рассылаются подключённым пирам через PUSH
//
// Wire-формат записи (SEND_DATA/PUSH): [id:8][ts:8][author:8][dlen:4][data][sig_len:1=64][sig:64]
//
// Нюансы:
// - Записи не редактируются и не удаляются явно — только TTL-очистка (per-instance)
// - Дубликаты определяются по (instance_hash, timestamp, datahash)
// - Дубликаты определяются по (timestamp, author)
// - БД хранится в SQLite, путь: <db_path>/sync
// - Синхронизация идёт через etcp_api (service ID 0x20), только прямые P2P соединения
#ifndef DB_SYNC_H
@@ -71,6 +81,9 @@ struct DB_SYNC_INSTANCE;
#define DB_REFINE_HASHES 16
#define DB_SEND_DATA_MAX 32
// Ed25519 signature size
#define DB_SIG_SIZE 64
// Global lifecycle
int db_sync_init(struct UTUN_INSTANCE* inst);
void db_sync_destroy(struct UTUN_INSTANCE* inst);
@@ -80,14 +93,16 @@ struct DB_SYNC_INSTANCE* db_sync_instance_add(struct UTUN_INSTANCE* inst, const
void db_sync_instance_remove(struct DB_SYNC_INSTANCE* si);
// Data operations (per-instance)
int db_sync_insert_len(struct DB_SYNC_INSTANCE* si, const char* json_data, size_t len);
int db_sync_insert(struct DB_SYNC_INSTANCE* si, const char* json_data);
// db_sync_insert_signed: sig MUST be non-NULL, 64 bytes.
// sig = Ed25519(ts[8] || author[8] || json).
// ts — call db_sync_next_timestamp(si) before signing to reserve monotonically increasing timestamp.
int db_sync_insert_signed(struct DB_SYNC_INSTANCE* si, const char* json_data, size_t len,
const uint8_t* sig, size_t sig_len);
const uint8_t* sig, size_t sig_len, uint64_t ts);
uint32_t db_sync_count(struct DB_SYNC_INSTANCE* si);
uint64_t db_sync_get_last_timestamp(struct DB_SYNC_INSTANCE* si);
uint64_t db_sync_next_timestamp(struct DB_SYNC_INSTANCE* si);
// Select: iterate records ordered by (timestamp, datahash), starting at offset, max limit (0=unlimited).
// Select: iterate records ordered by (timestamp, author), starting at offset, max limit (0=unlimited).
// Returns number of records passed to callback.
typedef void (*db_sync_select_cb)(void* arg, uint64_t id, uint64_t timestamp,
const char* data, size_t data_len, uint64_t author,
@@ -99,6 +114,7 @@ int db_sync_select(struct DB_SYNC_INSTANCE* si, uint32_t offset, uint32_t l
// Insert callback: fired after local or peer insert succeeds.
// author_node_id = self for local inserts, peer node_id for remote.
typedef void (*db_sync_insert_cb)(struct DB_SYNC_INSTANCE* si,
uint64_t record_timestamp,
const char* json_data, size_t len,
uint64_t author_node_id, void* arg);
void db_sync_set_insert_cb(struct DB_SYNC_INSTANCE* si, db_sync_insert_cb cb, void* arg);
+41
View File
@@ -526,6 +526,47 @@ void sc_stream_sign_cleanup(struct sc_stream_sign_state *state) {
state->initialized = 0;
}
// ── One-shot Ed25519 helpers (raw keys, any context) ──
sc_status_t sc_ed25519_sign(const uint8_t privkey[32], const uint8_t* msg, size_t msg_len, uint8_t sig_out[64])
{
if (!privkey || !msg || !sig_out) return SC_ERR_INVALID_ARG;
EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, privkey, 32);
if (!pkey) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_PKEY_new_raw_private_key failed"); return SC_ERR_CRYPTO; }
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
int rc = SC_ERR_CRYPTO;
if (ctx) {
size_t slen = 64;
if (EVP_DigestSignInit(ctx, NULL, NULL, NULL, pkey) == 1
&& EVP_DigestSign(ctx, sig_out, &slen, msg, msg_len) == 1)
rc = SC_OK;
else
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_DigestSign failed");
EVP_MD_CTX_free(ctx);
}
EVP_PKEY_free(pkey);
return rc;
}
sc_status_t sc_ed25519_verify(const uint8_t pubkey[32], const uint8_t* msg, size_t msg_len, const uint8_t sig[64])
{
if (!pubkey || !msg || !sig) return SC_ERR_INVALID_ARG;
EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, pubkey, 32);
if (!pkey) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_PKEY_new_raw_public_key failed"); return SC_ERR_CRYPTO; }
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
int rc = SC_ERR_CRYPTO;
if (ctx) {
if (EVP_DigestVerifyInit(ctx, NULL, NULL, NULL, pkey) == 1
&& EVP_DigestVerify(ctx, sig, 64, msg, msg_len) == 1)
rc = SC_OK;
else
rc = SC_ERR_AUTH_FAILED;
EVP_MD_CTX_free(ctx);
}
EVP_PKEY_free(pkey);
return rc;
}
// --- Common crypto utilities ---
sc_status_t sc_sha_transcode(const uint8_t *key, size_t key_len, uint8_t *data, size_t data_len) {
+3
View File
@@ -109,6 +109,9 @@ sc_status_t sc_stream_sign_final(struct sc_stream_sign_state *state, uint8_t *si
sc_status_t sc_stream_sign_verify(struct sc_stream_sign_state *state, const uint8_t *sig, size_t sig_len);
void sc_stream_sign_cleanup(struct sc_stream_sign_state *state);
sc_status_t sc_ed25519_sign(const uint8_t privkey[32], const uint8_t* msg, size_t msg_len, uint8_t sig_out[64]);
sc_status_t sc_ed25519_verify(const uint8_t pubkey[32], const uint8_t* msg, size_t msg_len, const uint8_t sig[64]);
sc_status_t sc_derive_ed25519_pubkey(const uint8_t *x25519_privkey, uint8_t *ed25519_pubkey_out);
uint64_t sc_derive_node_id(const uint8_t *private_key);
+17
View File
@@ -394,3 +394,20 @@ int topo_node_sqlite_node_get_online(sqlite3* db, uint64_t node_id) {
sqlite3_finalize(stmt);
return online;
}
int topo_node_sqlite_get_ed25519_pubkey(sqlite3* db, uint64_t node_id, uint8_t pubkey_out[32])
{
if (!db || !pubkey_out) return -1;
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, "SELECT ed25519_pubkey FROM nodes WHERE node_id=?",
-1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
int rc = -1;
if (sqlite3_step(stmt) == SQLITE_ROW) {
const void* b = sqlite3_column_blob(stmt, 0);
int bytes = sqlite3_column_bytes(stmt, 0);
if (b && bytes >= 32) { memcpy(pubkey_out, b, 32); rc = 0; }
}
sqlite3_finalize(stmt);
return rc;
}
+1
View File
@@ -47,5 +47,6 @@ int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id,
int topo_node_sqlite_node_set_online(sqlite3* db, uint64_t node_id, int online);
int topo_node_sqlite_node_get_online(sqlite3* db, uint64_t node_id);
int topo_node_sqlite_get_ed25519_pubkey(sqlite3* db, uint64_t node_id, uint8_t pubkey_out[32]);
#endif