Share complete membership verification for private grants

This commit is contained in:
evgeny committed 2026-10-18 02:10:11 +02:00
1 parent 6a90d79d04
commit e45401449e
3 files changed
+50 -45

No files matched your search

+4 -45
View File
@@ -102,48 +102,7 @@ static int fb_key(uint64_t channel, const struct chat_feedback_record* r, merkle
return 0; 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) { 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]; 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; if (fb_table(inst, channel, table, &id) < 0) return -1;
int bytes = fb_signed_bytes(id, r, body); 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) { 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", 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; 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) { static int fb_peer(void* ctx, const char* channel, uint64_t peer) {
struct UTUN_INSTANCE* inst = ctx; uint8_t key[32]; 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 = { 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) { 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; 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 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); 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 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) { || merkle_sync_recompute_path(inst, MT_DATA_FEEDBACK, channel, fb_read64(record_key.bytes)) < 0) {
+43
View File
@@ -1211,3 +1211,46 @@ int member_sync_count(struct UTUN_INSTANCE* inst, const char* ch_id) {
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return c; 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;
}
+3
View File
@@ -25,6 +25,9 @@ struct TOPO_GROUP;
struct sqlite3; struct sqlite3;
/* Результат применения рекорда мембера (compare+update по версиям двух блоков). */ /* Результат применения рекорда мембера (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_CHANGED 0x01 /* запись и дерево обновлены */
#define MS_APPLY_STALE 0x02 /* локальная версия новее; обратный проход доставит её пиру */ #define MS_APPLY_STALE 0x02 /* локальная версия новее; обратный проход доставит её пиру */