secubox-openwrt/package/secubox/zkp-hamiltonian/src/zkp_serialize.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

533 lines
12 KiB
C

/**
* @file zkp_serialize.c
* @brief Serialization functions for ZKP Hamiltonian protocol
* @version 1.0
*
* Provides binary serialization/deserialization for proofs, graphs, and cycles.
*
* Wire format is designed for embedded systems:
* - Fixed sizes where possible (no variable-length encoding complexity)
* - Big-endian for network/file portability
* - Version fields for forward compatibility
*
* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright (C) 2026 CyberMind.FR / SecuBox
*/
#include "zkp_hamiltonian.h"
#include "zkp_crypto.h"
#include <string.h>
/* Magic bytes for file identification */
#define ZKP_MAGIC_PROOF 0x5A4B5050 /* "ZKPP" - ZKP Proof */
#define ZKP_MAGIC_GRAPH 0x5A4B5047 /* "ZKPG" - ZKP Graph */
#define ZKP_MAGIC_CYCLE 0x5A4B5048 /* "ZKPH" - ZKP Hamiltonian cycle */
/* ============== Helper Functions ============== */
/**
* @brief Write uint32 in big-endian format
*/
static void write_be32(uint8_t *buf, uint32_t val)
{
buf[0] = (uint8_t)(val >> 24);
buf[1] = (uint8_t)(val >> 16);
buf[2] = (uint8_t)(val >> 8);
buf[3] = (uint8_t)(val);
}
/**
* @brief Read uint32 from big-endian format
*/
static uint32_t read_be32(const uint8_t *buf)
{
return ((uint32_t)buf[0] << 24) |
((uint32_t)buf[1] << 16) |
((uint32_t)buf[2] << 8) |
((uint32_t)buf[3]);
}
/**
* @brief Write uint64 in big-endian format
*/
static void write_be64(uint8_t *buf, uint64_t val)
{
buf[0] = (uint8_t)(val >> 56);
buf[1] = (uint8_t)(val >> 48);
buf[2] = (uint8_t)(val >> 40);
buf[3] = (uint8_t)(val >> 32);
buf[4] = (uint8_t)(val >> 24);
buf[5] = (uint8_t)(val >> 16);
buf[6] = (uint8_t)(val >> 8);
buf[7] = (uint8_t)(val);
}
/**
* @brief Read uint64 from big-endian format
*/
static uint64_t read_be64(const uint8_t *buf)
{
return ((uint64_t)buf[0] << 56) |
((uint64_t)buf[1] << 48) |
((uint64_t)buf[2] << 40) |
((uint64_t)buf[3] << 32) |
((uint64_t)buf[4] << 24) |
((uint64_t)buf[5] << 16) |
((uint64_t)buf[6] << 8) |
((uint64_t)buf[7]);
}
/* ============== Proof Serialization ============== */
/*
* Proof wire format:
*
* Header (42 bytes):
* [4B] magic = 0x5A4B5050
* [1B] version
* [1B] n (number of nodes)
* [32B] session_nonce
* [1B] challenge (0 or 1)
* [3B] reserved (padding)
*
* Body (variable, depends on n and challenge):
* [n*8B] gprime_adj (big-endian uint64 per row)
* [n*(n-1)/2 * 32B] commits (upper triangle only)
*
* Response (depends on challenge):
* If challenge == 0 (isomorphism):
* [n] perm
* [n*(n-1)/2 * 32B] nonces (upper triangle)
* If challenge == 1 (Hamiltonian):
* [n] cycle_nodes
* [n * 32B] cycle_nonces
*/
ZKPResult zkp_proof_serialize(const NIZKProof *proof, uint8_t *buf, size_t *len)
{
if (proof == NULL || len == NULL) {
return ZKP_ERR_PARAM;
}
uint8_t n = proof->n;
if (n == 0 || n > ZKP_MAX_N) {
return ZKP_ERR_PARAM;
}
/* Calculate required buffer size */
size_t header_size = 44; /* magic + version + n + nonce + challenge + reserved */
size_t gprime_size = (size_t)n * 8;
size_t commits_count = (size_t)n * (n - 1) / 2;
size_t commits_size = commits_count * ZKP_HASH_SIZE;
size_t response_size;
if (proof->challenge == 0) {
/* Isomorphism response: perm + all nonces */
response_size = (size_t)n + commits_count * ZKP_NONCE_SIZE;
} else {
/* Hamiltonian response: cycle nodes + cycle nonces */
response_size = (size_t)n + (size_t)n * ZKP_NONCE_SIZE;
}
size_t total_size = header_size + gprime_size + commits_size + response_size;
if (buf == NULL) {
/* Just return required size */
*len = total_size;
return ZKP_OK;
}
if (*len < total_size) {
*len = total_size;
return ZKP_ERR_MEM;
}
size_t offset = 0;
/* Header */
write_be32(buf + offset, ZKP_MAGIC_PROOF);
offset += 4;
buf[offset++] = proof->version;
buf[offset++] = n;
memcpy(buf + offset, proof->session_nonce, ZKP_NONCE_SIZE);
offset += ZKP_NONCE_SIZE;
buf[offset++] = proof->challenge;
buf[offset++] = 0; /* reserved */
buf[offset++] = 0;
buf[offset++] = 0;
/* G' adjacency */
uint8_t i, j;
for (i = 0; i < n; i++) {
write_be64(buf + offset, proof->gprime_adj[i]);
offset += 8;
}
/* Commits (upper triangle) */
for (i = 0; i < n; i++) {
for (j = (uint8_t)(i + 1); j < n; j++) {
memcpy(buf + offset, proof->commits[i][j], ZKP_HASH_SIZE);
offset += ZKP_HASH_SIZE;
}
}
/* Response */
if (proof->challenge == 0) {
/* Isomorphism: perm + nonces */
memcpy(buf + offset, proof->iso_response.perm, n);
offset += n;
for (i = 0; i < n; i++) {
for (j = (uint8_t)(i + 1); j < n; j++) {
memcpy(buf + offset, proof->iso_response.nonces[i][j], ZKP_NONCE_SIZE);
offset += ZKP_NONCE_SIZE;
}
}
} else {
/* Hamiltonian: cycle nodes + nonces */
memcpy(buf + offset, proof->ham_response.cycle_nodes, n);
offset += n;
for (i = 0; i < n; i++) {
memcpy(buf + offset, proof->ham_response.nonces[i], ZKP_NONCE_SIZE);
offset += ZKP_NONCE_SIZE;
}
}
*len = offset;
return ZKP_OK;
}
ZKPResult zkp_proof_deserialize(const uint8_t *buf, size_t len, NIZKProof *proof)
{
if (buf == NULL || proof == NULL) {
return ZKP_ERR_PARAM;
}
if (len < 44) {
return ZKP_ERR_PARAM; /* Too short for header */
}
size_t offset = 0;
/* Verify magic */
uint32_t magic = read_be32(buf + offset);
offset += 4;
if (magic != ZKP_MAGIC_PROOF) {
return ZKP_ERR_PARAM;
}
/* Read header */
memset(proof, 0, sizeof(NIZKProof));
proof->version = buf[offset++];
proof->n = buf[offset++];
uint8_t n = proof->n;
if (n == 0 || n > ZKP_MAX_N) {
return ZKP_ERR_PARAM;
}
memcpy(proof->session_nonce, buf + offset, ZKP_NONCE_SIZE);
offset += ZKP_NONCE_SIZE;
proof->challenge = buf[offset++];
offset += 3; /* skip reserved */
/* Calculate expected remaining size */
size_t gprime_size = (size_t)n * 8;
size_t commits_count = (size_t)n * (n - 1) / 2;
size_t commits_size = commits_count * ZKP_HASH_SIZE;
size_t response_size;
if (proof->challenge == 0) {
response_size = (size_t)n + commits_count * ZKP_NONCE_SIZE;
} else {
response_size = (size_t)n + (size_t)n * ZKP_NONCE_SIZE;
}
if (len < offset + gprime_size + commits_size + response_size) {
return ZKP_ERR_PARAM;
}
/* Read G' adjacency */
uint8_t i, j;
for (i = 0; i < n; i++) {
proof->gprime_adj[i] = read_be64(buf + offset);
offset += 8;
}
/* Read commits (upper triangle) */
for (i = 0; i < n; i++) {
for (j = (uint8_t)(i + 1); j < n; j++) {
memcpy(proof->commits[i][j], buf + offset, ZKP_HASH_SIZE);
/* Mirror to lower triangle */
memcpy(proof->commits[j][i], proof->commits[i][j], ZKP_HASH_SIZE);
offset += ZKP_HASH_SIZE;
}
}
/* Read response */
if (proof->challenge == 0) {
/* Isomorphism */
memcpy(proof->iso_response.perm, buf + offset, n);
offset += n;
for (i = 0; i < n; i++) {
for (j = (uint8_t)(i + 1); j < n; j++) {
memcpy(proof->iso_response.nonces[i][j], buf + offset, ZKP_NONCE_SIZE);
/* Mirror to lower triangle */
memcpy(proof->iso_response.nonces[j][i],
proof->iso_response.nonces[i][j], ZKP_NONCE_SIZE);
offset += ZKP_NONCE_SIZE;
}
}
} else {
/* Hamiltonian */
memcpy(proof->ham_response.cycle_nodes, buf + offset, n);
offset += n;
for (i = 0; i < n; i++) {
memcpy(proof->ham_response.nonces[i], buf + offset, ZKP_NONCE_SIZE);
offset += ZKP_NONCE_SIZE;
}
}
return ZKP_OK;
}
/* ============== Graph Serialization ============== */
/*
* Graph wire format:
* [4B] magic = 0x5A4B5047
* [1B] version
* [1B] n
* [2B] reserved
* [n*8B] adj (big-endian uint64 per row)
*/
ZKPResult zkp_graph_serialize(const Graph *G, uint8_t *buf, size_t *len)
{
if (G == NULL || len == NULL) {
return ZKP_ERR_PARAM;
}
uint8_t n = G->n;
if (n == 0 || n > ZKP_MAX_N) {
return ZKP_ERR_PARAM;
}
size_t total_size = 8 + (size_t)n * 8;
if (buf == NULL) {
*len = total_size;
return ZKP_OK;
}
if (*len < total_size) {
*len = total_size;
return ZKP_ERR_MEM;
}
size_t offset = 0;
write_be32(buf + offset, ZKP_MAGIC_GRAPH);
offset += 4;
buf[offset++] = ZKP_VERSION;
buf[offset++] = n;
buf[offset++] = 0; /* reserved */
buf[offset++] = 0;
uint8_t i;
for (i = 0; i < n; i++) {
write_be64(buf + offset, G->adj[i]);
offset += 8;
}
*len = offset;
return ZKP_OK;
}
ZKPResult zkp_graph_deserialize(const uint8_t *buf, size_t len, Graph *G)
{
if (buf == NULL || G == NULL) {
return ZKP_ERR_PARAM;
}
if (len < 8) {
return ZKP_ERR_PARAM;
}
size_t offset = 0;
uint32_t magic = read_be32(buf + offset);
offset += 4;
if (magic != ZKP_MAGIC_GRAPH) {
return ZKP_ERR_PARAM;
}
uint8_t version = buf[offset++];
(void)version; /* Currently unused, for forward compatibility */
uint8_t n = buf[offset++];
offset += 2; /* skip reserved */
if (n == 0 || n > ZKP_MAX_N) {
return ZKP_ERR_PARAM;
}
if (len < 8 + (size_t)n * 8) {
return ZKP_ERR_PARAM;
}
G->n = n;
memset(G->adj, 0, sizeof(G->adj));
uint8_t i;
for (i = 0; i < n; i++) {
G->adj[i] = read_be64(buf + offset);
offset += 8;
}
return ZKP_OK;
}
/* ============== Cycle Serialization ============== */
/*
* Cycle wire format:
* [4B] magic = 0x5A4B5048
* [1B] version
* [1B] n
* [2B] reserved
* [n] nodes
*/
ZKPResult zkp_cycle_serialize(const HamiltonianCycle *H, uint8_t *buf, size_t *len)
{
if (H == NULL || len == NULL) {
return ZKP_ERR_PARAM;
}
uint8_t n = H->n;
if (n == 0 || n > ZKP_MAX_N) {
return ZKP_ERR_PARAM;
}
size_t total_size = 8 + (size_t)n;
if (buf == NULL) {
*len = total_size;
return ZKP_OK;
}
if (*len < total_size) {
*len = total_size;
return ZKP_ERR_MEM;
}
size_t offset = 0;
write_be32(buf + offset, ZKP_MAGIC_CYCLE);
offset += 4;
buf[offset++] = ZKP_VERSION;
buf[offset++] = n;
buf[offset++] = 0; /* reserved */
buf[offset++] = 0;
memcpy(buf + offset, H->nodes, n);
offset += n;
*len = offset;
return ZKP_OK;
}
ZKPResult zkp_cycle_deserialize(const uint8_t *buf, size_t len, HamiltonianCycle *H)
{
if (buf == NULL || H == NULL) {
return ZKP_ERR_PARAM;
}
if (len < 8) {
return ZKP_ERR_PARAM;
}
size_t offset = 0;
uint32_t magic = read_be32(buf + offset);
offset += 4;
if (magic != ZKP_MAGIC_CYCLE) {
return ZKP_ERR_PARAM;
}
uint8_t version = buf[offset++];
(void)version;
uint8_t n = buf[offset++];
offset += 2; /* skip reserved */
if (n == 0 || n > ZKP_MAX_N) {
return ZKP_ERR_PARAM;
}
if (len < 8 + (size_t)n) {
return ZKP_ERR_PARAM;
}
H->n = n;
memset(H->nodes, 0, sizeof(H->nodes));
memcpy(H->nodes, buf + offset, n);
return ZKP_OK;
}
/* ============== Debug Functions ============== */
#ifndef OPENWRT_BUILD
#include <stdio.h>
void zkp_proof_print(const NIZKProof *proof)
{
if (proof == NULL) {
printf("Proof: NULL\n");
return;
}
printf("NIZKProof {\n");
printf(" version: %u\n", proof->version);
printf(" n: %u\n", proof->n);
printf(" challenge: %u (%s)\n", proof->challenge,
proof->challenge == 0 ? "isomorphism" : "hamiltonian");
printf(" session_nonce: ");
for (int i = 0; i < 8; i++) {
printf("%02x", proof->session_nonce[i]);
}
printf("...\n");
printf(" edges in G': %u\n", zkp_graph_edge_count(
&(Graph){.n = proof->n, .adj = {0}}
));
printf("}\n");
}
#else
void zkp_proof_print(const NIZKProof *proof)
{
(void)proof;
}
#endif