Optimize sync-utxos RPC calls and document bitcoind crash issues
**Motivations:** - Prevent bitcoind crashes caused by heavy RPC calls without timeout - Document bitcoind crash and wallet loading stuck issues - Clean up obsolete files (configure-nginx-proxy.sh, userwallet components, website-skeleton, old fixKnowledge docs) **Root causes:** - RPC calls without timeout causing bitcoind crashes - No pre-check of bitcoind health before heavy operations - Large wallet (315MB) causing long loading times and potential hangs - Missing retry mechanism for transient errors **Correctifs:** - Add timeouts on RPC calls (5 minutes for listunspent, 10 seconds for healthcheck) - Add bitcoind health check before synchronization - Implement retry with exponential backoff - Reduce maximumCount limit from 9999999 to 500000 UTXOs - Improve cron script with pre-checks and better error handling - Add container status verification before script execution **Evolutions:** - New check-services-status.sh script for service diagnostics - Documentation of crash issues in fixKnowledge - Improved logging with timestamps - Better error messages and handling **Pages affectées:** - data/sync-utxos-spent-status.mjs - data/sync-utxos-cron.sh - data/restart-services-cron.sh - data/check-services-status.sh (new) - fixKnowledge/sync-utxos-rpc-optimization.md (new) - fixKnowledge/signet-bitcoind-crash-mining-stopped.md (new) - fixKnowledge/signet-bitcoind-crash-wallet-loading-stuck.md (new) - Removed obsolete files: configure-nginx-proxy.sh, userwallet components, website-skeleton files, old fixKnowledge docs Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
937646cc45
commit
e0ce7a9d83
@ -1,391 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script de configuration Nginx pour les sous-domaines certificator.4nkweb.com
|
||||
# Usage: ./configure-nginx-proxy.sh
|
||||
|
||||
set -e
|
||||
|
||||
PROXY_HOST="192.168.1.100"
|
||||
PROXY_USER="ncantu"
|
||||
NGINX_SITES_AVAILABLE="/etc/nginx/sites-available"
|
||||
NGINX_SITES_ENABLED="/etc/nginx/sites-enabled"
|
||||
CERTBOT_BIN="/usr/bin/certbot"
|
||||
|
||||
echo "=== Configuration Nginx pour certificator.4nkweb.com ==="
|
||||
echo ""
|
||||
|
||||
# Vérifier que nous sommes sur le proxy ou que nous pouvons y accéder
|
||||
# Note: Le script peut être exécuté localement ou via SSH
|
||||
CURRENT_IP=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "")
|
||||
if [ "$CURRENT_IP" != "192.168.1.100" ] && [ -z "$SSH_CONNECTION" ]; then
|
||||
echo "ℹ️ Ce script peut être exécuté sur le proxy (192.168.1.100)"
|
||||
echo " Ou via SSH: ssh ${PROXY_USER}@${PROXY_HOST} 'sudo bash -s' < $0"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Vérifier les permissions (sudo disponible pour ncantu)
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
if command -v sudo &> /dev/null && sudo -n true 2>/dev/null; then
|
||||
echo "✅ Utilisation de sudo (droits non interactifs)"
|
||||
# Le script continuera avec sudo pour les commandes nécessitant root
|
||||
else
|
||||
echo "⚠️ Ce script nécessite les permissions root pour configurer Nginx"
|
||||
echo " Utilisez: sudo $0"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fonction pour exécuter les commandes nécessitant root
|
||||
SUDO_CMD=""
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
SUDO_CMD="sudo"
|
||||
fi
|
||||
|
||||
echo "✅ Vérification de Nginx..."
|
||||
# Vérifier Nginx (peut être dans /usr/sbin/nginx)
|
||||
NGINX_BIN=""
|
||||
if command -v nginx &> /dev/null; then
|
||||
NGINX_BIN="nginx"
|
||||
elif [ -f /usr/sbin/nginx ]; then
|
||||
NGINX_BIN="/usr/sbin/nginx"
|
||||
elif [ -f /usr/bin/nginx ]; then
|
||||
NGINX_BIN="/usr/bin/nginx"
|
||||
else
|
||||
echo "❌ Nginx n'est pas installé"
|
||||
exit 1
|
||||
fi
|
||||
echo " Nginx trouvé: ${NGINX_BIN}"
|
||||
|
||||
echo "✅ Vérification de Certbot..."
|
||||
# Vérifier Certbot (peut être dans /usr/bin/certbot)
|
||||
CERTBOT_BIN=""
|
||||
if command -v certbot &> /dev/null; then
|
||||
CERTBOT_BIN="certbot"
|
||||
elif [ -f /usr/bin/certbot ]; then
|
||||
CERTBOT_BIN="/usr/bin/certbot"
|
||||
else
|
||||
echo "⚠️ Certbot n'est pas installé. Installation..."
|
||||
${SUDO_CMD} apt-get update
|
||||
${SUDO_CMD} apt-get install -y certbot python3-certbot-nginx
|
||||
CERTBOT_BIN="certbot"
|
||||
fi
|
||||
echo " Certbot trouvé: ${CERTBOT_BIN}"
|
||||
|
||||
# Créer les configurations Nginx pour chaque sous-domaine
|
||||
|
||||
# 1. Dashboard (port 3020)
|
||||
echo ""
|
||||
echo "📝 Configuration de dashboard.certificator.4nkweb.com..."
|
||||
${SUDO_CMD} tee "${NGINX_SITES_AVAILABLE}/dashboard.certificator.4nkweb.com" > /dev/null << 'EOF'
|
||||
# Dashboard Bitcoin Signet
|
||||
server {
|
||||
listen 80;
|
||||
server_name dashboard.certificator.4nkweb.com;
|
||||
|
||||
# Logs
|
||||
access_log /var/log/nginx/dashboard.certificator.4nkweb.com.access.log;
|
||||
error_log /var/log/nginx/dashboard.certificator.4nkweb.com.error.log;
|
||||
|
||||
# Proxy vers le service Node.js (port 3020)
|
||||
# Note: Les services tournent sur 192.168.1.105
|
||||
location / {
|
||||
proxy_pass http://192.168.1.105:3020;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# 2. Faucet (port 3021)
|
||||
echo "📝 Configuration de faucet.certificator.4nkweb.com..."
|
||||
${SUDO_CMD} tee "${NGINX_SITES_AVAILABLE}/faucet.certificator.4nkweb.com" > /dev/null << 'EOF'
|
||||
# API Faucet Bitcoin Signet
|
||||
server {
|
||||
listen 80;
|
||||
server_name faucet.certificator.4nkweb.com;
|
||||
|
||||
# Logs
|
||||
access_log /var/log/nginx/faucet.certificator.4nkweb.com.access.log;
|
||||
error_log /var/log/nginx/faucet.certificator.4nkweb.com.error.log;
|
||||
|
||||
# Proxy vers le service Node.js (port 3021)
|
||||
# Note: Les services tournent sur 192.168.1.105
|
||||
location / {
|
||||
proxy_pass http://192.168.1.105:3021;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# 3. Anchorage (port 3010)
|
||||
echo "📝 Configuration de anchorage.certificator.4nkweb.com..."
|
||||
${SUDO_CMD} tee "${NGINX_SITES_AVAILABLE}/anchorage.certificator.4nkweb.com" > /dev/null << 'EOF'
|
||||
# API Anchorage Bitcoin Signet
|
||||
server {
|
||||
listen 80;
|
||||
server_name anchorage.certificator.4nkweb.com;
|
||||
|
||||
# Logs
|
||||
access_log /var/log/nginx/anchorage.certificator.4nkweb.com.access.log;
|
||||
error_log /var/log/nginx/anchorage.certificator.4nkweb.com.error.log;
|
||||
|
||||
# Proxy vers le service Node.js (port 3010)
|
||||
# Note: Les services tournent sur 192.168.1.105
|
||||
location / {
|
||||
proxy_pass http://192.168.1.105:3010;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# 4. Watermark (port 3022)
|
||||
echo "📝 Configuration de watermark.certificator.4nkweb.com..."
|
||||
${SUDO_CMD} tee "${NGINX_SITES_AVAILABLE}/watermark.certificator.4nkweb.com" > /dev/null << 'EOF'
|
||||
# API Watermark Bitcoin Signet
|
||||
server {
|
||||
listen 80;
|
||||
server_name watermark.certificator.4nkweb.com;
|
||||
|
||||
# Logs
|
||||
access_log /var/log/nginx/watermark.certificator.4nkweb.com.access.log;
|
||||
error_log /var/log/nginx/watermark.certificator.4nkweb.com.error.log;
|
||||
|
||||
# Proxy vers le service Node.js (port 3022)
|
||||
# Note: Les services tournent sur 192.168.1.105
|
||||
location / {
|
||||
proxy_pass http://192.168.1.105:3022;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# 5. UserWallet (port 3018)
|
||||
echo "📝 Configuration de userwallet.certificator.4nkweb.com..."
|
||||
${SUDO_CMD} tee "${NGINX_SITES_AVAILABLE}/userwallet.certificator.4nkweb.com" > /dev/null << 'EOF'
|
||||
# UserWallet frontend (Vite)
|
||||
server {
|
||||
listen 80;
|
||||
server_name userwallet.certificator.4nkweb.com;
|
||||
|
||||
# Logs
|
||||
access_log /var/log/nginx/userwallet.certificator.4nkweb.com.access.log;
|
||||
error_log /var/log/nginx/userwallet.certificator.4nkweb.com.error.log;
|
||||
|
||||
# Proxy vers le frontend UserWallet (port 3018) sur 192.168.1.105
|
||||
location / {
|
||||
proxy_pass http://192.168.1.105:3018;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# 6. Website skeleton (port 3024)
|
||||
echo "📝 Configuration de skeleton.certificator.4nkweb.com..."
|
||||
${SUDO_CMD} tee "${NGINX_SITES_AVAILABLE}/skeleton.certificator.4nkweb.com" > /dev/null << 'EOF'
|
||||
# Website skeleton (UserWallet iframe)
|
||||
server {
|
||||
listen 80;
|
||||
server_name skeleton.certificator.4nkweb.com;
|
||||
|
||||
access_log /var/log/nginx/skeleton.certificator.4nkweb.com.access.log;
|
||||
error_log /var/log/nginx/skeleton.certificator.4nkweb.com.error.log;
|
||||
|
||||
location / {
|
||||
proxy_pass http://192.168.1.105:3024;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# 7. Website data (port 3025)
|
||||
echo "📝 Configuration de data.certificator.4nkweb.com..."
|
||||
${SUDO_CMD} tee "${NGINX_SITES_AVAILABLE}/data.certificator.4nkweb.com" > /dev/null << 'EOF'
|
||||
# Website data (iframe data, non clés)
|
||||
server {
|
||||
listen 80;
|
||||
server_name data.certificator.4nkweb.com;
|
||||
|
||||
access_log /var/log/nginx/data.certificator.4nkweb.com.access.log;
|
||||
error_log /var/log/nginx/data.certificator.4nkweb.com.error.log;
|
||||
|
||||
location / {
|
||||
proxy_pass http://192.168.1.105:3025;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# 8. Relay / api-relay (port 3019)
|
||||
echo "📝 Configuration de relay.certificator.4nkweb.com..."
|
||||
${SUDO_CMD} tee "${NGINX_SITES_AVAILABLE}/relay.certificator.4nkweb.com" > /dev/null << 'EOF'
|
||||
# API Relay (UserWallet)
|
||||
server {
|
||||
listen 80;
|
||||
server_name relay.certificator.4nkweb.com;
|
||||
|
||||
# Logs
|
||||
access_log /var/log/nginx/relay.certificator.4nkweb.com.access.log;
|
||||
error_log /var/log/nginx/relay.certificator.4nkweb.com.error.log;
|
||||
|
||||
# Proxy vers api-relay (port 3019) sur 192.168.1.105
|
||||
location / {
|
||||
proxy_pass http://192.168.1.105:3019;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Activer les sites
|
||||
echo ""
|
||||
echo "🔗 Activation des sites..."
|
||||
${SUDO_CMD} ln -sf "${NGINX_SITES_AVAILABLE}/dashboard.certificator.4nkweb.com" "${NGINX_SITES_ENABLED}/dashboard.certificator.4nkweb.com"
|
||||
${SUDO_CMD} ln -sf "${NGINX_SITES_AVAILABLE}/faucet.certificator.4nkweb.com" "${NGINX_SITES_ENABLED}/faucet.certificator.4nkweb.com"
|
||||
${SUDO_CMD} ln -sf "${NGINX_SITES_AVAILABLE}/anchorage.certificator.4nkweb.com" "${NGINX_SITES_ENABLED}/anchorage.certificator.4nkweb.com"
|
||||
${SUDO_CMD} ln -sf "${NGINX_SITES_AVAILABLE}/watermark.certificator.4nkweb.com" "${NGINX_SITES_ENABLED}/watermark.certificator.4nkweb.com"
|
||||
${SUDO_CMD} ln -sf "${NGINX_SITES_AVAILABLE}/userwallet.certificator.4nkweb.com" "${NGINX_SITES_ENABLED}/userwallet.certificator.4nkweb.com"
|
||||
${SUDO_CMD} ln -sf "${NGINX_SITES_AVAILABLE}/skeleton.certificator.4nkweb.com" "${NGINX_SITES_ENABLED}/skeleton.certificator.4nkweb.com"
|
||||
${SUDO_CMD} ln -sf "${NGINX_SITES_AVAILABLE}/data.certificator.4nkweb.com" "${NGINX_SITES_ENABLED}/data.certificator.4nkweb.com"
|
||||
${SUDO_CMD} ln -sf "${NGINX_SITES_AVAILABLE}/relay.certificator.4nkweb.com" "${NGINX_SITES_ENABLED}/relay.certificator.4nkweb.com"
|
||||
|
||||
# Tester la configuration Nginx
|
||||
echo ""
|
||||
echo "🔍 Test de la configuration Nginx..."
|
||||
if ${SUDO_CMD} ${NGINX_BIN} -t; then
|
||||
echo "✅ Configuration Nginx valide"
|
||||
else
|
||||
echo "❌ Erreur dans la configuration Nginx"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Recharger Nginx (configuration HTTP uniquement pour l'instant)
|
||||
echo ""
|
||||
echo "🔄 Rechargement de Nginx (configuration HTTP)..."
|
||||
${SUDO_CMD} systemctl reload nginx || ${SUDO_CMD} service nginx reload
|
||||
|
||||
# Générer les certificats SSL avec Certbot
|
||||
echo ""
|
||||
echo "🔐 Génération des certificats SSL avec Certbot..."
|
||||
echo " Note: Certbot va automatiquement créer les configurations HTTPS"
|
||||
echo ""
|
||||
|
||||
# Générer les certificats (un par un pour éviter les erreurs)
|
||||
DOMAINS=(
|
||||
"dashboard.certificator.4nkweb.com"
|
||||
"faucet.certificator.4nkweb.com"
|
||||
"anchorage.certificator.4nkweb.com"
|
||||
"watermark.certificator.4nkweb.com"
|
||||
"userwallet.certificator.4nkweb.com"
|
||||
"skeleton.certificator.4nkweb.com"
|
||||
"data.certificator.4nkweb.com"
|
||||
"relay.certificator.4nkweb.com"
|
||||
)
|
||||
|
||||
for domain in "${DOMAINS[@]}"; do
|
||||
echo "📜 Génération du certificat pour ${domain}..."
|
||||
# Certbot va automatiquement modifier la config pour ajouter HTTPS et redirection
|
||||
if ${SUDO_CMD} ${CERTBOT_BIN} --nginx -d "${domain}" --non-interactive --agree-tos --email admin@4nkweb.com --redirect; then
|
||||
echo "✅ Certificat généré et configuration HTTPS créée pour ${domain}"
|
||||
else
|
||||
echo "⚠️ Erreur lors de la génération du certificat pour ${domain}"
|
||||
echo " Vous pouvez le générer manuellement avec:"
|
||||
echo " sudo ${CERTBOT_BIN} --nginx -d ${domain}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Recharger Nginx final
|
||||
echo ""
|
||||
echo "🔄 Rechargement final de Nginx..."
|
||||
${SUDO_CMD} systemctl reload nginx || ${SUDO_CMD} service nginx reload
|
||||
|
||||
echo ""
|
||||
echo "✅ Configuration terminée !"
|
||||
echo ""
|
||||
echo "📋 Résumé:"
|
||||
echo " - dashboard.certificator.4nkweb.com -> http://192.168.1.105:3020"
|
||||
echo " - faucet.certificator.4nkweb.com -> http://192.168.1.105:3021"
|
||||
echo " - anchorage.certificator.4nkweb.com -> http://192.168.1.105:3010"
|
||||
echo " - watermark.certificator.4nkweb.com -> http://192.168.1.105:3022"
|
||||
echo " - userwallet.certificator.4nkweb.com -> http://192.168.1.105:3018"
|
||||
echo " - skeleton.certificator.4nkweb.com -> http://192.168.1.105:3024"
|
||||
echo " - data.certificator.4nkweb.com -> http://192.168.1.105:3025"
|
||||
echo " - relay.certificator.4nkweb.com -> http://192.168.1.105:3019"
|
||||
echo ""
|
||||
echo "⚠️ Note: Si les services tournent sur une autre machine,"
|
||||
echo " modifiez les IP dans les fichiers de configuration Nginx"
|
||||
echo ""
|
||||
echo "🔍 Vérification:"
|
||||
echo " - Test Nginx: nginx -t"
|
||||
echo " - Status: systemctl status nginx"
|
||||
echo " - Logs: tail -f /var/log/nginx/*.error.log"
|
||||
echo ""
|
||||
81
data/check-services-status.sh
Executable file
81
data/check-services-status.sh
Executable file
@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
# Check status of Docker services (bitcoind signet, mempool) and mining.
|
||||
# Local only: no SSH. Run on the machine where Docker runs.
|
||||
# Usage: ./data/check-services-status.sh
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
BITCOIND_CONTAINER="bitcoin-signet-instance"
|
||||
DATADIR="/root/.bitcoin"
|
||||
|
||||
echo "=== État des services (${BITCOIND_CONTAINER}, mempool) ==="
|
||||
echo ""
|
||||
|
||||
# Docker containers
|
||||
echo "--- Conteneurs Docker ---"
|
||||
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo "Docker non disponible ou erreur"
|
||||
echo ""
|
||||
|
||||
# Bitcoind
|
||||
if docker ps -q -f "name=^${BITCOIND_CONTAINER}$" 2>/dev/null | grep -q .; then
|
||||
echo "--- Bitcoind (${BITCOIND_CONTAINER}) ---"
|
||||
BITCOIN_DIR=$(docker exec "$BITCOIND_CONTAINER" printenv BITCOIN_DIR 2>/dev/null || echo "$DATADIR")
|
||||
if docker exec "$BITCOIND_CONTAINER" bitcoin-cli -datadir="$BITCOIN_DIR" getblockchaininfo &>/dev/null; then
|
||||
BCI=$(docker exec "$BITCOIND_CONTAINER" bitcoin-cli -datadir="$BITCOIN_DIR" getblockchaininfo 2>/dev/null)
|
||||
BLOCKS=$(echo "$BCI" | jq -r '.blocks // "?"')
|
||||
CHAIN=$(echo "$BCI" | jq -r '.chain // "?"')
|
||||
HEADERS=$(echo "$BCI" | jq -r '.headers // "?"')
|
||||
echo " RPC: OK"
|
||||
echo " Chaîne: $CHAIN | Blocs: $BLOCKS | Headers: $HEADERS"
|
||||
if [ "$BLOCKS" != "?" ]; then
|
||||
TIP_TIME=$(docker exec "$BITCOIND_CONTAINER" bitcoin-cli -datadir="$BITCOIN_DIR" getblock "$(docker exec "$BITCOIND_CONTAINER" bitcoin-cli -datadir="$BITCOIN_DIR" getblockhash "$BLOCKS" 2>/dev/null)" 2>/dev/null | jq -r '.time // 0')
|
||||
if [ -n "$TIP_TIME" ] && [ "$TIP_TIME" != "0" ]; then
|
||||
echo " Dernier bloc (time): $TIP_TIME ($(date -d "@${TIP_TIME}" 2>/dev/null || echo "N/A"))"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo " RPC: HORS SERVICE (bitcoind ne répond pas)"
|
||||
RPC_ERROR=$(docker exec "$BITCOIND_CONTAINER" bitcoin-cli -datadir="$BITCOIN_DIR" getblockchaininfo 2>&1 || true)
|
||||
if echo "$RPC_ERROR" | grep -q "Loading wallet"; then
|
||||
echo " ⚠️ Bitcoind est bloqué en chargement de wallet"
|
||||
echo " Cause probable: wallet volumineux ou corrompu"
|
||||
echo " Solution: Attendre quelques minutes ou redémarrer: docker restart ${BITCOIND_CONTAINER}"
|
||||
elif echo "$RPC_ERROR" | grep -q "Could not connect"; then
|
||||
echo " ⚠️ Bitcoind ne répond pas du tout"
|
||||
echo " Cause probable: bitcoind a planté dans le conteneur"
|
||||
echo " Solution: Redémarrer le conteneur: docker restart ${BITCOIND_CONTAINER}"
|
||||
else
|
||||
echo " Erreur RPC: $RPC_ERROR"
|
||||
echo " Solution: Redémarrer le conteneur: docker restart ${BITCOIND_CONTAINER}"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
echo " Processus dans le conteneur:"
|
||||
docker exec "$BITCOIND_CONTAINER" ps aux 2>/dev/null | head -20 || true
|
||||
# Vérifier si bitcoind est présent dans les processus
|
||||
if ! docker exec "$BITCOIND_CONTAINER" ps aux 2>/dev/null | grep -q "[b]itcoind"; then
|
||||
echo " ⚠️ ATTENTION: Le processus bitcoind n'est pas présent dans le conteneur"
|
||||
echo " Le conteneur est actif mais bitcoind a planté"
|
||||
echo " Solution immédiate: docker restart ${BITCOIND_CONTAINER}"
|
||||
fi
|
||||
echo ""
|
||||
echo " Dernières lignes debug.log:"
|
||||
docker exec "$BITCOIND_CONTAINER" tail -3 "${BITCOIN_DIR}/signet/debug.log" 2>/dev/null || true
|
||||
else
|
||||
echo "--- Bitcoind: conteneur ${BITCOIND_CONTAINER} non trouvé ou arrêté ---"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Mempool stack
|
||||
echo "--- Mempool (docker-compose.signet.yml) ---"
|
||||
if [ -f "${PROJECT_DIR}/mempool/docker-compose.signet.yml" ]; then
|
||||
(cd "${PROJECT_DIR}/mempool" && docker compose -f docker-compose.signet.yml ps 2>/dev/null) || \
|
||||
(cd "${PROJECT_DIR}/mempool" && docker-compose -f docker-compose.signet.yml ps 2>/dev/null) || true
|
||||
else
|
||||
echo " Fichier compose non trouvé"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "=== Fin du rapport ==="
|
||||
@ -81,20 +81,33 @@ if docker ps -a -q -f "name=^${BITCOIND_CONTAINER}$" 2>/dev/null | grep -q .; th
|
||||
|
||||
if [ "$bitcoind_ok" -eq 1 ]; then
|
||||
log " $BITCOIND_CONTAINER started, waiting for RPC to be ready..."
|
||||
# Wait for bitcoind RPC to be ready (max 60 seconds)
|
||||
max_wait=60
|
||||
# Wait for bitcoind RPC to be ready (max 300 seconds = 5 minutes)
|
||||
# Increased timeout to handle large wallet loading (e.g., 315MB wallet can take several minutes)
|
||||
max_wait=300
|
||||
wait_count=0
|
||||
while [ $wait_count -lt $max_wait ]; do
|
||||
BITCOIN_DATADIR=$(docker exec "$BITCOIND_CONTAINER" printenv BITCOIN_DIR 2>/dev/null || echo "/root/.bitcoin")
|
||||
if docker exec "$BITCOIND_CONTAINER" bitcoin-cli -datadir="$BITCOIN_DATADIR" getblockchaininfo &>/dev/null; then
|
||||
log " $BITCOIND_CONTAINER RPC ready"
|
||||
RPC_RESPONSE=$(docker exec "$BITCOIND_CONTAINER" bitcoin-cli -datadir="$BITCOIN_DATADIR" getblockchaininfo 2>&1)
|
||||
if echo "$RPC_RESPONSE" | grep -q '"chain"'; then
|
||||
log " $BITCOIND_CONTAINER RPC ready after ${wait_count}s"
|
||||
break
|
||||
elif echo "$RPC_RESPONSE" | grep -q "Loading wallet"; then
|
||||
# Wallet is still loading, continue waiting
|
||||
if [ $((wait_count % 30)) -eq 0 ]; then
|
||||
log " Wallet still loading... (${wait_count}s elapsed)"
|
||||
fi
|
||||
elif echo "$RPC_RESPONSE" | grep -q "Could not connect"; then
|
||||
# Bitcoind not started yet, continue waiting
|
||||
if [ $((wait_count % 30)) -eq 0 ]; then
|
||||
log " Bitcoind starting... (${wait_count}s elapsed)"
|
||||
fi
|
||||
fi
|
||||
sleep 1
|
||||
wait_count=$((wait_count + 1))
|
||||
sleep 2
|
||||
wait_count=$((wait_count + 2))
|
||||
done
|
||||
if [ $wait_count -ge $max_wait ]; then
|
||||
log " WARN: $BITCOIND_CONTAINER RPC not ready after ${max_wait}s"
|
||||
log " WARN: $BITCOIND_CONTAINER RPC not ready after ${max_wait}s (wallet may still be loading)"
|
||||
log " Check with: docker exec $BITCOIND_CONTAINER bitcoin-cli -datadir=$BITCOIN_DATADIR getblockchaininfo"
|
||||
fi
|
||||
else
|
||||
log " $BITCOIND_CONTAINER FAILED"
|
||||
|
||||
13
data/start-docker-services.log
Normal file
13
data/start-docker-services.log
Normal file
@ -0,0 +1,13 @@
|
||||
2026-02-07T02:07:15+01:00 === Start Docker services at boot ===
|
||||
2026-02-07T02:07:15+01:00 Starting bitcoin-signet-instance...
|
||||
2026-02-07T02:07:15+01:00 bitcoin-signet-instance started
|
||||
2026-02-07T02:32:36+01:00 === Start Docker services at boot ===
|
||||
2026-02-07T02:32:37+01:00 bitcoin-signet-instance already running
|
||||
2026-02-07T02:32:37+01:00 Starting mempool stack...
|
||||
2026-02-07T02:32:37+01:00 mempool stack started
|
||||
2026-02-07T02:32:37+01:00 === Done ===
|
||||
2026-02-08T08:08:57+01:00 === Start Docker services at boot ===
|
||||
2026-02-08T08:08:58+01:00 bitcoin-signet-instance already running
|
||||
2026-02-08T08:08:58+01:00 Starting mempool stack...
|
||||
2026-02-08T08:08:58+01:00 mempool stack started
|
||||
2026-02-08T08:08:58+01:00 === Done ===
|
||||
@ -2,15 +2,50 @@
|
||||
|
||||
# Script de synchronisation des UTXOs dépensés
|
||||
# À exécuter via cron pour maintenir la synchronisation
|
||||
# Vérifie que bitcoind est disponible avant d'exécuter la synchronisation
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
LOG_FILE="$SCRIPT_DIR/sync-utxos.log"
|
||||
BITCOIND_CONTAINER="bitcoin-signet-instance"
|
||||
|
||||
log() {
|
||||
echo "$(date -Iseconds) $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
cd "$PROJECT_DIR" || exit 1
|
||||
|
||||
log "=== Synchronisation des UTXOs dépensés ==="
|
||||
|
||||
# Vérifier que bitcoind est disponible avant de commencer
|
||||
if docker ps -q -f "name=^${BITCOIND_CONTAINER}$" 2>/dev/null | grep -q .; then
|
||||
log "Vérification de la disponibilité de bitcoind..."
|
||||
if docker exec "$BITCOIND_CONTAINER" bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo &>/dev/null; then
|
||||
log " ✅ Bitcoind disponible, démarrage de la synchronisation..."
|
||||
else
|
||||
log " ❌ Bitcoind ne répond pas (RPC non disponible)"
|
||||
log " ⚠️ Arrêt de la synchronisation pour éviter de surcharger bitcoind"
|
||||
log " ℹ️ Le script sera réexécuté à la prochaine heure"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log " ❌ Conteneur bitcoind non trouvé ou arrêté"
|
||||
log " ⚠️ Arrêt de la synchronisation"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Exécuter le script de synchronisation
|
||||
log "Exécution du script de synchronisation..."
|
||||
node "$SCRIPT_DIR/sync-utxos-spent-status.mjs" >> "$LOG_FILE" 2>&1
|
||||
EXIT_CODE=$?
|
||||
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
log "✅ Synchronisation terminée avec succès"
|
||||
else
|
||||
log "❌ Synchronisation échouée (code: $EXIT_CODE)"
|
||||
fi
|
||||
|
||||
# Garder seulement les 100 dernières lignes du log
|
||||
tail -n 100 "$LOG_FILE" > "$LOG_FILE.tmp" && mv "$LOG_FILE.tmp" "$LOG_FILE"
|
||||
|
||||
exit $EXIT_CODE
|
||||
|
||||
@ -33,9 +33,43 @@ const RPC_WALLET = process.env.BITCOIN_RPC_WALLET || 'custom_signet';
|
||||
const DB_PATH = join(__dirname, 'signet.db');
|
||||
|
||||
/**
|
||||
* Effectue un appel RPC Bitcoin
|
||||
* Effectue un appel RPC Bitcoin avec timeout et retry
|
||||
*/
|
||||
function rpcCall(method, params = []) {
|
||||
function rpcCall(method, params = [], timeoutMs = 300000, maxRetries = 3) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let lastError;
|
||||
|
||||
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||
try {
|
||||
const result = await rpcCallOnce(method, params, timeoutMs);
|
||||
resolve(result);
|
||||
return;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
const isTimeoutError = error.message.includes('timeout') ||
|
||||
error.message.includes('ETIMEDOUT') ||
|
||||
error.message.includes('ECONNRESET') ||
|
||||
error.message.includes('socket hang up');
|
||||
|
||||
if (attempt < maxRetries - 1 && isTimeoutError) {
|
||||
const delay = Math.min(1000 * Math.pow(2, attempt), 10000); // Backoff exponentiel, max 10s
|
||||
console.log(` ⚠️ Tentative ${attempt + 1}/${maxRetries} échouée, nouvelle tentative dans ${delay}ms...`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
} else {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reject(lastError);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Effectue un appel RPC Bitcoin unique avec timeout
|
||||
*/
|
||||
function rpcCallOnce(method, params = [], timeoutMs = 300000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = new URL(RPC_URL);
|
||||
const isHttps = url.protocol === 'https:';
|
||||
@ -60,6 +94,7 @@ function rpcCall(method, params = []) {
|
||||
'Authorization': `Basic ${auth}`,
|
||||
'Content-Length': Buffer.byteLength(postData),
|
||||
},
|
||||
timeout: timeoutMs,
|
||||
};
|
||||
|
||||
const req = httpModule.request(options, (res) => {
|
||||
@ -87,17 +122,44 @@ function rpcCall(method, params = []) {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
reject(new Error(`RPC timeout after ${timeoutMs}ms for method ${method}`));
|
||||
});
|
||||
|
||||
req.write(postData);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie que bitcoind est disponible et répond
|
||||
*/
|
||||
async function checkBitcoindHealth() {
|
||||
try {
|
||||
await rpcCall('getblockchaininfo', [], 10000, 1); // Timeout court pour le healthcheck
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(` ❌ Bitcoind non disponible: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronise les UTXOs dépensés
|
||||
*/
|
||||
async function syncSpentUtxos() {
|
||||
console.log('🔍 Démarrage de la synchronisation des UTXOs dépensés...\n');
|
||||
|
||||
// Vérifier que bitcoind est disponible avant de commencer
|
||||
console.log('🔍 Vérification de la disponibilité de bitcoind...');
|
||||
const bitcoindAvailable = await checkBitcoindHealth();
|
||||
if (!bitcoindAvailable) {
|
||||
console.error('❌ Bitcoind n\'est pas disponible. Arrêt de la synchronisation.');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(' ✅ Bitcoind disponible\n');
|
||||
|
||||
// Ouvrir la base de données
|
||||
const db = new Database(DB_PATH);
|
||||
|
||||
@ -117,11 +179,17 @@ async function syncSpentUtxos() {
|
||||
}
|
||||
|
||||
// Récupérer tous les UTXOs disponibles depuis Bitcoin
|
||||
// Note: listunspent ne supporte pas la pagination avec skip
|
||||
// Limiter maximumCount pour éviter les problèmes de mémoire avec de gros wallets
|
||||
// Timeout augmenté à 5 minutes pour les gros wallets
|
||||
console.log('📡 Récupération des UTXOs depuis Bitcoin...');
|
||||
console.log(' ⏳ Cela peut prendre plusieurs minutes avec un wallet volumineux...');
|
||||
// Limite à 500000 UTXOs pour éviter les problèmes de mémoire
|
||||
// Timeout de 5 minutes (300000ms) pour permettre le traitement des gros wallets
|
||||
const unspent = await rpcCall('listunspent', [0, 9999999, [], false, {
|
||||
minimumAmount: 0,
|
||||
maximumCount: 9999999,
|
||||
}]);
|
||||
maximumCount: 500000, // Limite réduite de 9999999 à 500000 pour éviter les problèmes de mémoire
|
||||
}], 300000, 2); // Timeout 5 minutes, max 2 retries
|
||||
|
||||
console.log(`📊 UTXOs disponibles dans Bitcoin: ${unspent.length}`);
|
||||
|
||||
@ -195,6 +263,15 @@ async function syncSpentUtxos() {
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur lors de la synchronisation:', error.message);
|
||||
// Ne pas faire échouer le cron si c'est un problème temporaire de bitcoind
|
||||
// Le script sera réexécuté à l'heure suivante
|
||||
if (error.message.includes('timeout') ||
|
||||
error.message.includes('ECONNRESET') ||
|
||||
error.message.includes('socket hang up') ||
|
||||
error.message.includes('Could not connect')) {
|
||||
console.error(' ⚠️ Problème de connexion avec bitcoind (peut être temporaire)');
|
||||
console.error(' ℹ️ Le script sera réexécuté à la prochaine heure');
|
||||
}
|
||||
process.exit(1);
|
||||
} finally {
|
||||
db.close();
|
||||
|
||||
@ -1,100 +1,100 @@
|
||||
📊 UTXOs à vérifier: 80378
|
||||
📡 Récupération des UTXOs depuis Bitcoin...
|
||||
📊 UTXOs disponibles dans Bitcoin: 268376
|
||||
💾 Création de la table temporaire...
|
||||
💾 Insertion des UTXOs disponibles par batch...
|
||||
⏳ Traitement: 10000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 20000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 30000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 40000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 50000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 60000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 70000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 80000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 90000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 100000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 110000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 120000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 130000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 140000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 150000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 160000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 170000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 180000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 190000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 200000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 210000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 220000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 230000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 240000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 250000/268376 UTXOs insérés...
|
||||
⏳ Traitement: 260000/268376 UTXOs insérés...
|
||||
💾 Mise à jour des UTXOs dépensés...
|
||||
|
||||
📊 Résumé:
|
||||
- UTXOs vérifiés: 80378
|
||||
- UTXOs toujours disponibles: 80378
|
||||
- UTXOs dépensés détectés: 0
|
||||
|
||||
📈 Statistiques finales:
|
||||
- Total UTXOs: 283165
|
||||
- Dépensés: 202127
|
||||
- Non dépensés: 81038
|
||||
- Dépensés: 202787
|
||||
- Non dépensés: 80378
|
||||
|
||||
✅ Synchronisation terminée
|
||||
🔍 Démarrage de la synchronisation des UTXOs dépensés...
|
||||
|
||||
📊 UTXOs à vérifier: 80978
|
||||
📊 UTXOs à vérifier: 80378
|
||||
📡 Récupération des UTXOs depuis Bitcoin...
|
||||
📊 UTXOs disponibles dans Bitcoin: 267500
|
||||
📊 UTXOs disponibles dans Bitcoin: 268385
|
||||
💾 Création de la table temporaire...
|
||||
💾 Insertion des UTXOs disponibles par batch...
|
||||
⏳ Traitement: 10000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 20000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 30000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 40000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 50000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 60000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 70000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 80000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 90000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 100000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 110000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 120000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 130000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 140000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 150000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 160000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 170000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 180000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 190000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 200000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 210000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 220000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 230000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 240000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 250000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 260000/267500 UTXOs insérés...
|
||||
⏳ Traitement: 10000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 20000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 30000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 40000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 50000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 60000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 70000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 80000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 90000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 100000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 110000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 120000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 130000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 140000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 150000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 160000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 170000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 180000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 190000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 200000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 210000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 220000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 230000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 240000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 250000/268385 UTXOs insérés...
|
||||
⏳ Traitement: 260000/268385 UTXOs insérés...
|
||||
💾 Mise à jour des UTXOs dépensés...
|
||||
|
||||
📊 Résumé:
|
||||
- UTXOs vérifiés: 80978
|
||||
- UTXOs toujours disponibles: 80978
|
||||
- UTXOs vérifiés: 80378
|
||||
- UTXOs toujours disponibles: 80378
|
||||
- UTXOs dépensés détectés: 0
|
||||
|
||||
📈 Statistiques finales:
|
||||
- Total UTXOs: 283165
|
||||
- Dépensés: 202187
|
||||
- Non dépensés: 80978
|
||||
- Dépensés: 202787
|
||||
- Non dépensés: 80378
|
||||
|
||||
✅ Synchronisation terminée
|
||||
🔍 Démarrage de la synchronisation des UTXOs dépensés...
|
||||
|
||||
📊 UTXOs à vérifier: 80978
|
||||
📊 UTXOs à vérifier: 80378
|
||||
📡 Récupération des UTXOs depuis Bitcoin...
|
||||
📊 UTXOs disponibles dans Bitcoin: 267516
|
||||
💾 Création de la table temporaire...
|
||||
💾 Insertion des UTXOs disponibles par batch...
|
||||
⏳ Traitement: 10000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 20000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 30000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 40000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 50000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 60000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 70000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 80000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 90000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 100000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 110000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 120000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 130000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 140000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 150000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 160000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 170000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 180000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 190000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 200000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 210000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 220000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 230000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 240000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 250000/267516 UTXOs insérés...
|
||||
⏳ Traitement: 260000/267516 UTXOs insérés...
|
||||
💾 Mise à jour des UTXOs dépensés...
|
||||
❌ Erreur lors de la synchronisation: socket hang up
|
||||
🔍 Démarrage de la synchronisation des UTXOs dépensés...
|
||||
|
||||
📊 Résumé:
|
||||
- UTXOs vérifiés: 80978
|
||||
- UTXOs toujours disponibles: 80978
|
||||
- UTXOs dépensés détectés: 0
|
||||
|
||||
📈 Statistiques finales:
|
||||
- Total UTXOs: 283165
|
||||
- Dépensés: 202187
|
||||
- Non dépensés: 80978
|
||||
|
||||
✅ Synchronisation terminée
|
||||
📊 UTXOs à vérifier: 80378
|
||||
📡 Récupération des UTXOs depuis Bitcoin...
|
||||
❌ Erreur lors de la synchronisation: read ECONNRESET
|
||||
|
||||
@ -1,124 +0,0 @@
|
||||
# Dashboard mauvaise chaîne / API d'ancrage "Insufficient Balance"
|
||||
|
||||
**Auteur** : Équipe 4NK
|
||||
**Date** : 2026-02-02
|
||||
**Version** : 1.0
|
||||
|
||||
## Symptômes
|
||||
|
||||
- **Dashboard** (https://dashboard.certificator.4nkweb.com/) : affiche une mauvaise chaîne (hauteur 0, "-", ou valeurs incohérentes) au lieu d’environ 11535 blocs.
|
||||
- **Clients de l’API d’ancrage** : reçoivent `{"error":"Insufficient Balance","message":"Insufficient balance. Required: 0.00001 BTC, Available: 0 BTC"}` et l’API d’ancrage ne fonctionne pas correctement.
|
||||
|
||||
## Impacts
|
||||
|
||||
- Les utilisateurs ne voient pas l’état réel de la blockchain.
|
||||
- L’ancrage de documents échoue (solde 0 côté nœud utilisé par l’API).
|
||||
|
||||
## Cause
|
||||
|
||||
Une seule cause racine couvre les deux symptômes : **le nœud Bitcoin Signet auquel se connectent le Dashboard et l’API d’ancrage n’a pas la bonne chaîne ou n’a pas de solde**.
|
||||
|
||||
Causes possibles :
|
||||
|
||||
1. **Chaîne perdue** : le conteneur `bitcoin-signet-instance` a été recréé **sans volume persistant** (`-v signet-bitcoin-data:/root/.bitcoin`). Le nœud repart sur une nouvelle chaîne (hauteur 0 ou très basse), sans historique de mining → solde 0. Voir [signet-chain-lost-volume-persistent.md](./signet-chain-lost-volume-persistent.md).
|
||||
2. **Mauvais déploiement** : le Dashboard et/ou l’API d’ancrage tournent sur une **autre machine** (ex. prod 192.168.1.103). Avec `BITCOIN_RPC_HOST=127.0.0.1`, ils appellent alors le RPC de cette machine, où il n’y a pas de nœud Signet (ou un nœud vide) → hauteur 0 ou erreur, solde 0.
|
||||
3. **Wallet par défaut** : le nœud a la bonne chaîne mais le **wallet par défaut** utilisé par l’API n’est pas celui qui reçoit les récompenses de minage (`custom_signet`) → `getBalance()` retourne 0.
|
||||
|
||||
## Correctifs
|
||||
|
||||
### 0. Script de correction (machine bitcoin)
|
||||
|
||||
Sur la machine bitcoin (192.168.1.105), à la racine du projet :
|
||||
|
||||
```bash
|
||||
cd /home/ncantu/Bureau/code/bitcoin
|
||||
|
||||
# Vérifier, redémarrer Dashboard et API d’ancrage, lancer la vérification d’alignement
|
||||
./fix-dashboard-anchor-chain.sh
|
||||
|
||||
# Si la chaîne a été perdue, restaurer depuis une sauvegarde puis redémarrer
|
||||
./fix-dashboard-anchor-chain.sh backups/signet-datadir-YYYYMMDD-HHMMSS.tar.gz
|
||||
```
|
||||
|
||||
Le script teste d'abord la config RPC (même nœud que Mempool) avec `./test-mempool-rpc-config.sh 127.0.0.1 38332`, puis redémarre `signet-dashboard` et `anchorage-api`, lance `verify-chain-alignment.sh`, et affiche le wallet du nœud (solde).
|
||||
|
||||
**Configuration unique (une seule chaîne pour Mempool, dashboard, APIs, miner) :** Un seul nœud : **bitcoin-signet-instance** sur **38332** (Mempool = host.docker.internal:38332, dashboard/APIs/miner = 127.0.0.1:38332). **Volume par défaut (chaîne complète) :** `update-signet.sh` utilise par défaut le volume contenant la chaîne Signet complète (~11530 blocs) s'il existe : volume Docker d'ID **4b5dca4d940b9f6e5db67b460f40f230a5ef1195a3769e5f91fa02be6edde649** (`SIGNET_VOLUME_FULL_CHAIN` dans le script). Sinon, volume nommé **signet-bitcoin-data**. **Sauvegarde prête à télécharger :** `backups/signet-datadir-latest.tar.gz` (symlink vers la dernière archive créée par `./save-signet-datadir-backup.sh`). **Alignement :** Même machine : `BITCOIN_RPC_HOST=127.0.0.1`, `BITCOIN_RPC_PORT=38332`. Le seul processus sur 38332 doit être le conteneur **bitcoin-signet-instance** (Mempool utilise `host.docker.internal:38332` = ce même conteneur). Vérifier qu’il ne s’agit pas d’un autre Docker : `ss -tlnp | grep 38332` et `docker ps --format '{{.Names}}' | grep bitcoin-signet-instance`. Le miner utilise `BITCOIN_RPC_HOST` / `BITCOIN_RPC_PORT` en env (défaut 127.0.0.1:38332). Tester : `./test-mempool-rpc-config.sh 127.0.0.1 38332`. Vérifier le dashboard : `./verify-dashboard-signet.sh`.
|
||||
|
||||
### 1. Vérifier où tournent le Dashboard et l’API d’ancrage
|
||||
|
||||
- **Dashboard** : doit être sur la **machine bitcoin (192.168.1.105)**. Vérifier le service `signet-dashboard` sur cette machine.
|
||||
- **API d’ancrage** (`anchorage.certificator.4nkweb.com`) : doit être sur la **machine bitcoin (192.168.1.105)**. Vérifier le service `anchorage-api` sur cette machine.
|
||||
|
||||
Les deux doivent utiliser `BITCOIN_RPC_HOST=127.0.0.1` et `BITCOIN_RPC_PORT=38332` pour parler au nœud local (conteneur sur la même machine).
|
||||
|
||||
### 2. Source de vérité : Mempool et utilisateur ncantu
|
||||
|
||||
**Mempool** (machine bitcoin 192.168.1.105, `/srv/4NK/mempool.4nkweb.com`) se connecte au nœud Signet du même hôte (`host.docker.internal:38332`). Si Mempool affiche la bonne chaîne (~11535 blocs), le nœud utilisé par Mempool sur cette machine a encore la chaîne complète.
|
||||
|
||||
**Où chercher la chaîne / les sauvegardes (utilisateur ncantu, machine bitcoin 105) :**
|
||||
|
||||
- **Sauvegarde prête à télécharger** : `backups/signet-datadir-latest.tar.gz` (dernière archive datadir, ~11530 blocs). Créée par `./save-signet-datadir-backup.sh` ; le script met à jour le symlink à chaque sauvegarde.
|
||||
- **Sauvegardes horodatées** : `backups/signet-datadir-YYYYMMDD-HHMMSS.tar.gz`.
|
||||
- **Volume Docker « chaîne complète »** : volume d'ID `4b5dca4d940b9f6e5db67b460f40f230a5ef1195a3769e5f91fa02be6edde649` ; `update-signet.sh` l'utilise par défaut s'il existe (voir docs/MAINTENANCE.md).
|
||||
- **Volume nommé** : après restauration via `restore-signet-from-backup.sh`, le conteneur utilise `signet-bitcoin-data`. Vérifier avec `docker inspect bitcoin-signet-instance` (Mounts).
|
||||
- **Datadir dans le conteneur** : si le conteneur sur 105 n’a jamais été recréé sans volume, les blocs sont dans le conteneur ; faire une sauvegarde avec `save-signet-datadir-backup.sh` ou `docker exec bitcoin-signet-instance tar czf /tmp/bitcoin-backup.tar.gz /root/.bitcoin/` puis `docker cp` vers l’hôte.
|
||||
|
||||
Pour corriger la machine dont le nœud n’a que quelques blocs (ex. 6) : soit **restaurer** depuis une archive issue de 105 ou de `backups/` sous ncantu, soit **pointer** le Dashboard / l’API d’ancrage vers le RPC du nœud sur 105 (ex. `BITCOIN_RPC_HOST=192.168.1.105`) si l’architecture le permet.
|
||||
|
||||
### 3. Restaurer la chaîne si elle a été perdue
|
||||
|
||||
Si le nœud a une hauteur très basse (ex. 0, 6) ou pas de volume persistant :
|
||||
|
||||
1. Sur la machine qui a encore la chaîne ~11535 (ex. machine bitcoin 105, ou là où Mempool affiche la bonne chaîne) : exécuter `./save-signet-datadir-backup.sh`, ou récupérer une archive depuis `/home/ncantu/Bureau/code/bitcoin/backups/` (utilisateur ncantu).
|
||||
2. Copier l’archive sur la machine à corriger si besoin.
|
||||
3. Sur la machine à corriger : exécuter `./restore-signet-from-backup.sh backups/signet-datadir-YYYYMMDD-HHMMSS.tar.gz`.
|
||||
4. Redémarrer le conteneur si nécessaire et vérifier : `sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=$(docker exec bitcoin-signet-instance printenv BITCOIN_DIR 2>/dev/null || echo /root/.bitcoin) getblockchaininfo`.
|
||||
|
||||
Voir [signet-chain-lost-volume-persistent.md](./signet-chain-lost-volume-persistent.md) et [MAINTENANCE.md](../docs/MAINTENANCE.md).
|
||||
|
||||
### 4. Vérifier l’alignement chaîne / solde
|
||||
|
||||
Sur la machine bitcoin :
|
||||
|
||||
```bash
|
||||
./verify-chain-alignment.sh
|
||||
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo | grep -E '"chain"|"blocks"'
|
||||
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo
|
||||
```
|
||||
|
||||
- `chain` doit être `"signet"`, `blocks` proche de 11535.
|
||||
- Le wallet utilisé par défaut (ex. `custom_signet`) doit avoir un solde > 0 après mining.
|
||||
|
||||
### 5. Si la chaîne est bonne mais le solde reste 0
|
||||
|
||||
Vérifier que le wallet contenant les récompenses de minage est bien celui utilisé par l’API :
|
||||
|
||||
- Le miner utilise en général le wallet `custom_signet`.
|
||||
- L’API d’ancrage appelle `getBalance()` sans nom de wallet → utilise le **wallet par défaut** du nœud.
|
||||
- Si le nœud a plusieurs wallets, s’assurer que le wallet par défaut est celui qui a du solde (ou charger `custom_signet` au démarrage du nœud comme wallet par défaut).
|
||||
|
||||
## Modalités de déploiement
|
||||
|
||||
- Sur la machine bitcoin : exécuter `./fix-dashboard-anchor-chain.sh` (avec chemin de backup si la chaîne a été perdue). Le script redémarre `signet-dashboard` et `anchorage-api`.
|
||||
- Recréer le conteneur Bitcoin via `./update-signet.sh` : le script utilise par défaut le volume chaîne complète (ID `4b5dca4d940b9f6e5db67b460f40f230a5ef1195a3769e5f91fa02be6edde649`) s'il existe, sinon `signet-bitcoin-data`. Ne pas recréer manuellement sans volume persistant.
|
||||
- Sauvegarde prête à télécharger : `backups/signet-datadir-latest.tar.gz` (créée par `./save-signet-datadir-backup.sh`).
|
||||
|
||||
## Modalités d’analyse
|
||||
|
||||
- Consulter les logs du Dashboard : `sudo journalctl -u signet-dashboard -f` (erreurs RPC).
|
||||
- Consulter les logs de l’API d’ancrage : `sudo journalctl -u anchorage-api -f` (erreurs "Insufficient balance", connexion RPC).
|
||||
- Vérifier la hauteur et le wallet sur le nœud : commandes ci-dessus.
|
||||
|
||||
## Pages affectées
|
||||
|
||||
- fixKnowledge/dashboard-anchor-wrong-chain-insufficient-balance.md (ce fichier)
|
||||
- fixKnowledge/signet-chain-lost-volume-persistent.md (volume chaîne complète, sauvegarde latest)
|
||||
- update-signet.sh (SIGNET_VOLUME_FULL_CHAIN, utilisation par défaut du volume chaîne complète)
|
||||
- save-signet-datadir-backup.sh (symlink signet-datadir-latest.tar.gz, tolérance tar exit 1)
|
||||
- backups/README.md (sauvegarde prête à télécharger, volume par défaut)
|
||||
- docs/MAINTENANCE.md (volume chaîne complète, sauvegarde latest)
|
||||
- test-mempool-rpc-config.sh (test de la config RPC utilisée par Mempool)
|
||||
- verify-dashboard-signet.sh (vérification que le dashboard affiche le signet custom)
|
||||
- fix-dashboard-anchor-chain.sh (script de correction sur la machine bitcoin)
|
||||
- signet-dashboard.service, anchorage-api.service, faucet-api.service (alignement RPC sur le même nœud que Mempool)
|
||||
- signet-dashboard/public/app.js (vérification response.ok et alerte chaîne anormale)
|
||||
@ -1,88 +0,0 @@
|
||||
# Fix: Mempool API Healthcheck - curl not found
|
||||
|
||||
**Date:** 2026-01-27
|
||||
**Auteur:** Équipe 4NK
|
||||
|
||||
## Problème
|
||||
|
||||
Le conteneur Docker `mempool_api_1` était marqué comme "unhealthy" avec un FailingStreak de 2963 échecs consécutifs.
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Statut Docker: `unhealthy`
|
||||
- Erreur répétée: `/bin/sh: 1: curl: not found`
|
||||
- Le healthcheck ne pouvait pas s'exécuter car `curl` n'est pas installé dans l'image `mempool/backend:latest`
|
||||
|
||||
### Impact
|
||||
|
||||
- Le conteneur fonctionnait normalement (les logs montraient une synchronisation correcte des index Bitcoin)
|
||||
- Le statut "unhealthy" générait des alertes et masquait l'état réel du service
|
||||
- Pas d'impact fonctionnel direct, mais confusion sur l'état réel du service
|
||||
|
||||
## Root cause
|
||||
|
||||
Le healthcheck dans `docker-compose.signet.yml` utilisait la commande `curl` qui n'est pas disponible dans l'image Docker `mempool/backend:latest`. L'image ne contient que les dépendances minimales nécessaires au backend Node.js.
|
||||
|
||||
## Correctifs
|
||||
|
||||
### Modification du healthcheck
|
||||
|
||||
**Fichier modifié:** `mempool/docker-compose.signet.yml`
|
||||
|
||||
**Avant:**
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8999/api/v1/backend-info | grep -q . || exit 1"]
|
||||
```
|
||||
|
||||
**Après:**
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:8999/api/v1/backend-info', (r) => { process.exit(r.statusCode === 200 ? 0 : 1); }).on('error', () => process.exit(1));\""]
|
||||
```
|
||||
|
||||
### Justification
|
||||
|
||||
- `node` est disponible dans l'image (backend Node.js)
|
||||
- Utilisation de l'API HTTP native de Node.js au lieu de `curl`
|
||||
- Même logique de vérification: requête HTTP vers `/api/v1/backend-info` avec vérification du code de statut 200
|
||||
|
||||
## Modifications
|
||||
|
||||
- `mempool/docker-compose.signet.yml`: Modification du healthcheck du service `api`
|
||||
|
||||
## Modalités de déploiement
|
||||
|
||||
1. Modifier le fichier `docker-compose.signet.yml`
|
||||
2. Recréer le conteneur pour appliquer la nouvelle configuration:
|
||||
```bash
|
||||
cd /srv/4NK/mempool.4nkweb.com
|
||||
docker-compose -f docker-compose.signet.yml up -d --force-recreate api
|
||||
```
|
||||
3. Vérifier que le healthcheck passe à "healthy" après le délai de démarrage (40s)
|
||||
|
||||
## Modalités d'analyse
|
||||
|
||||
### Vérification du statut
|
||||
|
||||
```bash
|
||||
docker inspect mempool_api_1 --format='{{.State.Health.Status}}'
|
||||
```
|
||||
|
||||
### Vérification des logs du healthcheck
|
||||
|
||||
```bash
|
||||
docker inspect mempool_api_1 --format='{{json .State.Health}}' | python3 -m json.tool
|
||||
```
|
||||
|
||||
### Test manuel du healthcheck
|
||||
|
||||
```bash
|
||||
docker exec mempool_api_1 node -e "require('http').get('http://localhost:8999/api/v1/backend-info', (r) => { console.log('Status:', r.statusCode); process.exit(r.statusCode === 200 ? 0 : 1); }).on('error', (e) => { console.error('Error:', e.message); process.exit(1); });"
|
||||
```
|
||||
|
||||
## Résultat
|
||||
|
||||
- Le conteneur `mempool_api_1` est maintenant marqué comme "healthy"
|
||||
- Le healthcheck fonctionne correctement avec Node.js
|
||||
- Aucun impact sur le fonctionnement du service
|
||||
@ -1,106 +0,0 @@
|
||||
# Correction: Mempool affiche "hors connexion"
|
||||
|
||||
**Date**: 2026-01-26
|
||||
**Auteur**: Équipe 4NK
|
||||
|
||||
## Motivations
|
||||
|
||||
- Le mempool affichait le site mais indiquait "hors connexion"
|
||||
- Le WebSocket ne pouvait pas se connecter au backend
|
||||
- L'utilisateur ne pouvait pas utiliser l'explorateur blockchain
|
||||
|
||||
## Root causes
|
||||
|
||||
- Le backend mempool était en état "unhealthy" mais fonctionnait partiellement
|
||||
- Le backend avait des problèmes de connexion avec Bitcoin RPC (erreurs `ECONNRESET`)
|
||||
- Le processus de mise à jour des blocs était bloqué (`$updateBlocks stalled`)
|
||||
- Le backend nécessitait un redémarrage pour se resynchroniser correctement
|
||||
|
||||
## Correctifs
|
||||
|
||||
- Redémarrage du backend mempool pour résoudre les problèmes de connexion
|
||||
- Vérification de la configuration nginx du frontend (proxy WebSocket vers `http://api:8999/`)
|
||||
- Vérification de la connectivité entre le frontend et le backend via le réseau Docker
|
||||
- Restauration de la configuration nginx après des modifications incorrectes
|
||||
|
||||
## Evolutions
|
||||
|
||||
- Création d'un script de diagnostic (`mempool/diagnose-mempool.sh`) pour vérifier l'état des services localement
|
||||
- Création d'un script de diagnostic et correction (`mempool/fix-mempool-websocket.sh`) pour diagnostiquer et corriger les problèmes sur la machine bitcoin
|
||||
- Amélioration de la configuration nginx pour le WebSocket (ajout des headers nécessaires dans `mempool/nginx-mempool.conf`)
|
||||
|
||||
## Pages affectées
|
||||
|
||||
- `mempool/diagnose-mempool.sh` : Script de diagnostic local
|
||||
- `mempool/fix-mempool-websocket.sh` : Script de diagnostic et correction distant
|
||||
- `mempool/nginx-mempool.conf` : Configuration nginx pour le WebSocket (amélioration des headers)
|
||||
|
||||
## Modalités de déploiement
|
||||
|
||||
1. **Diagnostic local** (depuis la racine du projet `/home/ncantu/Bureau/code/bitcoin/`) :
|
||||
```bash
|
||||
cd mempool
|
||||
./diagnose-mempool.sh
|
||||
```
|
||||
|
||||
2. **Redémarrage du backend si nécessaire** :
|
||||
```bash
|
||||
docker-compose -f docker-compose.signet.yml restart api
|
||||
```
|
||||
|
||||
3. **Vérification de la connectivité** :
|
||||
```bash
|
||||
docker-compose -f docker-compose.signet.yml exec -T web curl -f -s http://api:8999/api/v1/backend-info
|
||||
```
|
||||
|
||||
4. **Redémarrage du frontend si la configuration nginx a été modifiée** :
|
||||
```bash
|
||||
docker-compose -f docker-compose.signet.yml restart web
|
||||
```
|
||||
|
||||
## Modalités d'analyse
|
||||
|
||||
### Vérifier l'état des services
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.signet.yml ps
|
||||
```
|
||||
|
||||
### Vérifier les logs du backend
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.signet.yml logs --tail=50 api | grep -E "(ERROR|ERR|WARN|failed|error)"
|
||||
```
|
||||
|
||||
### Tester la connectivité backend
|
||||
|
||||
```bash
|
||||
# Depuis le conteneur frontend
|
||||
docker-compose -f docker-compose.signet.yml exec -T web curl -f -s http://api:8999/api/v1/backend-info
|
||||
|
||||
# Depuis l'extérieur
|
||||
curl -s http://localhost:3015/api/v1/backend-info
|
||||
```
|
||||
|
||||
### Vérifier la configuration nginx WebSocket
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.signet.yml exec -T web cat /etc/nginx/conf.d/nginx-mempool.conf | grep -A 5 "api/v1/ws"
|
||||
```
|
||||
|
||||
La configuration doit être :
|
||||
```nginx
|
||||
location /api/v1/ws {
|
||||
proxy_pass http://api:8999/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Le backend mempool écoute sur toutes les routes pour les WebSockets (WebSocket.Server créé directement sur le serveur HTTP)
|
||||
- Le nginx du frontend proxyfie `/api/v1/ws` vers `http://api:8999/` (racine du backend)
|
||||
- Le backend peut être en état "unhealthy" mais fonctionner quand même si le healthcheck échoue pour une raison technique (par exemple, `curl` non disponible dans le conteneur)
|
||||
- Les erreurs `ECONNRESET` avec Bitcoin RPC peuvent être temporaires et se résoudre avec un redémarrage
|
||||
55
fixKnowledge/signet-bitcoind-crash-mining-stopped.md
Normal file
55
fixKnowledge/signet-bitcoind-crash-mining-stopped.md
Normal file
@ -0,0 +1,55 @@
|
||||
# Signet: bitcoind crash, mining stopped
|
||||
|
||||
**Date:** 2026-02-05
|
||||
**Auteur:** Équipe 4NK
|
||||
|
||||
## Problème
|
||||
|
||||
La chaîne signet ne mine plus. Les logs du miner affichent en boucle :
|
||||
`error: timeout on transient error: Could not connect to the server 127.0.0.1:38332`.
|
||||
|
||||
## Cause
|
||||
|
||||
Le processus **bitcoind** a cessé de tourner à l’intérieur du conteneur `bitcoin-signet-instance`. Le conteneur reste « Up », mais seul le script `run.sh` et `mine.sh` sont actifs ; bitcoind n’apparaît plus dans `ps aux`. Le RPC bitcoind (port 38332) ne répond donc pas.
|
||||
|
||||
Dans `signet/debug.log`, la dernière ligne utile est un `CreateNewBlock()` ou `UpdateTip` ; aucune ligne d’erreur ou de shutdown explicite (crash ou kill possible, ex. OOM).
|
||||
|
||||
## Diagnostic
|
||||
|
||||
1. Lancer le script d’état :
|
||||
```bash
|
||||
./data/check-services-status.sh
|
||||
```
|
||||
2. Si « RPC: HORS SERVICE » pour bitcoind alors que le conteneur est Up : confirmer avec
|
||||
```bash
|
||||
docker exec bitcoin-signet-instance ps aux
|
||||
```
|
||||
(absence du processus `bitcoind`).
|
||||
3. Dernières lignes du log bitcoind :
|
||||
```bash
|
||||
docker exec bitcoin-signet-instance tail -20 /root/.bitcoin/signet/debug.log
|
||||
```
|
||||
|
||||
## Correction
|
||||
|
||||
Redémarrer le conteneur pour relancer bitcoind (et donc le minage) :
|
||||
|
||||
```bash
|
||||
docker restart bitcoin-signet-instance
|
||||
```
|
||||
|
||||
Attendre ~30–60 s que bitcoind et le wallet soient prêts, puis vérifier :
|
||||
|
||||
```bash
|
||||
docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo
|
||||
```
|
||||
|
||||
## Prévention / suivi
|
||||
|
||||
- Utiliser `./data/check-services-status.sh` régulièrement (ou via cron) pour détecter RPC hors service.
|
||||
- En cas de répétition des crashes, investiguer (RAM, disque, `dmesg` OOM, logs bitcoind complets).
|
||||
|
||||
## Pages affectées
|
||||
|
||||
- `data/check-services-status.sh` (script de vérification)
|
||||
- `fixKnowledge/signet-bitcoind-crash-mining-stopped.md` (ce document)
|
||||
186
fixKnowledge/signet-bitcoind-crash-wallet-loading-stuck.md
Normal file
186
fixKnowledge/signet-bitcoind-crash-wallet-loading-stuck.md
Normal file
@ -0,0 +1,186 @@
|
||||
# Signet: bitcoind crash puis blocage en chargement de wallet
|
||||
|
||||
**Date:** 2026-02-08
|
||||
**Auteur:** Équipe 4NK
|
||||
|
||||
## Problème
|
||||
|
||||
Bitcoind a planté silencieusement dans le conteneur `bitcoin-signet-instance` vers 3h08 du matin. Le conteneur reste actif, mais le processus bitcoind n'est plus en cours d'exécution. Au redémarrage du conteneur à 07:12, bitcoind est bloqué en chargement de wallet ("Loading wallet…").
|
||||
|
||||
### Symptômes
|
||||
|
||||
1. **Conteneur actif mais bitcoind planté** :
|
||||
- Le conteneur Docker est "Up" mais bitcoind n'est plus en cours d'exécution
|
||||
- `ps aux` dans le conteneur montre uniquement `entrypoint.sh`, `run.sh`, `mine.sh`, `tail -f /dev/null`
|
||||
- Pas de processus `bitcoind`
|
||||
|
||||
2. **RPC inaccessible** :
|
||||
- `bitcoin-cli` retourne : `Could not connect to the server 127.0.0.1:38332`
|
||||
- `curl` vers le RPC depuis l'hôte : connexion reset (exit 56)
|
||||
- Le port 38332 est toujours en écoute côté hôte (mapping Docker) mais il n'y a plus de processus RPC derrière
|
||||
|
||||
3. **Blocage en chargement de wallet** :
|
||||
- Après redémarrage, bitcoind est bloqué en "Loading wallet…"
|
||||
- `bitcoin-cli getblockchaininfo` retourne : `error code: -28 error message: Loading wallet…`
|
||||
- CPU à 100% mais aucun progrès visible
|
||||
- Wallet volumineux : 315MB (`wallet.dat`)
|
||||
|
||||
4. **Erreurs des services dépendants** :
|
||||
- Dashboard : erreurs "socket hang up" et "read ECONNRESET"
|
||||
- Script `sync-utxos-cron.sh` : erreurs de connexion RPC
|
||||
|
||||
### Impact
|
||||
|
||||
- **Fonctionnalité** : Tous les services dépendants de bitcoind sont hors service
|
||||
- **Mining** : Le minage est arrêté
|
||||
- **API** : L'API d'ancrage ne peut plus fonctionner
|
||||
- **Dashboard** : Le dashboard ne peut plus afficher les données de la blockchain
|
||||
|
||||
## Root causes
|
||||
|
||||
1. **Crash silencieux de bitcoind** :
|
||||
- Bitcoind a planté silencieusement vers 3h08 sans message d'erreur dans les logs
|
||||
- Dernière activité normale : bloc miné à 02:54:29 (height 13139)
|
||||
- Nouveau bloc reçu à 03:08:48 (height 13140)
|
||||
- Après cela, plus aucune activité jusqu'au redémarrage à 07:12:16
|
||||
|
||||
2. **Wallet volumineux** :
|
||||
- Le wallet `custom_signet` fait 315MB
|
||||
- Le chargement du wallet peut prendre beaucoup de temps ou bloquer si le wallet est corrompu ou trop volumineux
|
||||
|
||||
3. **Absence de détection automatique** :
|
||||
- Le conteneur Docker reste actif même si bitcoind plante
|
||||
- Aucun mécanisme de redémarrage automatique de bitcoind dans le conteneur
|
||||
- Le script `check-services-status.sh` détecte le problème mais ne le corrige pas automatiquement
|
||||
|
||||
## Diagnostic
|
||||
|
||||
### Vérifier l'état actuel
|
||||
|
||||
1. **Vérifier les processus dans le conteneur** :
|
||||
```bash
|
||||
docker exec bitcoin-signet-instance ps aux
|
||||
```
|
||||
Si bitcoind n'apparaît pas, il a planté.
|
||||
|
||||
2. **Vérifier l'état du RPC** :
|
||||
```bash
|
||||
docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo
|
||||
```
|
||||
Si erreur "Could not connect" ou "Loading wallet…", bitcoind est planté ou bloqué.
|
||||
|
||||
3. **Vérifier les logs** :
|
||||
```bash
|
||||
docker logs bitcoin-signet-instance --tail 100
|
||||
docker exec bitcoin-signet-instance tail -50 /root/.bitcoin/signet/debug.log
|
||||
```
|
||||
|
||||
4. **Vérifier la taille du wallet** :
|
||||
```bash
|
||||
docker exec bitcoin-signet-instance du -sh /root/.bitcoin/signet/wallets/custom_signet/
|
||||
```
|
||||
|
||||
### Utiliser le script de diagnostic
|
||||
|
||||
```bash
|
||||
./data/check-services-status.sh
|
||||
```
|
||||
|
||||
Si "RPC: HORS SERVICE", bitcoind a planté.
|
||||
|
||||
## Correctifs
|
||||
|
||||
### Solution immédiate : Redémarrer le conteneur
|
||||
|
||||
```bash
|
||||
docker restart bitcoin-signet-instance
|
||||
```
|
||||
|
||||
Attendre 60-120 secondes que bitcoind démarre et charge le wallet, puis vérifier :
|
||||
|
||||
```bash
|
||||
docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo
|
||||
```
|
||||
|
||||
### Si le wallet reste bloqué en chargement
|
||||
|
||||
Si après redémarrage, bitcoind reste bloqué en "Loading wallet…" pendant plus de 5 minutes :
|
||||
|
||||
1. **Arrêter le conteneur** :
|
||||
```bash
|
||||
docker stop bitcoin-signet-instance
|
||||
```
|
||||
|
||||
2. **Vérifier l'intégrité du wallet** :
|
||||
```bash
|
||||
docker exec bitcoin-signet-instance ls -lah /root/.bitcoin/signet/wallets/custom_signet/
|
||||
```
|
||||
Vérifier la présence de `wallet.dat-journal` (fichier de journalisation)
|
||||
|
||||
3. **Redémarrer avec vérification** :
|
||||
```bash
|
||||
docker start bitcoin-signet-instance
|
||||
docker logs bitcoin-signet-instance -f
|
||||
```
|
||||
Surveiller les logs pour voir si le wallet se charge correctement.
|
||||
|
||||
4. **Si le wallet est corrompu** :
|
||||
- Sauvegarder le wallet actuel
|
||||
- Essayer de réparer avec `bitcoin-wallet` (si disponible)
|
||||
- Ou restaurer depuis une sauvegarde
|
||||
|
||||
### Solution préventive : Améliorer la détection et le redémarrage automatique
|
||||
|
||||
1. **Améliorer le script `check-services-status.sh`** :
|
||||
- Détecter si bitcoind est planté même si le conteneur est actif
|
||||
- Proposer automatiquement un redémarrage si le problème est détecté
|
||||
|
||||
2. **Ajouter un healthcheck Docker** :
|
||||
- Créer un healthcheck qui vérifie que le RPC bitcoind répond
|
||||
- Configurer Docker pour redémarrer automatiquement le conteneur si le healthcheck échoue
|
||||
|
||||
3. **Surveillance proactive** :
|
||||
- Ajouter un cron job qui exécute `check-services-status.sh` toutes les 5 minutes
|
||||
- Envoyer une alerte si bitcoind est hors service
|
||||
|
||||
## Prévention / suivi
|
||||
|
||||
- **Surveillance régulière** : Exécuter `./data/check-services-status.sh` régulièrement (ou via cron toutes les 5 minutes)
|
||||
- **Logs** : Surveiller les logs bitcoind pour détecter les problèmes avant qu'ils ne causent un crash
|
||||
- **Sauvegardes** : Sauvegarder régulièrement le wallet et la chaîne
|
||||
- **Investigation** : En cas de répétition des crashes, investiguer (RAM, disque, `dmesg` OOM, logs bitcoind complets)
|
||||
|
||||
## Note importante sur les crons et la cause probable
|
||||
|
||||
**Le script `restart-services-cron.sh` n'est PAS actuellement dans les crontabs.**
|
||||
|
||||
**Cause probable du crash récurrent vers 3h** : Le script `sync-utxos-cron.sh` s'exécute à 3h00 et fait un appel RPC très lourd (`listunspent` avec `maximumCount: 9999999`). Avec un wallet volumineux (315MB) et potentiellement des centaines de milliers d'UTXOs, cet appel peut :
|
||||
- Consommer beaucoup de mémoire
|
||||
- Prendre beaucoup de temps
|
||||
- Causer un crash de bitcoind si la mémoire est insuffisante ou si bitcoind est déjà dans un état fragile
|
||||
|
||||
**Recommandation pour ajouter `restart-services-cron.sh` à 4h** :
|
||||
|
||||
✅ **OUI, c'est prudent d'ajouter `restart-services-cron.sh` à 4h**, car :
|
||||
- Le script a été amélioré pour gérer les wallets volumineux (timeout de 5 minutes, détection du chargement de wallet)
|
||||
- Cela permettra de redémarrer bitcoind automatiquement après un crash potentiel vers 3h
|
||||
- Le redémarrage à 4h laisse le temps au script `sync-utxos-cron.sh` de se terminer (ou d'échouer) avant le redémarrage
|
||||
|
||||
⚠️ **Mais il faut aussi** :
|
||||
- Surveiller les logs pour confirmer que le crash est bien lié au script `sync-utxos-cron.sh`
|
||||
- Considérer d'optimiser le script `sync-utxos-spent-status.mjs` pour réduire la charge sur bitcoind
|
||||
- Surveiller la mémoire disponible pour bitcoind
|
||||
|
||||
**Configuration recommandée** :
|
||||
```bash
|
||||
# Ajouter dans crontab
|
||||
0 4 * * * /home/ncantu/Bureau/code/bitcoin/data/restart-services-cron.sh
|
||||
```
|
||||
|
||||
Cela redémarrera bitcoind tous les jours à 4h, ce qui permettra de récupérer automatiquement après un crash potentiel vers 3h.
|
||||
|
||||
## Pages affectées
|
||||
|
||||
- `data/check-services-status.sh` (script de vérification)
|
||||
- `fixKnowledge/signet-bitcoind-crash-wallet-loading-stuck.md` (ce document)
|
||||
- `fixKnowledge/signet-bitcoind-crash-mining-stopped.md` (documentation similaire)
|
||||
135
fixKnowledge/sync-utxos-rpc-optimization.md
Normal file
135
fixKnowledge/sync-utxos-rpc-optimization.md
Normal file
@ -0,0 +1,135 @@
|
||||
# Optimisation du script sync-utxos pour éviter les crashes RPC
|
||||
|
||||
**Date:** 2026-02-08
|
||||
**Auteur:** Équipe 4NK
|
||||
|
||||
## Problème
|
||||
|
||||
Le script `sync-utxos-cron.sh` s'exécute toutes les heures (à 3h00 notamment) et fait un appel RPC très lourd (`listunspent` avec `maximumCount: 9999999`). Avec un wallet volumineux (315MB) et potentiellement des centaines de milliers d'UTXOs, cet appel peut :
|
||||
|
||||
- Consommer beaucoup de mémoire
|
||||
- Prendre beaucoup de temps (plusieurs minutes)
|
||||
- Causer un crash de bitcoind si la mémoire est insuffisante ou si bitcoind est déjà dans un état fragile
|
||||
- Bloquer le RPC pendant plusieurs minutes, empêchant les autres services de fonctionner
|
||||
|
||||
### Symptômes observés
|
||||
|
||||
- Crash silencieux de bitcoind vers 3h08 (après l'exécution du script à 3h00)
|
||||
- Erreurs "socket hang up" et "read ECONNRESET" dans les logs du dashboard
|
||||
- Bitcoind bloqué en chargement de wallet après redémarrage
|
||||
- Wallet volumineux (315MB) causant des chargements longs
|
||||
|
||||
## Root causes
|
||||
|
||||
1. **Appel RPC sans timeout** : L'appel `listunspent` n'avait pas de timeout, pouvant bloquer indéfiniment
|
||||
2. **Pas de vérification préalable** : Le script ne vérifiait pas si bitcoind était disponible avant de faire l'appel lourd
|
||||
3. **Pas de retry avec backoff** : En cas d'échec temporaire, le script échouait immédiatement
|
||||
4. **Limite trop élevée** : `maximumCount: 9999999` pouvait charger des centaines de milliers d'UTXOs en mémoire d'un coup
|
||||
5. **Pas de gestion d'erreurs** : Les erreurs n'étaient pas gérées de manière gracieuse
|
||||
|
||||
## Correctifs appliqués
|
||||
|
||||
### 1. Ajout de timeout sur les appels RPC
|
||||
|
||||
**Fichier** : `data/sync-utxos-spent-status.mjs`
|
||||
|
||||
- **Timeout de 5 minutes** (300000ms) pour l'appel `listunspent` avec gros wallet
|
||||
- **Timeout de 10 secondes** pour le healthcheck `getblockchaininfo`
|
||||
- Les timeouts sont configurables via les paramètres de la fonction `rpcCall`
|
||||
|
||||
### 2. Vérification de santé de bitcoind avant synchronisation
|
||||
|
||||
**Fichier** : `data/sync-utxos-spent-status.mjs`
|
||||
|
||||
- Ajout de la fonction `checkBitcoindHealth()` qui vérifie que bitcoind répond avant de commencer
|
||||
- Si bitcoind n'est pas disponible, le script s'arrête immédiatement sans faire d'appel RPC lourd
|
||||
|
||||
### 3. Retry avec backoff exponentiel
|
||||
|
||||
**Fichier** : `data/sync-utxos-spent-status.mjs`
|
||||
|
||||
- Ajout de retry automatique avec backoff exponentiel (1s, 2s, 4s, max 10s)
|
||||
- Maximum 3 tentatives pour les erreurs de timeout/connexion
|
||||
- Maximum 2 tentatives pour l'appel `listunspent` (pour éviter de surcharger bitcoind)
|
||||
|
||||
### 4. Réduction de la limite maximumCount
|
||||
|
||||
**Fichier** : `data/sync-utxos-spent-status.mjs`
|
||||
|
||||
- Réduction de `maximumCount` de 9999999 à 500000 UTXOs
|
||||
- Limite la consommation mémoire et réduit le temps de traitement
|
||||
- Si vous avez plus de 500000 UTXOs, il faudra augmenter cette limite ou optimiser l'approche
|
||||
|
||||
### 5. Amélioration du script cron
|
||||
|
||||
**Fichier** : `data/sync-utxos-cron.sh`
|
||||
|
||||
- Vérification que le conteneur bitcoind est actif avant d'exécuter le script Node.js
|
||||
- Vérification que le RPC bitcoind répond avant de commencer
|
||||
- Logs améliorés avec timestamps
|
||||
- Gestion des codes de sortie pour permettre la détection d'échecs
|
||||
|
||||
### 6. Gestion d'erreurs améliorée
|
||||
|
||||
**Fichier** : `data/sync-utxos-spent-status.mjs`
|
||||
|
||||
- Distinction entre erreurs temporaires (timeout, connexion) et erreurs permanentes
|
||||
- Messages d'erreur plus informatifs
|
||||
- Le script ne fait pas échouer le cron si c'est un problème temporaire de bitcoind
|
||||
|
||||
## Modifications
|
||||
|
||||
**Fichiers modifiés :**
|
||||
|
||||
- `data/sync-utxos-spent-status.mjs` :
|
||||
- Ajout de `rpcCall()` avec timeout et retry
|
||||
- Ajout de `rpcCallOnce()` avec timeout HTTP
|
||||
- Ajout de `checkBitcoindHealth()`
|
||||
- Réduction de `maximumCount` à 500000
|
||||
- Timeout de 5 minutes pour `listunspent`
|
||||
- Gestion d'erreurs améliorée
|
||||
|
||||
- `data/sync-utxos-cron.sh` :
|
||||
- Vérification de disponibilité de bitcoind avant exécution
|
||||
- Logs améliorés avec timestamps
|
||||
- Gestion des codes de sortie
|
||||
|
||||
## Modalités de déploiement
|
||||
|
||||
1. **Vérifier que les scripts sont exécutables** :
|
||||
```bash
|
||||
chmod +x data/sync-utxos-cron.sh
|
||||
```
|
||||
|
||||
2. **Tester manuellement** :
|
||||
```bash
|
||||
./data/sync-utxos-cron.sh
|
||||
```
|
||||
|
||||
3. **Vérifier les logs** :
|
||||
```bash
|
||||
tail -f data/sync-utxos.log
|
||||
```
|
||||
|
||||
4. **Surveiller la prochaine exécution** (à 3h00) pour vérifier que le problème est résolu
|
||||
|
||||
## Modalités d'analyse
|
||||
|
||||
- **Logs du script** : `data/sync-utxos.log` (100 dernières lignes conservées)
|
||||
- **Vérifier les timeouts** : Chercher "timeout" dans les logs
|
||||
- **Vérifier les retries** : Chercher "Tentative" dans les logs
|
||||
- **Vérifier les erreurs** : Chercher "❌" dans les logs
|
||||
|
||||
## Prévention / suivi
|
||||
|
||||
- **Surveillance** : Surveiller les logs après chaque exécution pour détecter les problèmes
|
||||
- **Ajustement** : Si vous avez plus de 500000 UTXOs, augmenter `maximumCount` ou optimiser l'approche
|
||||
- **Timeout** : Si le timeout de 5 minutes est insuffisant, l'augmenter progressivement
|
||||
- **Investigation** : En cas de répétition des problèmes, investiguer la mémoire disponible pour bitcoind
|
||||
|
||||
## Pages affectées
|
||||
|
||||
- `data/sync-utxos-spent-status.mjs` (script de synchronisation amélioré)
|
||||
- `data/sync-utxos-cron.sh` (script cron amélioré)
|
||||
- `fixKnowledge/sync-utxos-rpc-optimization.md` (ce document)
|
||||
- `fixKnowledge/signet-bitcoind-crash-wallet-loading-stuck.md` (documentation du problème)
|
||||
@ -1,269 +0,0 @@
|
||||
import { useState, FormEvent, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
addRemotePairFromWords,
|
||||
ensureLocalPairForSetup,
|
||||
getStoredPairs,
|
||||
parseAndValidatePairingWords,
|
||||
removePair,
|
||||
} from '../utils/pairing';
|
||||
import { usePairingWordsContext } from '../contexts/PairingWordsContext';
|
||||
import { useIdentity } from '../hooks/useIdentity';
|
||||
import { usePairingConnected } from '../hooks/usePairingConnected';
|
||||
import { getStoredRelays } from '../utils/relay';
|
||||
import { runDevice2Confirmation } from '../services/pairingConfirm';
|
||||
import { WordInputGrid } from './WordInputGrid';
|
||||
|
||||
export function PairingDisplayScreen(): JSX.Element {
|
||||
const ctx = usePairingWordsContext();
|
||||
const { identity, isLoading, createNewIdentity } = useIdentity();
|
||||
const { connected: pairingConnected } = usePairingConnected();
|
||||
const [words2nd, setWords2nd] = useState<string[]>([]);
|
||||
const [wordInput, setWordInput] = useState<string[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [isConfirming, setIsConfirming] = useState(false);
|
||||
const [justConnected, setJustConnected] = useState(false);
|
||||
const [visibleWordIndices, setVisibleWordIndices] = useState<Set<number>>(new Set());
|
||||
|
||||
const toggleWordVisibility = (index: number): void => {
|
||||
setVisibleWordIndices((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(index)) {
|
||||
next.delete(index);
|
||||
} else {
|
||||
next.add(index);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleGenerateAnotherKey = (): void => {
|
||||
const pairs = getStoredPairs();
|
||||
const local = pairs.find((p) => p.is_local);
|
||||
if (local !== undefined) {
|
||||
removePair(local.uuid);
|
||||
}
|
||||
createNewIdentity();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (identity !== null && identity.publicKey !== undefined) {
|
||||
const w = ensureLocalPairForSetup(identity.publicKey);
|
||||
setWords2nd(w);
|
||||
}
|
||||
}, [identity]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading || identity !== null) {
|
||||
return;
|
||||
}
|
||||
createNewIdentity();
|
||||
}, [isLoading, identity, createNewIdentity]);
|
||||
|
||||
useEffect(() => {
|
||||
ctx?.setOfferWords(words2nd);
|
||||
return () => {
|
||||
ctx?.setOfferWords(null);
|
||||
};
|
||||
}, [ctx, words2nd]);
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>): void => {
|
||||
e.preventDefault();
|
||||
void (async (): Promise<void> => {
|
||||
setError(null);
|
||||
const wordsText = wordInput.join(' ');
|
||||
const parsed = parseAndValidatePairingWords(wordsText);
|
||||
if (parsed === null) {
|
||||
setError('Mots invalides. 17 mots requis.');
|
||||
return;
|
||||
}
|
||||
const pair = addRemotePairFromWords(parsed, []);
|
||||
if (pair === null) {
|
||||
setError('Mots invalides. Vérifiez la saisie.');
|
||||
return;
|
||||
}
|
||||
const pairs = getStoredPairs();
|
||||
const local = pairs.find((p) => p.is_local);
|
||||
const remote = pairs.find((p) => !p.is_local);
|
||||
if (
|
||||
identity === null ||
|
||||
local === undefined ||
|
||||
remote === undefined ||
|
||||
identity.privateKey === undefined
|
||||
) {
|
||||
setSuccess(true);
|
||||
return;
|
||||
}
|
||||
const relays = getStoredRelays().filter((r) => r.enabled);
|
||||
if (relays.length === 0) {
|
||||
setError('Aucun relais activé. Configurez les relais pour finaliser le pairing.');
|
||||
return;
|
||||
}
|
||||
// Use pair's publicKey if available (will be updated from signatures if not)
|
||||
const remotePublicKey = remote.publicKey;
|
||||
setIsConfirming(true);
|
||||
try {
|
||||
const ok = await runDevice2Confirmation({
|
||||
pairLocal: local.uuid,
|
||||
pairRemote: remote.uuid,
|
||||
identity,
|
||||
relays,
|
||||
start: identity.t0_anniversaire,
|
||||
end: Date.now(),
|
||||
remotePublicKey,
|
||||
});
|
||||
setJustConnected(ok);
|
||||
} catch (err) {
|
||||
console.error('Pairing confirmation (device 2):', err);
|
||||
setError(
|
||||
err instanceof Error ? err.message : 'Erreur lors de la confirmation du pairing.',
|
||||
);
|
||||
setIsConfirming(false);
|
||||
return;
|
||||
}
|
||||
setIsConfirming(false);
|
||||
setSuccess(true);
|
||||
})();
|
||||
};
|
||||
|
||||
if (success) {
|
||||
const showConnected = pairingConnected || justConnected;
|
||||
return (
|
||||
<main>
|
||||
{showConnected && (
|
||||
<p role="status" style={{ fontWeight: 'bold', color: 'var(--color-ok, green)' }}>
|
||||
Connecté
|
||||
</p>
|
||||
)}
|
||||
<h1>Pair associé</h1>
|
||||
<p>Le pair du 1ᵉʳ appareil a été ajouté.</p>
|
||||
<div
|
||||
role="region"
|
||||
aria-labelledby="words-2nd-heading"
|
||||
id="mots-2e-appareil"
|
||||
style={{
|
||||
display: 'block',
|
||||
marginTop: '1.5rem',
|
||||
marginBottom: '1.5rem',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'var(--color-info-bg, #dbeafe)',
|
||||
border: '2px solid var(--color-info-border, #93c5fd)',
|
||||
borderRadius: '8px',
|
||||
color: 'var(--color-text)',
|
||||
}}
|
||||
>
|
||||
<h2 id="words-2nd-heading" style={{ marginTop: 0, marginBottom: '0.5rem' }}>
|
||||
Mots du 2ᵉ appareil — à copier sur le 1ᵉʳ
|
||||
</h2>
|
||||
<p style={{ marginTop: 0, marginBottom: '0.5rem' }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleGenerateAnotherKey}
|
||||
aria-label="Générer une autre clé publique"
|
||||
>
|
||||
Générer une autre clé publique
|
||||
</button>
|
||||
</p>
|
||||
{words2nd.length > 0 ? (
|
||||
<div
|
||||
aria-label="Mots 2e appareil"
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '0.5rem',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '1rem',
|
||||
marginTop: '0.5rem',
|
||||
padding: '0.75rem',
|
||||
backgroundColor: 'var(--color-background)',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid var(--color-info-border, #93c5fd)',
|
||||
}}
|
||||
>
|
||||
{words2nd.map((word, index) => {
|
||||
const visible = visibleWordIndices.has(index);
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.25rem',
|
||||
padding: '0.25rem 0.5rem',
|
||||
backgroundColor: 'var(--color-info-bg, #dbeafe)',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid var(--color-info-border, #93c5fd)',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontWeight: 'bold' }}>{index + 1}.</span>
|
||||
<span style={{ minWidth: '80px' }}>
|
||||
{visible ? word : '•••••'}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleWordVisibility(index)}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '0.125rem',
|
||||
fontSize: '0.875rem',
|
||||
color: 'var(--color-text-secondary, #666)',
|
||||
}}
|
||||
aria-label={`${visible ? 'Masquer' : 'Afficher'} le mot ${index + 1}`}
|
||||
title={`${visible ? 'Masquer' : 'Afficher'} le mot ${index + 1}`}
|
||||
>
|
||||
{visible ? '👁' : '👁🗨'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<p style={{ margin: 0, padding: '0.75rem' }}>—</p>
|
||||
)}
|
||||
</div>
|
||||
<p>
|
||||
<Link to="/">Accueil</Link> — <Link to="/manage-pairs">Gérer les pairs</Link>
|
||||
</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main>
|
||||
<h1>Saisir les mots du 1ᵉʳ appareil</h1>
|
||||
<p>
|
||||
Saisissez les 17 mots affichés par le 1ᵉʳ appareil.
|
||||
</p>
|
||||
<form
|
||||
onSubmit={(ev) => void handleSubmit(ev)}
|
||||
aria-label="Saisir les mots du 1er appareil"
|
||||
>
|
||||
<label htmlFor="pairing-words-display">
|
||||
Mots du 1ᵉʳ appareil
|
||||
<WordInputGrid
|
||||
id="pairing-words-display"
|
||||
value={wordInput}
|
||||
onChange={setWordInput}
|
||||
aria-describedby={error !== null ? 'pairing-display-err' : undefined}
|
||||
aria-label="Saisir les 17 mots du 1er appareil"
|
||||
wordCount={17}
|
||||
/>
|
||||
</label>
|
||||
{error !== null && (
|
||||
<p id="pairing-display-err" role="alert" style={{ color: 'var(--color-error)' }}>
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
<button type="submit" disabled={isConfirming}>
|
||||
{isConfirming ? 'Finalisation…' : 'Valider'}
|
||||
</button>
|
||||
</form>
|
||||
<p>
|
||||
<Link to="/">Accueil</Link> — <Link to="/manage-pairs">Gérer les pairs</Link>
|
||||
</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@ -1,275 +0,0 @@
|
||||
import { useEffect, useMemo, useState, FormEvent } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import QRCode from 'qrcode';
|
||||
import {
|
||||
ensureLocalPairForSetup,
|
||||
addRemotePairFromWords,
|
||||
getStoredPairs,
|
||||
parseAndValidatePairingWords,
|
||||
removePair,
|
||||
} from '../utils/pairing';
|
||||
import { useIdentity } from '../hooks/useIdentity';
|
||||
import { getStoredRelays } from '../utils/relay';
|
||||
import { runDevice1Confirmation } from '../services/pairingConfirm';
|
||||
import { WordInputGrid } from './WordInputGrid';
|
||||
|
||||
const PAIRING_DISPLAY_PATH = '/pairing-display';
|
||||
const QR_SIZE = 256;
|
||||
|
||||
function buildPairingDisplayUrl(): string {
|
||||
return `${window.location.origin}${PAIRING_DISPLAY_PATH}`;
|
||||
}
|
||||
|
||||
export interface PairingSetupBlockProps {
|
||||
onDone?: () => void;
|
||||
isAddingDevice?: boolean;
|
||||
}
|
||||
|
||||
export function PairingSetupBlock({
|
||||
onDone,
|
||||
isAddingDevice = false,
|
||||
}: PairingSetupBlockProps): JSX.Element {
|
||||
const navigate = useNavigate();
|
||||
const { identity, createNewIdentity } = useIdentity();
|
||||
const [words, setWords] = useState<string[]>([]);
|
||||
const [qrDataUrl, setQrDataUrl] = useState<string | null>(null);
|
||||
const [remoteWordsInput, setRemoteWordsInput] = useState<string[]>([]);
|
||||
const [remoteError, setRemoteError] = useState<string | null>(null);
|
||||
const [hasCopiedToSecondDevice, setHasCopiedToSecondDevice] = useState(false);
|
||||
const [isConfirming, setIsConfirming] = useState(false);
|
||||
const [visibleWordIndices, setVisibleWordIndices] = useState<Set<number>>(new Set());
|
||||
|
||||
const toggleWordVisibility = (index: number): void => {
|
||||
setVisibleWordIndices((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(index)) {
|
||||
next.delete(index);
|
||||
} else {
|
||||
next.add(index);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleGenerateAnotherKey = (): void => {
|
||||
const pairs = getStoredPairs();
|
||||
const local = pairs.find((p) => p.is_local);
|
||||
if (local !== undefined) {
|
||||
removePair(local.uuid);
|
||||
}
|
||||
createNewIdentity();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (identity !== null && identity.publicKey !== undefined) {
|
||||
const w = ensureLocalPairForSetup(identity.publicKey);
|
||||
setWords(w);
|
||||
}
|
||||
}, [identity]);
|
||||
|
||||
const url = useMemo(() => buildPairingDisplayUrl(), []);
|
||||
|
||||
useEffect(() => {
|
||||
if (words.length === 0) {
|
||||
return;
|
||||
}
|
||||
QRCode.toDataURL(url, { width: QR_SIZE })
|
||||
.then(setQrDataUrl)
|
||||
.catch((err: unknown) => {
|
||||
console.error('QR generation failed:', err);
|
||||
});
|
||||
}, [words, url]);
|
||||
|
||||
const handleSubmitRemote = async (e: FormEvent<HTMLFormElement>): Promise<void> => {
|
||||
e.preventDefault();
|
||||
setRemoteError(null);
|
||||
const wordsText = remoteWordsInput.join(' ');
|
||||
const parsed = parseAndValidatePairingWords(wordsText);
|
||||
if (parsed === null) {
|
||||
setRemoteError('Mots invalides. 17 mots requis.');
|
||||
return;
|
||||
}
|
||||
const newPair = addRemotePairFromWords(parsed, []);
|
||||
if (newPair === null) {
|
||||
setRemoteError('Mots invalides. Vérifiez la saisie.');
|
||||
return;
|
||||
}
|
||||
const pairs = getStoredPairs();
|
||||
const local = pairs.find((p) => p.is_local);
|
||||
if (identity === null || local === undefined || identity.privateKey === undefined) {
|
||||
setRemoteWordsInput([]);
|
||||
if (onDone !== undefined) {
|
||||
onDone();
|
||||
} else {
|
||||
navigate('/manage-pairs');
|
||||
}
|
||||
return;
|
||||
}
|
||||
const relays = getStoredRelays().filter((r) => r.enabled);
|
||||
if (relays.length === 0) {
|
||||
setRemoteError('Aucun relais activé. Configurez les relais pour finaliser le pairing.');
|
||||
return;
|
||||
}
|
||||
const remotePublicKey = newPair.publicKey;
|
||||
setIsConfirming(true);
|
||||
try {
|
||||
await runDevice1Confirmation({
|
||||
pairLocal: local.uuid,
|
||||
pairRemote: newPair.uuid,
|
||||
identity,
|
||||
relays,
|
||||
remotePublicKey,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Pairing confirmation (device 1):', err);
|
||||
setRemoteError(
|
||||
err instanceof Error ? err.message : 'Erreur lors de la confirmation du pairing.',
|
||||
);
|
||||
setIsConfirming(false);
|
||||
return;
|
||||
}
|
||||
setIsConfirming(false);
|
||||
setRemoteWordsInput([]);
|
||||
if (onDone !== undefined) {
|
||||
onDone();
|
||||
} else {
|
||||
navigate('/manage-pairs');
|
||||
}
|
||||
};
|
||||
|
||||
const setupHeading = isAddingDevice
|
||||
? 'Ajouter un appareil'
|
||||
: 'Configurer le pairing avec un 2ᵉ appareil';
|
||||
|
||||
return (
|
||||
<div role="region" aria-labelledby="pairing-setup-block-heading">
|
||||
<h3 id="pairing-setup-block-heading">{setupHeading}</h3>
|
||||
{words.length > 0 && (
|
||||
<>
|
||||
{!hasCopiedToSecondDevice ? (
|
||||
<>
|
||||
<p>
|
||||
<strong>Mots du 1ᵉʳ appareil</strong> — à saisir sur le 2ᵉ (QR) :
|
||||
</p>
|
||||
<p style={{ marginTop: 0, marginBottom: '0.5rem' }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleGenerateAnotherKey}
|
||||
aria-label="Générer une autre clé publique"
|
||||
>
|
||||
Générer une autre clé publique
|
||||
</button>
|
||||
</p>
|
||||
<div
|
||||
aria-label="Mots 1er appareil"
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '0.5rem',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '1rem',
|
||||
marginTop: '0.5rem',
|
||||
}}
|
||||
>
|
||||
{words.map((word, index) => {
|
||||
const visible = visibleWordIndices.has(index);
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.25rem',
|
||||
padding: '0.25rem 0.5rem',
|
||||
backgroundColor: 'var(--color-info-bg, #e8f4fd)',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid var(--color-info-border, #93c5fd)',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontWeight: 'bold' }}>{index + 1}.</span>
|
||||
<span style={{ minWidth: '80px' }}>
|
||||
{visible ? word : '•••••'}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleWordVisibility(index)}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '0.125rem',
|
||||
fontSize: '0.875rem',
|
||||
color: 'var(--color-text-secondary, #666)',
|
||||
}}
|
||||
aria-label={`${visible ? 'Masquer' : 'Afficher'} le mot ${index + 1}`}
|
||||
title={`${visible ? 'Masquer' : 'Afficher'} le mot ${index + 1}`}
|
||||
>
|
||||
{visible ? '👁' : '👁🗨'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{qrDataUrl !== null && (
|
||||
<p>
|
||||
<img
|
||||
src={qrDataUrl}
|
||||
alt="QR code : URL pour ouvrir la page de pairing sur le 2e appareil"
|
||||
width={QR_SIZE}
|
||||
height={QR_SIZE}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
<strong>URL pour le 2ᵉ appareil :</strong>{' '}
|
||||
<a href={url} target="_blank" rel="noopener noreferrer">
|
||||
{url}
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setHasCopiedToSecondDevice(true)}
|
||||
>
|
||||
{isAddingDevice
|
||||
? "J'ai copié ces mots sur le nouvel appareil"
|
||||
: "J'ai copié ces mots sur mon deuxième device"}
|
||||
</button>
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<section aria-labelledby="remote-words-heading">
|
||||
<h4 id="remote-words-heading">
|
||||
{isAddingDevice ? 'Mots du nouvel appareil' : 'Mots du 2ᵉ appareil'}
|
||||
</h4>
|
||||
<form
|
||||
onSubmit={(ev) => void handleSubmitRemote(ev)}
|
||||
aria-label="Saisir les mots du 2e appareil"
|
||||
>
|
||||
<label htmlFor="remote-pairing-words">
|
||||
Mots affichés par le 2ᵉ appareil
|
||||
<WordInputGrid
|
||||
id="remote-pairing-words"
|
||||
value={remoteWordsInput}
|
||||
onChange={setRemoteWordsInput}
|
||||
aria-describedby={remoteError !== null ? 'remote-words-err' : undefined}
|
||||
aria-label="Saisir les 17 mots du 2e appareil"
|
||||
wordCount={17}
|
||||
/>
|
||||
</label>
|
||||
{remoteError !== null && (
|
||||
<p id="remote-words-err" role="alert" style={{ color: 'var(--color-error)' }}>
|
||||
{remoteError}
|
||||
</p>
|
||||
)}
|
||||
<button type="submit" disabled={isConfirming}>
|
||||
{isConfirming ? 'Finalisation…' : 'Associer le pair'}
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,242 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Le contrat – 4NK un nouveau web</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
h1 { font-size: 1.5rem; margin-bottom: 1rem; color: #222; }
|
||||
h2 { font-size: 1.2rem; margin-top: 1.5rem; margin-bottom: 0.5rem; color: #333; }
|
||||
h3 { font-size: 1rem; margin-top: 1rem; margin-bottom: 0.35rem; font-weight: 600; }
|
||||
h4 { font-size: 0.95rem; margin-top: 0.75rem; margin-bottom: 0.25rem; font-weight: 600; color: #555; }
|
||||
p { margin: 0.75rem 0; }
|
||||
ul { margin: 0.5rem 0; padding-left: 1.5rem; }
|
||||
li { margin: 0.4rem 0; }
|
||||
a { color: #007bff; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.highlight { background: #e8f4fd; padding: 1rem; border-radius: 8px; border-left: 4px solid #007bff; margin: 1rem 0; }
|
||||
.commitment { background: #d4edda; padding: 1rem; border-radius: 8px; border-left: 4px solid #28a745; margin: 1rem 0; }
|
||||
.your-part { background: #fff3cd; padding: 1rem; border-radius: 8px; border-left: 4px solid #ffc107; margin: 1rem 0; }
|
||||
details { margin: 1rem 0; }
|
||||
summary { cursor: pointer; font-weight: 600; color: #555; }
|
||||
.meta { font-family: ui-monospace, monospace; font-size: 0.85rem; color: #666; background: #f5f5f5; padding: 0.2em 0.4em; border-radius: 4px; word-break: break-all; }
|
||||
.member-list { list-style: none; padding-left: 0; }
|
||||
.member-list > li { background: #f8f9fa; padding: 0.75rem 1rem; border-radius: 8px; border: 1px solid #e0e0e0; margin: 0.5rem 0; }
|
||||
.member-list > li > em { color: #666; font-size: 0.9rem; }
|
||||
.pair-list { list-style: none; margin-top: 0.5rem; padding-left: 1rem; border-left: 3px solid #007bff; }
|
||||
.pair-list > li { background: #fff; padding: 0.5rem 0.75rem; border-radius: 6px; margin: 0.4rem 0; border: 1px solid #e8e8e8; }
|
||||
.roles-intro { margin-bottom: 1rem; }
|
||||
.role-block { background: #f8f9fa; border: 1px solid #e0e0e0; border-radius: 8px; padding: 0.75rem 1rem; margin: 0.5rem 0; }
|
||||
.role-block h4 { margin-top: 0; }
|
||||
.role-block .usage, .role-block .validation { font-size: 0.9rem; color: #555; margin: 0.25rem 0; }
|
||||
.role-block .usage strong, .role-block .validation strong { color: #333; }
|
||||
@media (max-width: 768px) {
|
||||
body { padding: 0.75rem; }
|
||||
h1 { font-size: 1.25rem; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Le contrat</h1>
|
||||
<p><a href="index.html">← Retour à l'accueil</a> · <a href="membre.html">Qui êtes-vous ?</a> · <a href="technique.html">Réseau P2P</a> · <a href="cryptographie.html">Cryptographie</a></p>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>En résumé :</strong> Ce contrat définit les règles de confiance entre vous et le service.
|
||||
Il précise <strong>qui peut faire quoi</strong> et <strong>comment prouver son identité</strong>.
|
||||
Tout est vérifiable et transparent.
|
||||
</div>
|
||||
|
||||
<h2>Qu'est-ce que ce contrat ?</h2>
|
||||
<p>
|
||||
Ce n'est pas un contrat papier, mais un <strong>accord numérique</strong> qui établit les règles du jeu.
|
||||
Il définit :
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Le service</strong> : Website Skeleton (ce site de démonstration).</li>
|
||||
<li><strong>Les actions possibles</strong> : se connecter (login).</li>
|
||||
<li><strong>Les validateurs</strong> : qui a le droit de vérifier les connexions.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Ce que le service s'engage à faire</h2>
|
||||
<div class="commitment">
|
||||
<ul>
|
||||
<li>✓ <strong>Ne jamais stocker vos clés privées</strong> — elles restent sur votre appareil.</li>
|
||||
<li>✓ <strong>Vérifier votre identité de façon transparente</strong> — via une preuve cryptographique que vous fournissez.</li>
|
||||
<li>✓ <strong>Respecter les règles du contrat</strong> — publiquement vérifiables.</li>
|
||||
<li>✓ <strong>Utiliser une clé de service déclarée</strong> — jamais exposée, mais vérifiable.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Ce que vous vous engagez à faire</h2>
|
||||
<div class="your-part">
|
||||
<ul>
|
||||
<li>✓ <strong>Protéger votre appareil</strong> — c'est votre coffre-fort numérique.</li>
|
||||
<li>✓ <strong>Fournir une preuve valide</strong> — en signant avec vos clés lors de la connexion.</li>
|
||||
<li>✓ <strong>Être responsable de vos clés</strong> — si vous les perdez, personne ne peut les récupérer.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Les parties prenantes</h2>
|
||||
|
||||
<h3>Le service (Website Skeleton)</h3>
|
||||
<p>
|
||||
C'est le site que vous utilisez. Il possède sa propre identité (un « validateur ») qui permet
|
||||
de vérifier que les connexions sont légitimes.
|
||||
</p>
|
||||
|
||||
<h3>Vous (le membre connecté)</h3>
|
||||
<p>
|
||||
Vous êtes l'utilisateur qui souhaite accéder au service. Votre identité est prouvée par
|
||||
la signature de votre appareil (<a href="membre.html">en savoir plus</a>).
|
||||
</p>
|
||||
|
||||
<h2>Comment fonctionne la validation ?</h2>
|
||||
<ol>
|
||||
<li>Vous demandez à vous connecter.</li>
|
||||
<li>Votre appareil crée une <strong>preuve</strong> (login-proof) signée avec vos clés.</li>
|
||||
<li>Le service vérifie cette preuve grâce aux règles définies dans ce contrat.</li>
|
||||
<li>Si la preuve est valide, vous êtes connecté. Sinon, l'accès est refusé.</li>
|
||||
</ol>
|
||||
<p>
|
||||
Ce système est <strong>plus sûr</strong> qu'un mot de passe classique car il n'y a rien à voler côté serveur.
|
||||
</p>
|
||||
|
||||
<h2>Pourquoi c'est plus sûr ?</h2>
|
||||
<ul>
|
||||
<li><strong>Pas de base de mots de passe</strong> à pirater.</li>
|
||||
<li><strong>Vos clés ne transitent jamais</strong> sur le réseau.</li>
|
||||
<li><strong>Chaque connexion est unique</strong> (signature à usage unique).</li>
|
||||
<li><strong>Vérifiable par tous</strong> — les règles du contrat sont publiques.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Infrastructure, gouvernance et preuves</h2>
|
||||
<p>
|
||||
L'<strong>infrastructure</strong> (relais, stockage des messages et des signatures) est <strong>décentralisée et neutre</strong> :
|
||||
elle ne dépend pas d'un acteur unique.
|
||||
</p>
|
||||
<p>
|
||||
Les <strong>membres</strong> ont une <strong>liberté de gouvernance</strong>. Certains peuvent choisir une organisation
|
||||
<strong>centralisée</strong> lorsque c'est préférable (équipe, processus, conformité). D'autres restent décentralisés.
|
||||
</p>
|
||||
<p>
|
||||
L'important est d'avoir les <strong>preuves de la bonne exécution du contrat entre les parties</strong> :
|
||||
signatures, nonces, règles publiques. Ces preuves restent vérifiables quelle que soit l'organisation
|
||||
(décentralisée ou centralisée) des membres.
|
||||
</p>
|
||||
|
||||
<details>
|
||||
<summary>Détails techniques du contrat</summary>
|
||||
<h3>Identifiants</h3>
|
||||
<ul>
|
||||
<li>Contrat UUID : <span class="meta">f9b9b336-4282-4c1c-b70b-e5197aeae3fa</span></li>
|
||||
<li>Service UUID : <span class="meta">32b9095a-562d-4239-ae45-2d7ffb1a40de</span></li>
|
||||
<li>Action login UUID : <span class="meta">0ac7de59-9e81-4bdc-bd19-c07750fad48e</span></li>
|
||||
<li>Validateur (membre) : <span class="meta">0e865301-362f-4951-bfbc-531b7bddf820</span></li>
|
||||
</ul>
|
||||
<h3>Rôles par défaut (types de Champ)</h3>
|
||||
<p class="roles-intro">
|
||||
Les <strong>11 rôles</strong> ci‑dessous existent par défaut. Chacun peut être <strong>vide</strong> (aucun membre)
|
||||
ou <strong>plein</strong> (membres et pairs définis). Tous sont <strong>connus des participants</strong> ;
|
||||
les conditions d'usage et de validation s'appliquent dès qu'un rôle est rempli.
|
||||
</p>
|
||||
|
||||
<div class="role-block">
|
||||
<h4>1. Partage avec les institutions</h4>
|
||||
<p class="usage"><strong>Usage :</strong> Partager des données avec des institutions.</p>
|
||||
<p class="validation"><strong>Validation :</strong> Conformité aux validateurs du champ ; signatures requises selon <code>membres_du_role</code>.</p>
|
||||
</div>
|
||||
<div class="role-block">
|
||||
<h4>2. Messages au RSSI</h4>
|
||||
<p class="usage"><strong>Usage :</strong> Messages au RSSI de la société responsable du service.</p>
|
||||
<p class="validation"><strong>Validation :</strong> Signatures des membres du rôle conformes aux <code>signatures_obligatoires</code>.</p>
|
||||
</div>
|
||||
<div class="role-block">
|
||||
<h4>3. Messages au Correspondant CNIL</h4>
|
||||
<p class="usage"><strong>Usage :</strong> Messages au Correspondant CNIL de la société responsable du service.</p>
|
||||
<p class="validation"><strong>Validation :</strong> Signatures des membres du rôle ; attributs CNIL requis si applicables.</p>
|
||||
</div>
|
||||
<div class="role-block">
|
||||
<h4>4. Messages au Responsable cybersécurité</h4>
|
||||
<p class="usage"><strong>Usage :</strong> Messages au Responsable cybersécurité de la société responsable du service.</p>
|
||||
<p class="validation"><strong>Validation :</strong> Signatures conformes aux validateurs du champ.</p>
|
||||
</div>
|
||||
<div class="role-block">
|
||||
<h4>5. Messages de support infogérant</h4>
|
||||
<p class="usage"><strong>Usage :</strong> Support infogérant du service (peut inclure un membre du miner pour la gestion des clés API).</p>
|
||||
<p class="validation"><strong>Validation :</strong> Signatures des membres du rôle ; <code>membre_miner_uuid</code> optionnel dans <code>datajson</code> si applicable.</p>
|
||||
</div>
|
||||
<div class="role-block">
|
||||
<h4>6. Messages de support administrateur système</h4>
|
||||
<p class="usage"><strong>Usage :</strong> Support administrateur système du service.</p>
|
||||
<p class="validation"><strong>Validation :</strong> Signatures conformes aux validateurs du champ.</p>
|
||||
</div>
|
||||
<div class="role-block">
|
||||
<h4>7. Messages de support niveau 1</h4>
|
||||
<p class="usage"><strong>Usage :</strong> Support niveau 1 du service.</p>
|
||||
<p class="validation"><strong>Validation :</strong> Signatures conformes aux validateurs du champ.</p>
|
||||
</div>
|
||||
<div class="role-block">
|
||||
<h4>8. Messages de support niveau 2</h4>
|
||||
<p class="usage"><strong>Usage :</strong> Support niveau 2 du service.</p>
|
||||
<p class="validation"><strong>Validation :</strong> Signatures conformes aux validateurs du champ.</p>
|
||||
</div>
|
||||
<div class="role-block">
|
||||
<h4>9. Messages de support niveau 3</h4>
|
||||
<p class="usage"><strong>Usage :</strong> Support niveau 3 du service.</p>
|
||||
<p class="validation"><strong>Validation :</strong> Signatures conformes aux validateurs du champ.</p>
|
||||
</div>
|
||||
<div class="role-block">
|
||||
<h4>10. Validation (contrat)</h4>
|
||||
<p class="usage"><strong>Usage :</strong> Valider le contrat ; définir qui peut signer pour le contrat (<code>contrat.validateurs.membres_du_role</code>).</p>
|
||||
<p class="validation"><strong>Validation :</strong> Au moins une signature valide par membre du rôle ; clés publiques autorisées dans <code>signatures_obligatoires</code>.</p>
|
||||
</div>
|
||||
<div class="role-block">
|
||||
<h4>11. Validation du login</h4>
|
||||
<p class="usage"><strong>Usage :</strong> Valider le login ; définir qui peut signer pour l'action login (<code>action.validateurs_action.membres_du_role</code>).</p>
|
||||
<p class="validation"><strong>Validation :</strong> Preuve de login (hash, nonce) signée par les pairs des membres du rôle ; nonce unique ; clés autorisées.</p>
|
||||
</div>
|
||||
|
||||
<h3>Membres par rôles (ce contrat)</h3>
|
||||
<p>Ce contrat skeleton remplit uniquement les rôles <strong>Validation (contrat)</strong> et <strong>Validation du login</strong>. Les autres rôles sont vides mais connus.</p>
|
||||
|
||||
<h4>Validation (contrat)</h4>
|
||||
<ul class="member-list">
|
||||
<li>
|
||||
<strong>Membre</strong> : <span class="meta">0e865301-362f-4951-bfbc-531b7bddf820</span>
|
||||
<br><em>Signatures obligatoires : 1</em>
|
||||
<ul class="pair-list">
|
||||
<li>
|
||||
<strong>Pair 1</strong> : <span class="meta">f2779304-0d9b-4139-9aee-8d3347819d98</span>
|
||||
<br>Clé publique : <span class="meta">0244f299538f4a091d93561dcee0c77de3e0d8bb917c9378405653c57f7800f174</span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h4>Validation du login</h4>
|
||||
<ul class="member-list">
|
||||
<li>
|
||||
<strong>Membre</strong> : <span class="meta">0e865301-362f-4951-bfbc-531b7bddf820</span>
|
||||
<br><em>Signatures obligatoires : 1</em>
|
||||
<ul class="pair-list">
|
||||
<li>
|
||||
<strong>Pair 1</strong> : <span class="meta">f2779304-0d9b-4139-9aee-8d3347819d98</span>
|
||||
<br>Clé publique : <span class="meta">0244f299538f4a091d93561dcee0c77de3e0d8bb917c9378405653c57f7800f174</span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
<p><a href="index.html">← Retour à l'accueil</a> · <a href="membre.html">Qui êtes-vous ?</a> · <a href="technique.html">Réseau P2P</a> · <a href="cryptographie.html">Cryptographie</a></p>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,495 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Cryptographie expliquée – 4NK un nouveau web</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
h1 { font-size: 1.5rem; margin-bottom: 1rem; color: #222; }
|
||||
h2 { font-size: 1.2rem; margin-top: 1.5rem; margin-bottom: 0.5rem; color: #333; }
|
||||
h3 { font-size: 1rem; margin-top: 1rem; margin-bottom: 0.35rem; font-weight: 600; }
|
||||
p { margin: 0.75rem 0; }
|
||||
ul, ol { margin: 0.5rem 0; padding-left: 1.5rem; }
|
||||
li { margin: 0.4rem 0; }
|
||||
a { color: #007bff; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.highlight { background: #e8f4fd; padding: 1rem; border-radius: 8px; border-left: 4px solid #007bff; margin: 1rem 0; }
|
||||
.info { background: #d4edda; padding: 1rem; border-radius: 8px; border-left: 4px solid #28a745; margin: 1rem 0; }
|
||||
.warning { background: #fff3cd; padding: 1rem; border-radius: 8px; border-left: 4px solid #ffc107; margin: 1rem 0; }
|
||||
.algo { background: #f8f9fa; padding: 1rem; border-radius: 8px; border: 1px solid #e0e0e0; margin: 1rem 0; }
|
||||
.algo-title { font-weight: 700; color: #007bff; margin-bottom: 0.5rem; }
|
||||
details { margin: 1rem 0; }
|
||||
summary { cursor: pointer; font-weight: 600; color: #555; }
|
||||
.meta { font-family: ui-monospace, monospace; font-size: 0.85rem; color: #666; background: #f5f5f5; padding: 0.2em 0.4em; border-radius: 4px; word-break: break-all; }
|
||||
.workflow { background: #f5f5f5; padding: 1rem; border-radius: 8px; margin: 1rem 0; font-family: ui-monospace, monospace; font-size: 0.85rem; white-space: pre-wrap; line-height: 1.4; overflow-x: auto; }
|
||||
.step { background: #fff; border: 1px solid #ddd; border-radius: 8px; padding: 1rem; margin: 0.75rem 0; }
|
||||
.step-num { display: inline-block; width: 28px; height: 28px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 50%; text-align: center; line-height: 28px; font-weight: 700; margin-right: 0.75rem; font-size: 0.9rem; }
|
||||
.algo-table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
|
||||
.algo-table th, .algo-table td { padding: 0.5rem; text-align: left; border-bottom: 1px solid #ddd; }
|
||||
.algo-table th { background: #f5f5f5; font-weight: 600; }
|
||||
.algo-table code { background: #f5f5f5; padding: 0.2em 0.4em; border-radius: 4px; font-size: 0.9em; }
|
||||
@media (max-width: 768px) {
|
||||
body { padding: 0.75rem; }
|
||||
h1 { font-size: 1.25rem; }
|
||||
.workflow { font-size: 0.75rem; padding: 0.75rem; }
|
||||
.algo-table { font-size: 0.9rem; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Cryptographie expliquée</h1>
|
||||
<p><a href="index.html">← Retour à l'accueil</a> · <a href="contrat.html">Le contrat</a> · <a href="membre.html">Qui êtes-vous ?</a> · <a href="technique.html">Réseau P2P</a></p>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>En résumé :</strong> Vos messages sont protégés par plusieurs couches de cryptographie.
|
||||
Seul le destinataire peut les lire (chiffrement), et il peut vérifier que c'est bien vous qui les avez envoyés (signature).
|
||||
Tout cela sans que personne ne connaisse vos clés secrètes.
|
||||
</div>
|
||||
|
||||
<h2>Comment ça marche en simple ?</h2>
|
||||
<p>
|
||||
Imaginez que vous voulez envoyer une lettre secrète à un ami. Dans le monde réel, vous pourriez :
|
||||
</p>
|
||||
<ol>
|
||||
<li><strong>Mettre la lettre dans une enveloppe fermée</strong> (chiffrement) — seul celui qui a la clé peut l'ouvrir</li>
|
||||
<li><strong>Signer l'enveloppe</strong> (signature) — pour prouver que c'est bien vous qui l'avez envoyée</li>
|
||||
<li><strong>Donner la clé au destinataire</strong> (échange de clé) — mais sans que personne d'autre ne puisse l'intercepter</li>
|
||||
</ol>
|
||||
<p>
|
||||
C'est exactement ce que fait la cryptographie numérique, mais de façon <strong>mathématiquement impossible à falsifier</strong>.
|
||||
</p>
|
||||
|
||||
<h2>Les algorithmes utilisés</h2>
|
||||
<p>
|
||||
Voici les « outils » cryptographiques du système. Chacun a un rôle précis :
|
||||
</p>
|
||||
|
||||
<table class="algo-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Algorithme</th>
|
||||
<th>Rôle</th>
|
||||
<th>Analogie</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>SHA-256</code></td>
|
||||
<td>Empreinte unique du message</td>
|
||||
<td>Comme une empreinte digitale : unique et impossible à inverser</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ECDH secp256k1</code></td>
|
||||
<td>Échange de clé sécurisé</td>
|
||||
<td>Comme mélanger deux couleurs en public pour créer un secret commun</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>HKDF-SHA256</code></td>
|
||||
<td>Dérivation de clé</td>
|
||||
<td>Transformer le secret partagé en une clé utilisable</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>AES-256-GCM</code></td>
|
||||
<td>Chiffrement du message</td>
|
||||
<td>Un coffre-fort numérique ultra-sécurisé</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Schnorr secp256k1</code></td>
|
||||
<td>Signature numérique</td>
|
||||
<td>Votre signature manuscrite, mais impossible à falsifier</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Détail de chaque algorithme</h2>
|
||||
|
||||
<div class="algo">
|
||||
<div class="algo-title">SHA-256 — L'empreinte digitale</div>
|
||||
<p>
|
||||
SHA-256 crée une « empreinte » unique de 64 caractères hexadécimaux (256 bits) pour n'importe quel message.
|
||||
Deux messages différents auront toujours des empreintes différentes.
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Entrée</strong> : n'importe quel texte ou données</li>
|
||||
<li><strong>Sortie</strong> : 64 caractères hexadécimaux (ex: <span class="meta">a7f3c9...</span>)</li>
|
||||
<li><strong>Propriété clé</strong> : impossible de retrouver le message à partir de l'empreinte</li>
|
||||
</ul>
|
||||
<p><em>Usage</em> : identifier chaque message de façon unique sur le relais.</p>
|
||||
</div>
|
||||
|
||||
<div class="algo">
|
||||
<div class="algo-title">ECDH secp256k1 — Le secret partagé</div>
|
||||
<p>
|
||||
ECDH (Elliptic Curve Diffie-Hellman) permet à deux personnes de créer un <strong>secret commun</strong>
|
||||
sans jamais l'échanger directement. C'est comme de la magie mathématique !
|
||||
</p>
|
||||
<div class="info">
|
||||
<strong>L'analogie des couleurs :</strong> Imaginez qu'Alice et Bob veulent créer une couleur secrète.
|
||||
<ul>
|
||||
<li>Alice mélange une couleur publique avec sa couleur secrète → obtient « couleur A »</li>
|
||||
<li>Bob mélange la même couleur publique avec sa couleur secrète → obtient « couleur B »</li>
|
||||
<li>Ils échangent « couleur A » et « couleur B » (publiquement, tout le monde peut voir)</li>
|
||||
<li>Alice mélange « couleur B » + sa couleur secrète → obtient la couleur finale</li>
|
||||
<li>Bob mélange « couleur A » + sa couleur secrète → obtient <strong>la même couleur finale</strong></li>
|
||||
</ul>
|
||||
Résultat : Alice et Bob ont la même couleur secrète, sans l'avoir jamais échangée !
|
||||
Un espion qui a vu « couleur A » et « couleur B » ne peut pas retrouver la couleur finale.
|
||||
</div>
|
||||
<ul>
|
||||
<li><strong>Courbe</strong> : secp256k1 (la même que Bitcoin)</li>
|
||||
<li><strong>Entrée</strong> : votre clé privée + la clé publique du destinataire</li>
|
||||
<li><strong>Sortie</strong> : un secret partagé (32 octets)</li>
|
||||
</ul>
|
||||
<p><em>Usage</em> : créer un secret pour chiffrer les messages entre deux personnes.</p>
|
||||
</div>
|
||||
|
||||
<div class="algo">
|
||||
<div class="algo-title">HKDF-SHA256 — Le raffineur de clé</div>
|
||||
<p>
|
||||
HKDF (HMAC-based Key Derivation Function) transforme le secret partagé ECDH en une clé
|
||||
de chiffrement de haute qualité.
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Entrée</strong> : le secret partagé ECDH (32 octets)</li>
|
||||
<li><strong>Sortie</strong> : une clé AES-256 (32 octets) de qualité cryptographique</li>
|
||||
<li><strong>Pourquoi</strong> : s'assurer que la clé est uniformément aléatoire</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="algo">
|
||||
<div class="algo-title">AES-256-GCM — Le coffre-fort</div>
|
||||
<p>
|
||||
AES-256-GCM est l'algorithme de chiffrement qui protège le contenu du message.
|
||||
Il offre à la fois la <strong>confidentialité</strong> (personne ne peut lire) et
|
||||
l'<strong>intégrité</strong> (personne ne peut modifier sans qu'on le sache).
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Taille de clé</strong> : 256 bits (très sécurisé)</li>
|
||||
<li><strong>Mode</strong> : GCM (Galois/Counter Mode) avec authentification</li>
|
||||
<li><strong>IV</strong> : vecteur d'initialisation de 12 octets (différent à chaque chiffrement)</li>
|
||||
<li><strong>Tag</strong> : code d'authentification de 16 octets (détecte les modifications)</li>
|
||||
</ul>
|
||||
<p><em>Usage</em> : chiffrer le message pour que seul le destinataire puisse le lire.</p>
|
||||
</div>
|
||||
|
||||
<div class="algo">
|
||||
<div class="algo-title">Schnorr secp256k1 — La signature</div>
|
||||
<p>
|
||||
La signature Schnorr prouve que vous êtes bien l'auteur du message, sans révéler votre clé privée.
|
||||
Elle est <strong>plus efficace</strong> et <strong>plus simple</strong> que les signatures ECDSA traditionnelles.
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Entrée</strong> : le hash du message + votre clé privée</li>
|
||||
<li><strong>Sortie</strong> : une signature de 64 octets (128 caractères hex)</li>
|
||||
<li><strong>Vérification</strong> : n'importe qui peut vérifier avec votre clé publique</li>
|
||||
</ul>
|
||||
<div class="warning">
|
||||
<strong>Important :</strong> La signature prouve que vous avez signé, mais elle ne révèle
|
||||
jamais votre clé privée. Même en voyant 1000 de vos signatures, personne ne peut
|
||||
retrouver votre clé secrète.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Dérivation de clés et clés multiples par pair</h2>
|
||||
<p>
|
||||
Chaque pair (appareil local ou distant) peut être associé à <strong>plusieurs clés publiques</strong> :
|
||||
une clé principale et, optionnellement, une liste de clés supplémentaires. Cela permet d’utiliser
|
||||
plusieurs clés pour un même pair sans multiplier les secrets à gérer.
|
||||
</p>
|
||||
<h3>Dérivation déterministe à partir d’une clé privée</h3>
|
||||
<p>
|
||||
À partir d’une seule clé privée (celle de l’identité), le système peut calculer d’autres paires
|
||||
clé privée / clé publique de façon <strong>déterministe</strong> : pour un index entier (0, 1, 2, …),
|
||||
le calcul produit toujours la même paire. Aucune donnée aléatoire n’est utilisée pour cette dérivation.
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Entrée</strong> : la clé privée parente (64 caractères hexadécimaux) et un index (entier ≥ 0).</li>
|
||||
<li><strong>Procédé</strong> : une fonction de dérivation (HMAC-SHA256 avec un domaine fixe et l’index)
|
||||
produit 32 octets, puis une réduction modulo l’ordre de la courbe secp256k1 donne un nombre
|
||||
valide comme clé privée sur la courbe ; la clé publique enfant est obtenue par multiplication
|
||||
du point de base de la courbe par ce nombre (format compressé, 66 caractères hex).</li>
|
||||
<li><strong>Sortie</strong> : une paire (clé privée enfant, clé publique enfant). La clé « principale »
|
||||
est celle dérivée directement de la clé privée de l’identité (sans index enfant).</li>
|
||||
</ul>
|
||||
<p>
|
||||
Ainsi, une même identité peut exposer plusieurs clés publiques (la principale et les dérivées 0, 1, 2, …)
|
||||
tout en ne stockant qu’une seule clé privée ; les autres sont recalculées à la demande.
|
||||
</p>
|
||||
<h3>Vérification rapide : une clé publique appartient-elle à mon identité ?</h3>
|
||||
<p>
|
||||
Pour savoir si une clé publique donnée correspond à votre clé privée (identité), le système fait :
|
||||
</p>
|
||||
<ol>
|
||||
<li>Calculer la clé publique à partir de votre clé privée (une opération sur la courbe).</li>
|
||||
<li>Comparer le résultat à la clé publique donnée. Si elles sont égales, la clé appartient à votre identité.</li>
|
||||
<li>Si on autorise les clés dérivées : calculer les clés publiques dérivées pour les indices 0, 1, 2, …
|
||||
jusqu’à une borne fixée, et comparer chacune à la clé donnée. Dès qu’une égalité est trouvée, la réponse est oui.</li>
|
||||
</ol>
|
||||
<p>
|
||||
Le coût est donc une seule opération pour la clé principale, puis au plus N opérations si on teste
|
||||
N clés dérivées. En limitant N (par exemple 100), la vérification reste rapide.
|
||||
</p>
|
||||
<p>
|
||||
Pour un pair distant (autre appareil), les clés sont celles enregistrées pour ce pair (clé principale
|
||||
et liste optionnelle) : la vérification consiste à tester si la clé donnée est dans cet ensemble,
|
||||
sans dérivation côté local.
|
||||
</p>
|
||||
|
||||
<h2>Le workflow complet</h2>
|
||||
<p>
|
||||
Voici ce qui se passe quand vous envoyez un message sécurisé, étape par étape :
|
||||
</p>
|
||||
|
||||
<h3>Phase 1 : Préparation</h3>
|
||||
<div class="step">
|
||||
<span class="step-num">1</span>
|
||||
<strong>Création du message</strong><br>
|
||||
Votre message est structuré avec : les données, un horodatage, un identifiant unique (UUID), et les règles de validation.
|
||||
</div>
|
||||
<div class="step">
|
||||
<span class="step-num">2</span>
|
||||
<strong>Calcul de l'empreinte (hash)</strong><br>
|
||||
<span class="meta">Message → SHA-256 → Hash (64 caractères)</span><br>
|
||||
Cette empreinte unique identifie le message sur le réseau.
|
||||
</div>
|
||||
<div class="step">
|
||||
<span class="step-num">3</span>
|
||||
<strong>Génération du nonce</strong><br>
|
||||
Un nombre aléatoire unique (nonce) est créé pour éviter qu'un même message soit rejoué.
|
||||
</div>
|
||||
|
||||
<h3>Phase 2 : Chiffrement</h3>
|
||||
<div class="step">
|
||||
<span class="step-num">4</span>
|
||||
<strong>Échange de clé ECDH</strong><br>
|
||||
<span class="meta">Votre clé privée + Clé publique du destinataire → ECDH → Secret partagé</span><br>
|
||||
Un secret commun est calculé mathématiquement, sans jamais être transmis.
|
||||
</div>
|
||||
<div class="step">
|
||||
<span class="step-num">5</span>
|
||||
<strong>Dérivation de la clé AES</strong><br>
|
||||
<span class="meta">Secret partagé → HKDF-SHA256 → Clé AES-256</span><br>
|
||||
Le secret est transformé en une clé de chiffrement de qualité.
|
||||
</div>
|
||||
<div class="step">
|
||||
<span class="step-num">6</span>
|
||||
<strong>Chiffrement du message</strong><br>
|
||||
<span class="meta">Clé AES + IV aléatoire + Message → AES-256-GCM → Message chiffré + Tag</span><br>
|
||||
Le message est enfermé dans un « coffre-fort » numérique.
|
||||
</div>
|
||||
|
||||
<h3>Phase 3 : Signature</h3>
|
||||
<div class="step">
|
||||
<span class="step-num">7</span>
|
||||
<strong>Signature Schnorr</strong><br>
|
||||
<span class="meta">Hash + Nonce + Votre clé privée → Schnorr → Signature</span><br>
|
||||
Vous signez l'empreinte du message avec votre clé privée.
|
||||
</div>
|
||||
|
||||
<h3>Phase 4 : Publication sur le relais</h3>
|
||||
<div class="step">
|
||||
<span class="step-num">8</span>
|
||||
<strong>Envoi au relais</strong><br>
|
||||
Trois éléments sont envoyés séparément :
|
||||
<ul style="margin-top: 0.5rem;">
|
||||
<li><strong>MsgChiffre</strong> : le message chiffré + son hash</li>
|
||||
<li><strong>MsgCle</strong> : l'IV + votre clé publique (pour que le destinataire puisse déchiffrer)</li>
|
||||
<li><strong>MsgSignature</strong> : votre signature + clé publique</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h3>Phase 5 : Collecte des signatures (multi-signature)</h3>
|
||||
<div class="step">
|
||||
<span class="step-num">9</span>
|
||||
<strong>Attente des co-signataires</strong><br>
|
||||
Si le contrat exige plusieurs signatures (ex: 2 appareils sur 3), le système attend que les autres signent :
|
||||
<ul style="margin-top: 0.5rem;">
|
||||
<li>Interrogation du relais toutes les 2 secondes</li>
|
||||
<li>Timeout après 5 minutes si signatures manquantes</li>
|
||||
<li>Progression affichée (ex: "2/3 signatures")</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="step">
|
||||
<span class="step-num">10</span>
|
||||
<strong>Validation</strong><br>
|
||||
Le message est validé quand :
|
||||
<ul style="margin-top: 0.5rem;">
|
||||
<li>Toutes les signatures requises sont présentes (cardinalité)</li>
|
||||
<li>Les dépendances sont respectées (ex: "A doit signer avant B")</li>
|
||||
<li>Les clés publiques correspondent aux signataires autorisés</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h3>Phase 6 : Réception et déchiffrement</h3>
|
||||
<div class="step">
|
||||
<span class="step-num">11</span>
|
||||
<strong>Scan des clés</strong><br>
|
||||
Le destinataire interroge le relais pour récupérer les MsgCle récentes.
|
||||
</div>
|
||||
<div class="step">
|
||||
<span class="step-num">12</span>
|
||||
<strong>Récupération du message</strong><br>
|
||||
Avec le hash trouvé dans la MsgCle, il récupère le message chiffré.
|
||||
</div>
|
||||
<div class="step">
|
||||
<span class="step-num">13</span>
|
||||
<strong>Déchiffrement ECDH inverse</strong><br>
|
||||
<span class="meta">Sa clé privée + Clé publique de l'émetteur (df) → ECDH → Même secret partagé</span><br>
|
||||
Il recalcule le même secret, puis déchiffre avec AES-GCM.
|
||||
</div>
|
||||
<div class="step">
|
||||
<span class="step-num">14</span>
|
||||
<strong>Vérification des signatures</strong><br>
|
||||
Chaque signature est vérifiée avec la clé publique du signataire.
|
||||
</div>
|
||||
|
||||
<h2>Schéma récapitulatif</h2>
|
||||
<div class="workflow">┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ÉMETTEUR │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Message → SHA-256 → Hash │
|
||||
│ │
|
||||
│ Clé privée émetteur ─┐ │
|
||||
│ ├──→ ECDH → Secret partagé │
|
||||
│ Clé publique dest. ──┘ │
|
||||
│ │
|
||||
│ Secret partagé → HKDF-SHA256 → Clé AES-256 │
|
||||
│ │
|
||||
│ Clé AES + IV + Message → AES-256-GCM → Message chiffré │
|
||||
│ │
|
||||
│ Hash + Nonce + Clé privée → Schnorr → Signature │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ RELAIS │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ POST /messages ← MsgChiffre (hash, message_chiffre) │
|
||||
│ POST /keys ← MsgCle (hash, iv, df_ecdh = clé pub émett.) │
|
||||
│ POST /signatures← MsgSignature (hash, signature, clé publique) │
|
||||
│ │
|
||||
│ ... attente des co-signatures (GET /signatures/:hash) ... │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ DESTINATAIRE │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ GET /keys?start=&end= → Liste des MsgCle récentes │
|
||||
│ GET /messages/:hash → MsgChiffre │
|
||||
│ GET /signatures/:hash → MsgSignature[] │
|
||||
│ │
|
||||
│ Clé privée dest. ────┐ │
|
||||
│ ├──→ ECDH → Même secret partagé │
|
||||
│ Clé publique émett. ─┘ (df_ecdh_scannable) │
|
||||
│ │
|
||||
│ Secret partagé → HKDF-SHA256 → Clé AES-256 │
|
||||
│ │
|
||||
│ Clé AES + IV + Ciphertext → AES-256-GCM → Message original │
|
||||
│ │
|
||||
│ Vérification : Signature + Clé publique → Schnorr verify → OK │
|
||||
└─────────────────────────────────────────────────────────────────┘</div>
|
||||
|
||||
<h2>Pourquoi c'est sécurisé ?</h2>
|
||||
|
||||
<h3>Confidentialité</h3>
|
||||
<ul>
|
||||
<li><strong>Seul le destinataire peut lire</strong> : le secret partagé ECDH ne peut être calculé que par l'émetteur et le destinataire</li>
|
||||
<li><strong>AES-256</strong> : considéré incassable avec les technologies actuelles (2²⁵⁶ combinaisons possibles)</li>
|
||||
<li><strong>IV unique</strong> : même si vous envoyez le même message deux fois, le résultat chiffré sera différent</li>
|
||||
</ul>
|
||||
|
||||
<h3>Intégrité</h3>
|
||||
<ul>
|
||||
<li><strong>GCM</strong> : le mode Galois/Counter détecte toute modification du message chiffré</li>
|
||||
<li><strong>Hash</strong> : l'empreinte SHA-256 garantit que le message n'a pas été altéré</li>
|
||||
</ul>
|
||||
|
||||
<h3>Authenticité</h3>
|
||||
<ul>
|
||||
<li><strong>Signature Schnorr</strong> : prouve mathématiquement que c'est bien vous qui avez signé</li>
|
||||
<li><strong>Multi-signature</strong> : plusieurs appareils peuvent être requis pour valider</li>
|
||||
<li><strong>Anti-rejeu</strong> : le nonce empêche de réutiliser une ancienne signature</li>
|
||||
</ul>
|
||||
|
||||
<h3>Séparation des données</h3>
|
||||
<ul>
|
||||
<li><strong>Message, clé et signature séparés</strong> : même si quelqu'un intercepte un élément, il ne peut rien faire sans les autres</li>
|
||||
<li><strong>Clé privée jamais transmise</strong> : seules les clés publiques et les signatures circulent</li>
|
||||
</ul>
|
||||
|
||||
<details>
|
||||
<summary>Détails techniques pour les développeurs</summary>
|
||||
|
||||
<h3>Paramètres cryptographiques</h3>
|
||||
<table class="algo-table">
|
||||
<tr><th>Paramètre</th><th>Valeur</th></tr>
|
||||
<tr><td>Courbe elliptique</td><td>secp256k1 (256 bits)</td></tr>
|
||||
<tr><td>Taille clé AES</td><td>256 bits</td></tr>
|
||||
<tr><td>Taille IV (AES-GCM)</td><td>96 bits (12 octets)</td></tr>
|
||||
<tr><td>Taille Tag (AES-GCM)</td><td>128 bits (16 octets)</td></tr>
|
||||
<tr><td>Hash</td><td>SHA-256 (256 bits)</td></tr>
|
||||
<tr><td>KDF</td><td>HKDF-SHA256</td></tr>
|
||||
<tr><td>Signature</td><td>Schnorr secp256k1 (64 octets)</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>Bibliothèques utilisées</h3>
|
||||
<ul>
|
||||
<li><code>@noble/secp256k1</code> : ECDH, Schnorr, manipulation de clés</li>
|
||||
<li><code>@noble/hashes</code> : SHA-256, HKDF</li>
|
||||
<li><code>Web Crypto API</code> : AES-256-GCM (navigateur)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Format des clés</h3>
|
||||
<ul>
|
||||
<li><strong>Clé privée</strong> : 32 octets (64 caractères hex)</li>
|
||||
<li><strong>Clé publique (compressée)</strong> : 33 octets (66 caractères hex, préfixe 02 ou 03)</li>
|
||||
<li><strong>Signature Schnorr</strong> : 64 octets (128 caractères hex)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Fichiers source</h3>
|
||||
<ul>
|
||||
<li><code>userwallet/src/utils/encryption.ts</code> : encryptWithECDH, decryptWithECDH</li>
|
||||
<li><code>userwallet/src/utils/crypto.ts</code> : signMessage, verifySignature, generateChallenge, deriveChildKeyPair, getDerivedPublicKeys, publicKeyBelongsToIdentity</li>
|
||||
<li><code>userwallet/src/utils/pairing.ts</code> : getPairPublicKeys, pairContainsPublicKey, addPairPublicKey</li>
|
||||
<li><code>userwallet/src/utils/relay.ts</code> : postMessageChiffre, postSignature, postKey</li>
|
||||
<li><code>userwallet/src/utils/collectSignatures.ts</code> : runCollectLoop, fetchSignaturesForHash</li>
|
||||
<li><code>userwallet/src/utils/loginValidation.ts</code> : hasEnoughSignatures, checkDependenciesSatisfied</li>
|
||||
<li><code>service-login-verify/</code> : verifyLoginProof (côté parent)</li>
|
||||
</ul>
|
||||
<p>Dérivation de clés et clés multiples par pair : <code>docs/USERWALLET_KEY_DERIVATION.md</code>.</p>
|
||||
|
||||
<h3>Collecte des signatures</h3>
|
||||
<table class="algo-table">
|
||||
<tr><th>Constante</th><th>Valeur</th><th>Description</th></tr>
|
||||
<tr><td>COLLECT_POLL_MS</td><td>2 000 ms</td><td>Intervalle entre chaque interrogation</td></tr>
|
||||
<tr><td>COLLECT_TIMEOUT_MS</td><td>300 000 ms</td><td>Timeout global (5 minutes)</td></tr>
|
||||
<tr><td>COLLECT_FETCH_TIMEOUT_MS</td><td>15 000 ms</td><td>Timeout par requête</td></tr>
|
||||
</table>
|
||||
</details>
|
||||
|
||||
<h2>En résumé</h2>
|
||||
<div class="info">
|
||||
<strong>Le système combine plusieurs couches de protection :</strong>
|
||||
<ol>
|
||||
<li><strong>ECDH</strong> pour créer un secret sans jamais l'échanger</li>
|
||||
<li><strong>AES-256-GCM</strong> pour chiffrer le message</li>
|
||||
<li><strong>Schnorr</strong> pour prouver votre identité</li>
|
||||
<li><strong>SHA-256</strong> pour identifier chaque message de façon unique</li>
|
||||
<li><strong>Multi-signature</strong> pour exiger plusieurs validations</li>
|
||||
</ol>
|
||||
Résultat : vos messages sont <strong>confidentiels</strong>, <strong>authentiques</strong> et <strong>infalsifiables</strong>.
|
||||
</div>
|
||||
|
||||
<p><a href="index.html">← Retour à l'accueil</a> · <a href="contrat.html">Le contrat</a> · <a href="membre.html">Qui êtes-vous ?</a> · <a href="technique.html">Réseau P2P</a></p>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,232 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>4NK un nouveau web - site d'exemple</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.button-group {
|
||||
margin: 1rem 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
button {
|
||||
padding: 0.625rem 1.25rem;
|
||||
font-size: 1rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
min-height: 44px;
|
||||
}
|
||||
button:hover {
|
||||
background: #f5f5f5;
|
||||
border-color: #999;
|
||||
}
|
||||
button:active {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
button.primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border-color: #007bff;
|
||||
}
|
||||
button.primary:hover {
|
||||
background: #0056b3;
|
||||
border-color: #0056b3;
|
||||
}
|
||||
button.danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border-color: #dc3545;
|
||||
}
|
||||
button.danger:hover {
|
||||
background: #c82333;
|
||||
border-color: #c82333;
|
||||
}
|
||||
#iframe-container {
|
||||
margin: 1.5rem 0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
.iframe-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 0.75rem 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.iframe-header-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: rgba(255,255,255,0.2);
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
.iframe-header-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.iframe-header-subtitle {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.85;
|
||||
margin-left: auto;
|
||||
}
|
||||
#iframe-container iframe {
|
||||
width: 100%;
|
||||
height: 550px;
|
||||
border: 0;
|
||||
display: block;
|
||||
background: #fafafa;
|
||||
}
|
||||
#connected-section {
|
||||
padding: 0;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
.connected-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem 0;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
.connected-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 1.25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.connected-notifications {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.connected-notifications:hover {
|
||||
background: #f5f5f5;
|
||||
border-color: #999;
|
||||
}
|
||||
.connected-notifications svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
#user-info {
|
||||
margin: 1rem 0;
|
||||
padding: 1rem;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
#iframe-container {
|
||||
margin: 1rem 0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
#iframe-container iframe {
|
||||
height: 500px;
|
||||
}
|
||||
.iframe-header {
|
||||
padding: 0.6rem 0.8rem;
|
||||
}
|
||||
.iframe-header-subtitle {
|
||||
display: none;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.button-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
#iframe-container iframe {
|
||||
height: 450px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>4NK un nouveau web - site d'exemple</h1>
|
||||
|
||||
<div id="login-section">
|
||||
<p><a href="contrat.html">Le contrat</a> · <a href="membre.html">Qui êtes-vous ?</a> · <a href="technique.html">Réseau P2P</a> · <a href="cryptographie.html">Cryptographie</a></p>
|
||||
<p id="waiting-status" style="display: none;" role="status" aria-live="polite">Vérification du statut pairing et relais…</p>
|
||||
<div class="button-group">
|
||||
<button type="button" id="btn-login" class="primary">Se connecter</button>
|
||||
</div>
|
||||
<div id="iframe-container" style="display: none;">
|
||||
<div class="iframe-header">
|
||||
<div class="iframe-header-icon">🔐</div>
|
||||
<span class="iframe-header-title">UserWallet</span>
|
||||
<span class="iframe-header-subtitle">Connexion sécurisée</span>
|
||||
</div>
|
||||
<iframe id="userwallet" title="UserWallet"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="connected-section" style="display: none;">
|
||||
<header class="connected-header" aria-label="Zone connectée">
|
||||
<div class="connected-avatar" aria-hidden="true" title="Avatar">👤</div>
|
||||
<button type="button" class="connected-notifications" aria-label="Notifications" title="Notifications">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
|
||||
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</header>
|
||||
<h2>Vous êtes connecté</h2>
|
||||
<div id="user-info"></div>
|
||||
<p><a href="contrat.html">Le contrat</a> · <a href="membre.html">Qui êtes-vous ?</a> · <a href="technique.html">Réseau P2P</a> · <a href="cryptographie.html">Cryptographie</a></p>
|
||||
<div class="button-group">
|
||||
<button type="button" id="btn-logout" class="danger">Se déconnecter</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,182 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Qui êtes-vous ? – 4NK un nouveau web</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
h1 { font-size: 1.5rem; margin-bottom: 1rem; color: #222; }
|
||||
h2 { font-size: 1.2rem; margin-top: 1.5rem; margin-bottom: 0.5rem; color: #333; }
|
||||
p { margin: 0.75rem 0; }
|
||||
ul { margin: 0.5rem 0; padding-left: 1.5rem; }
|
||||
li { margin: 0.4rem 0; }
|
||||
a { color: #007bff; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.highlight { background: #e8f4fd; padding: 1rem; border-radius: 8px; border-left: 4px solid #007bff; margin: 1rem 0; }
|
||||
.warning { background: #fff3cd; padding: 1rem; border-radius: 8px; border-left: 4px solid #ffc107; margin: 1rem 0; }
|
||||
details { margin: 1rem 0; }
|
||||
summary { cursor: pointer; font-weight: 600; color: #555; }
|
||||
.meta { font-family: ui-monospace, monospace; font-size: 0.85rem; color: #666; background: #f5f5f5; padding: 0.2em 0.4em; border-radius: 4px; word-break: break-all; }
|
||||
details h3 { font-size: 0.95rem; margin-top: 1rem; margin-bottom: 0.5rem; font-weight: 600; color: #555; }
|
||||
#user-pairs-info ul { list-style: none; padding-left: 0; }
|
||||
#user-pairs-info li { background: #f9f9f9; padding: 0.75rem; border-radius: 6px; margin: 0.5rem 0; border: 1px solid #e0e0e0; }
|
||||
@media (max-width: 768px) {
|
||||
body { padding: 0.75rem; }
|
||||
h1 { font-size: 1.25rem; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Qui êtes-vous ?</h1>
|
||||
<p><a href="index.html">← Retour à l'accueil</a> · <a href="contrat.html">Voir le contrat</a> · <a href="technique.html">Réseau P2P</a> · <a href="cryptographie.html">Cryptographie</a></p>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>En résumé :</strong> Vous êtes un <strong>membre</strong> qui peut avoir plusieurs appareils (Pairs).
|
||||
Chaque appareil possède ses propres clés et peut signer selon <strong>vos règles</strong>.
|
||||
Vos données sont stockées selon les membres du contrat — vous gardez le contrôle total.
|
||||
</div>
|
||||
|
||||
<h2>Vous êtes le « membre connecté »</h2>
|
||||
<p>
|
||||
Quand vous vous connectez à ce service, vous devenez un <strong>membre</strong>.
|
||||
Contrairement aux sites classiques où vos identifiants sont stockés sur un serveur distant,
|
||||
ici votre identité reste <strong>chez vous</strong>.
|
||||
</p>
|
||||
|
||||
<h2>Un membre = plusieurs appareils</h2>
|
||||
<p>
|
||||
Un membre n'est pas limité à un seul appareil. Vous pouvez avoir <strong>plusieurs appareils</strong>
|
||||
(ordinateur, téléphone, tablette) qui forment ensemble votre identité :
|
||||
</p>
|
||||
<ul>
|
||||
<li>Chaque appareil s'appelle un « <strong>Pair</strong> » (device).</li>
|
||||
<li>Chaque Pair possède <strong>sa propre paire de clés</strong> cryptographiques.</li>
|
||||
<li>Tous vos Pairs peuvent signer en votre nom, selon les règles que <strong>vous</strong> définissez.</li>
|
||||
</ul>
|
||||
<p>
|
||||
<em>Exemple :</em> Vous pouvez configurer votre ordinateur principal et votre téléphone comme deux Pairs.
|
||||
Si l'un est perdu, vous gardez l'accès via l'autre.
|
||||
</p>
|
||||
|
||||
<h2>Vous définissez les règles</h2>
|
||||
<p>
|
||||
Chaque membre a un <strong>contrat</strong> qui définit les règles de signature et de validation.
|
||||
C'est <strong>vous</strong> qui contrôlez ces règles :
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Quels Pairs peuvent signer</strong> — vous décidez quels appareils sont autorisés.</li>
|
||||
<li><strong>Combien de signatures sont requises</strong> — une seule, ou plusieurs pour plus de sécurité.</li>
|
||||
<li><strong>Pour quelles actions</strong> — certaines actions peuvent nécessiter plus de validations.</li>
|
||||
</ul>
|
||||
<div class="highlight">
|
||||
<strong>Exemple :</strong> Vous pouvez exiger qu'une action sensible (comme un paiement) soit signée
|
||||
par <strong>2 de vos 3 appareils</strong> — c'est le principe du « multi-signature ».
|
||||
</div>
|
||||
|
||||
<h2>Où sont stockées vos données ?</h2>
|
||||
<p>
|
||||
Les données du service sont stockées selon les <strong>membres définis dans le contrat</strong>.
|
||||
Chaque membre a ses propres données, séparées des autres :
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Vos clés privées</strong> — sur vos appareils (Pairs), jamais sur le serveur.</li>
|
||||
<li><strong>Vos données utilisateur</strong> — associées à votre identité de membre.</li>
|
||||
<li><strong>Les preuves de signature</strong> — vérifiables publiquement, liées à vos Pairs.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Votre appareil = votre coffre-fort</h2>
|
||||
<p>
|
||||
Chaque appareil (Pair) joue le rôle de <strong>coffre-fort numérique</strong> :
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Vos clés de sécurité</strong> sont créées directement dans votre navigateur (dans la fenêtre de connexion).</li>
|
||||
<li><strong>Elles ne quittent jamais votre appareil</strong> — elles sont stockées localement (IndexedDB).</li>
|
||||
<li><strong>Personne d'autre n'y a accès</strong>, pas même le service.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Comment ça fonctionne ?</h2>
|
||||
<ol>
|
||||
<li>Vous cliquez sur « Se connecter ».</li>
|
||||
<li>Une fenêtre s'ouvre (UserWallet) où vous déverrouillez votre identité.</li>
|
||||
<li>Votre appareil <strong>signe</strong> une preuve que c'est bien vous (comme une signature manuscrite, mais numérique).</li>
|
||||
<li>Le service vérifie cette preuve et vous donne accès.</li>
|
||||
</ol>
|
||||
<p>
|
||||
À aucun moment vos clés secrètes ne sont transmises — seule la <strong>preuve</strong> de votre identité l'est.
|
||||
</p>
|
||||
|
||||
<div class="warning">
|
||||
<strong>Important :</strong> Si vous perdez l'accès à votre appareil (panne, vol, perte),
|
||||
vous perdez vos clés. Pensez à configurer un second appareil ou une sauvegarde.
|
||||
</div>
|
||||
|
||||
<h2>Quelle différence avec un mot de passe classique ?</h2>
|
||||
<ul>
|
||||
<li><strong>Mot de passe classique</strong> : stocké sur le serveur du site → risque de fuite en cas de piratage.</li>
|
||||
<li><strong>Ici</strong> : vos clés restent sur votre appareil → même si le service est piraté, vos clés sont en sécurité.</li>
|
||||
</ul>
|
||||
|
||||
<details>
|
||||
<summary>Détails techniques (pour les curieux)</summary>
|
||||
<ul>
|
||||
<li>Vos clés utilisent la cryptographie <strong>secp256k1</strong> (la même que Bitcoin).</li>
|
||||
<li>Elles sont stockées dans <strong>IndexedDB</strong> de votre navigateur.</li>
|
||||
<li>La connexion utilise l'authentification multi-facteur (<strong>MFA</strong>).</li>
|
||||
<li>Le service possède son propre portefeuille (wallet) séparé du vôtre, jamais exposé.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Vos Pairs et clés publiques</h3>
|
||||
<div id="user-pairs-info">
|
||||
<p><em>Non connecté — connectez-vous pour voir vos Pairs.</em></p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const SESSION_STORAGE_KEY = 'website-skeleton-session';
|
||||
const container = document.getElementById('user-pairs-info');
|
||||
if (!container) return;
|
||||
|
||||
const stored = sessionStorage.getItem(SESSION_STORAGE_KEY);
|
||||
if (!stored) return;
|
||||
|
||||
try {
|
||||
const session = JSON.parse(stored);
|
||||
if (!session || !session.proof || !session.proof.signatures) return;
|
||||
|
||||
const signatures = session.proof.signatures;
|
||||
if (signatures.length === 0) {
|
||||
container.innerHTML = '<p><em>Aucun Pair enregistré.</em></p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<ul>';
|
||||
signatures.forEach(function(sig, index) {
|
||||
const pairUuid = sig.pair_uuid || 'Non spécifié';
|
||||
const pubKey = sig.cle_publique || 'Non disponible';
|
||||
html += '<li>';
|
||||
html += '<strong>Pair ' + (index + 1) + '</strong><br>';
|
||||
html += 'UUID : <span class="meta">' + pairUuid + '</span><br>';
|
||||
html += 'Clé publique : <span class="meta">' + pubKey + '</span>';
|
||||
html += '</li>';
|
||||
});
|
||||
html += '</ul>';
|
||||
container.innerHTML = html;
|
||||
} catch (e) {
|
||||
// Session parsing error - keep default message
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<p><a href="index.html">← Retour à l'accueil</a> · <a href="contrat.html">Voir le contrat</a> · <a href="technique.html">Réseau P2P</a> · <a href="cryptographie.html">Cryptographie</a></p>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,312 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Réseau P2P – 4NK un nouveau web</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
h1 { font-size: 1.5rem; margin-bottom: 1rem; color: #222; }
|
||||
h2 { font-size: 1.2rem; margin-top: 1.5rem; margin-bottom: 0.5rem; color: #333; }
|
||||
h3 { font-size: 1rem; margin-top: 1rem; margin-bottom: 0.35rem; font-weight: 600; }
|
||||
p { margin: 0.75rem 0; }
|
||||
ul { margin: 0.5rem 0; padding-left: 1.5rem; }
|
||||
li { margin: 0.4rem 0; }
|
||||
a { color: #007bff; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.highlight { background: #e8f4fd; padding: 1rem; border-radius: 8px; border-left: 4px solid #007bff; margin: 1rem 0; }
|
||||
.info { background: #d4edda; padding: 1rem; border-radius: 8px; border-left: 4px solid #28a745; margin: 1rem 0; }
|
||||
details { margin: 1rem 0; }
|
||||
summary { cursor: pointer; font-weight: 600; color: #555; }
|
||||
.meta { font-family: ui-monospace, monospace; font-size: 0.85rem; color: #666; background: #f5f5f5; padding: 0.2em 0.4em; border-radius: 4px; word-break: break-all; }
|
||||
.relay-list { list-style: none; padding-left: 0; }
|
||||
.relay-list > li { background: #f8f9fa; padding: 0.75rem 1rem; border-radius: 8px; border: 1px solid #e0e0e0; margin: 0.5rem 0; }
|
||||
.relay-list > li > strong { color: #007bff; }
|
||||
.endpoint-table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
|
||||
.endpoint-table th, .endpoint-table td { padding: 0.5rem; text-align: left; border-bottom: 1px solid #ddd; }
|
||||
.endpoint-table th { background: #f5f5f5; font-weight: 600; }
|
||||
.endpoint-table code { background: #f5f5f5; padding: 0.2em 0.4em; border-radius: 4px; font-size: 0.9em; }
|
||||
@media (max-width: 768px) {
|
||||
body { padding: 0.75rem; }
|
||||
h1 { font-size: 1.25rem; }
|
||||
.endpoint-table { font-size: 0.9rem; }
|
||||
.endpoint-table th, .endpoint-table td { padding: 0.4rem; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Réseau P2P</h1>
|
||||
<p><a href="index.html">← Retour à l'accueil</a> · <a href="contrat.html">Le contrat</a> · <a href="membre.html">Qui êtes-vous ?</a> · <a href="cryptographie.html">Cryptographie</a></p>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>En résumé :</strong> Au lieu d'utiliser un seul serveur central (comme Gmail ou Facebook),
|
||||
le système utilise plusieurs <strong>relais</strong> qui fonctionnent comme des boîtes aux lettres publiques.
|
||||
Vos messages chiffrés sont stockés dans ces relais, et vous pouvez choisir lesquels utiliser.
|
||||
</div>
|
||||
|
||||
<h2>Qu'est-ce qu'un réseau P2P ?</h2>
|
||||
<p>
|
||||
Imaginez que vous voulez envoyer une lettre. Dans un système classique (comme la poste traditionnelle),
|
||||
tous les courriers passent par un seul bureau central. Si ce bureau tombe en panne, plus rien ne fonctionne.
|
||||
</p>
|
||||
<p>
|
||||
Avec un réseau pair-à-pair (P2P), c'est différent : il y a plusieurs <strong>relais</strong> (comme plusieurs
|
||||
boîtes aux lettres) répartis sur Internet. Vous pouvez choisir où déposer vos messages, et si un relais
|
||||
ne fonctionne plus, vous pouvez utiliser un autre.
|
||||
</p>
|
||||
<p>
|
||||
Les avantages de cette approche :
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Plus de robustesse</strong> : si un relais tombe en panne, d'autres continuent de fonctionner</li>
|
||||
<li><strong>Pas de point unique de défaillance</strong> : personne ne contrôle tout le système</li>
|
||||
<li><strong>Vous choisissez</strong> : vous décidez quels relais utiliser, comme choisir votre opérateur téléphonique</li>
|
||||
<li><strong>Copies multiples</strong> : vos messages peuvent être copiés sur plusieurs relais pour plus de sécurité</li>
|
||||
</ul>
|
||||
|
||||
<h2>Comment fonctionnent les relais ?</h2>
|
||||
<p>
|
||||
Un relais, c'est comme une boîte aux lettres publique sur Internet. Quand vous voulez communiquer avec quelqu'un,
|
||||
vous déposez trois choses séparément dans cette boîte :
|
||||
</p>
|
||||
<ol>
|
||||
<li><strong>Le message chiffré</strong> : votre message codé (comme une lettre dans une enveloppe scellée)</li>
|
||||
<li><strong>La signature</strong> : une preuve que c'est bien vous qui avez envoyé le message (comme votre signature manuscrite)</li>
|
||||
<li><strong>La clé de déchiffrement</strong> : la clé pour décoder le message (comme la clé de votre boîte aux lettres)</li>
|
||||
</ol>
|
||||
<div class="info">
|
||||
<strong>Pourquoi séparer ces trois éléments ?</strong> C'est comme mettre votre lettre, votre signature et votre clé
|
||||
dans trois enveloppes différentes. Même si quelqu'un trouve une enveloppe, il ne peut pas tout faire sans les autres.
|
||||
C'est plus sûr !
|
||||
</div>
|
||||
|
||||
<h3>Comment récupérer vos messages ?</h3>
|
||||
<p>
|
||||
Le système fonctionne comme une boîte aux lettres classique : vous allez vérifier régulièrement s'il y a du courrier.
|
||||
C'est ce qu'on appelle le modèle <strong>"pull-only"</strong> (vous tirez les informations, elles ne vous sont pas poussées).
|
||||
</p>
|
||||
<ul>
|
||||
<li>Votre appareil vérifie périodiquement les relais pour voir s'il y a de nouveaux messages</li>
|
||||
<li>Pas de notification instantanée (comme une alerte SMS), mais une vérification régulière</li>
|
||||
<li>Communication simple via Internet, comme consulter une page web</li>
|
||||
</ul>
|
||||
<p>
|
||||
Cette méthode fonctionne partout, même dans les environnements les plus restrictifs, car elle utilise
|
||||
simplement le protocole HTTP (comme quand vous visitez un site web).
|
||||
</p>
|
||||
|
||||
<h3>Les relais se parlent entre eux</h3>
|
||||
<p>
|
||||
Les relais peuvent être configurés pour se copier mutuellement les messages :
|
||||
</p>
|
||||
<ul>
|
||||
<li>Quand vous déposez un message sur un relais, il peut automatiquement le copier vers d'autres relais</li>
|
||||
<li>Un système intelligent évite de copier plusieurs fois le même message (grâce à une empreinte unique)</li>
|
||||
<li>Résultat : vos messages sont disponibles sur plusieurs relais, comme avoir plusieurs copies de sauvegarde</li>
|
||||
</ul>
|
||||
<p>
|
||||
<em>Exemple :</em> Si vous déposez un message sur le relais A, et que A est configuré pour copier vers B et C,
|
||||
votre message sera disponible sur les trois relais. Si A tombe en panne, vous pouvez toujours récupérer votre message depuis B ou C.
|
||||
</p>
|
||||
|
||||
<h2>Quels relais utiliser ?</h2>
|
||||
<p>
|
||||
Par défaut, un relais principal est configuré. Vous pouvez en ajouter d'autres, jusqu'à <strong>8 relais au maximum</strong>.
|
||||
L'application tente de se connecter à tous les relais activés pour déposer et récupérer les messages.
|
||||
</p>
|
||||
<ul class="relay-list">
|
||||
<li>
|
||||
<strong>Relais principal (configuré automatiquement)</strong>
|
||||
<br>Adresse : <span class="meta">https://relay.certificator.4nkweb.com</span>
|
||||
<br>Ce relais est déjà configuré quand vous utilisez le système pour la première fois.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="info">
|
||||
<strong>Jusqu'à 8 relais :</strong> Dans les paramètres de UserWallet, vous pouvez ajouter vos propres relais
|
||||
(comme plusieurs boîtes aux lettres). Maximum 8 au total. Vous activez ou désactivez chaque relais,
|
||||
et choisissez l'ordre de priorité. L'application utilise tous les relais activés pour déposer et récupérer.
|
||||
</div>
|
||||
|
||||
<h2>Pourquoi dupliquer les relais et les flux ?</h2>
|
||||
<p>
|
||||
Plus vous utilisez de relais, plus vos messages ont de chances d'être disponibles et de circuler.
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Déposer sur plusieurs relais</strong> : quand vous envoyez un message, l'application le dépose sur chaque relais activé.
|
||||
Si un relais est indisponible, les autres reçoivent quand même le message. Comme poster la même lettre à plusieurs boîtes.</li>
|
||||
<li><strong>Récupérer depuis plusieurs relais</strong> : pour lire vos messages, l'application interroge chaque relais.
|
||||
Si un relais ne répond pas ou n'a pas le message, un autre peut l'avoir. Comme vérifier plusieurs boîtes aux lettres.</li>
|
||||
<li><strong>Résultat</strong> : plus de robustesse (un relais en panne ne bloque pas tout), plus de chances que vos messages
|
||||
soient bien livrés, et les relais peuvent se copier entre eux pour multiplier les copies.</li>
|
||||
</ul>
|
||||
<p>
|
||||
En résumé : configurer jusqu'à 8 relais et tous les activer, c'est maximiser les chemins pour envoyer et recevoir,
|
||||
sans dépendre d'un seul point.
|
||||
</p>
|
||||
|
||||
<h2>Les actions possibles avec un relais</h2>
|
||||
<p>
|
||||
Un relais offre plusieurs "actions" (appelées endpoints) que vous pouvez utiliser. C'est comme une boîte aux lettres
|
||||
avec plusieurs fonctions : déposer, récupérer, vérifier l'état, etc.
|
||||
</p>
|
||||
<p>
|
||||
<em>Note technique :</em> Ces actions utilisent le protocole HTTP (comme les sites web) avec des méthodes GET (lire)
|
||||
et POST (écrire).
|
||||
</p>
|
||||
<table class="endpoint-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Méthode</th>
|
||||
<th>Endpoint</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>GET</code></td>
|
||||
<td><code>/health</code></td>
|
||||
<td>Vérifier si le relais fonctionne correctement</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GET</code></td>
|
||||
<td><code>/messages?start=<ts>&end=<ts>&service=<uuid></code></td>
|
||||
<td>Récupérer les messages entre deux dates (optionnel : filtrer par service)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>POST</code></td>
|
||||
<td><code>/messages</code></td>
|
||||
<td>Déposer un message chiffré dans le relais</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GET</code></td>
|
||||
<td><code>/messages/:hash</code></td>
|
||||
<td>Récupérer un message précis grâce à son identifiant unique (hash)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GET</code></td>
|
||||
<td><code>/signatures/:hash</code></td>
|
||||
<td>Récupérer les signatures associées à un message</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>POST</code></td>
|
||||
<td><code>/signatures</code></td>
|
||||
<td>Déposer une signature dans le relais</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GET</code></td>
|
||||
<td><code>/keys/:hash</code></td>
|
||||
<td>Récupérer les clés pour déchiffrer un message</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>POST</code></td>
|
||||
<td><code>/keys</code></td>
|
||||
<td>Déposer une clé de déchiffrement dans le relais</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GET</code></td>
|
||||
<td><code>/metrics</code></td>
|
||||
<td>Consulter les statistiques du relais (nombre de messages, signatures, clés stockées)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GET</code></td>
|
||||
<td><code>/bloom</code></td>
|
||||
<td>Obtenir une liste des messages déjà vus (pour éviter de demander plusieurs fois le même message)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Comment les relais stockent les données</h2>
|
||||
<h3>Organisation du stockage</h3>
|
||||
<p>
|
||||
Les relais organisent les données comme une bibliothèque bien rangée :
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Messages</strong> : rangés par identifiant unique (hash) pour les retrouver rapidement</li>
|
||||
<li><strong>Signatures</strong> : classées par message associé</li>
|
||||
<li><strong>Clés</strong> : classées par message associé</li>
|
||||
<li><strong>Éviter les doublons</strong> : le système se souvient des messages déjà vus pour ne pas les stocker plusieurs fois</li>
|
||||
<li><strong>Indexation</strong> : un système de classement permet de retrouver rapidement les messages d'un service particulier</li>
|
||||
</ul>
|
||||
|
||||
<h3>Protection contre les abus</h3>
|
||||
<p>
|
||||
Les relais sont protégés contre les utilisations abusives :
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Limitation des requêtes</strong> : chaque adresse IP ne peut faire qu'un nombre limité de requêtes par minute</li>
|
||||
<li><strong>Contrôle d'accès</strong> : seuls les sites autorisés peuvent utiliser le relais</li>
|
||||
<li><strong>Timeout</strong> : si une requête prend trop de temps, elle est annulée automatiquement</li>
|
||||
<li><strong>Compression</strong> : les réponses sont compressées pour être plus rapides</li>
|
||||
</ul>
|
||||
|
||||
<h3>Éviter les doublons</h3>
|
||||
<p>
|
||||
Chaque message a une empreinte unique (comme une empreinte digitale). Le système utilise cette empreinte
|
||||
pour s'assurer qu'un même message n'est pas stocké plusieurs fois, même s'il arrive depuis plusieurs relais.
|
||||
Cela évite aussi que les messages tournent en boucle entre les relais.
|
||||
</p>
|
||||
|
||||
<details>
|
||||
<summary>Détails techniques pour les développeurs</summary>
|
||||
<h3>Configuration des relais pairs</h3>
|
||||
<p>
|
||||
Les administrateurs de relais peuvent configurer leurs relais pour se copier mutuellement les messages.
|
||||
Cela se fait via une variable de configuration :
|
||||
</p>
|
||||
<pre class="meta">PEER_RELAYS=http://relay1:3019,http://relay2:3019</pre>
|
||||
<p>
|
||||
Quand un message arrive sur un relais, il est automatiquement copié vers tous
|
||||
les relais pairs configurés (si le message n'a pas déjà été vu).
|
||||
</p>
|
||||
|
||||
<h3>Bloom filter</h3>
|
||||
<p>
|
||||
Le endpoint <code>/bloom</code> retourne une liste compacte des messages déjà vus par le relais.
|
||||
Les applications peuvent utiliser cette liste pour éviter de demander plusieurs fois le même message,
|
||||
ce qui réduit la charge sur le réseau et améliore les performances.
|
||||
</p>
|
||||
|
||||
<h3>Statistiques du relais</h3>
|
||||
<p>
|
||||
Le endpoint <code>/metrics</code> expose des statistiques au format Prometheus (pour le monitoring) :
|
||||
</p>
|
||||
<ul>
|
||||
<li>Nombre de messages stockés</li>
|
||||
<li>Nombre de signatures stockées</li>
|
||||
<li>Nombre de clés stockées</li>
|
||||
</ul>
|
||||
|
||||
<h3>Gestion des erreurs et timeouts</h3>
|
||||
<p>
|
||||
Les applications utilisent un délai d'attente de 15 secondes pour toutes les requêtes vers les relais.
|
||||
Si une requête échoue ou prend trop de temps, l'application peut essayer un autre relais configuré,
|
||||
ou réessayer plus tard avec un délai progressif (backoff exponentiel).
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<h2>Pourquoi cette architecture ?</h2>
|
||||
<p>
|
||||
Cette façon de faire a été choisie pour plusieurs raisons importantes :
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Simplicité</strong> : utilise le protocole HTTP standard (comme les sites web), donc ça fonctionne partout</li>
|
||||
<li><strong>Fiabilité</strong> : pas besoin de maintenir une connexion permanente, comme consulter une page web</li>
|
||||
<li><strong>Capacité</strong> : chaque relais peut gérer de nombreux utilisateurs en même temps</li>
|
||||
<li><strong>Contrôle</strong> : vous choisissez quels relais utiliser, personne ne vous impose un choix</li>
|
||||
<li><strong>Sécurité</strong> : les messages, signatures et clés sont séparés, ce qui rend le système plus sûr</li>
|
||||
</ul>
|
||||
<p>
|
||||
En résumé, c'est un système simple, fiable et qui vous donne le contrôle, tout en étant sécurisé.
|
||||
</p>
|
||||
|
||||
<p><a href="index.html">← Retour à l'accueil</a> · <a href="contrat.html">Le contrat</a> · <a href="membre.html">Qui êtes-vous ?</a> · <a href="cryptographie.html">Cryptographie</a></p>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user