.
This commit is contained in:
@@ -478,7 +478,7 @@
|
||||
bounceSection.id = `bounce-${bounceId}`;
|
||||
|
||||
bounceSection.innerHTML = `
|
||||
<h2>🏀 Bounce ${bounceId} (Kind 22222 Routing Event)</h2>
|
||||
<h2 id="bounce-${bounceId}-header">🏀 Bounce ${bounceId} (Kind 22222 Routing Event)</h2>
|
||||
<div class="input-group">
|
||||
<label for="superball-pubkey-${bounceId}">Superball Pubkey:</label>
|
||||
<div style="display: flex; gap: 10px;">
|
||||
@@ -506,7 +506,7 @@
|
||||
<label for="audit-tag-${bounceId}">Audit Tag (auto-generated):</label>
|
||||
<input type="text" id="audit-tag-${bounceId}" readonly style="background: #f5f5f5;">
|
||||
</div>
|
||||
<button onclick="createBounce(${bounceId})">Create Bounce ${bounceId}</button>
|
||||
<button onclick="createBounce(${bounceId})" id="create-bounce-btn-${bounceId}">Create Bounce ${bounceId}</button>
|
||||
|
||||
<div id="bounce-${bounceId}-display" class="json-display"></div>
|
||||
<div style="text-align: right; margin-top: 5px;">
|
||||
@@ -524,8 +524,37 @@
|
||||
// Automatically generate and fill in the audit tag
|
||||
const auditTag = generateAuditTag();
|
||||
document.getElementById(`audit-tag-${bounceId}`).value = auditTag;
|
||||
|
||||
// Update bounce labels to reflect execution order
|
||||
updateBounceLabels();
|
||||
}
|
||||
|
||||
// Update bounce labels to reflect execution order (newest bounce is Bounce 1, oldest is Bounce N)
|
||||
function updateBounceLabels() {
|
||||
// Get all existing bounce sections
|
||||
const bounceContainer = document.getElementById('bounces-container');
|
||||
const bounceSections = bounceContainer.querySelectorAll('.bounce-section');
|
||||
|
||||
// Update labels in reverse order (newest first gets Bounce 1)
|
||||
bounceSections.forEach((section, index) => {
|
||||
const bounceId = section.id.replace('bounce-', '');
|
||||
const executionOrder = bounceSections.length - index; // Reverse the index
|
||||
|
||||
// Update the header
|
||||
const header = document.getElementById(`bounce-${bounceId}-header`);
|
||||
if (header) {
|
||||
header.textContent = `🏀 Bounce ${executionOrder} (Kind 22222 Routing Event)`;
|
||||
}
|
||||
|
||||
// Update the create button text
|
||||
const createBtn = document.getElementById(`create-bounce-btn-${bounceId}`);
|
||||
if (createBtn) {
|
||||
createBtn.textContent = `Create Bounce ${executionOrder}`;
|
||||
}
|
||||
});
|
||||
|
||||
console.log('INFO: Updated bounce labels for execution order');
|
||||
}
|
||||
|
||||
// Update all timeline absolute times continuously
|
||||
function updateAllTimelineTimes() {
|
||||
@@ -634,10 +663,11 @@
|
||||
|
||||
// Only add 'p' field if this isn't the last bounce
|
||||
if (!isLastBounce) {
|
||||
// Get the superball pubkey from the previous bounce
|
||||
// Get the superball pubkey from the previous bounce (the next hop in the chain)
|
||||
const prevBounce = bounces[bounces.length - 1];
|
||||
routingInstructions.p = prevBounce.superballPubkey;
|
||||
}
|
||||
// Note: If this IS the last bounce (first one created), no 'p' field means final posting
|
||||
|
||||
// Create the payload to encrypt
|
||||
const payload = {
|
||||
@@ -712,6 +742,9 @@
|
||||
generateVisualization();
|
||||
|
||||
console.log('SUCCESS', `Bounce ${bounceId} created successfully`);
|
||||
|
||||
// Update bounce labels after creation to reflect execution order
|
||||
updateBounceLabels();
|
||||
|
||||
} catch (error) {
|
||||
console.log('ERROR', `Failed to create bounce ${bounceId}: ${error.message}`);
|
||||
@@ -779,12 +812,12 @@
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the first (outermost) bounce to publish
|
||||
const firstBounce = bounces[0];
|
||||
const routingEvent = firstBounce.routingEvent;
|
||||
// Get the last (outermost) bounce to publish - the most recently created bounce
|
||||
const outermostBounce = bounces[bounces.length - 1];
|
||||
const routingEvent = outermostBounce.routingEvent;
|
||||
|
||||
// Get relays to publish to - use the first bounce's target relays
|
||||
const targetRelays = firstBounce.payload.routing.relays;
|
||||
// Get relays to publish to - use the outermost bounce's target relays
|
||||
const targetRelays = outermostBounce.payload.routing.relays;
|
||||
|
||||
if (!targetRelays || targetRelays.length === 0) {
|
||||
alert('No target relays configured for the first bounce');
|
||||
|
@@ -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