Implements NIZK (Non-Interactive Zero-Knowledge) proof protocol using Blum's Hamiltonian Cycle construction with Fiat-Shamir transformation. Features: - Complete C99 library with SHA3-256 commitments (via OpenSSL) - Graph generation with embedded trapdoor (Hamiltonian cycle) - NIZK proof generation and verification - Binary serialization for proofs, graphs, and cycles - CLI tools: zkp_keygen, zkp_prover, zkp_verifier - Comprehensive test suite (41 tests) Security properties: - Completeness: honest prover always convinces verifier - Soundness: cheater fails with probability >= 1 - 2^(-128) - Zero-Knowledge: verifier learns nothing about the secret cycle Target: OpenWrt ARM (SecuBox authentication module) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
345 lines
8.3 KiB
C
345 lines
8.3 KiB
C
/**
|
|
* @file test_graph.c
|
|
* @brief Tests for graph operations
|
|
* @version 1.0
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
* Copyright (C) 2026 CyberMind.FR / SecuBox
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "zkp_types.h"
|
|
#include "zkp_graph.h"
|
|
#include "zkp_crypto.h"
|
|
|
|
/* ============== Test Framework ============== */
|
|
|
|
static int tests_run = 0;
|
|
static int tests_passed = 0;
|
|
|
|
#define TEST(name) \
|
|
do { \
|
|
tests_run++; \
|
|
printf(" [%02d] %-50s ", tests_run, name); \
|
|
fflush(stdout); \
|
|
} while(0)
|
|
|
|
#define PASS() \
|
|
do { \
|
|
tests_passed++; \
|
|
printf("\033[32mPASS\033[0m\n"); \
|
|
} while(0)
|
|
|
|
#define FAIL(msg) \
|
|
do { \
|
|
printf("\033[31mFAIL\033[0m (%s)\n", msg); \
|
|
return 1; \
|
|
} while(0)
|
|
|
|
#define ASSERT(cond, msg) \
|
|
do { \
|
|
if (!(cond)) { FAIL(msg); } \
|
|
} while(0)
|
|
|
|
/* ============== Tests ============== */
|
|
|
|
static int test_graph_init(void)
|
|
{
|
|
TEST("Graph init: creates empty graph");
|
|
|
|
Graph G;
|
|
zkp_graph_init(&G, 10);
|
|
|
|
ASSERT(G.n == 10, "Wrong n");
|
|
ASSERT(zkp_graph_edge_count(&G) == 0, "Not empty");
|
|
|
|
PASS();
|
|
return 0;
|
|
}
|
|
|
|
static int test_graph_add_edge(void)
|
|
{
|
|
TEST("Graph add edge: adds undirected edge");
|
|
|
|
Graph G;
|
|
zkp_graph_init(&G, 10);
|
|
|
|
zkp_graph_add_edge(&G, 2, 5);
|
|
|
|
ASSERT(zkp_graph_has_edge(&G, 2, 5) == true, "Edge not found (2,5)");
|
|
ASSERT(zkp_graph_has_edge(&G, 5, 2) == true, "Edge not found (5,2)");
|
|
ASSERT(zkp_graph_edge_count(&G) == 1, "Wrong edge count");
|
|
|
|
PASS();
|
|
return 0;
|
|
}
|
|
|
|
static int test_graph_has_edge_symmetry(void)
|
|
{
|
|
TEST("Graph has edge: symmetric for undirected");
|
|
|
|
Graph G;
|
|
zkp_graph_init(&G, 20);
|
|
|
|
for (uint8_t i = 0; i < 10; i++) {
|
|
zkp_graph_add_edge(&G, i, (uint8_t)(i + 5));
|
|
}
|
|
|
|
for (uint8_t i = 0; i < 10; i++) {
|
|
uint8_t u = i;
|
|
uint8_t v = (uint8_t)(i + 5);
|
|
ASSERT(zkp_graph_has_edge(&G, u, v) == zkp_graph_has_edge(&G, v, u),
|
|
"Not symmetric");
|
|
}
|
|
|
|
PASS();
|
|
return 0;
|
|
}
|
|
|
|
static int test_generate_graph_n20(void)
|
|
{
|
|
TEST("Generate graph n=20: creates valid graph with cycle");
|
|
|
|
Graph G;
|
|
HamiltonianCycle H;
|
|
|
|
ZKPResult res = zkp_generate_graph(20, 1.0, &G, &H);
|
|
ASSERT(res == ZKP_OK, "Generation failed");
|
|
|
|
ASSERT(G.n == 20, "Wrong graph size");
|
|
ASSERT(H.n == 20, "Wrong cycle size");
|
|
ASSERT(zkp_graph_is_connected(&G) == true, "Not connected");
|
|
ASSERT(zkp_validate_hamiltonian_cycle(&G, &H) == true, "Invalid cycle");
|
|
|
|
/* Should have at least 20 edges (the cycle) */
|
|
ASSERT(zkp_graph_edge_count(&G) >= 20, "Too few edges");
|
|
|
|
PASS();
|
|
return 0;
|
|
}
|
|
|
|
static int test_generate_graph_n50(void)
|
|
{
|
|
TEST("Generate graph n=50: creates valid graph with cycle");
|
|
|
|
Graph G;
|
|
HamiltonianCycle H;
|
|
|
|
ZKPResult res = zkp_generate_graph(50, 0.8, &G, &H);
|
|
ASSERT(res == ZKP_OK, "Generation failed");
|
|
|
|
ASSERT(G.n == 50, "Wrong graph size");
|
|
ASSERT(H.n == 50, "Wrong cycle size");
|
|
ASSERT(zkp_graph_is_connected(&G) == true, "Not connected");
|
|
ASSERT(zkp_validate_hamiltonian_cycle(&G, &H) == true, "Invalid cycle");
|
|
|
|
PASS();
|
|
return 0;
|
|
}
|
|
|
|
static int test_graph_is_connected(void)
|
|
{
|
|
TEST("Graph is connected: detects connectivity");
|
|
|
|
Graph G;
|
|
zkp_graph_init(&G, 5);
|
|
|
|
/* Initially disconnected (no edges) */
|
|
ASSERT(zkp_graph_is_connected(&G) == false, "Empty graph connected");
|
|
|
|
/* Add edges to make connected: 0-1-2-3-4 */
|
|
zkp_graph_add_edge(&G, 0, 1);
|
|
zkp_graph_add_edge(&G, 1, 2);
|
|
zkp_graph_add_edge(&G, 2, 3);
|
|
zkp_graph_add_edge(&G, 3, 4);
|
|
|
|
ASSERT(zkp_graph_is_connected(&G) == true, "Path graph not connected");
|
|
|
|
PASS();
|
|
return 0;
|
|
}
|
|
|
|
static int test_validate_hamiltonian_cycle_valid(void)
|
|
{
|
|
TEST("Validate cycle: accepts valid cycle");
|
|
|
|
Graph G;
|
|
HamiltonianCycle H;
|
|
|
|
/* Create a simple cycle graph: 0-1-2-3-4-0 */
|
|
zkp_graph_init(&G, 5);
|
|
zkp_graph_add_edge(&G, 0, 1);
|
|
zkp_graph_add_edge(&G, 1, 2);
|
|
zkp_graph_add_edge(&G, 2, 3);
|
|
zkp_graph_add_edge(&G, 3, 4);
|
|
zkp_graph_add_edge(&G, 4, 0);
|
|
|
|
H.n = 5;
|
|
H.nodes[0] = 0;
|
|
H.nodes[1] = 1;
|
|
H.nodes[2] = 2;
|
|
H.nodes[3] = 3;
|
|
H.nodes[4] = 4;
|
|
|
|
ASSERT(zkp_validate_hamiltonian_cycle(&G, &H) == true, "Valid cycle rejected");
|
|
|
|
PASS();
|
|
return 0;
|
|
}
|
|
|
|
static int test_validate_hamiltonian_cycle_invalid(void)
|
|
{
|
|
TEST("Validate cycle: rejects invalid cycle");
|
|
|
|
Graph G;
|
|
HamiltonianCycle H;
|
|
|
|
/* Create path (not cycle): 0-1-2-3-4 */
|
|
zkp_graph_init(&G, 5);
|
|
zkp_graph_add_edge(&G, 0, 1);
|
|
zkp_graph_add_edge(&G, 1, 2);
|
|
zkp_graph_add_edge(&G, 2, 3);
|
|
zkp_graph_add_edge(&G, 3, 4);
|
|
/* Missing edge 4-0 */
|
|
|
|
H.n = 5;
|
|
H.nodes[0] = 0;
|
|
H.nodes[1] = 1;
|
|
H.nodes[2] = 2;
|
|
H.nodes[3] = 3;
|
|
H.nodes[4] = 4;
|
|
|
|
ASSERT(zkp_validate_hamiltonian_cycle(&G, &H) == false, "Invalid cycle accepted");
|
|
|
|
PASS();
|
|
return 0;
|
|
}
|
|
|
|
static int test_validate_hamiltonian_cycle_duplicate(void)
|
|
{
|
|
TEST("Validate cycle: rejects duplicate nodes");
|
|
|
|
Graph G;
|
|
HamiltonianCycle H;
|
|
|
|
zkp_graph_init(&G, 5);
|
|
/* Complete graph - all edges exist */
|
|
for (uint8_t i = 0; i < 5; i++) {
|
|
for (uint8_t j = (uint8_t)(i + 1); j < 5; j++) {
|
|
zkp_graph_add_edge(&G, i, j);
|
|
}
|
|
}
|
|
|
|
/* Cycle with duplicate node */
|
|
H.n = 5;
|
|
H.nodes[0] = 0;
|
|
H.nodes[1] = 1;
|
|
H.nodes[2] = 2;
|
|
H.nodes[3] = 1; /* Duplicate! */
|
|
H.nodes[4] = 4;
|
|
|
|
ASSERT(zkp_validate_hamiltonian_cycle(&G, &H) == false, "Duplicate accepted");
|
|
|
|
PASS();
|
|
return 0;
|
|
}
|
|
|
|
static int test_permute_preserves_structure(void)
|
|
{
|
|
TEST("Graph permute: preserves edge structure");
|
|
|
|
Graph G, Gprime;
|
|
uint8_t perm[ZKP_MAX_N] = {0};
|
|
uint8_t n = 10;
|
|
|
|
/* Create test graph */
|
|
zkp_graph_init(&G, n);
|
|
zkp_graph_add_edge(&G, 0, 1);
|
|
zkp_graph_add_edge(&G, 1, 2);
|
|
zkp_graph_add_edge(&G, 2, 3);
|
|
zkp_graph_add_edge(&G, 3, 0);
|
|
|
|
uint32_t orig_edges = zkp_graph_edge_count(&G);
|
|
|
|
/* Generate random permutation */
|
|
ASSERT(zkp_random_permutation(perm, n) == ZKP_OK, "Permutation failed");
|
|
|
|
/* Apply permutation */
|
|
zkp_graph_permute(&G, perm, &Gprime);
|
|
|
|
/* Should have same number of edges */
|
|
ASSERT(zkp_graph_edge_count(&Gprime) == orig_edges, "Edge count changed");
|
|
|
|
/* Verify edge mapping: if (u,v) in G, then (perm[u], perm[v]) in G' */
|
|
for (uint8_t u = 0; u < n; u++) {
|
|
for (uint8_t v = (uint8_t)(u + 1); v < n; v++) {
|
|
bool orig = zkp_graph_has_edge(&G, u, v);
|
|
bool perm_edge = zkp_graph_has_edge(&Gprime, perm[u], perm[v]);
|
|
ASSERT(orig == perm_edge, "Edge mapping wrong");
|
|
}
|
|
}
|
|
|
|
PASS();
|
|
return 0;
|
|
}
|
|
|
|
static int test_permute_preserves_cycle(void)
|
|
{
|
|
TEST("Permute preserves cycle: π(H) valid in π(G)");
|
|
|
|
Graph G, Gprime;
|
|
HamiltonianCycle H, Hprime;
|
|
uint8_t perm[ZKP_MAX_N];
|
|
uint8_t n = 20;
|
|
|
|
/* Generate graph with cycle */
|
|
ASSERT(zkp_generate_graph(n, 0.5, &G, &H) == ZKP_OK, "Gen failed");
|
|
|
|
/* Generate permutation */
|
|
ASSERT(zkp_random_permutation(perm, n) == ZKP_OK, "Perm failed");
|
|
|
|
/* Apply permutation to both */
|
|
zkp_graph_permute(&G, perm, &Gprime);
|
|
zkp_cycle_permute(&H, perm, &Hprime);
|
|
|
|
/* Permuted cycle should be valid in permuted graph */
|
|
ASSERT(zkp_validate_hamiltonian_cycle(&Gprime, &Hprime) == true,
|
|
"Permuted cycle invalid");
|
|
|
|
PASS();
|
|
return 0;
|
|
}
|
|
|
|
/* ============== Main ============== */
|
|
|
|
int main(void)
|
|
{
|
|
printf("\n=== ZKP Graph Tests ===\n\n");
|
|
|
|
int result = 0;
|
|
|
|
result |= test_graph_init();
|
|
result |= test_graph_add_edge();
|
|
result |= test_graph_has_edge_symmetry();
|
|
result |= test_generate_graph_n20();
|
|
result |= test_generate_graph_n50();
|
|
result |= test_graph_is_connected();
|
|
result |= test_validate_hamiltonian_cycle_valid();
|
|
result |= test_validate_hamiltonian_cycle_invalid();
|
|
result |= test_validate_hamiltonian_cycle_duplicate();
|
|
result |= test_permute_preserves_structure();
|
|
result |= test_permute_preserves_cycle();
|
|
|
|
printf("\n");
|
|
printf("Tests: %d/%d passed\n", tests_passed, tests_run);
|
|
|
|
if (tests_passed == tests_run) {
|
|
printf("\033[32m✓ All tests passed!\033[0m\n\n");
|
|
return 0;
|
|
} else {
|
|
printf("\033[31m✗ Some tests failed\033[0m\n\n");
|
|
return 1;
|
|
}
|
|
}
|