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

458 lines
14 KiB
Markdown

# 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 :
```c
#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
```c
// 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
```c
// 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
```c
// 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
```c
// 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
```c
// 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)
```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)
```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 r1r2
- `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
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.