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/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`
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -416,7 +416,8 @@
|
||||
"Bash(./zkp_prover:*)",
|
||||
"Bash(./zkp_verifier:*)",
|
||||
"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