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>
211 lines
5.5 KiB
C
211 lines
5.5 KiB
C
/**
|
|
* @file zkp_verify.c
|
|
* @brief NIZK proof verification for ZKP Hamiltonian protocol
|
|
* @version 1.0
|
|
*
|
|
* Implements the Verifier side of the Blum Hamiltonian Cycle ZKP
|
|
* with NIZK transformation via Fiat-Shamir heuristic.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
* Copyright (C) 2026 CyberMind.FR / SecuBox
|
|
*/
|
|
|
|
#include "zkp_hamiltonian.h"
|
|
#include "zkp_crypto.h"
|
|
#include "zkp_graph.h"
|
|
#include <string.h>
|
|
|
|
/**
|
|
* @brief Verify that perm is a valid permutation of {0..n-1}
|
|
*/
|
|
static bool is_valid_permutation(const uint8_t *perm, uint8_t n)
|
|
{
|
|
if (perm == NULL || n == 0 || n > ZKP_MAX_N) {
|
|
return false;
|
|
}
|
|
|
|
/* Use bitmap to track seen values */
|
|
uint64_t seen_low = 0; /* 0-63 */
|
|
uint64_t seen_high = 0; /* 64-127 */
|
|
uint8_t i;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
uint8_t val = perm[i];
|
|
|
|
if (val >= n) {
|
|
return false; /* Value out of range */
|
|
}
|
|
|
|
if (val < 64) {
|
|
if (seen_low & ((uint64_t)1 << val)) {
|
|
return false; /* Duplicate value */
|
|
}
|
|
seen_low |= ((uint64_t)1 << val);
|
|
} else {
|
|
if (seen_high & ((uint64_t)1 << (val - 64))) {
|
|
return false; /* Duplicate value */
|
|
}
|
|
seen_high |= ((uint64_t)1 << (val - 64));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Compare two graphs for equality
|
|
*/
|
|
static bool graphs_equal(const Graph *a, const Graph *b)
|
|
{
|
|
if (a == NULL || b == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (a->n != b->n) {
|
|
return false;
|
|
}
|
|
|
|
uint8_t i;
|
|
for (i = 0; i < a->n; i++) {
|
|
if (a->adj[i] != b->adj[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ZKPResult zkp_verify(const Graph *G, const NIZKProof *proof)
|
|
{
|
|
/*
|
|
* NIZK Proof Verification Algorithm:
|
|
*
|
|
* 1. Validate proof->version == ZKP_VERSION
|
|
* 2. Validate proof->n == G->n
|
|
* 3. Recalculate Fiat-Shamir challenge from transcript
|
|
* 4. Verify challenge matches
|
|
* 5. Reconstruct G' from proof->gprime_adj
|
|
* 6. If challenge == 0 (isomorphism):
|
|
* a. Verify π is valid permutation
|
|
* b. Verify π(G) == G'
|
|
* c. Verify all commitments with provided nonces
|
|
* 7. If challenge == 1 (Hamiltonian):
|
|
* a. Reconstruct H' from proof
|
|
* b. Verify H' is valid Hamiltonian cycle in G'
|
|
* c. Verify commitments for cycle edges
|
|
*/
|
|
|
|
if (G == NULL || proof == NULL) {
|
|
return ZKP_ERR;
|
|
}
|
|
|
|
/* Step 1: Validate version */
|
|
if (proof->version != ZKP_VERSION) {
|
|
return ZKP_REJECT;
|
|
}
|
|
|
|
/* Step 2: Validate n */
|
|
uint8_t n = proof->n;
|
|
if (n != G->n || n == 0 || n > ZKP_MAX_N) {
|
|
return ZKP_REJECT;
|
|
}
|
|
|
|
/* Step 3: Recalculate Fiat-Shamir challenge */
|
|
uint8_t computed_challenge = zkp_fiat_shamir_challenge(
|
|
G,
|
|
proof->gprime_adj,
|
|
proof->commits,
|
|
n,
|
|
proof->session_nonce
|
|
);
|
|
|
|
/* Step 4: Verify challenge matches */
|
|
if (proof->challenge != computed_challenge) {
|
|
return ZKP_REJECT;
|
|
}
|
|
|
|
/* Step 5: Reconstruct G' from proof */
|
|
Graph gprime;
|
|
gprime.n = n;
|
|
uint8_t i;
|
|
for (i = 0; i < n; i++) {
|
|
gprime.adj[i] = proof->gprime_adj[i];
|
|
}
|
|
for (i = n; i < ZKP_MAX_N; i++) {
|
|
gprime.adj[i] = 0;
|
|
}
|
|
|
|
/* Step 6-7: Verify based on challenge type */
|
|
if (proof->challenge == 0) {
|
|
/*
|
|
* Isomorphism verification:
|
|
* Verify that G' = π(G) and all commitments are valid
|
|
*/
|
|
const uint8_t *perm = proof->iso_response.perm;
|
|
|
|
/* 6a: Verify π is a valid permutation */
|
|
if (!is_valid_permutation(perm, n)) {
|
|
return ZKP_REJECT;
|
|
}
|
|
|
|
/* 6b: Compute G_check = π(G) and verify G_check == G' */
|
|
Graph g_check;
|
|
zkp_graph_permute(G, perm, &g_check);
|
|
|
|
if (!graphs_equal(&g_check, &gprime)) {
|
|
return ZKP_REJECT;
|
|
}
|
|
|
|
/* 6c: Verify all commitments */
|
|
uint8_t j;
|
|
for (i = 0; i < n; i++) {
|
|
for (j = (uint8_t)(i + 1); j < n; j++) {
|
|
uint8_t bit = zkp_graph_has_edge(&gprime, i, j) ? 1 : 0;
|
|
|
|
if (!zkp_commit_verify(bit,
|
|
proof->iso_response.nonces[i][j],
|
|
proof->commits[i][j])) {
|
|
return ZKP_REJECT;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
/*
|
|
* Hamiltonian cycle verification:
|
|
* Verify that H' is a valid Hamiltonian cycle in G'
|
|
* and the cycle edge commitments are valid
|
|
*/
|
|
|
|
/* 7a: Reconstruct H' */
|
|
HamiltonianCycle hprime;
|
|
hprime.n = n;
|
|
memcpy(hprime.nodes, proof->ham_response.cycle_nodes, n);
|
|
|
|
/* 7b: Verify H' is valid Hamiltonian cycle in G' */
|
|
if (!zkp_validate_hamiltonian_cycle(&gprime, &hprime)) {
|
|
return ZKP_REJECT;
|
|
}
|
|
|
|
/* 7c: Verify commitments for cycle edges */
|
|
for (i = 0; i < n; i++) {
|
|
uint8_t u = hprime.nodes[i];
|
|
uint8_t v = hprime.nodes[(i + 1) % n];
|
|
|
|
/* Normalize to upper triangle */
|
|
uint8_t u_norm = (u < v) ? u : v;
|
|
uint8_t v_norm = (u < v) ? v : u;
|
|
|
|
/* Cycle edges should all be present (bit = 1) */
|
|
if (!zkp_commit_verify(1,
|
|
proof->ham_response.nonces[i],
|
|
proof->commits[u_norm][v_norm])) {
|
|
return ZKP_REJECT;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* All checks passed */
|
|
return ZKP_ACCEPT;
|
|
}
|