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 <noreply@anthropic.com>
This commit is contained in:
parent
ec8e96a7fd
commit
b60d7fd009
@ -3231,3 +3231,11 @@ git checkout HEAD -- index.html
|
|||||||
- `zkp-hamiltonian/tests/{test_crypto,test_graph,test_protocol,test_serialize}.c`
|
- `zkp-hamiltonian/tests/{test_crypto,test_graph,test_protocol,test_serialize}.c`
|
||||||
- `zkp-hamiltonian/CMakeLists.txt`
|
- `zkp-hamiltonian/CMakeLists.txt`
|
||||||
- **Commit:** `65539368 feat(zkp-hamiltonian): Add Zero-Knowledge Proof library based on Hamiltonian Cycle`
|
- **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`
|
||||||
|
|||||||
@ -866,6 +866,12 @@ _Last updated: 2026-02-24 (Service Stability Fixes)_
|
|||||||
|
|
||||||
### Just Completed (2026-02-24)
|
### 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)
|
- **ZKP Hamiltonian Library** — DONE (2026-02-24)
|
||||||
- Zero-Knowledge Proof implementation based on Hamiltonian Cycle (Blum 1986)
|
- Zero-Knowledge Proof implementation based on Hamiltonian Cycle (Blum 1986)
|
||||||
- NIZK via Fiat-Shamir heuristic, SHA3-256 commitments (OpenSSL)
|
- NIZK via Fiat-Shamir heuristic, SHA3-256 commitments (OpenSSL)
|
||||||
|
|||||||
@ -416,7 +416,8 @@
|
|||||||
"Bash(./zkp_prover:*)",
|
"Bash(./zkp_prover:*)",
|
||||||
"Bash(./zkp_verifier:*)",
|
"Bash(./zkp_verifier:*)",
|
||||||
"Bash(printf:*)",
|
"Bash(printf:*)",
|
||||||
"Bash(dd:*)"
|
"Bash(dd:*)",
|
||||||
|
"Bash(gh release create:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
package/secubox/luci-app-zkp/Makefile
Normal file
20
package/secubox/luci-app-zkp/Makefile
Normal file
@ -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 <contact@secubox.fr>
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
# call BuildPackage - OpenWrt buildance
|
||||||
|
$(eval $(call BuildPackage,luci-app-zkp))
|
||||||
@ -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)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
397
package/secubox/luci-app-zkp/root/usr/libexec/rpcd/luci.zkp
Executable file
397
package/secubox/luci-app-zkp/root/usr/libexec/rpcd/luci.zkp
Executable file
@ -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 <<EOF
|
||||||
|
{"tools_available":$tools_ok,"version":"$version","key_count":$key_count,"graphs_dir":"$GRAPHS_DIR","keys_dir":"$KEYS_DIR","proofs_dir":"$PROOFS_DIR"}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
method_list_keys() {
|
||||||
|
init_dirs
|
||||||
|
|
||||||
|
local first=1
|
||||||
|
echo -n '{"keys":['
|
||||||
|
|
||||||
|
for keyfile in "$KEYS_DIR"/*.key; do
|
||||||
|
[ -f "$keyfile" ] || continue
|
||||||
|
|
||||||
|
local name=$(basename "$keyfile" .key)
|
||||||
|
local graphfile="$GRAPHS_DIR/${name}.graph"
|
||||||
|
local nodes=0
|
||||||
|
local graph_size=0
|
||||||
|
local key_size=0
|
||||||
|
local created=0
|
||||||
|
|
||||||
|
if [ -f "$graphfile" ]; then
|
||||||
|
graph_size=$(stat -c %s "$graphfile" 2>/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
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"admin/status/zkp": {
|
||||||
|
"title": "ZKP Cryptography",
|
||||||
|
"order": 85,
|
||||||
|
"action": {
|
||||||
|
"type": "view",
|
||||||
|
"path": "zkp/overview"
|
||||||
|
},
|
||||||
|
"depends": {
|
||||||
|
"acl": ["luci-app-zkp"],
|
||||||
|
"uci": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user