.
This commit is contained in:
@@ -898,6 +898,14 @@
|
||||
if (message[0] === 'EVENT' && message[1] === subscriptionId) {
|
||||
const nostrEvent = message[2];
|
||||
addLogEntry('success', `Received EVENT from ${relayUrl}: ${nostrEvent.id.substring(0, 16)}...`);
|
||||
|
||||
// Truncate content for readability
|
||||
const truncatedEvent = { ...nostrEvent };
|
||||
if (truncatedEvent.content && truncatedEvent.content.length > 10) {
|
||||
const content = truncatedEvent.content;
|
||||
truncatedEvent.content = content.substring(0, 5) + '...' + content.substring(content.length - 5);
|
||||
}
|
||||
addLogEntry('info', `Full received event:\n${JSON.stringify(truncatedEvent, null, 2)}`);
|
||||
handleIncomingEvent(nostrEvent);
|
||||
} else if (message[0] === 'EOSE' && message[1] === subscriptionId) {
|
||||
addLogEntry('info', `End of stored events from ${relayUrl}`);
|
||||
@@ -928,18 +936,59 @@
|
||||
|
||||
try {
|
||||
// Decrypt the event payload
|
||||
const decryptedPayload = await decryptRoutingEvent(event);
|
||||
let decryptedPayload = await decryptRoutingEvent(event);
|
||||
|
||||
if (!decryptedPayload) {
|
||||
addLogEntry('error', `Failed to decrypt event ${event.id.substring(0,16)}...`);
|
||||
return;
|
||||
}
|
||||
|
||||
addLogEntry('success', `Successfully decrypted routing event ${event.id.substring(0,16)}...`);
|
||||
addLogEntry('success', `First decryption successful for event ${event.id.substring(0,16)}...`);
|
||||
|
||||
// Parse routing instructions
|
||||
// Check payload type according to corrected DAEMON.md protocol
|
||||
if (decryptedPayload.padding !== undefined) {
|
||||
addLogEntry('info', `Detected Type 2 (Padding Payload) - discarding padding and performing second decryption`);
|
||||
|
||||
// This is a padding layer from previous daemon - discard padding and decrypt again
|
||||
const innerEvent = decryptedPayload.event;
|
||||
addLogEntry('info', `Discarding padding: "${decryptedPayload.padding}"`);
|
||||
|
||||
// Second decryption to get the actual routing instructions that were encrypted for me
|
||||
decryptedPayload = await decryptRoutingEvent(innerEvent);
|
||||
|
||||
if (!decryptedPayload) {
|
||||
addLogEntry('error', `Failed to decrypt inner event ${event.id.substring(0,16)}...`);
|
||||
return;
|
||||
}
|
||||
|
||||
addLogEntry('success', `Second decryption successful - found my original routing instructions from builder`);
|
||||
} else {
|
||||
addLogEntry('info', `Detected Type 1 (Routing Payload) - processing routing instructions directly`);
|
||||
}
|
||||
|
||||
// Log the complete decrypted payload with truncated content
|
||||
const truncatedPayload = { ...decryptedPayload };
|
||||
if (truncatedPayload.event && truncatedPayload.event.content && truncatedPayload.event.content.length > 10) {
|
||||
const content = truncatedPayload.event.content;
|
||||
truncatedPayload.event.content = content.substring(0, 5) + '...' + content.substring(content.length - 5);
|
||||
}
|
||||
addLogEntry('info', `Final routing payload:\n${JSON.stringify(truncatedPayload, null, 2)}`);
|
||||
|
||||
// Parse routing instructions (these are from the builder, specific to this daemon)
|
||||
const { event: wrappedEvent, routing } = decryptedPayload;
|
||||
|
||||
if (!routing) {
|
||||
addLogEntry('error', `No routing instructions found in final payload for ${event.id.substring(0,16)}...`);
|
||||
return;
|
||||
}
|
||||
|
||||
// DEBUG: Log routing decision
|
||||
addLogEntry('info', `DEBUG routing.p = "${routing.p}" (type: ${typeof routing.p})`);
|
||||
if (routing.add_padding_bytes) {
|
||||
addLogEntry('info', `DEBUG add_padding_bytes = ${routing.add_padding_bytes}`);
|
||||
}
|
||||
addLogEntry('info', `DEBUG Will ${routing.p ? 'FORWARD to next hop' : 'POST FINAL EVENT'}`);
|
||||
|
||||
if (!validateRoutingInstructions(routing)) {
|
||||
addLogEntry('error', `Invalid routing instructions in event ${event.id.substring(0,16)}...`);
|
||||
return;
|
||||
@@ -990,7 +1039,7 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process a queued event
|
||||
// Process a queued event according to corrected DAEMON.md protocol
|
||||
async function processQueuedEvent(queueItem) {
|
||||
if (!daemonRunning) return;
|
||||
|
||||
@@ -1001,19 +1050,19 @@
|
||||
try {
|
||||
const { wrappedEvent, routing } = queueItem;
|
||||
|
||||
// Apply padding if specified
|
||||
let eventToForward = { ...wrappedEvent };
|
||||
if (routing.padding) {
|
||||
eventToForward = applyPadding(eventToForward, routing.padding);
|
||||
}
|
||||
|
||||
// Check if this is final posting or continued routing
|
||||
addLogEntry('info', `DEBUG Decision point - routing.p = "${routing.p}" (${typeof routing.p})`);
|
||||
if (routing.add_padding_bytes) {
|
||||
addLogEntry('info', `DEBUG add_padding_bytes = ${routing.add_padding_bytes} (will add padding when forwarding)`);
|
||||
}
|
||||
addLogEntry('info', `DEBUG Will ${routing.p ? 'FORWARD with padding wrapper' : 'POST FINAL EVENT'}`);
|
||||
|
||||
if (routing.p) {
|
||||
// Continue routing to next Superball
|
||||
await forwardToNextSuperball(eventToForward, routing);
|
||||
// Continue routing to next Superball with padding-only wrapper
|
||||
await forwardToNextSuperball(wrappedEvent, routing);
|
||||
} else {
|
||||
// Final posting - post original event directly
|
||||
await postFinalEvent(eventToForward, routing.relays);
|
||||
// Final posting - post the wrapped event directly
|
||||
await postFinalEvent(wrappedEvent, routing.relays);
|
||||
}
|
||||
|
||||
processedEvents++;
|
||||
@@ -1037,43 +1086,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Apply padding to event
|
||||
// Legacy padding function - no longer used in corrected protocol
|
||||
// Padding is now handled during forwarding with add_padding_bytes
|
||||
function applyPadding(event, paddingInstruction) {
|
||||
if (!paddingInstruction || typeof paddingInstruction !== 'string') return event;
|
||||
|
||||
const match = paddingInstruction.match(/^([+-])(\d+)$/);
|
||||
if (!match) return event;
|
||||
|
||||
const [, operation, amount] = match;
|
||||
const padding = '1'.repeat(parseInt(amount));
|
||||
|
||||
const modifiedEvent = { ...event };
|
||||
|
||||
if (operation === '+') {
|
||||
// Add padding
|
||||
if (!modifiedEvent.tags.find(tag => tag[0] === 'padding')) {
|
||||
modifiedEvent.tags.push(['padding', padding]);
|
||||
}
|
||||
addLogEntry('info', `Added ${amount} bytes of padding`);
|
||||
} else if (operation === '-') {
|
||||
// Remove padding
|
||||
const paddingTagIndex = modifiedEvent.tags.findIndex(tag => tag[0] === 'padding');
|
||||
if (paddingTagIndex !== -1) {
|
||||
const currentPadding = modifiedEvent.tags[paddingTagIndex][1] || '';
|
||||
const newPadding = currentPadding.substring(parseInt(amount));
|
||||
if (newPadding.length > 0) {
|
||||
modifiedEvent.tags[paddingTagIndex][1] = newPadding;
|
||||
} else {
|
||||
modifiedEvent.tags.splice(paddingTagIndex, 1);
|
||||
}
|
||||
}
|
||||
addLogEntry('info', `Removed ${amount} bytes of padding`);
|
||||
}
|
||||
|
||||
return modifiedEvent;
|
||||
// This function is deprecated in the corrected protocol
|
||||
// Padding is now generated during forwarding based on routing.add_padding_bytes
|
||||
addLogEntry('info', 'Legacy padding function called - no action taken (using new protocol)');
|
||||
return event;
|
||||
}
|
||||
|
||||
// Forward event to next Superball (Always Rewrap)
|
||||
// Forward event to next Superball with padding-only wrapper (DAEMON.md corrected protocol)
|
||||
async function forwardToNextSuperball(event, routing) {
|
||||
addLogEntry('info', `Forwarding to next Superball: ${routing.p.substring(0,16)}...`);
|
||||
|
||||
@@ -1081,54 +1103,84 @@
|
||||
const ephemeralKey = window.NostrTools.generateSecretKey();
|
||||
const ephemeralPubkey = window.NostrTools.getPublicKey(ephemeralKey);
|
||||
|
||||
// Create new encrypted payload
|
||||
const payload = {
|
||||
event: event,
|
||||
routing: {
|
||||
relays: routing.relays,
|
||||
delay: routing.delay,
|
||||
padding: routing.padding,
|
||||
p: routing.p,
|
||||
audit: routing.audit,
|
||||
payment: routing.payment
|
||||
// Generate padding based on add_padding_bytes instruction
|
||||
let paddingData = '';
|
||||
if (routing.add_padding_bytes && routing.add_padding_bytes > 0) {
|
||||
// Create random padding of specified length
|
||||
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
for (let i = 0; i < routing.add_padding_bytes; i++) {
|
||||
paddingData += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
addLogEntry('info', `Generated ${paddingData.length} bytes of padding`);
|
||||
}
|
||||
|
||||
// Create padding-only payload (NEVER create routing instructions - only builder does that)
|
||||
const paddingPayload = {
|
||||
event: event, // This is the still-encrypted inner event
|
||||
padding: paddingData // Padding to discard
|
||||
};
|
||||
|
||||
// Encrypt to next Superball
|
||||
addLogEntry('info', `DEBUG Creating padding payload with ${paddingData.length} bytes of padding`);
|
||||
|
||||
// Log the complete padding payload with truncated content before encryption
|
||||
const truncatedPaddingPayload = { ...paddingPayload };
|
||||
if (truncatedPaddingPayload.event && truncatedPaddingPayload.event.content && truncatedPaddingPayload.event.content.length > 10) {
|
||||
const content = truncatedPaddingPayload.event.content;
|
||||
truncatedPaddingPayload.event.content = content.substring(0, 5) + '...' + content.substring(content.length - 5);
|
||||
}
|
||||
if (truncatedPaddingPayload.padding && truncatedPaddingPayload.padding.length > 10) {
|
||||
const padding = truncatedPaddingPayload.padding;
|
||||
truncatedPaddingPayload.padding = padding.substring(0, 5) + '...' + padding.substring(padding.length - 5);
|
||||
}
|
||||
addLogEntry('info', `Padding payload to encrypt:\n${JSON.stringify(truncatedPaddingPayload, null, 2)}`);
|
||||
|
||||
// Encrypt padding payload to next Superball
|
||||
let ephemeralKeyHex;
|
||||
if (window.NostrTools.utils && window.NostrTools.utils.bytesToHex) {
|
||||
ephemeralKeyHex = window.NostrTools.utils.bytesToHex(ephemeralKey);
|
||||
} else if (window.NostrTools.bytesToHex) {
|
||||
ephemeralKeyHex = window.NostrTools.bytesToHex(ephemeralKey);
|
||||
} else {
|
||||
// Fallback: convert Uint8Array to hex manually
|
||||
ephemeralKeyHex = Array.from(ephemeralKey).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
const conversationKey = window.NostrTools.nip44.v2.utils.getConversationKey(
|
||||
window.NostrTools.bytesToHex(ephemeralKey),
|
||||
ephemeralKeyHex,
|
||||
routing.p
|
||||
);
|
||||
|
||||
const encryptedContent = window.NostrTools.nip44.v2.encrypt(
|
||||
JSON.stringify(payload),
|
||||
JSON.stringify(paddingPayload),
|
||||
conversationKey
|
||||
);
|
||||
|
||||
// Create new routing event
|
||||
// Create new routing event (forwarding to next hop)
|
||||
const routingEvent = {
|
||||
kind: 22222,
|
||||
content: encryptedContent,
|
||||
tags: [
|
||||
['p', routing.p], // Next Superball
|
||||
['p', routing.audit] // Audit tag (looks like pubkey)
|
||||
['p', routing.audit] // Audit tag (camouflage)
|
||||
],
|
||||
created_at: Math.floor(Date.now() / 1000)
|
||||
};
|
||||
|
||||
// Add padding tag if present
|
||||
const paddingTag = event.tags.find(tag => tag[0] === 'padding');
|
||||
if (paddingTag) {
|
||||
routingEvent.tags.push(['padding', paddingTag[1]]);
|
||||
}
|
||||
|
||||
// Sign with ephemeral key
|
||||
const signedEvent = window.NostrTools.finalizeEvent(routingEvent, ephemeralKey);
|
||||
|
||||
// Log the complete rewrapped event with truncated content before publishing
|
||||
const truncatedSignedEvent = { ...signedEvent };
|
||||
if (truncatedSignedEvent.content && truncatedSignedEvent.content.length > 10) {
|
||||
const content = truncatedSignedEvent.content;
|
||||
truncatedSignedEvent.content = content.substring(0, 5) + '...' + content.substring(content.length - 5);
|
||||
}
|
||||
addLogEntry('info', `Padding-wrapped event to publish:\n${JSON.stringify(truncatedSignedEvent, null, 2)}`);
|
||||
|
||||
// Publish to specified relays
|
||||
await publishToRelays(signedEvent, routing.relays);
|
||||
|
||||
addLogEntry('success', `Forwarded event with audit tag ${routing.audit.substring(0,16)}...`);
|
||||
addLogEntry('success', `Forwarded with padding layer to ${routing.p.substring(0,16)}... (audit: ${routing.audit.substring(0,16)}...)`);
|
||||
}
|
||||
|
||||
// Post final event directly to relays
|
||||
@@ -1147,6 +1199,14 @@
|
||||
try {
|
||||
await Promise.any(pool.publish(relays, event));
|
||||
addLogEntry('success', `Published to relays: ${relays.join(', ')}`);
|
||||
|
||||
// Truncate content for readability
|
||||
const truncatedEvent = { ...event };
|
||||
if (truncatedEvent.content && truncatedEvent.content.length > 10) {
|
||||
const content = truncatedEvent.content;
|
||||
truncatedEvent.content = content.substring(0, 5) + '...' + content.substring(content.length - 5);
|
||||
}
|
||||
addLogEntry('info', `Full published event:\n${JSON.stringify(truncatedEvent, null, 2)}`);
|
||||
} catch (aggregateError) {
|
||||
const errorMessages = aggregateError.errors.map((err, index) =>
|
||||
`${relays[index]}: ${err.message}`
|
||||
@@ -1249,9 +1309,11 @@
|
||||
logEntries.slice().reverse().forEach(entry => {
|
||||
const div = document.createElement('div');
|
||||
div.className = `log-entry ${entry.type}`;
|
||||
div.innerHTML = `
|
||||
<span class="log-timestamp">${entry.timestamp}</span> ${entry.message}
|
||||
`;
|
||||
// Handle multiline messages (like JSON) with proper formatting
|
||||
const messageContent = entry.message.includes('\n') ?
|
||||
`<span class="log-timestamp">${entry.timestamp}</span><br><pre style="margin: 5px 0; font-family: monospace; font-size: 11px; white-space: pre-wrap;">${entry.message}</pre>` :
|
||||
`<span class="log-timestamp">${entry.timestamp}</span> ${entry.message}`;
|
||||
div.innerHTML = messageContent;
|
||||
container.appendChild(div);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user