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:
CyberMind-FR 2026-02-24 10:31:39 +01:00
parent ec8e96a7fd
commit b60d7fd009
8 changed files with 1099 additions and 1 deletions

View File

@ -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`

View File

@ -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)

View File

@ -416,7 +416,8 @@
"Bash(./zkp_prover:*)",
"Bash(./zkp_verifier:*)",
"Bash(printf:*)",
"Bash(dd:*)"
"Bash(dd:*)",
"Bash(gh release create:*)"
]
}
}

View 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))

View File

@ -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)));
});
}
});

View 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

View File

@ -0,0 +1,14 @@
{
"admin/status/zkp": {
"title": "ZKP Cryptography",
"order": 85,
"action": {
"type": "view",
"path": "zkp/overview"
},
"depends": {
"acl": ["luci-app-zkp"],
"uci": {}
}
}
}

View File

@ -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"]
}
}
}
}