secubox-openwrt/package/secubox/zkp-hamiltonian/PROMPT.md
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

14 KiB

Prompt — Agent Claude.ai dans VS Code

Projet : zkp-hamiltonian

À coller dans la fenêtre de chat Claude (VS Code Extension ou claude.ai)


CONTEXTE ET RÔLE

Tu es un expert en cryptographie appliquée et en développement C embarqué. Tu vas implémenter un protocole de preuve à divulgation nulle de connaissance (Zero-Knowledge Proof) basé sur le problème du cycle hamiltonien (Blum 1986 + transformation NIZK Fiat-Shamir).

Cible : OpenWrt sur ARM (routeurs embarqués, intégration SecuBox). Langage : C99 exclusivement. Dépendance crypto : libsodium (disponible dans les feeds OpenWrt).

La spécification complète est dans ZKP_Hamiltonian_Spec.md à la racine du projet. Lis ce fichier en premier avant toute implémentation.


ÉTAPE 1 — INITIALISATION DU PROJET

Crée la structure complète du projet :

zkp-hamiltonian/
├── CMakeLists.txt
├── README.md
├── include/
│   ├── zkp_hamiltonian.h
│   ├── zkp_types.h
│   ├── zkp_crypto.h
│   └── zkp_graph.h
├── src/
│   ├── zkp_graph.c
│   ├── zkp_crypto.c
│   ├── zkp_prove.c
│   ├── zkp_verify.c
│   └── zkp_serialize.c
├── tests/
│   ├── test_graph.c
│   ├── test_crypto.c
│   ├── test_protocol.c
│   └── test_nizk.c
├── tools/
│   ├── zkp_keygen.c
│   ├── zkp_prover.c
│   └── zkp_verifier.c
└── openwrt/
    └── Makefile

ÉTAPE 2 — TYPES ET CONSTANTES (zkp_types.h)

Implémente exactement ces types :

#ifndef ZKP_TYPES_H
#define ZKP_TYPES_H

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>

#define ZKP_MAX_N        128
#define ZKP_NONCE_SIZE   32
#define ZKP_HASH_SIZE    32
#define ZKP_VERSION      0x01
#define ZKP_PROTOCOL_ID  "ZKP-HAM-v1"

typedef struct {
    uint8_t  n;
    uint64_t adj[ZKP_MAX_N];
} Graph;

typedef struct {
    uint8_t  n;
    uint8_t  nodes[ZKP_MAX_N];
} HamiltonianCycle;

typedef struct {
    uint8_t  version;
    uint8_t  n;
    uint8_t  session_nonce[ZKP_NONCE_SIZE];
    uint64_t gprime_adj[ZKP_MAX_N];
    uint8_t  commits[ZKP_MAX_N][ZKP_MAX_N][ZKP_HASH_SIZE];
    uint8_t  challenge;
    union {
        struct {
            uint8_t perm[ZKP_MAX_N];
            uint8_t nonces[ZKP_MAX_N][ZKP_MAX_N][ZKP_NONCE_SIZE];
        } iso_response;
        struct {
            uint8_t cycle_nodes[ZKP_MAX_N];
            uint8_t nonces[ZKP_MAX_N][ZKP_NONCE_SIZE];
        } ham_response;
    };
} NIZKProof;

typedef enum {
    ZKP_OK       =  0,
    ZKP_REJECT   =  0,
    ZKP_ACCEPT   =  1,
    ZKP_ERR      = -1,
    ZKP_ERR_MEM  = -2,
    ZKP_ERR_RNG  = -3,
    ZKP_ERR_PARAM= -4,
} ZKPResult;

#endif /* ZKP_TYPES_H */

ÉTAPE 3 — PRIMITIVES CRYPTO (zkp_crypto.h / zkp_crypto.c)

3.1 Commit SHA3-256

// Commit(bit, nonce) = SHA3-256(bit_byte || nonce[32])
// bit_byte = 0x01 si bit=1, 0x00 si bit=0
void zkp_commit(uint8_t bit, const uint8_t nonce[ZKP_NONCE_SIZE],
                uint8_t out[ZKP_HASH_SIZE]);

// Vérifie un commit en temps constant (pas de timing leak)
bool zkp_commit_verify(uint8_t bit, const uint8_t nonce[ZKP_NONCE_SIZE],
                       const uint8_t expected[ZKP_HASH_SIZE]);

IMPORTANT : utilise SHA3-256 via EVP_DigestInit(ctx, EVP_sha3_256()) (OpenSSL) ou l'équivalent libsodium. La comparaison finale doit être en temps constant : utilise sodium_memcmp() ou une boucle XOR sans branchement.

3.2 Hash Fiat-Shamir

// H_FS(G, G', commits, session_nonce) → 1 bit (le LSB de SHA3-256)
uint8_t zkp_fiat_shamir_challenge(
    const Graph *G,
    const uint64_t gprime_adj[ZKP_MAX_N],
    const uint8_t commits[ZKP_MAX_N][ZKP_MAX_N][ZKP_HASH_SIZE],
    uint8_t n,
    const uint8_t session_nonce[ZKP_NONCE_SIZE]
);

Ordre de hachage canonique :

  1. ZKP_PROTOCOL_ID (string ASCII, sans null)
  2. n (1 octet)
  3. Matrice adj de G, rangée par rangée, big-endian uint64
  4. Matrice adj de G', même ordre
  5. Tous les commits[i][j] pour i < j, ordre lexicographique (i,j)
  6. session_nonce

3.3 Génération aléatoire sécurisée

// Remplit buf[len] avec des octets cryptographiquement aléatoires
// Utilise getrandom() si disponible, sinon /dev/urandom
// Retourne ZKP_OK ou ZKP_ERR_RNG
ZKPResult zkp_random_bytes(uint8_t *buf, size_t len);

// Génère une permutation uniforme de {0..n-1} via Fisher-Yates
// Utilise zkp_random_bytes pour chaque swap
ZKPResult zkp_random_permutation(uint8_t perm[ZKP_MAX_N], uint8_t n);

ÉTAPE 4 — GÉNÉRATEUR DE GRAPHE (zkp_graph.h / zkp_graph.c)

4.1 Fonctions sur les graphes

// Initialise un graphe vide
void zkp_graph_init(Graph *G, uint8_t n);

// Ajoute une arête non-orientée (u,v) à G
void zkp_graph_add_edge(Graph *G, uint8_t u, uint8_t v);

// Vérifie si (u,v) est une arête de G
bool zkp_graph_has_edge(const Graph *G, uint8_t u, uint8_t v);

// Vérifie que H est un cycle hamiltonien valide dans G
bool zkp_validate_hamiltonian_cycle(const Graph *G, const HamiltonianCycle *H);

// Vérifie que G est connexe (BFS/DFS)
bool zkp_graph_is_connected(const Graph *G);

// Applique la permutation π à G : retourne G' = π(G)
void zkp_graph_permute(const Graph *G, const uint8_t perm[ZKP_MAX_N], Graph *out);

// Applique la permutation π au cycle H : retourne H' = π(H)
void zkp_cycle_permute(const HamiltonianCycle *H, const uint8_t perm[ZKP_MAX_N],
                       HamiltonianCycle *out);

4.2 Générateur à trapdoor

// Génère un graphe G avec cycle hamiltonien H intégré (la trapdoor)
// n : nombre de nœuds (recommandé : 50-80 pour production)
// extra_ratio : ratio arêtes leurres / n (recommandé : 0.5 à 1.5)
// Retourne ZKP_OK ou code d'erreur
ZKPResult zkp_generate_graph(uint8_t n, double extra_ratio,
                             Graph *out_graph, HamiltonianCycle *out_cycle);

Algorithme exact à implémenter :

1. Générer permutation π aléatoire de {0..n-1}
2. H.nodes = [π[0], π[1], ..., π[n-1]]
   H.n = n
3. Initialiser G vide, ajouter toutes les arêtes de H
4. Calculer nb_extra = (int)(n * extra_ratio)
5. Tentatives max = nb_extra * 10 (éviter boucle infinie)
6. Tant que nb_extra > 0 et tentatives > 0 :
     Choisir u, v aléatoires dans {0..n-1}, u ≠ v
     Si (u,v) ∉ G :
         Ajouter (u,v) à G
         nb_extra--
     tentatives--
7. Vérifier zkp_graph_is_connected(G) → si non, recommencer
8. Vérifier zkp_validate_hamiltonian_cycle(G, H) → assertion
9. Retourner (G, H)

ÉTAPE 5 — PROUVEUR (zkp_prove.c)

// Génère une preuve NIZK complète
// G : graphe public
// H : cycle hamiltonien (trapdoor)
// out_proof : preuve générée
ZKPResult zkp_prove(const Graph *G, const HamiltonianCycle *H,
                    NIZKProof *out_proof);

Algorithme exact :

1. Valider les paramètres (G, H non NULL, G->n == H->n)
2. Initialiser proof->version = ZKP_VERSION, proof->n = G->n
3. Générer proof->session_nonce via zkp_random_bytes
4. Générer permutation π via zkp_random_permutation
5. Calculer G' = zkp_graph_permute(G, π)
   Copier G'.adj dans proof->gprime_adj
6. Pour chaque (i,j) avec i < j < n :
     Générer nonce_matrix[i][j] via zkp_random_bytes
     bit = zkp_graph_has_edge(&G', i, j) ? 1 : 0
     proof->commits[i][j] = zkp_commit(bit, nonce_matrix[i][j])
     proof->commits[j][i] = proof->commits[i][j]  // symétrique
7. proof->challenge = zkp_fiat_shamir_challenge(
       G, proof->gprime_adj, proof->commits, n, proof->session_nonce)
8. Si challenge == 0 (réponse isomorphisme) :
     Copier π dans proof->iso_response.perm
     Copier nonce_matrix dans proof->iso_response.nonces
9. Si challenge == 1 (réponse cycle) :
     Calculer H' = zkp_cycle_permute(H, π)
     Copier H'.nodes dans proof->ham_response.cycle_nodes
     Pour chaque arête (u,v) = (H'[i], H'[(i+1)%n]) :
         u_norm = min(u,v), v_norm = max(u,v)
         proof->ham_response.nonces[i] = nonce_matrix[u_norm][v_norm]
10. Effacer π et nonce_matrix de la mémoire (sodium_memzero ou explicit_bzero)
11. Retourner ZKP_OK

ÉTAPE 6 — VÉRIFIEUR (zkp_verify.c)

// Vérifie une preuve NIZK
// Retourne ZKP_ACCEPT (1), ZKP_REJECT (0), ou ZKP_ERR (-1)
ZKPResult zkp_verify(const Graph *G, const NIZKProof *proof);

Algorithme exact :

1. Valider proof->version == ZKP_VERSION
2. Valider proof->n == G->n
3. Recalculer b = zkp_fiat_shamir_challenge(
       G, proof->gprime_adj, proof->commits, n, proof->session_nonce)
4. Si proof->challenge ≠ b → REJECT (preuve falsifiée)

5. Reconstruire G' depuis proof->gprime_adj

6. Si b == 0 (vérifier isomorphisme) :
     π = proof->iso_response.perm
     Vérifier π est une permutation valide de {0..n-1}
     Calculer G_check = zkp_graph_permute(G, π)
     Vérifier G_check.adj == G'.adj  // graphes identiques
     Pour chaque (i,j), i < j :
         bit = zkp_graph_has_edge(&G', i, j) ? 1 : 0
         Si !zkp_commit_verify(bit, proof->iso_response.nonces[i][j],
                               proof->commits[i][j]) → REJECT

7. Si b == 1 (vérifier cycle hamiltonien) :
     Reconstruire HamiltonianCycle H' depuis proof->ham_response.cycle_nodes
     Si !zkp_validate_hamiltonian_cycle(&G', &H') → REJECT
     Pour chaque arête i du cycle :
         u = H'.nodes[i], v = H'.nodes[(i+1)%n]
         u_norm = min(u,v), v_norm = max(u,v)
         Si !zkp_commit_verify(1, proof->ham_response.nonces[i],
                               proof->commits[u_norm][v_norm]) → REJECT

8. Retourner ACCEPT

ÉTAPE 7 — TESTS (tests/)

Implémente les tests suivants avec un framework minimaliste (pas de dépendance externe) :

test_graph.c

  • test_generate_graph_n20() : génère graphe n=20, vérifie connexité + cycle valide
  • test_generate_graph_n50() : idem n=50
  • test_permute_preserves_cycle() : permuter G+H, cycle reste hamiltonien dans G'
  • test_has_edge_symmetry() : (u,v) ∈ G ⟺ (v,u) ∈ G

test_crypto.c

  • test_commit_deterministic() : même (bit, nonce) → même hash
  • test_commit_binding() : Commit(0,r) ≠ Commit(1,r)
  • test_commit_hiding() : Commit(0,r1) ≠ Commit(0,r2) pour r1≠r2
  • test_commit_verify_valid() : verify accepte commit correct
  • test_commit_verify_tampered() : verify rejette commit modifié
  • test_fiat_shamir_deterministic() : mêmes entrées → même challenge

test_protocol.c

  • test_completeness_n20() : prove + verify → ACCEPT, 100 répétitions
  • test_soundness_bad_cycle() : preuve avec cycle invalide → REJECT
  • test_tamper_commit() : modifier commits[0][1] → REJECT
  • test_tamper_challenge() : modifier proof.challenge → REJECT
  • test_antireplay() : deux preuves → session_nonces différents

test_nizk.c

  • test_nizk_full_n50() : prove + verify pour n=50, mesurer temps
  • test_nizk_challenge_distribution() : 1000 preuves, vérifier ~50% challenge 0/1

ÉTAPE 8 — OUTILS CLI (tools/)

zkp_keygen.c

Usage: zkp_keygen -n <noeuds> [-r <ratio>] -o <fichier>
Génère (G, H) et sauvegarde en binaire.
G → <fichier>.graph
H → <fichier>.key  (confidentiel !)

zkp_prover.c

Usage: zkp_prover -g <fichier>.graph -k <fichier>.key -o <preuve>.proof
Lit G et H, génère la preuve NIZK, sauvegarde.

zkp_verifier.c

Usage: zkp_verifier -g <fichier>.graph -p <preuve>.proof
Lit G et la preuve, affiche ACCEPT ou REJECT.
Exit code : 0=ACCEPT, 1=REJECT, 2=ERREUR

ÉTAPE 9 — CMAKE

cmake_minimum_required(VERSION 3.16)
project(zkp_hamiltonian C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror")

# Trouver libsodium ou OpenSSL
find_package(PkgConfig REQUIRED)
pkg_check_modules(SODIUM libsodium)
if(NOT SODIUM_FOUND)
    find_package(OpenSSL REQUIRED)
    add_compile_definitions(USE_OPENSSL)
endif()

# Bibliothèque principale
add_library(zkp_hamiltonian STATIC
    src/zkp_graph.c
    src/zkp_crypto.c
    src/zkp_prove.c
    src/zkp_verify.c
    src/zkp_serialize.c
)
target_include_directories(zkp_hamiltonian PUBLIC include)
target_link_libraries(zkp_hamiltonian ${SODIUM_LIBRARIES})

# Tests
enable_testing()
foreach(TEST graph crypto protocol nizk)
    add_executable(test_${TEST} tests/test_${TEST}.c)
    target_link_libraries(test_${TEST} zkp_hamiltonian)
    add_test(NAME ${TEST} COMMAND test_${TEST})
endforeach()

# Outils CLI
foreach(TOOL keygen prover verifier)
    add_executable(zkp_${TOOL} tools/zkp_${TOOL}.c)
    target_link_libraries(zkp_${TOOL} zkp_hamiltonian)
endforeach()

CONTRAINTES ABSOLUES

Sécurité :

  1. Ne jamais utiliser rand(), random(), ou time() pour la cryptographie
  2. Toutes les comparaisons de secrets en temps constant (pas de memcmp standard)
  3. Effacer les secrets de la mémoire avec sodium_memzero() ou explicit_bzero() après usage
  4. Valider toutes les entrées avant traitement (taille n, indices, version)

Qualité : 5. Chaque fichier .c compile sans warnings avec -Wall -Wextra -Werror 6. Aucune allocation dynamique dans le code critique (pile uniquement, struct statiques) 7. Chaque fonction retourne un code d'erreur vérifié 8. Commentaires sur toutes les fonctions publiques (format Doxygen)

Embarqué : 9. Pas de printf dans les bibliothèques (seulement dans tools/ et tests/) 10. Stack frame < 4Ko par fonction 11. Tester avec -DOPENWRT_BUILD qui active -Os -ffunction-sections


ORDRE D'IMPLÉMENTATION

Respecte cet ordre et attends ma validation entre chaque étape :

[1] zkp_types.h                    → types de base
[2] zkp_crypto.h + zkp_crypto.c   → primitives, tester avec test_crypto.c
[3] zkp_graph.h + zkp_graph.c     → graphes, tester avec test_graph.c
[4] zkp_prove.c                    → prouveur
[5] zkp_verify.c                   → vérifieur, tester avec test_protocol.c
[6] zkp_serialize.c                → sérialisation
[7] test_nizk.c + benchmarks       → tests complets
[8] tools/ (keygen, prover, verifier)
[9] CMakeLists.txt + openwrt/Makefile

Commence par l'étape [1] et [2] uniquement. Présente le code complet pour ces deux étapes, compilable immédiatement.