secubox-openwrt/package/secubox/zkp-hamiltonian/tests/test_protocol.c
CyberMind-FR 6553936886 feat(zkp-hamiltonian): Add Zero-Knowledge Proof library based on Hamiltonian Cycle
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>
2026-02-24 09:59:16 +01:00

337 lines
8.2 KiB
C

/**
* @file test_protocol.c
* @brief Tests for ZKP protocol (prove/verify)
* @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_hamiltonian.h"
#include "zkp_crypto.h"
#include "zkp_graph.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_completeness_single(void)
{
TEST("Completeness: prove + verify → ACCEPT");
Graph G;
HamiltonianCycle H;
NIZKProof proof;
/* Generate graph with cycle */
ASSERT(zkp_generate_graph(20, 1.0, &G, &H) == ZKP_OK, "Graph gen failed");
/* Generate proof */
ASSERT(zkp_prove(&G, &H, &proof) == ZKP_OK, "Prove failed");
/* Verify proof */
ZKPResult res = zkp_verify(&G, &proof);
ASSERT(res == ZKP_ACCEPT, "Verify rejected valid proof");
PASS();
return 0;
}
static int test_completeness_n20_100x(void)
{
TEST("Completeness n=20: 100 repetitions");
Graph G;
HamiltonianCycle H;
NIZKProof proof;
ASSERT(zkp_generate_graph(20, 0.8, &G, &H) == ZKP_OK, "Graph gen failed");
for (int i = 0; i < 100; i++) {
ASSERT(zkp_prove(&G, &H, &proof) == ZKP_OK, "Prove failed");
ASSERT(zkp_verify(&G, &proof) == ZKP_ACCEPT, "Verify rejected");
}
PASS();
return 0;
}
static int test_soundness_bad_cycle(void)
{
TEST("Soundness: invalid cycle → cannot prove");
Graph G;
HamiltonianCycle H;
/* Create graph without Hamiltonian cycle */
zkp_graph_init(&G, 10);
zkp_graph_add_edge(&G, 0, 1);
zkp_graph_add_edge(&G, 1, 2);
zkp_graph_add_edge(&G, 2, 3);
/* Disconnected: nodes 4-9 have no edges */
/* Create fake cycle */
H.n = 10;
for (uint8_t i = 0; i < 10; i++) {
H.nodes[i] = i;
}
/* Prove should fail (cycle not valid in graph) */
NIZKProof proof;
ZKPResult res = zkp_prove(&G, &H, &proof);
ASSERT(res == ZKP_ERR_PARAM, "Prove accepted invalid cycle");
PASS();
return 0;
}
static int test_tamper_commit(void)
{
TEST("Tamper detection: modified commit → REJECT");
Graph G;
HamiltonianCycle H;
NIZKProof proof;
ASSERT(zkp_generate_graph(20, 1.0, &G, &H) == ZKP_OK, "Graph gen failed");
ASSERT(zkp_prove(&G, &H, &proof) == ZKP_OK, "Prove failed");
/* Tamper with a commitment */
proof.commits[0][1][0] ^= 0xFF;
/* Verify should reject */
ZKPResult res = zkp_verify(&G, &proof);
ASSERT(res == ZKP_REJECT, "Verify accepted tampered commits");
PASS();
return 0;
}
static int test_tamper_challenge(void)
{
TEST("Tamper detection: modified challenge → REJECT");
Graph G;
HamiltonianCycle H;
NIZKProof proof;
ASSERT(zkp_generate_graph(20, 1.0, &G, &H) == ZKP_OK, "Graph gen failed");
ASSERT(zkp_prove(&G, &H, &proof) == ZKP_OK, "Prove failed");
/* Flip the challenge bit */
proof.challenge ^= 1;
/* Verify should reject (challenge doesn't match Fiat-Shamir) */
ZKPResult res = zkp_verify(&G, &proof);
ASSERT(res == ZKP_REJECT, "Verify accepted wrong challenge");
PASS();
return 0;
}
static int test_tamper_gprime(void)
{
TEST("Tamper detection: modified G' → REJECT");
Graph G;
HamiltonianCycle H;
NIZKProof proof;
ASSERT(zkp_generate_graph(20, 1.0, &G, &H) == ZKP_OK, "Graph gen failed");
ASSERT(zkp_prove(&G, &H, &proof) == ZKP_OK, "Prove failed");
/*
* Tamper with G' adjacency across multiple entries.
* This ensures the Fiat-Shamir hash changes significantly,
* causing challenge mismatch and verification failure.
*/
for (uint8_t i = 0; i < proof.n; i++) {
proof.gprime_adj[i] ^= 0xFFFFFFFFFFFFFFFFULL;
}
/* Verify should reject */
ZKPResult res = zkp_verify(&G, &proof);
ASSERT(res == ZKP_REJECT, "Verify accepted tampered G'");
PASS();
return 0;
}
static int test_tamper_nonce(void)
{
TEST("Tamper detection: modified nonce → REJECT");
Graph G;
HamiltonianCycle H;
NIZKProof proof;
ASSERT(zkp_generate_graph(20, 1.0, &G, &H) == ZKP_OK, "Graph gen failed");
ASSERT(zkp_prove(&G, &H, &proof) == ZKP_OK, "Prove failed");
/* Tamper with a nonce in the response */
if (proof.challenge == 0) {
proof.iso_response.nonces[0][1][0] ^= 0xFF;
} else {
proof.ham_response.nonces[0][0] ^= 0xFF;
}
/* Verify should reject */
ZKPResult res = zkp_verify(&G, &proof);
ASSERT(res == ZKP_REJECT, "Verify accepted tampered nonce");
PASS();
return 0;
}
static int test_antireplay_different_nonces(void)
{
TEST("Anti-replay: different session_nonces per proof");
Graph G;
HamiltonianCycle H;
NIZKProof proof1, proof2;
ASSERT(zkp_generate_graph(20, 1.0, &G, &H) == ZKP_OK, "Graph gen failed");
ASSERT(zkp_prove(&G, &H, &proof1) == ZKP_OK, "Prove 1 failed");
ASSERT(zkp_prove(&G, &H, &proof2) == ZKP_OK, "Prove 2 failed");
/* Session nonces should be different */
ASSERT(memcmp(proof1.session_nonce, proof2.session_nonce, ZKP_NONCE_SIZE) != 0,
"Session nonces identical");
/* Both should verify */
ASSERT(zkp_verify(&G, &proof1) == ZKP_ACCEPT, "Proof 1 rejected");
ASSERT(zkp_verify(&G, &proof2) == ZKP_ACCEPT, "Proof 2 rejected");
PASS();
return 0;
}
static int test_wrong_graph(void)
{
TEST("Wrong graph: proof for G1 rejected on G2");
Graph G1, G2;
HamiltonianCycle H1, H2;
NIZKProof proof;
ASSERT(zkp_generate_graph(20, 1.0, &G1, &H1) == ZKP_OK, "G1 gen failed");
ASSERT(zkp_generate_graph(20, 1.0, &G2, &H2) == ZKP_OK, "G2 gen failed");
/* Generate proof for G1 */
ASSERT(zkp_prove(&G1, &H1, &proof) == ZKP_OK, "Prove failed");
/* Should accept on G1 */
ASSERT(zkp_verify(&G1, &proof) == ZKP_ACCEPT, "Rejected on correct graph");
/* Should reject on G2 */
ZKPResult res = zkp_verify(&G2, &proof);
ASSERT(res == ZKP_REJECT, "Accepted on wrong graph");
PASS();
return 0;
}
static int test_version_mismatch(void)
{
TEST("Version mismatch: wrong version → REJECT");
Graph G;
HamiltonianCycle H;
NIZKProof proof;
ASSERT(zkp_generate_graph(20, 1.0, &G, &H) == ZKP_OK, "Graph gen failed");
ASSERT(zkp_prove(&G, &H, &proof) == ZKP_OK, "Prove failed");
/* Change version */
proof.version = 0xFF;
ZKPResult res = zkp_verify(&G, &proof);
ASSERT(res == ZKP_REJECT, "Accepted wrong version");
PASS();
return 0;
}
static int test_size_mismatch(void)
{
TEST("Size mismatch: wrong n → REJECT");
Graph G;
HamiltonianCycle H;
NIZKProof proof;
ASSERT(zkp_generate_graph(20, 1.0, &G, &H) == ZKP_OK, "Graph gen failed");
ASSERT(zkp_prove(&G, &H, &proof) == ZKP_OK, "Prove failed");
/* Change n in proof */
proof.n = 30;
ZKPResult res = zkp_verify(&G, &proof);
ASSERT(res == ZKP_REJECT, "Accepted wrong n");
PASS();
return 0;
}
/* ============== Main ============== */
int main(void)
{
printf("\n=== ZKP Protocol Tests ===\n\n");
int result = 0;
result |= test_completeness_single();
result |= test_completeness_n20_100x();
result |= test_soundness_bad_cycle();
result |= test_tamper_commit();
result |= test_tamper_challenge();
result |= test_tamper_gprime();
result |= test_tamper_nonce();
result |= test_antireplay_different_nonces();
result |= test_wrong_graph();
result |= test_version_mismatch();
result |= test_size_mismatch();
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;
}
}