db_sync: datahash removed, Ed25519 signature mandatory, PK=(timestamp,author_signature), chain_hash includes signature, signed message=ts||json
This commit is contained in:
1 parent
05b24d36bb
commit
f0bb03c2cf
9 files changed
+640
-1017
No files matched your search
+500
-936
File diff suppressed because it is too large.
Load diff
+22
-6
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in new issue
Block a user