From b60d7fd00988ed8a929c2ca4ae2321689b464de5 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Tue, 24 Feb 2026 10:31:39 +0100 Subject: [PATCH] feat(luci-app-zkp): Add ZKP Hamiltonian cryptographic dashboard LuCI web interface for the ZKP Hamiltonian library: - Status display: version, key count, storage paths - Key generation: node count (4-50), edge density selection - Prove/Verify workflow with ACCEPT/REJECT display - Keys table with actions (Prove, Verify, Delete) - KISS theme with dark mode support RPCD backend methods: - status: library info and stats - keygen: generate graph + Hamiltonian cycle - prove: create NIZK proof - verify: validate proof - list_keys, delete_key, get_graph Note: Requires zkp-hamiltonian CLI tools to be installed. Co-Authored-By: Claude Opus 4.5 --- .claude/HISTORY.md | 8 + .claude/WIP.md | 6 + .claude/settings.local.json | 3 +- package/secubox/luci-app-zkp/Makefile | 20 + .../resources/view/zkp/overview.js | 637 ++++++++++++++++++ .../root/usr/libexec/rpcd/luci.zkp | 397 +++++++++++ .../usr/share/luci/menu.d/luci-app-zkp.json | 14 + .../usr/share/rpcd/acl.d/luci-app-zkp.json | 15 + 8 files changed, 1099 insertions(+), 1 deletion(-) create mode 100644 package/secubox/luci-app-zkp/Makefile create mode 100644 package/secubox/luci-app-zkp/htdocs/luci-static/resources/view/zkp/overview.js create mode 100755 package/secubox/luci-app-zkp/root/usr/libexec/rpcd/luci.zkp create mode 100644 package/secubox/luci-app-zkp/root/usr/share/luci/menu.d/luci-app-zkp.json create mode 100644 package/secubox/luci-app-zkp/root/usr/share/rpcd/acl.d/luci-app-zkp.json diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md index 0966fae4..575a775d 100644 --- a/.claude/HISTORY.md +++ b/.claude/HISTORY.md @@ -3231,3 +3231,11 @@ git checkout HEAD -- index.html - `zkp-hamiltonian/tests/{test_crypto,test_graph,test_protocol,test_serialize}.c` - `zkp-hamiltonian/CMakeLists.txt` - **Commit:** `65539368 feat(zkp-hamiltonian): Add Zero-Knowledge Proof library based on Hamiltonian Cycle` + +41. **MetaBlogizer Upload Workflow Fix (2026-02-24)** + - Sites now work immediately after upload without needing unpublish + expose. + - **Root cause:** Upload created HAProxy vhost and mitmproxy route file entry, but mitmproxy never received a reload signal to activate the route. + - **Fix:** `reload_haproxy()` now calls `mitmproxyctl sync-routes` to ensure mitmproxy picks up new routes immediately after vhost creation. + - **Files:** + - `luci-app-metablogizer/root/usr/libexec/rpcd/luci.metablogizer` + - **Commit:** `ec8e96a7 fix(metablogizer): Auto-sync mitmproxy routes on HAProxy reload` diff --git a/.claude/WIP.md b/.claude/WIP.md index 7ecd875f..87452c90 100644 --- a/.claude/WIP.md +++ b/.claude/WIP.md @@ -866,6 +866,12 @@ _Last updated: 2026-02-24 (Service Stability Fixes)_ ### Just Completed (2026-02-24) +- **MetaBlogizer Upload Workflow Fix** — DONE (2026-02-24) + - Sites now work immediately after upload without unpublish + expose cycle + - Root cause: mitmproxy never received reload signal after route creation + - Fix: `reload_haproxy()` now calls `mitmproxyctl sync-routes` + - Commit: ec8e96a7 + - **ZKP Hamiltonian Library** — DONE (2026-02-24) - Zero-Knowledge Proof implementation based on Hamiltonian Cycle (Blum 1986) - NIZK via Fiat-Shamir heuristic, SHA3-256 commitments (OpenSSL) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 56bf271d..13379459 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -416,7 +416,8 @@ "Bash(./zkp_prover:*)", "Bash(./zkp_verifier:*)", "Bash(printf:*)", - "Bash(dd:*)" + "Bash(dd:*)", + "Bash(gh release create:*)" ] } } diff --git a/package/secubox/luci-app-zkp/Makefile b/package/secubox/luci-app-zkp/Makefile new file mode 100644 index 00000000..f5afa669 --- /dev/null +++ b/package/secubox/luci-app-zkp/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 CyberMind.FR / SecuBox + +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=LuCI ZKP Hamiltonian Dashboard +LUCI_DESCRIPTION:=Zero-Knowledge Proof dashboard for graph-based cryptographic proofs +LUCI_DEPENDS:=+zkp-hamiltonian +LUCI_PKGARCH:=all + +PKG_NAME:=luci-app-zkp +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 +PKG_LICENSE:=GPL-2.0-or-later +PKG_MAINTAINER:=SecuBox + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildance +$(eval $(call BuildPackage,luci-app-zkp)) diff --git a/package/secubox/luci-app-zkp/htdocs/luci-static/resources/view/zkp/overview.js b/package/secubox/luci-app-zkp/htdocs/luci-static/resources/view/zkp/overview.js new file mode 100644 index 00000000..e7a30a19 --- /dev/null +++ b/package/secubox/luci-app-zkp/htdocs/luci-static/resources/view/zkp/overview.js @@ -0,0 +1,637 @@ +'use strict'; +'require view'; +'require rpc'; +'require ui'; +'require poll'; + +var callStatus = rpc.declare({ + object: 'luci.zkp', + method: 'status', + expect: {} +}); + +var callListKeys = rpc.declare({ + object: 'luci.zkp', + method: 'list_keys', + expect: {} +}); + +var callKeygen = rpc.declare({ + object: 'luci.zkp', + method: 'keygen', + params: ['nodes', 'density', 'name'], + expect: {} +}); + +var callProve = rpc.declare({ + object: 'luci.zkp', + method: 'prove', + params: ['name'], + expect: {} +}); + +var callVerify = rpc.declare({ + object: 'luci.zkp', + method: 'verify', + params: ['name'], + expect: {} +}); + +var callDeleteKey = rpc.declare({ + object: 'luci.zkp', + method: 'delete_key', + params: ['name'], + expect: {} +}); + +function injectStyles() { + if (document.getElementById('zkp-styles')) return; + + var style = document.createElement('style'); + style.id = 'zkp-styles'; + style.textContent = ` + .zkp-container { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + max-width: 1200px; + margin: 0 auto; + padding: 1rem; + } + + .zkp-header { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1.5rem; + padding-bottom: 1rem; + border-bottom: 2px solid #3b82f6; + } + + .zkp-header h2 { + margin: 0; + font-size: 1.5rem; + color: #1e293b; + } + + .zkp-badge { + display: inline-block; + padding: 0.25rem 0.75rem; + border-radius: 9999px; + font-size: 0.75rem; + font-weight: 600; + } + + .zkp-badge-success { background: #dcfce7; color: #166534; } + .zkp-badge-error { background: #fee2e2; color: #991b1b; } + .zkp-badge-info { background: #dbeafe; color: #1e40af; } + + .zkp-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1rem; + margin-bottom: 1.5rem; + } + + .zkp-card { + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 0.5rem; + padding: 1rem; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + + .zkp-card-title { + font-size: 0.875rem; + font-weight: 600; + color: #64748b; + margin-bottom: 0.5rem; + text-transform: uppercase; + letter-spacing: 0.05em; + } + + .zkp-card-value { + font-size: 1.5rem; + font-weight: 700; + color: #1e293b; + } + + .zkp-section { + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 0.5rem; + padding: 1.5rem; + margin-bottom: 1.5rem; + } + + .zkp-section-title { + font-size: 1.125rem; + font-weight: 600; + color: #1e293b; + margin-bottom: 1rem; + display: flex; + align-items: center; + gap: 0.5rem; + } + + .zkp-form-row { + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-bottom: 1rem; + align-items: flex-end; + } + + .zkp-form-group { + flex: 1; + min-width: 150px; + } + + .zkp-form-group label { + display: block; + font-size: 0.875rem; + font-weight: 500; + color: #475569; + margin-bottom: 0.25rem; + } + + .zkp-form-group input, + .zkp-form-group select { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 0.875rem; + } + + .zkp-form-group input:focus, + .zkp-form-group select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); + } + + .zkp-btn { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + border: none; + border-radius: 0.375rem; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: all 0.15s; + } + + .zkp-btn-primary { + background: #3b82f6; + color: #fff; + } + + .zkp-btn-primary:hover { + background: #2563eb; + } + + .zkp-btn-success { + background: #22c55e; + color: #fff; + } + + .zkp-btn-success:hover { + background: #16a34a; + } + + .zkp-btn-danger { + background: #ef4444; + color: #fff; + } + + .zkp-btn-danger:hover { + background: #dc2626; + } + + .zkp-btn-secondary { + background: #64748b; + color: #fff; + } + + .zkp-btn-secondary:hover { + background: #475569; + } + + .zkp-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .zkp-table { + width: 100%; + border-collapse: collapse; + } + + .zkp-table th, + .zkp-table td { + padding: 0.75rem; + text-align: left; + border-bottom: 1px solid #e2e8f0; + } + + .zkp-table th { + font-weight: 600; + color: #64748b; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.05em; + } + + .zkp-table tr:hover { + background: #f8fafc; + } + + .zkp-result { + padding: 1rem; + border-radius: 0.5rem; + margin-top: 1rem; + font-family: monospace; + font-size: 0.875rem; + } + + .zkp-result-accept { + background: #dcfce7; + border: 1px solid #86efac; + color: #166534; + } + + .zkp-result-reject { + background: #fee2e2; + border: 1px solid #fca5a5; + color: #991b1b; + } + + .zkp-result-info { + background: #f1f5f9; + border: 1px solid #cbd5e1; + color: #475569; + } + + .zkp-actions { + display: flex; + gap: 0.5rem; + } + + .zkp-empty { + text-align: center; + padding: 2rem; + color: #64748b; + } + + /* Dark mode */ + @media (prefers-color-scheme: dark) { + .zkp-header h2 { color: #f1f5f9; } + .zkp-card { background: #1e293b; border-color: #334155; } + .zkp-card-title { color: #94a3b8; } + .zkp-card-value { color: #f1f5f9; } + .zkp-section { background: #1e293b; border-color: #334155; } + .zkp-section-title { color: #f1f5f9; } + .zkp-form-group label { color: #94a3b8; } + .zkp-form-group input, + .zkp-form-group select { + background: #0f172a; + border-color: #334155; + color: #f1f5f9; + } + .zkp-table th { color: #94a3b8; } + .zkp-table td { color: #e2e8f0; } + .zkp-table th, + .zkp-table td { border-color: #334155; } + .zkp-table tr:hover { background: #334155; } + .zkp-result-info { background: #334155; border-color: #475569; color: #e2e8f0; } + } + `; + document.head.appendChild(style); +} + +function formatBytes(bytes) { + if (bytes === 0) return '0 B'; + var k = 1024; + var sizes = ['B', 'KB', 'MB']; + var i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; +} + +function formatDate(timestamp) { + if (!timestamp) return '-'; + var date = new Date(timestamp * 1000); + return date.toLocaleString(); +} + +return view.extend({ + handleSaveApply: null, + handleSave: null, + handleReset: null, + + load: function() { + return Promise.all([ + callStatus(), + callListKeys() + ]); + }, + + render: function(data) { + var status = data[0] || {}; + var keysData = data[1] || {}; + var keys = keysData.keys || []; + + injectStyles(); + + var view = E('div', { 'class': 'zkp-container' }, [ + // Header + E('div', { 'class': 'zkp-header' }, [ + E('h2', {}, 'ZKP Hamiltonian Cryptography'), + status.tools_available ? + E('span', { 'class': 'zkp-badge zkp-badge-success' }, 'v' + (status.version || '1.0')) : + E('span', { 'class': 'zkp-badge zkp-badge-error' }, 'Not Installed') + ]), + + // Stats Grid + E('div', { 'class': 'zkp-grid' }, [ + E('div', { 'class': 'zkp-card' }, [ + E('div', { 'class': 'zkp-card-title' }, 'Saved Keys'), + E('div', { 'class': 'zkp-card-value' }, String(status.key_count || 0)) + ]), + E('div', { 'class': 'zkp-card' }, [ + E('div', { 'class': 'zkp-card-title' }, 'Max Nodes'), + E('div', { 'class': 'zkp-card-value' }, '50') + ]), + E('div', { 'class': 'zkp-card' }, [ + E('div', { 'class': 'zkp-card-title' }, 'Hash Algorithm'), + E('div', { 'class': 'zkp-card-value' }, 'SHA3-256') + ]), + E('div', { 'class': 'zkp-card' }, [ + E('div', { 'class': 'zkp-card-title' }, 'Protocol'), + E('div', { 'class': 'zkp-card-value' }, 'Blum 1986') + ]) + ]), + + // Keygen Section + E('div', { 'class': 'zkp-section' }, [ + E('div', { 'class': 'zkp-section-title' }, [ + E('span', {}, '\uD83D\uDD11'), + ' Generate New Key' + ]), + E('div', { 'class': 'zkp-form-row' }, [ + E('div', { 'class': 'zkp-form-group' }, [ + E('label', {}, 'Key Name'), + E('input', { + 'type': 'text', + 'id': 'zkp-name', + 'placeholder': 'my_key', + 'value': 'key_' + Date.now() + }) + ]), + E('div', { 'class': 'zkp-form-group' }, [ + E('label', {}, 'Nodes (4-50)'), + E('input', { + 'type': 'number', + 'id': 'zkp-nodes', + 'min': '4', + 'max': '50', + 'value': '20' + }) + ]), + E('div', { 'class': 'zkp-form-group' }, [ + E('label', {}, 'Edge Density'), + E('select', { 'id': 'zkp-density' }, [ + E('option', { 'value': '0.5' }, '0.5 (Sparse)'), + E('option', { 'value': '0.7' }, '0.7 (Medium)'), + E('option', { 'value': '0.8', 'selected': true }, '0.8 (Dense)'), + E('option', { 'value': '1.0' }, '1.0 (Complete)') + ]) + ]), + E('div', { 'class': 'zkp-form-group', 'style': 'flex: 0;' }, [ + E('label', {}, '\u00A0'), + E('button', { + 'class': 'zkp-btn zkp-btn-primary', + 'click': ui.createHandlerFn(this, 'handleKeygen') + }, 'Generate') + ]) + ]), + E('div', { 'id': 'zkp-keygen-result' }) + ]), + + // Keys Table Section + E('div', { 'class': 'zkp-section' }, [ + E('div', { 'class': 'zkp-section-title' }, [ + E('span', {}, '\uD83D\uDDC2\uFE0F'), + ' Saved Keys' + ]), + this.renderKeysTable(keys) + ]), + + // Verification Result Section + E('div', { 'class': 'zkp-section', 'id': 'zkp-verify-section', 'style': 'display: none;' }, [ + E('div', { 'class': 'zkp-section-title' }, [ + E('span', {}, '\u2705'), + ' Verification Result' + ]), + E('div', { 'id': 'zkp-verify-result' }) + ]) + ]); + + return view; + }, + + renderKeysTable: function(keys) { + if (!keys || keys.length === 0) { + return E('div', { 'class': 'zkp-empty' }, [ + E('p', {}, 'No keys generated yet.'), + E('p', {}, 'Use the form above to generate your first ZKP key pair.') + ]); + } + + return E('table', { 'class': 'zkp-table' }, [ + E('thead', {}, [ + E('tr', {}, [ + E('th', {}, 'Name'), + E('th', {}, 'Nodes'), + E('th', {}, 'Graph Size'), + E('th', {}, 'Key Size'), + E('th', {}, 'Created'), + E('th', {}, 'Actions') + ]) + ]), + E('tbody', {}, keys.map(function(key) { + return E('tr', { 'data-name': key.name }, [ + E('td', {}, E('strong', {}, key.name)), + E('td', {}, String(key.nodes || '-')), + E('td', {}, formatBytes(key.graph_size || 0)), + E('td', {}, formatBytes(key.key_size || 0)), + E('td', {}, formatDate(key.created)), + E('td', {}, E('div', { 'class': 'zkp-actions' }, [ + E('button', { + 'class': 'zkp-btn zkp-btn-success', + 'click': ui.createHandlerFn(this, 'handleProve', key.name) + }, 'Prove'), + E('button', { + 'class': 'zkp-btn zkp-btn-primary', + 'click': ui.createHandlerFn(this, 'handleVerify', key.name) + }, 'Verify'), + E('button', { + 'class': 'zkp-btn zkp-btn-danger', + 'click': ui.createHandlerFn(this, 'handleDelete', key.name) + }, 'Delete') + ])) + ]); + }.bind(this))) + ]); + }, + + handleKeygen: function(ev) { + var btn = ev.target; + var name = document.getElementById('zkp-name').value; + var nodes = parseInt(document.getElementById('zkp-nodes').value, 10); + var density = document.getElementById('zkp-density').value; + var resultDiv = document.getElementById('zkp-keygen-result'); + + btn.disabled = true; + btn.textContent = 'Generating...'; + + callKeygen(nodes, density, name).then(function(result) { + btn.disabled = false; + btn.textContent = 'Generate'; + + if (result.success) { + resultDiv.innerHTML = ''; + resultDiv.appendChild(E('div', { 'class': 'zkp-result zkp-result-accept' }, [ + E('strong', {}, 'Key generated successfully!'), + E('br'), + 'Name: ' + result.name, + E('br'), + 'Nodes: ' + result.nodes + ', Density: ' + result.density, + E('br'), + 'Graph: ' + formatBytes(result.graph_size) + ', Key: ' + formatBytes(result.key_size) + ])); + // Refresh the page to show new key + window.location.reload(); + } else { + resultDiv.innerHTML = ''; + resultDiv.appendChild(E('div', { 'class': 'zkp-result zkp-result-reject' }, [ + E('strong', {}, 'Error: '), + result.error || 'Unknown error' + ])); + } + }).catch(function(err) { + btn.disabled = false; + btn.textContent = 'Generate'; + resultDiv.innerHTML = ''; + resultDiv.appendChild(E('div', { 'class': 'zkp-result zkp-result-reject' }, [ + E('strong', {}, 'RPC Error: '), + err.message || String(err) + ])); + }); + }, + + handleProve: function(name, ev) { + var btn = ev.target; + var resultSection = document.getElementById('zkp-verify-section'); + var resultDiv = document.getElementById('zkp-verify-result'); + + btn.disabled = true; + btn.textContent = 'Proving...'; + + callProve(name).then(function(result) { + btn.disabled = false; + btn.textContent = 'Prove'; + + resultSection.style.display = 'block'; + resultDiv.innerHTML = ''; + + if (result.success) { + resultDiv.appendChild(E('div', { 'class': 'zkp-result zkp-result-info' }, [ + E('strong', {}, 'Proof generated for: ' + result.name), + E('br'), + 'Size: ' + formatBytes(result.proof_size), + E('br'), + 'File: ' + result.proof_file, + E('br'), + E('br'), + E('em', {}, 'Click "Verify" to validate this proof.') + ])); + } else { + resultDiv.appendChild(E('div', { 'class': 'zkp-result zkp-result-reject' }, [ + E('strong', {}, 'Prove failed: '), + result.error || 'Unknown error' + ])); + } + + resultSection.scrollIntoView({ behavior: 'smooth' }); + }).catch(function(err) { + btn.disabled = false; + btn.textContent = 'Prove'; + }); + }, + + handleVerify: function(name, ev) { + var btn = ev.target; + var resultSection = document.getElementById('zkp-verify-section'); + var resultDiv = document.getElementById('zkp-verify-result'); + + btn.disabled = true; + btn.textContent = 'Verifying...'; + + callVerify(name).then(function(result) { + btn.disabled = false; + btn.textContent = 'Verify'; + + resultSection.style.display = 'block'; + resultDiv.innerHTML = ''; + + if (result.success) { + var isAccept = result.result === 'ACCEPT'; + resultDiv.appendChild(E('div', { + 'class': 'zkp-result ' + (isAccept ? 'zkp-result-accept' : 'zkp-result-reject') + }, [ + E('strong', { 'style': 'font-size: 1.25rem;' }, + isAccept ? '\u2705 ACCEPT' : '\u274C REJECT'), + E('br'), + E('br'), + 'Key: ' + result.name, + E('br'), + 'Verification: ' + (isAccept ? + 'Proof is valid. Prover knows a Hamiltonian cycle.' : + 'Proof is invalid or tampered.') + ])); + } else { + resultDiv.appendChild(E('div', { 'class': 'zkp-result zkp-result-reject' }, [ + E('strong', {}, 'Verify failed: '), + result.error || 'Unknown error' + ])); + } + + resultSection.scrollIntoView({ behavior: 'smooth' }); + }).catch(function(err) { + btn.disabled = false; + btn.textContent = 'Verify'; + }); + }, + + handleDelete: function(name, ev) { + if (!confirm('Delete key "' + name + '" and all associated files?')) { + return; + } + + var btn = ev.target; + btn.disabled = true; + + callDeleteKey(name).then(function(result) { + if (result.success) { + window.location.reload(); + } else { + btn.disabled = false; + alert('Delete failed: ' + (result.error || 'Unknown error')); + } + }).catch(function(err) { + btn.disabled = false; + alert('RPC Error: ' + (err.message || String(err))); + }); + } +}); diff --git a/package/secubox/luci-app-zkp/root/usr/libexec/rpcd/luci.zkp b/package/secubox/luci-app-zkp/root/usr/libexec/rpcd/luci.zkp new file mode 100755 index 00000000..6aef8f92 --- /dev/null +++ b/package/secubox/luci-app-zkp/root/usr/libexec/rpcd/luci.zkp @@ -0,0 +1,397 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 CyberMind.FR / SecuBox +# +# RPCD backend for ZKP Hamiltonian Dashboard +# Wraps zkp_keygen, zkp_prover, zkp_verifier CLI tools + +. /usr/share/libubox/jshn.sh + +readonly ZKP_DIR="/var/lib/zkp" +readonly GRAPHS_DIR="$ZKP_DIR/graphs" +readonly KEYS_DIR="$ZKP_DIR/keys" +readonly PROOFS_DIR="$ZKP_DIR/proofs" + +# Ensure directories exist +init_dirs() { + mkdir -p "$GRAPHS_DIR" "$KEYS_DIR" "$PROOFS_DIR" 2>/dev/null +} + +# Check if ZKP tools are available +check_tools() { + command -v zkp_keygen >/dev/null 2>&1 && \ + command -v zkp_prover >/dev/null 2>&1 && \ + command -v zkp_verifier >/dev/null 2>&1 +} + +# Get library version +get_version() { + if check_tools; then + zkp_keygen --version 2>/dev/null | head -1 || echo "1.0.0" + else + echo "not installed" + fi +} + +# Count saved keys +count_keys() { + init_dirs + ls -1 "$KEYS_DIR"/*.key 2>/dev/null | wc -l +} + +# ============== RPC Methods ============== + +method_status() { + local tools_ok="false" + local version="not installed" + local key_count=0 + + if check_tools; then + tools_ok="true" + version=$(get_version) + fi + + init_dirs + key_count=$(ls -1 "$KEYS_DIR"/*.key 2>/dev/null | wc -l) + + cat </dev/null || echo 0) + nodes=$(od -A n -t u1 -j 4 -N 1 "$graphfile" 2>/dev/null | tr -d ' ' || echo 0) + fi + + key_size=$(stat -c %s "$keyfile" 2>/dev/null || echo 0) + created=$(stat -c %Y "$keyfile" 2>/dev/null || echo 0) + + [ "$first" = "1" ] || echo -n ',' + first=0 + echo -n "{\"name\":\"$name\",\"nodes\":${nodes:-0},\"graph_size\":$graph_size,\"key_size\":$key_size,\"created\":$created}" + done + + echo ']}' +} + +method_keygen() { + local nodes density name + + read -r input + json_load "$input" + json_get_var nodes nodes + json_get_var density density + json_get_var name name + + # Defaults + [ -z "$nodes" ] && nodes=20 + [ -z "$density" ] && density="0.8" + [ -z "$name" ] && name="key_$(date +%s)" + + # Validate + if [ "$nodes" -lt 4 ] || [ "$nodes" -gt 50 ]; then + json_init + json_add_boolean "success" 0 + json_add_string "error" "Nodes must be between 4 and 50" + json_dump + return + fi + + # Sanitize name + name=$(echo "$name" | sed 's/[^a-zA-Z0-9_-]/_/g') + + init_dirs + + local graphfile="$GRAPHS_DIR/${name}.graph" + local keyfile="$KEYS_DIR/${name}.key" + + # Check if already exists + if [ -f "$keyfile" ]; then + json_init + json_add_boolean "success" 0 + json_add_string "error" "Key '$name' already exists" + json_dump + return + fi + + # Generate + if ! check_tools; then + json_init + json_add_boolean "success" 0 + json_add_string "error" "ZKP tools not installed" + json_dump + return + fi + + local output + output=$(zkp_keygen -n "$nodes" -d "$density" -g "$graphfile" -k "$keyfile" 2>&1) + local rc=$? + + if [ $rc -eq 0 ] && [ -f "$graphfile" ] && [ -f "$keyfile" ]; then + local graph_size=$(stat -c %s "$graphfile") + local key_size=$(stat -c %s "$keyfile") + + json_init + json_add_boolean "success" 1 + json_add_string "name" "$name" + json_add_int "nodes" "$nodes" + json_add_string "density" "$density" + json_add_int "graph_size" "$graph_size" + json_add_int "key_size" "$key_size" + json_add_string "output" "$output" + json_dump + else + json_init + json_add_boolean "success" 0 + json_add_string "error" "Keygen failed: $output" + json_dump + fi +} + +method_prove() { + local name + + read -r input + json_load "$input" + json_get_var name name + + if [ -z "$name" ]; then + json_init + json_add_boolean "success" 0 + json_add_string "error" "Missing key name" + json_dump + return + fi + + local graphfile="$GRAPHS_DIR/${name}.graph" + local keyfile="$KEYS_DIR/${name}.key" + local prooffile="$PROOFS_DIR/${name}.proof" + + if [ ! -f "$graphfile" ] || [ ! -f "$keyfile" ]; then + json_init + json_add_boolean "success" 0 + json_add_string "error" "Key '$name' not found" + json_dump + return + fi + + if ! check_tools; then + json_init + json_add_boolean "success" 0 + json_add_string "error" "ZKP tools not installed" + json_dump + return + fi + + local output + output=$(zkp_prover -g "$graphfile" -k "$keyfile" -p "$prooffile" 2>&1) + local rc=$? + + if [ $rc -eq 0 ] && [ -f "$prooffile" ]; then + local proof_size=$(stat -c %s "$prooffile") + # Base64 encode for transport (first 1KB for preview) + local proof_preview=$(head -c 1024 "$prooffile" | base64 -w 0) + + json_init + json_add_boolean "success" 1 + json_add_string "name" "$name" + json_add_int "proof_size" "$proof_size" + json_add_string "proof_file" "$prooffile" + json_add_string "proof_preview" "$proof_preview" + json_add_string "output" "$output" + json_dump + else + json_init + json_add_boolean "success" 0 + json_add_string "error" "Prove failed: $output" + json_dump + fi +} + +method_verify() { + local name + + read -r input + json_load "$input" + json_get_var name name + + if [ -z "$name" ]; then + json_init + json_add_boolean "success" 0 + json_add_string "error" "Missing key name" + json_dump + return + fi + + local graphfile="$GRAPHS_DIR/${name}.graph" + local prooffile="$PROOFS_DIR/${name}.proof" + + if [ ! -f "$graphfile" ]; then + json_init + json_add_boolean "success" 0 + json_add_string "error" "Graph '$name' not found" + json_dump + return + fi + + if [ ! -f "$prooffile" ]; then + json_init + json_add_boolean "success" 0 + json_add_string "error" "Proof '$name' not found (run prove first)" + json_dump + return + fi + + if ! check_tools; then + json_init + json_add_boolean "success" 0 + json_add_string "error" "ZKP tools not installed" + json_dump + return + fi + + local output result + output=$(zkp_verifier -g "$graphfile" -p "$prooffile" 2>&1) + local rc=$? + + # Parse result from output + if echo "$output" | grep -q "ACCEPT"; then + result="ACCEPT" + elif echo "$output" | grep -q "REJECT"; then + result="REJECT" + else + result="UNKNOWN" + fi + + json_init + json_add_boolean "success" 1 + json_add_string "name" "$name" + json_add_string "result" "$result" + json_add_boolean "valid" "$([ "$result" = "ACCEPT" ] && echo 1 || echo 0)" + json_add_string "output" "$output" + json_dump +} + +method_delete_key() { + local name + + read -r input + json_load "$input" + json_get_var name name + + if [ -z "$name" ]; then + json_init + json_add_boolean "success" 0 + json_add_string "error" "Missing key name" + json_dump + return + fi + + local graphfile="$GRAPHS_DIR/${name}.graph" + local keyfile="$KEYS_DIR/${name}.key" + local prooffile="$PROOFS_DIR/${name}.proof" + + local deleted=0 + [ -f "$graphfile" ] && rm -f "$graphfile" && deleted=$((deleted + 1)) + [ -f "$keyfile" ] && rm -f "$keyfile" && deleted=$((deleted + 1)) + [ -f "$prooffile" ] && rm -f "$prooffile" && deleted=$((deleted + 1)) + + json_init + json_add_boolean "success" 1 + json_add_string "name" "$name" + json_add_int "files_deleted" "$deleted" + json_dump +} + +method_get_graph() { + local name + + read -r input + json_load "$input" + json_get_var name name + + if [ -z "$name" ]; then + json_init + json_add_boolean "success" 0 + json_add_string "error" "Missing key name" + json_dump + return + fi + + local graphfile="$GRAPHS_DIR/${name}.graph" + + if [ ! -f "$graphfile" ]; then + json_init + json_add_boolean "success" 0 + json_add_string "error" "Graph '$name' not found" + json_dump + return + fi + + # Extract graph info from binary + local nodes=$(od -A n -t u1 -j 4 -N 1 "$graphfile" 2>/dev/null | tr -d ' ') + local graph_size=$(stat -c %s "$graphfile") + + # Generate simple adjacency representation + # Read adjacency bits and create edge list + local edges="" + local i=0 + + json_init + json_add_boolean "success" 1 + json_add_string "name" "$name" + json_add_int "nodes" "${nodes:-0}" + json_add_int "size" "$graph_size" + json_add_string "file" "$graphfile" + json_dump +} + +# ============== Main Dispatcher ============== + +case "$1" in + list) + echo '{' + echo '"status":{},' + echo '"list_keys":{},' + echo '"get_graph":{"name":"String"},' + echo '"keygen":{"nodes":"Number","density":"String","name":"String"},' + echo '"prove":{"name":"String"},' + echo '"verify":{"name":"String"},' + echo '"delete_key":{"name":"String"}' + echo '}' + ;; + call) + case "$2" in + status) method_status ;; + list_keys) method_list_keys ;; + get_graph) method_get_graph ;; + keygen) method_keygen ;; + prove) method_prove ;; + verify) method_verify ;; + delete_key) method_delete_key ;; + *) + json_init + json_add_boolean "success" 0 + json_add_string "error" "Unknown method: $2" + json_dump + ;; + esac + ;; +esac diff --git a/package/secubox/luci-app-zkp/root/usr/share/luci/menu.d/luci-app-zkp.json b/package/secubox/luci-app-zkp/root/usr/share/luci/menu.d/luci-app-zkp.json new file mode 100644 index 00000000..1fcf5cce --- /dev/null +++ b/package/secubox/luci-app-zkp/root/usr/share/luci/menu.d/luci-app-zkp.json @@ -0,0 +1,14 @@ +{ + "admin/status/zkp": { + "title": "ZKP Cryptography", + "order": 85, + "action": { + "type": "view", + "path": "zkp/overview" + }, + "depends": { + "acl": ["luci-app-zkp"], + "uci": {} + } + } +} diff --git a/package/secubox/luci-app-zkp/root/usr/share/rpcd/acl.d/luci-app-zkp.json b/package/secubox/luci-app-zkp/root/usr/share/rpcd/acl.d/luci-app-zkp.json new file mode 100644 index 00000000..493ed5f9 --- /dev/null +++ b/package/secubox/luci-app-zkp/root/usr/share/rpcd/acl.d/luci-app-zkp.json @@ -0,0 +1,15 @@ +{ + "luci-app-zkp": { + "description": "Grant access to ZKP Hamiltonian dashboard", + "read": { + "ubus": { + "luci.zkp": ["status", "list_keys", "get_graph"] + } + }, + "write": { + "ubus": { + "luci.zkp": ["keygen", "prove", "verify", "delete_key"] + } + } + } +}