21 KiB
Edge-Net QDAG Credit System Architecture
Table of Contents
- Overview
- Architecture Components
- Credit Flow
- Security Model
- Multi-Device Synchronization
- API Reference
- Data Models
- Implementation Details
Overview
The Edge-Net QDAG (Quantum Directed Acyclic Graph) credit system is a secure, distributed ledger for tracking computational contributions across the Edge-Net network. Credits (denominated in rUv) are earned by processing tasks and stored in a Firestore-backed persistent ledger that serves as the single source of truth.
Key Principles
- Identity-Based Ledger: Credits are tied to Ed25519 public keys, not device IDs
- Relay Authority: Only the relay server can credit accounts via verified task completions
- No Self-Reporting: Clients cannot increase their own credit balances
- Multi-Device Sync: Same public key = same balance across all devices
- Firestore Truth: The QDAG ledger in Firestore is the authoritative state
Architecture Components
┌─────────────────────────────────────────────────────────────────┐
│ Edge-Net QDAG System │
└─────────────────────────────────────────────────────────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Dashboard │◄───────►│ Relay │◄───────►│ Firestore │
│ (Client) │ WSS │ Server │ QDAG │ Database │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
│ WASM Edge-Net │ Task Assignment │ Ledger
│ Local Compute │ Credit Verification │ Storage
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ PiKey ID │ │ Assigned │ │ Credit Ledger│
│ (Ed25519) │ │ Tasks Map │ │ (by pubkey) │
└──────────────┘ └──────────────┘ └──────────────┘
Components
1. Dashboard (Client)
- Location:
/examples/edge-net/dashboard/ - Role: Browser-based UI for user interaction
- Technology: React + TypeScript + WASM
- Responsibilities:
- Generate Ed25519 identity (PiKey) via WASM
- Connect to relay server via WebSocket
- Process assigned computational tasks
- Display credit balance from QDAG
- Store local cache in IndexedDB (backup only)
2. Relay Server
- Location:
/examples/edge-net/relay/index.js - Role: Central coordination and credit authority
- Technology: Node.js + WebSocket + Firestore
- Responsibilities:
- Track task assignments (prevent spoofing)
- Verify task completions
- Credit accounts in Firestore QDAG
- Synchronize balances across devices
- Enforce rate limits and security
3. Firestore QDAG
- Collection:
edge-net-qdag - Document Key: Ed25519 public key (hex string)
- Role: Persistent, authoritative credit ledger
- Technology: Google Cloud Firestore
- Responsibilities:
- Store credit balances (earned, spent)
- Track task completion count
- Enable multi-device sync
- Provide audit trail
4. CLI (Optional)
- Location:
/examples/edge-net/cli/ - Role: Command-line interface for headless nodes
- Technology: Node.js + WASM
- Responsibilities:
- Same as dashboard, but CLI-based
- Uses same PiKey identity system
- Syncs to same QDAG ledger
Credit Flow
How Credits Are Earned
1. Task Submission
User A submits task → Relay adds to queue → Assigns to User B
2. Task Assignment (SECURITY CHECKPOINT)
Relay tracks: {
taskId → assignedTo: User B's nodeId,
assignedToPublicKey: User B's Ed25519 key,
submitter: User A's nodeId,
maxCredits: 1000000 (1 rUv)
}
3. Task Processing
User B's WASM node processes task → Completes task
4. Task Completion (SECURITY VERIFICATION)
User B sends: { type: 'task_complete', taskId }
Relay verifies:
✓ Task exists in assignedTasks map
✓ Task was assigned to User B (prevent spoofing)
✓ Task not already completed (prevent replay)
✓ User B has valid public key for crediting
5. Credit Award (QDAG UPDATE)
Relay calls: creditAccount(publicKey, amount, taskId)
Firestore update:
- ledger.earned += 1.0 rUv
- ledger.tasksCompleted += 1
- ledger.lastTaskId = taskId
- ledger.updatedAt = Date.now()
6. Balance Notification
Relay → User B: {
type: 'credit_earned',
amount: '1000000000' (nanoRuv),
balance: { earned, spent, available }
}
7. Client Update
Dashboard updates UI with new balance from QDAG
Credit Storage Format
Firestore Document (edge-net-qdag/{publicKey}):
{
"earned": 42.5, // Total rUv earned (float)
"spent": 10.0, // Total rUv spent (float)
"tasksCompleted": 123, // Number of tasks
"lastTaskId": "task-...",
"createdAt": 1704067200000,
"updatedAt": 1704153600000
}
Client Representation (nanoRuv):
{
earned: "42500000000", // 42.5 rUv in nanoRuv
spent: "10000000000", // 10.0 rUv in nanoRuv
available: "32500000000" // earned - spent
}
Conversion: 1 rUv = 1,000,000,000 nanoRuv (1e9)
Security Model
What Prevents Cheating?
1. Task Assignment Tracking
// Relay tracks assignments BEFORE tasks are sent
const assignedTasks = new Map(); // taskId → assignment details
// On task assignment:
assignedTasks.set(task.id, {
assignedTo: targetNodeId,
assignedToPublicKey: targetWs.publicKey,
submitter: task.submitter,
maxCredits: task.maxCredits,
assignedAt: Date.now(),
});
// On task completion - verify assignment:
if (assignment.assignedTo !== nodeId) {
console.warn('[SECURITY] SPOOFING ATTEMPT');
return; // Reject
}
Protection: Prevents nodes from claiming credit for tasks they didn't receive.
2. Double Completion Prevention
const completedTasks = new Set(); // Track completed task IDs
if (completedTasks.has(taskId)) {
console.warn('[SECURITY] REPLAY ATTEMPT');
return; // Reject
}
completedTasks.add(taskId); // Mark as completed BEFORE crediting
Protection: Prevents replay attacks where the same completion is submitted multiple times.
3. Client Cannot Self-Report Credits
case 'ledger_update':
// DEPRECATED: Clients cannot increase their own balance
console.warn('[SECURITY] Rejected ledger_update from client');
ws.send({ type: 'error', message: 'Credit self-reporting disabled' });
break;
Protection: Only the relay can call creditAccount() in Firestore.
4. Public Key Verification
// Credits require valid public key
if (!processorPublicKey) {
ws.send({ type: 'error', message: 'Public key required for credit' });
return;
}
// Credit is tied to public key, not node ID
await creditAccount(processorPublicKey, rewardRuv, taskId);
Protection: Credits tied to cryptographic identity, not ephemeral node IDs.
5. Task Expiration
setInterval(() => {
const TASK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
for (const [taskId, task] of assignedTasks) {
if (Date.now() - task.assignedAt > TASK_TIMEOUT) {
assignedTasks.delete(taskId);
}
}
}, 60000);
Protection: Prevents indefinite task hoarding or delayed completion attacks.
6. Rate Limiting
const RATE_LIMIT_WINDOW = 60000; // 1 minute
const RATE_LIMIT_MAX = 100; // max messages per window
function checkRateLimit(nodeId) {
// Track message count per node
// Reject if exceeded
}
Protection: Prevents spam and rapid task completion abuse.
7. Origin Validation
const ALLOWED_ORIGINS = new Set([
'http://localhost:3000',
'https://edge-net.ruv.io',
// ...
]);
if (!isOriginAllowed(origin)) {
ws.close(4001, 'Unauthorized origin');
}
Protection: Prevents unauthorized clients from connecting.
8. Firestore as Single Source of Truth
// Load from Firestore
const ledger = await loadLedger(publicKey);
// Cache locally but Firestore is authoritative
// Save to Firestore
await ledgerCollection.doc(publicKey).set(ledger, { merge: true });
Protection: Clients cannot manipulate balances; Firestore is immutable to clients.
Multi-Device Synchronization
Same Identity = Same Balance Everywhere
Identity Generation (PiKey)
Dashboard (identityStore.ts):
// Generate Ed25519 key pair via WASM
const piKey = await edgeNetService.generateIdentity();
const identity = {
publicKey: bytesToHex(piKey.getPublicKey()), // hex string
shortId: piKey.getShortId(), // abbreviated ID
identityHex: piKey.getIdentityHex(), // full hex
hasPiMagic: piKey.verifyPiMagic(), // WASM validation
};
CLI (same WASM module):
const piKey = edgeNet.PiKey.generate();
const publicKey = Buffer.from(piKey.getPublicKey()).toString('hex');
Key Point: Both use the same WASM PiKey module → same Ed25519 keys.
Ledger Synchronization Flow
1. Device A connects to relay
→ Sends: { type: 'register', publicKey: '0x123abc...' }
→ Relay stores: ws.publicKey = '0x123abc...'
2. Device A requests balance
→ Sends: { type: 'ledger_sync', publicKey: '0x123abc...' }
3. Relay loads from QDAG
→ Firestore.get('edge-net-qdag/0x123abc...')
→ Returns: { earned: 42.5, spent: 10.0 }
4. Device A receives authoritative balance
→ { type: 'ledger_sync_response', ledger: { earned, spent } }
→ Updates local UI
5. Device A completes task
→ Relay credits: creditAccount('0x123abc...', 1.0)
→ Firestore updates: earned = 43.5
6. Device B connects with SAME publicKey
→ Sends: { type: 'ledger_sync', publicKey: '0x123abc...' }
→ Receives: { earned: 43.5, spent: 10.0 }
→ Same balance as Device A ✓
Backup and Recovery
Export Identity (Dashboard):
// Create encrypted backup with Argon2id
const backup = currentPiKey.createEncryptedBackup(password);
const backupHex = bytesToHex(backup); // Store securely
Import on New Device:
// Restore from encrypted backup
const seed = hexToBytes(backupHex);
const piKey = await edgeNetService.generateIdentity(seed);
// → Same public key → Same QDAG balance
API Reference
WebSocket Message Types
Client → Relay
register
Register a new node with the relay.
{
type: 'register',
nodeId: string, // Session node ID
publicKey?: string, // Ed25519 public key (hex) for QDAG
capabilities: string[], // ['compute', 'storage']
version: string // Client version
}
Response: welcome message
ledger_sync
Request current balance from QDAG.
{
type: 'ledger_sync',
publicKey: string, // Ed25519 public key (hex)
nodeId: string
}
Response: ledger_sync_response
task_submit
Submit a new task to the network.
{
type: 'task_submit',
task: {
taskType: string, // 'compute' | 'inference' | 'storage'
payload: number[], // Task data as byte array
maxCredits: string // Max reward in nanoRuv
}
}
Response: task_accepted with taskId
task_complete
Report task completion (triggers credit award).
{
type: 'task_complete',
taskId: string,
result: unknown, // Task output
reward?: string // Requested reward (capped by maxCredits)
}
Response: credit_earned (if verified)
heartbeat
Keep connection alive.
{
type: 'heartbeat'
}
Response: heartbeat_ack
Relay → Client
welcome
Initial connection confirmation.
{
type: 'welcome',
nodeId: string,
networkState: {
genesisTime: number,
totalNodes: number,
activeNodes: number,
totalTasks: number,
totalRuvDistributed: string, // bigint as string
timeCrystalPhase: number
},
peers: string[] // Connected peer node IDs
}
ledger_sync_response
Authoritative balance from QDAG.
{
type: 'ledger_sync_response',
ledger: {
publicKey: string,
nodeId: string,
earned: string, // nanoRuv
spent: string, // nanoRuv
available: string, // earned - spent
tasksCompleted: number,
lastUpdated: number, // timestamp
signature: string // 'qdag-verified'
}
}
task_assignment
Assigned task to process.
{
type: 'task_assignment',
task: {
id: string,
submitter: string,
taskType: string,
payload: number[], // Task data
maxCredits: string, // Max reward in nanoRuv
submittedAt: number
}
}
credit_earned
Credit awarded after task completion.
{
type: 'credit_earned',
amount: string, // nanoRuv earned
taskId: string,
balance: {
earned: string, // Total earned (nanoRuv)
spent: string, // Total spent (nanoRuv)
available: string // Available (nanoRuv)
}
}
time_crystal_sync
Network-wide time synchronization.
{
type: 'time_crystal_sync',
phase: number, // 0-1 phase value
timestamp: number, // Unix timestamp
activeNodes: number
}
node_joined / node_left
Peer connectivity events.
{
type: 'node_joined' | 'node_left',
nodeId: string,
totalNodes: number
}
error
Error response.
{
type: 'error',
message: string
}
HTTP Endpoints
GET /health
Health check endpoint.
Response:
{
"status": "healthy",
"nodes": 42,
"uptime": 3600000
}
GET /stats
Network statistics.
Response:
{
"genesisTime": 1704067200000,
"totalNodes": 150,
"activeNodes": 142,
"totalTasks": 9876,
"totalRuvDistributed": "1234567890",
"timeCrystalPhase": 0.618,
"connectedNodes": ["node-1", "node-2", ...]
}
Data Models
Firestore Schema
Collection: edge-net-qdag
Document ID: Ed25519 public key (hex string)
{
earned: number, // Total rUv earned (float)
spent: number, // Total rUv spent (float)
tasksCompleted: number, // Count of completed tasks
lastTaskId?: string, // Most recent task ID
createdAt: number, // First entry timestamp
updatedAt: number // Last update timestamp
}
Example:
{
"earned": 127.3,
"spent": 25.0,
"tasksCompleted": 456,
"lastTaskId": "task-1704153600000-abc123",
"createdAt": 1704067200000,
"updatedAt": 1704153600000
}
Client State
networkStore.ts - Credit Balance
interface CreditBalance {
available: number, // earned - spent (rUv)
pending: number, // Credits not yet confirmed
earned: number, // Total earned (rUv)
spent: number // Total spent (rUv)
}
Updated by:
onCreditEarned: Increment earned when task completesonLedgerSync: Replace with QDAG authoritative values
identityStore.ts - PiKey Identity
interface PeerIdentity {
id: string, // Libp2p-style peer ID
publicKey: string, // Ed25519 public key (hex)
publicKeyBytes?: Uint8Array,
displayName: string,
createdAt: Date,
shortId: string, // Abbreviated ID
identityHex: string, // Full identity hex
hasPiMagic: boolean // WASM PiKey validation
}
IndexedDB Schema
Store: edge-net-store
Purpose: Local cache (NOT source of truth)
{
id: 'primary',
nodeId: string,
creditsEarned: number, // Cache from QDAG
creditsSpent: number, // Cache from QDAG
tasksCompleted: number,
tasksSubmitted: number,
totalUptime: number,
lastActiveTimestamp: number,
consentGiven: boolean,
consentTimestamp: number | null,
cpuLimit: number,
gpuEnabled: boolean,
gpuLimit: number,
respectBattery: boolean,
onlyWhenIdle: boolean
}
Note: IndexedDB is a backup only. QDAG is the source of truth.
Implementation Details
Credit Award Flow (Relay)
// /examples/edge-net/relay/index.js
case 'task_complete': {
const taskId = message.taskId;
// 1. Verify task assignment
const assignment = assignedTasks.get(taskId);
if (!assignment || assignment.assignedTo !== nodeId) {
return; // Reject spoofing attempt
}
// 2. Check double completion
if (completedTasks.has(taskId)) {
return; // Reject replay attack
}
// 3. Get processor's public key
const publicKey = assignment.assignedToPublicKey || ws.publicKey;
if (!publicKey) {
return; // Reject - no identity
}
// 4. Mark as completed (prevent race conditions)
completedTasks.add(taskId);
assignedTasks.delete(taskId);
// 5. Credit the account in QDAG
const rewardRuv = Number(message.reward || assignment.maxCredits) / 1e9;
const updatedLedger = await creditAccount(publicKey, rewardRuv, taskId);
// 6. Notify client
ws.send({
type: 'credit_earned',
amount: (rewardRuv * 1e9).toString(),
balance: {
earned: (updatedLedger.earned * 1e9).toString(),
spent: (updatedLedger.spent * 1e9).toString(),
available: ((updatedLedger.earned - updatedLedger.spent) * 1e9).toString(),
},
});
}
Ledger Sync Flow (Dashboard)
// /examples/edge-net/dashboard/src/stores/networkStore.ts
connectToRelay: async () => {
// 1. Get identity public key
const identityState = useIdentityStore.getState();
const publicKey = identityState.identity?.publicKey;
// 2. Connect to relay with public key
const connected = await relayClient.connect(nodeId, publicKey);
// 3. Request QDAG balance after connection
if (connected && publicKey) {
setTimeout(() => {
relayClient.requestLedgerSync(publicKey);
}, 500);
}
},
// 4. Handle QDAG response (authoritative)
onLedgerSync: (ledger) => {
const earnedRuv = Number(ledger.earned) / 1e9;
const spentRuv = Number(ledger.spent) / 1e9;
// Replace local state with QDAG values
set({
credits: {
earned: earnedRuv,
spent: spentRuv,
available: earnedRuv - spentRuv,
pending: 0,
},
});
// Save to IndexedDB as backup
get().saveToIndexedDB();
},
Task Processing Flow (Dashboard)
// /examples/edge-net/dashboard/src/stores/networkStore.ts
processAssignedTask: async (task) => {
// 1. Process task using WASM
const result = await edgeNetService.submitTask(
task.taskType,
task.payload,
task.maxCredits
);
await edgeNetService.processNextTask();
// 2. Report completion to relay
const reward = task.maxCredits / BigInt(2); // Earn half the max
relayClient.completeTask(task.id, task.submitter, result, reward);
// 3. Relay verifies and credits QDAG
// 4. Client receives credit_earned message
// 5. Balance updates automatically
},
Summary
The Edge-Net QDAG credit system provides a secure, distributed ledger for tracking computational contributions:
✅ Identity-Based: Credits tied to Ed25519 public keys, not devices ✅ Relay Authority: Only relay can credit accounts via verified tasks ✅ Multi-Device Sync: Same key = same balance everywhere ✅ Firestore Truth: QDAG in Firestore is the authoritative state ✅ Security: Prevents spoofing, replay, self-reporting, and double-completion ✅ IndexedDB Cache: Local backup, but QDAG is source of truth
Key Insight: The relay server acts as a trusted coordinator that verifies task completions before updating the QDAG ledger in Firestore. Clients cannot manipulate their balances; they can only earn credits by processing assigned tasks.