From e45401449e962434395fe3fd10edf6cac0210521 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sun, 18 Oct 2026 02:10:11 +0200 Subject: [PATCH] Share complete membership verification for private grants --- src/chat/chat_feedback.c | 49 ++++------------------------------------ src/chat/member_sync.c | 43 +++++++++++++++++++++++++++++++++++ src/chat/member_sync.h | 3 +++ 3 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/chat/chat_feedback.c b/src/chat/chat_feedback.c index 6c55ee02..a332389e 100644 --- a/src/chat/chat_feedback.c +++ b/src/chat/chat_feedback.c @@ -102,48 +102,7 @@ static int fb_key(uint64_t channel, const struct chat_feedback_record* r, merkle return 0; } -/* Полный мембер, доверенный ключ узла, join_sig и подпись дерева приглашений обязательны. */ -static int fb_author_key(struct UTUN_INSTANCE* inst, const char* channel, uint64_t author, uint8_t key[32]) { - char sql[384]; - snprintf(sql, sizeof(sql), "SELECT x25519_pubkey,ed25519_pubkey,signed_by,signature,join_sig,join_ts" - " FROM peers_%s WHERE node_id=? AND source=0", channel); - sqlite3_stmt* st = NULL; sqlite3* db = inst->topo_sqlite_db; - if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return fb_db_error(inst, channel, "author lookup"); - sqlite3_bind_int64(st, 1, (sqlite3_int64)author); - int rc = sqlite3_step(st), ok = 0; - int x_bytes = rc == SQLITE_ROW ? sqlite3_column_bytes(st, 0) : 0, ed_bytes = rc == SQLITE_ROW ? sqlite3_column_bytes(st, 1) : 0; - int tree_bytes = rc == SQLITE_ROW ? sqlite3_column_bytes(st, 3) : 0, join_bytes = rc == SQLITE_ROW ? sqlite3_column_bytes(st, 4) : 0; - const char* reason = "member absent"; - if (rc != SQLITE_ROW) goto done; - reason = "invalid member fields"; - if (x_bytes != 32 || ed_bytes != 32 || tree_bytes != 64 - || join_bytes != 64 || sqlite3_column_int64(st, 5) <= 0) goto done; - struct ms_member_rec member = { .node_id = author, .x25519 = sqlite3_column_blob(st, 0), - .ed25519 = sqlite3_column_blob(st, 1), .signed_by = (uint64_t)sqlite3_column_int64(st, 2), - .signature = sqlite3_column_blob(st, 3), .join_sig = sqlite3_column_blob(st, 4), - .join_ts = (uint64_t)sqlite3_column_int64(st, 5) }; - reason = "node_id/X25519 mismatch"; - if (sc_derive_node_id_from_pubkey(member.x25519) != author) goto done; - uint8_t trusted[32], x[32], ed[32], body[112]; - reason = "trusted Ed25519 key unavailable"; - if (topo_node_sqlite_get_ed25519_pubkey(db, author, trusted) != 0) goto done; - reason = "trusted Ed25519 key mismatch"; - if (memcmp(trusted, member.ed25519, 32)) goto done; - reason = "channel keys unavailable"; - if (topo_node_sqlite_channel_get(db, channel, NULL, 0, NULL, x, ed, NULL) != 0) goto done; - reason = "invalid join signature"; - int bytes = member_sync_build_join_msg(x, ed, author, member.x25519, member.join_ts, body, sizeof(body)); - if (bytes != 112 || sc_ed25519_verify(member.ed25519, body, bytes, member.join_sig) != SC_OK) goto done; - reason = "invalid or unresolved invitation signature"; - if (member_sync_verify_signature(db, channel, &member) != 1) goto done; - memcpy(key, member.ed25519, 32); ok = 1; -done: - sqlite3_finalize(st); - if (ok) return 0; - DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, - "feedback: author not verified ch=%s node=%016llx reason=%s rc=%d x_bytes=%d ed_bytes=%d tree_bytes=%d join_bytes=%d", - channel, (unsigned long long)author, reason, rc, x_bytes, ed_bytes, tree_bytes, join_bytes); return -1; -} + /* Создаёт одну таблицу подписанных состояний и индекс обхода листьев. */ int chat_feedback_ensure_channel(struct UTUN_INSTANCE* inst, const char* channel) { @@ -197,7 +156,7 @@ static int fb_apply_row(struct UTUN_INSTANCE* inst, const char* channel, const s char table[40], sql[512]; uint64_t id; uint8_t body[FB_WIRE_MAX + 32], key[32]; if (fb_table(inst, channel, table, &id) < 0) return -1; int bytes = fb_signed_bytes(id, r, body); - if (bytes < 0 || fb_author_key(inst, channel, r->node_id, key) < 0) return -1; + if (bytes < 0 || member_sync_verified_ed25519(inst, channel, r->node_id, key) < 0) return -1; if (sc_ed25519_verify(key, body, bytes, r->signature) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "feedback: invalid signature ch=%s node=%016llx kind=%u ver=%llu", channel, (unsigned long long)r->node_id, r->kind, (unsigned long long)r->ver); return -1; @@ -395,7 +354,7 @@ static int fb_apply_page(void* ctx, const char* channel, uint64_t peer, const ui static int fb_peer(void* ctx, const char* channel, uint64_t peer) { struct UTUN_INSTANCE* inst = ctx; uint8_t key[32]; - return fb_author_key(inst, channel, peer, key) == 0; + return member_sync_verified_ed25519(inst, channel, peer, key) == 0; } static const struct merkle_sync_data_ops fb_ops = { @@ -422,7 +381,7 @@ int chat_feedback_init(struct UTUN_INSTANCE* inst) { while ((rc = sqlite3_step(st)) == SQLITE_ROW) { struct chat_feedback_record r; uint64_t id; uint8_t body[FB_WIRE_MAX + 32], key[32]; merkle_key_t record_key; if (fb_read_row(st, &r) < 0 || fb_table(inst, channel, table, &id) < 0 - || fb_author_key(inst, channel, r.node_id, key) < 0) { result = -1; break; } + || member_sync_verified_ed25519(inst, channel, r.node_id, key) < 0) { result = -1; break; } int bytes = fb_signed_bytes(id, &r, body); if (bytes < 0 || sc_ed25519_verify(key, body, bytes, r.signature) != SC_OK || fb_key(id, &r, &record_key) < 0 || merkle_sync_recompute_path(inst, MT_DATA_FEEDBACK, channel, fb_read64(record_key.bytes)) < 0) { diff --git a/src/chat/member_sync.c b/src/chat/member_sync.c index 333a0ac1..0430fc60 100644 --- a/src/chat/member_sync.c +++ b/src/chat/member_sync.c @@ -1211,3 +1211,46 @@ int member_sync_count(struct UTUN_INSTANCE* inst, const char* ch_id) { sqlite3_finalize(stmt); return c; } + +/* Полный мембер, доверенный ключ узла, join_sig и подпись дерева приглашений обязательны. */ +int member_sync_verified_ed25519(struct UTUN_INSTANCE* inst, const char* channel, uint64_t author, uint8_t key[32]) { + char sql[384]; + snprintf(sql, sizeof(sql), "SELECT x25519_pubkey,ed25519_pubkey,signed_by,signature,join_sig,join_ts" + " FROM peers_%s WHERE node_id=? AND source=0", channel); + sqlite3_stmt* st = NULL; sqlite3* db = inst->topo_sqlite_db; + if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "verified member lookup: %s", sqlite3_errmsg(db)); return -1; } + sqlite3_bind_int64(st, 1, (sqlite3_int64)author); + int rc = sqlite3_step(st), ok = 0; + int x_bytes = rc == SQLITE_ROW ? sqlite3_column_bytes(st, 0) : 0, ed_bytes = rc == SQLITE_ROW ? sqlite3_column_bytes(st, 1) : 0; + int tree_bytes = rc == SQLITE_ROW ? sqlite3_column_bytes(st, 3) : 0, join_bytes = rc == SQLITE_ROW ? sqlite3_column_bytes(st, 4) : 0; + const char* reason = "member absent"; + if (rc != SQLITE_ROW) goto done; + reason = "invalid member fields"; + if (x_bytes != 32 || ed_bytes != 32 || tree_bytes != 64 + || join_bytes != 64 || sqlite3_column_int64(st, 5) <= 0) goto done; + struct ms_member_rec member = { .node_id = author, .x25519 = sqlite3_column_blob(st, 0), + .ed25519 = sqlite3_column_blob(st, 1), .signed_by = (uint64_t)sqlite3_column_int64(st, 2), + .signature = sqlite3_column_blob(st, 3), .join_sig = sqlite3_column_blob(st, 4), + .join_ts = (uint64_t)sqlite3_column_int64(st, 5) }; + reason = "node_id/X25519 mismatch"; + if (sc_derive_node_id_from_pubkey(member.x25519) != author) goto done; + uint8_t trusted[32], x[32], ed[32], body[112]; + reason = "trusted Ed25519 key unavailable"; + if (topo_node_sqlite_get_ed25519_pubkey(db, author, trusted) != 0) goto done; + reason = "trusted Ed25519 key mismatch"; + if (memcmp(trusted, member.ed25519, 32)) goto done; + reason = "channel keys unavailable"; + if (topo_node_sqlite_channel_get(db, channel, NULL, 0, NULL, x, ed, NULL) != 0) goto done; + reason = "invalid join signature"; + int bytes = member_sync_build_join_msg(x, ed, author, member.x25519, member.join_ts, body, sizeof(body)); + if (bytes != 112 || sc_ed25519_verify(member.ed25519, body, bytes, member.join_sig) != SC_OK) goto done; + reason = "invalid or unresolved invitation signature"; + if (member_sync_verify_signature(db, channel, &member) != 1) goto done; + memcpy(key, member.ed25519, 32); ok = 1; +done: + sqlite3_finalize(st); + if (ok) return 0; + DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, + "member verification failed ch=%s node=%016llx reason=%s rc=%d x_bytes=%d ed_bytes=%d tree_bytes=%d join_bytes=%d", + channel, (unsigned long long)author, reason, rc, x_bytes, ed_bytes, tree_bytes, join_bytes); return -1; +} diff --git a/src/chat/member_sync.h b/src/chat/member_sync.h index 565b3403..6d62629a 100644 --- a/src/chat/member_sync.h +++ b/src/chat/member_sync.h @@ -25,6 +25,9 @@ struct TOPO_GROUP; struct sqlite3; /* Результат применения рекорда мембера (compare+update по версиям двух блоков). */ +/* Полная проверка членства, join и дерева приглашений; возвращает доверенный ключ, 0/-1. */ +int member_sync_verified_ed25519(struct UTUN_INSTANCE* inst, const char* channel, uint64_t author, uint8_t key[32]); + #define MS_APPLY_CHANGED 0x01 /* запись и дерево обновлены */ #define MS_APPLY_STALE 0x02 /* локальная версия новее; обратный проход доставит её пиру */