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>
398 lines
9.4 KiB
C
398 lines
9.4 KiB
C
/**
|
|
* @file zkp_graph.c
|
|
* @brief Graph operations for ZKP Hamiltonian protocol
|
|
* @version 1.0
|
|
*
|
|
* Provides graph manipulation, cycle validation, and trapdoor generation.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
* Copyright (C) 2026 CyberMind.FR / SecuBox
|
|
*/
|
|
|
|
#include "zkp_graph.h"
|
|
#include "zkp_crypto.h"
|
|
#include <string.h>
|
|
|
|
/* ============== Basic Graph Operations ============== */
|
|
|
|
void zkp_graph_init(Graph *G, uint8_t n)
|
|
{
|
|
if (G == NULL || n > ZKP_MAX_N) {
|
|
return;
|
|
}
|
|
|
|
G->n = n;
|
|
memset(G->adj, 0, sizeof(G->adj));
|
|
}
|
|
|
|
void zkp_graph_add_edge(Graph *G, uint8_t u, uint8_t v)
|
|
{
|
|
if (G == NULL || u >= G->n || v >= G->n || u == v) {
|
|
return;
|
|
}
|
|
|
|
/* Set bit v in adj[u] */
|
|
G->adj[u] |= ((uint64_t)1 << v);
|
|
/* Set bit u in adj[v] (undirected) */
|
|
G->adj[v] |= ((uint64_t)1 << u);
|
|
}
|
|
|
|
bool zkp_graph_has_edge(const Graph *G, uint8_t u, uint8_t v)
|
|
{
|
|
if (G == NULL || u >= G->n || v >= G->n) {
|
|
return false;
|
|
}
|
|
|
|
return (G->adj[u] & ((uint64_t)1 << v)) != 0;
|
|
}
|
|
|
|
uint32_t zkp_graph_edge_count(const Graph *G)
|
|
{
|
|
if (G == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t count = 0;
|
|
uint8_t i, j;
|
|
|
|
/* Count edges in upper triangle only (undirected graph) */
|
|
for (i = 0; i < G->n; i++) {
|
|
for (j = (uint8_t)(i + 1); j < G->n; j++) {
|
|
if (zkp_graph_has_edge(G, i, j)) {
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/* ============== Graph Validation ============== */
|
|
|
|
bool zkp_validate_hamiltonian_cycle(const Graph *G, const HamiltonianCycle *H)
|
|
{
|
|
if (G == NULL || H == NULL) {
|
|
return false;
|
|
}
|
|
|
|
/* Check cycle length matches graph size */
|
|
if (H->n != G->n) {
|
|
return false;
|
|
}
|
|
|
|
uint8_t n = H->n;
|
|
if (n == 0 || n > ZKP_MAX_N) {
|
|
return false;
|
|
}
|
|
|
|
/* Check all nodes are distinct using a visited bitmap */
|
|
uint64_t visited_low = 0; /* nodes 0-63 */
|
|
uint64_t visited_high = 0; /* nodes 64-127 */
|
|
uint8_t i;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
uint8_t node = H->nodes[i];
|
|
|
|
if (node >= n) {
|
|
return false; /* Node out of range */
|
|
}
|
|
|
|
/* Check if already visited */
|
|
if (node < 64) {
|
|
if (visited_low & ((uint64_t)1 << node)) {
|
|
return false; /* Duplicate node */
|
|
}
|
|
visited_low |= ((uint64_t)1 << node);
|
|
} else {
|
|
if (visited_high & ((uint64_t)1 << (node - 64))) {
|
|
return false; /* Duplicate node */
|
|
}
|
|
visited_high |= ((uint64_t)1 << (node - 64));
|
|
}
|
|
}
|
|
|
|
/* Check all cycle edges exist in G */
|
|
for (i = 0; i < n; i++) {
|
|
uint8_t u = H->nodes[i];
|
|
uint8_t v = H->nodes[(i + 1) % n];
|
|
|
|
if (!zkp_graph_has_edge(G, u, v)) {
|
|
return false; /* Missing edge in graph */
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool zkp_graph_is_connected(const Graph *G)
|
|
{
|
|
if (G == NULL || G->n == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (G->n == 1) {
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* BFS to check connectivity.
|
|
* Use a simple queue implemented with array.
|
|
*/
|
|
uint8_t n = G->n;
|
|
uint64_t visited_low = 0; /* nodes 0-63 */
|
|
uint64_t visited_high = 0; /* nodes 64-127 */
|
|
uint8_t queue[ZKP_MAX_N];
|
|
uint8_t head = 0, tail = 0;
|
|
uint8_t visited_count = 0;
|
|
|
|
/* Start BFS from node 0 */
|
|
queue[tail++] = 0;
|
|
visited_low |= 1;
|
|
visited_count = 1;
|
|
|
|
while (head < tail) {
|
|
uint8_t current = queue[head++];
|
|
uint8_t neighbor;
|
|
|
|
/* Check all potential neighbors */
|
|
for (neighbor = 0; neighbor < n; neighbor++) {
|
|
if (neighbor == current) {
|
|
continue;
|
|
}
|
|
|
|
if (!zkp_graph_has_edge(G, current, neighbor)) {
|
|
continue;
|
|
}
|
|
|
|
/* Check if neighbor already visited */
|
|
bool already_visited;
|
|
if (neighbor < 64) {
|
|
already_visited = (visited_low & ((uint64_t)1 << neighbor)) != 0;
|
|
} else {
|
|
already_visited = (visited_high & ((uint64_t)1 << (neighbor - 64))) != 0;
|
|
}
|
|
|
|
if (!already_visited) {
|
|
/* Mark as visited and add to queue */
|
|
if (neighbor < 64) {
|
|
visited_low |= ((uint64_t)1 << neighbor);
|
|
} else {
|
|
visited_high |= ((uint64_t)1 << (neighbor - 64));
|
|
}
|
|
queue[tail++] = neighbor;
|
|
visited_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (visited_count == n);
|
|
}
|
|
|
|
/* ============== Permutation Operations ============== */
|
|
|
|
void zkp_graph_permute(const Graph *G, const uint8_t perm[ZKP_MAX_N], Graph *out)
|
|
{
|
|
if (G == NULL || perm == NULL || out == NULL) {
|
|
return;
|
|
}
|
|
|
|
uint8_t n = G->n;
|
|
|
|
/*
|
|
* Apply permutation π to graph G:
|
|
* If edge (u, v) exists in G, then edge (π(u), π(v)) exists in out.
|
|
*/
|
|
zkp_graph_init(out, n);
|
|
|
|
uint8_t u, v;
|
|
for (u = 0; u < n; u++) {
|
|
for (v = (uint8_t)(u + 1); v < n; v++) {
|
|
if (zkp_graph_has_edge(G, u, v)) {
|
|
zkp_graph_add_edge(out, perm[u], perm[v]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void zkp_cycle_permute(const HamiltonianCycle *H, const uint8_t perm[ZKP_MAX_N],
|
|
HamiltonianCycle *out)
|
|
{
|
|
if (H == NULL || perm == NULL || out == NULL) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Apply permutation π to cycle H:
|
|
* out->nodes[i] = π(H->nodes[i])
|
|
*/
|
|
out->n = H->n;
|
|
|
|
uint8_t i;
|
|
for (i = 0; i < H->n; i++) {
|
|
out->nodes[i] = perm[H->nodes[i]];
|
|
}
|
|
}
|
|
|
|
/* ============== Trapdoor Generation ============== */
|
|
|
|
ZKPResult zkp_generate_graph(uint8_t n, double extra_ratio,
|
|
Graph *out_graph, HamiltonianCycle *out_cycle)
|
|
{
|
|
if (out_graph == NULL || out_cycle == NULL) {
|
|
return ZKP_ERR_PARAM;
|
|
}
|
|
|
|
if (n < 3 || n > ZKP_MAX_N) {
|
|
return ZKP_ERR_PARAM;
|
|
}
|
|
|
|
if (extra_ratio < 0.0 || extra_ratio > 10.0) {
|
|
return ZKP_ERR_PARAM;
|
|
}
|
|
|
|
/*
|
|
* Algorithm:
|
|
* 1. Generate random permutation π of {0..n-1}
|
|
* 2. H.nodes = [π[0], π[1], ..., π[n-1]]
|
|
* 3. Initialize G with edges of H
|
|
* 4. Add nb_extra = (int)(n * extra_ratio) random edges as decoys
|
|
* 5. Verify graph is connected (should always be true since H is connected)
|
|
* 6. Verify H is valid Hamiltonian cycle in G
|
|
*/
|
|
|
|
uint8_t perm[ZKP_MAX_N];
|
|
ZKPResult res;
|
|
int attempts = 0;
|
|
const int max_attempts = 10; /* Should rarely need more than 1 */
|
|
|
|
retry:
|
|
if (attempts++ >= max_attempts) {
|
|
return ZKP_ERR;
|
|
}
|
|
|
|
/* Step 1: Generate random permutation */
|
|
res = zkp_random_permutation(perm, n);
|
|
if (res != ZKP_OK) {
|
|
return res;
|
|
}
|
|
|
|
/* Step 2: Create Hamiltonian cycle from permutation */
|
|
out_cycle->n = n;
|
|
uint8_t i;
|
|
for (i = 0; i < n; i++) {
|
|
out_cycle->nodes[i] = perm[i];
|
|
}
|
|
|
|
/* Step 3: Initialize graph with cycle edges */
|
|
zkp_graph_init(out_graph, n);
|
|
for (i = 0; i < n; i++) {
|
|
uint8_t u = out_cycle->nodes[i];
|
|
uint8_t v = out_cycle->nodes[(i + 1) % n];
|
|
zkp_graph_add_edge(out_graph, u, v);
|
|
}
|
|
|
|
/* Step 4: Add decoy edges */
|
|
int nb_extra = (int)(n * extra_ratio);
|
|
int max_tries = nb_extra * 10;
|
|
uint8_t rand_buf[2];
|
|
|
|
while (nb_extra > 0 && max_tries > 0) {
|
|
max_tries--;
|
|
|
|
/* Get random u, v */
|
|
res = zkp_random_bytes(rand_buf, 2);
|
|
if (res != ZKP_OK) {
|
|
return res;
|
|
}
|
|
|
|
uint8_t u = rand_buf[0] % n;
|
|
uint8_t v = rand_buf[1] % n;
|
|
|
|
if (u == v) {
|
|
continue; /* No self-loops */
|
|
}
|
|
|
|
if (!zkp_graph_has_edge(out_graph, u, v)) {
|
|
zkp_graph_add_edge(out_graph, u, v);
|
|
nb_extra--;
|
|
}
|
|
}
|
|
|
|
/* Step 5: Verify connectivity (should always pass) */
|
|
if (!zkp_graph_is_connected(out_graph)) {
|
|
/* This shouldn't happen since the Hamiltonian cycle ensures connectivity */
|
|
goto retry;
|
|
}
|
|
|
|
/* Step 6: Verify the cycle is valid (assertion) */
|
|
if (!zkp_validate_hamiltonian_cycle(out_graph, out_cycle)) {
|
|
/* This should never fail - indicates a bug */
|
|
return ZKP_ERR;
|
|
}
|
|
|
|
/* Zero the permutation (sensitive data) */
|
|
zkp_secure_zero(perm, sizeof(perm));
|
|
|
|
return ZKP_OK;
|
|
}
|
|
|
|
/* ============== Debug Functions (tools/tests only) ============== */
|
|
|
|
#ifndef OPENWRT_BUILD
|
|
|
|
#include <stdio.h>
|
|
|
|
void zkp_graph_print(const Graph *G)
|
|
{
|
|
if (G == NULL) {
|
|
printf("Graph: NULL\n");
|
|
return;
|
|
}
|
|
|
|
printf("Graph (n=%u, edges=%u):\n", G->n, zkp_graph_edge_count(G));
|
|
|
|
uint8_t i, j;
|
|
for (i = 0; i < G->n; i++) {
|
|
printf(" %3u: ", i);
|
|
for (j = 0; j < G->n; j++) {
|
|
if (zkp_graph_has_edge(G, i, j)) {
|
|
printf("%u ", j);
|
|
}
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
void zkp_cycle_print(const HamiltonianCycle *H)
|
|
{
|
|
if (H == NULL) {
|
|
printf("Cycle: NULL\n");
|
|
return;
|
|
}
|
|
|
|
printf("Hamiltonian Cycle (n=%u): ", H->n);
|
|
uint8_t i;
|
|
for (i = 0; i < H->n; i++) {
|
|
printf("%u", H->nodes[i]);
|
|
if (i < H->n - 1) {
|
|
printf(" -> ");
|
|
}
|
|
}
|
|
printf(" -> %u\n", H->nodes[0]); /* Back to start */
|
|
}
|
|
|
|
#else
|
|
|
|
/* Stub implementations for OpenWrt build */
|
|
void zkp_graph_print(const Graph *G)
|
|
{
|
|
(void)G;
|
|
}
|
|
|
|
void zkp_cycle_print(const HamiltonianCycle *H)
|
|
{
|
|
(void)H;
|
|
}
|
|
|
|
#endif
|