From ecf248dfc279e4667da74c9ce5b3eee94a047af4 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 18 Sep 2025 10:17:34 -0400 Subject: [PATCH] . --- DAEMON.md | 121 ++++++----- debug.md | 420 +++++++++++++++++++++++++++++++++++++ web/superball-builder.html | 49 ++++- web/superball.html | 208 +++++++++++------- 4 files changed, 669 insertions(+), 129 deletions(-) create mode 100644 debug.md diff --git a/DAEMON.md b/DAEMON.md index 18143f8..2fe8995 100644 --- a/DAEMON.md +++ b/DAEMON.md @@ -31,48 +31,59 @@ I am Superball - an anonymizing node that provides location privacy for Nostr us - Confirm the `p` tag contains my pubkey - Ensure it's kind 22222 -### 2. Decrypt +### 2. Decrypt and Identify Payload Type - Use my private key with NIP-44 to decrypt the content -- Extract the payload which contains: - ```json - { - "event": { /* The event to forward */ }, +- Check payload structure to determine type: - "routing": { /* Superball Instructions */ - "relays": ["wss://relay1.com", "wss://relay2.com"], - "delay": 30, - "padding": "+150", // or "-50" - "p": "next_superball_pubkey", // Optional - missing means final posting - "audit": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456", // Required audit tag - "payment": "eCash_token" // Optional - } +#### Type 1: Routing Payload (Created by Builder) +```json +{ + "event": { /* Final event or inner wrapped event */ }, + "routing": { /* My routing instructions from builder */ + "relays": ["wss://relay1.com", "wss://relay2.com"], + "delay": 30, + "p": "next_superball_pubkey", // Optional - missing means final posting + "audit": "audit_tag", // Required audit tag + "payment": "eCash_token", // Optional + "add_padding_bytes": 256 // Optional } - ``` +} +``` -### 3. Process Routing Instructions +#### Type 2: Padding Payload (Created by Previous Daemon) +```json +{ + "event": { /* Still-encrypted inner event */ }, + "padding": "01234567890123" // Padding data to discard +} +``` + +### 3. Handle Payload Type + +#### If Padding Payload: +1. **Discard padding** - Ignore padding field completely +2. **Decrypt again** - The "event" field contains another encrypted payload +3. **Process the inner payload** - This will be a routing payload meant for me + +#### If Routing Payload: +1. **Process routing instructions** - These were created by the builder specifically for me +2. **Continue with normal processing** + +### 4. Process Routing Instructions #### Delay - Wait the specified number of seconds before forwarding - Add random jitter (±10%) to prevent timing analysis - Queue the event for delayed processing -#### Padding -- **Remove padding (`"padding": "-N"`)**: Delete N bytes worth of padding tags from the event -- **Add padding (`"padding": "+N"`)**: Create new routing wrapper with N bytes of padding tags - #### Relays - Post to ALL relays in the `relays` array - Validate all relay URLs are properly formatted - Provides redundancy and availability #### Next Hop Logic -- **`p` field present**: Create routing event for specified next Superball (can apply padding) -- **`p` field missing**: Extract inner event and post directly to relays (end chain, no padding changes) - -#### Padding Logic -- **`p` field present + `padding` field**: Apply padding changes when creating routing wrapper -- **`p` field missing**: Ignore any `padding` field - cannot modify signed event -- **Final hop rule**: Never modify signed events, post exactly as received +- **`p` field present**: Forward to next Superball with padding-only wrapper +- **`p` field missing**: Post inner event directly to relays (end chain) #### Audit Tag Processing - **`audit` field**: Always present - include as `["p", ""]` in routing event @@ -83,30 +94,34 @@ I am Superball - an anonymizing node that provides location privacy for Nostr us - **`payment` field present**: Process eCash token for service payment - **`payment` field missing**: Process for free (if daemon allows) -### 4. Forward Event +### 5. Forward Event -#### Always Rewrap (Important for Privacy) -**ALWAYS** create a new routing event to hide whether padding was added or removed: +#### Two-Path Processing +**Path 1: Forward to Next Superball (`p` field present)** +- Create padding-only wrapper (never create routing instructions) +- Generate fresh ephemeral keypair +- Create padding payload: ```json { - "kind": 22222, // Always use routing event - "pubkey": "", // Generate fresh ephemeral key - "content": "", // Re-encrypt with my key - "tags": [ - ["p", ""], - ["p", ""], // Always include audit tag as p tag - ["padding", ""], // Adjusted padding - ["padding", ""] // May be more or less than before - ] + "event": { /* The still-encrypted inner event */ }, + "padding": "random_padding_data_123456789" } ``` +- Encrypt to next Superball's pubkey +- Create routing event with next hop's pubkey in p tag -#### Next Hop Handling -- **If `p` field in routing**: Create routing event with that pubkey in p tag -- **If no `p` field in routing**: Extract inner event and post directly to all relays -- **Multi-relay posting**: Post to every relay in the `relays` array -- **End of chain**: When no `p` field, I am the final hop +**Path 2: Final Posting (`p` field missing)** +- Extract inner event from payload +- Post directly to all relays in routing.relays array +- No wrapping or encryption needed +- End of chain + +#### Critical Rules +1. **Daemons NEVER create routing instructions** - Only padding +2. **Routing instructions come ONLY from the builder** - Pre-encrypted for each hop +3. **Always use fresh ephemeral keys** when forwarding +4. **Include audit tag** in routing event p tags for camouflage ## My Rules @@ -144,11 +159,21 @@ I am Superball - an anonymizing node that provides location privacy for Nostr us ## Example Processing Flow +### Single Unwrapping (Routing Payload) 1. **Receive**: Kind 22222 event with my pubkey in p tag -2. **Decrypt**: Extract inner event + routing instructions -3. **Queue**: Schedule for delayed processing (e.g., 30 seconds + jitter) -4. **Process**: Apply padding changes and prepare for forwarding -5. **Forward**: Post to target relay(s) -6. **Clean**: Clear all decrypted data from memory +2. **Decrypt**: Get payload with routing instructions +3. **Process**: These routing instructions were created for me by builder +4. **Forward or Post**: Based on routing.p field + +### Double Unwrapping (Padding Payload) +1. **Receive**: Kind 22222 event with my pubkey in p tag +2. **First Decrypt**: Get padding payload - discard padding +3. **Second Decrypt**: Decrypt the inner event to get my routing instructions +4. **Process**: These routing instructions were created for me by builder +5. **Forward or Post**: Based on routing.p field + +### Clean Up +- **Queue**: Schedule for delayed processing (e.g., 30 seconds + jitter) +- **Clean**: Clear all decrypted data from memory after processing I am a privacy-preserving relay that helps users post content while hiding their location. I ask no questions, store no logs, and remember nothing about the events that pass through me. \ No newline at end of file diff --git a/debug.md b/debug.md new file mode 100644 index 0000000..7862711 --- /dev/null +++ b/debug.md @@ -0,0 +1,420 @@ +# Ball Structure + +Final Event (What gets posted at the end) +Message Content: +2 bounce +Create Event That Will Be Published Publicly +{ + "kind": 1, + "content": "2 bounce", + "tags": [], + "created_at": 1758196136, + "pubkey": "8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e", + "id": "2451f908dba9b2281751ecb91df143b83866795d29bbadde60093dd17cd039dd", + "sig": "31400031068313ba8338faf852ca718ef3495e393a6ef66a925db6e26ef21dd825fe5beca36ad9a2af7ea87ecd1dbc50550112505b7ee30c19125ae89b610b4c" +} +🏀 Bounce 2 (Kind 22222 Routing Event) +Superball Pubkey: +03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25 +🎲 Random +Target Relays (comma separated): +wss://relay.laantungir.net +Delay (seconds): +10 +Padding (+N or -N bytes): ++150 or -50 +Payment (optional): +eCash token or payment info +Audit Tag (auto-generated): +3413dc76625f59dc88b4ff4c290643110f21208c9f4f99a678a75730fd35cef3 +Create Bounce 2 +{ + "kind": 22222, + "content": "Aob4...2at6", + "tags": [ + [ + "p", + "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + ], + [ + "p", + "3413dc76625f59dc88b4ff4c290643110f21208c9f4f99a678a75730fd35cef3" + ] + ], + "created_at": 1758196144, + "pubkey": "acb7a5fe35eb69a0f30e58dc790558a86143d5c489749fe9c4db52bdb3f7b310", + "id": "00d7536a425b2d955084f920c45c1d5ddf86b5503374daa02922af60f536b953", + "sig": "a2f9ad4f85bda8fc6ddbce28b2773e8938a747964dc278b2e89553666f777e9d27cbf37d6c69a30223dd8e2e2ace3155d31711ebb9b0b7cd0c5d1c92f8ecefec" +} +🔓 Decrypt Content + + +🔓 Decrypted Payload: + + +{ + "event": { + "kind": 1, + "content": "2 bounce", + "tags": [], + "created_at": 1758196136, + "pubkey": "8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e", + "id": "2451f908dba9b2281751ecb91df143b83866795d29bbadde60093dd17cd039dd", + "sig": "31400031068313ba8338faf852ca718ef3495e393a6ef66a925db6e26ef21dd825fe5beca36ad9a2af7ea87ecd1dbc50550112505b7ee30c19125ae89b610b4c" + }, + "routing": { + "relays": [ + "wss://relay.laantungir.net" + ], + "delay": 10, + "audit": "3413dc76625f59dc88b4ff4c290643110f21208c9f4f99a678a75730fd35cef3" + } +} + + +🏀 Bounce 1 (Kind 22222 Routing Event) +Superball Pubkey: +e295a831c9a3ae50da2ca3115f8b13b035805a5ebbbd68d95a1e0caf13bff440 +🎲 Random +Target Relays (comma separated): +wss://relay.laantungir.net +Delay (seconds): +10 +Padding (+N or -N bytes): ++150 or -50 +Payment (optional): +eCash token or payment info +Audit Tag (auto-generated): +ab35d9f342afd9541dac9679dae7a68d79eb52d01e35743d108ce0b4c56facd7 +Create Bounce 1 +{ + "kind": 22222, + "content": "AnMH3c...B3LPiw==", + "tags": [ + [ + "p", + "e295a831c9a3ae50da2ca3115f8b13b035805a5ebbbd68d95a1e0caf13bff440" + ], + [ + "p", + "ab35d9f342afd9541dac9679dae7a68d79eb52d01e35743d108ce0b4c56facd7" + ] + ], + "created_at": 1758196166, + "pubkey": "ef4663112f67ab47450605fc33e03f9cf19ddcd4d8ecf83c13bf3ecc9215461d", + "id": "1fa0869733fa11781b9130f8392b672e452e5025d7604182f83551542de21517", + "sig": "9a0a9deee3971e62deb9ff6974251c8599c4e6fd822bca12f28fe3df1646845fa0537ae5c3764e4227ee76874c233813896ee08a1b11e8b9f6e03192577891e6" +} +🔓 Decrypt Content + + +🔓 Decrypted Payload: + + +{ + "event": { + "kind": 22222, + "content": "Aob44...w2at6", + "tags": [ + [ + "p", + "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + ], + [ + "p", + "3413dc76625f59dc88b4ff4c290643110f21208c9f4f99a678a75730fd35cef3" + ] + ], + "created_at": 1758196144, + "pubkey": "acb7a5fe35eb69a0f30e58dc790558a86143d5c489749fe9c4db52bdb3f7b310", + "id": "00d7536a425b2d955084f920c45c1d5ddf86b5503374daa02922af60f536b953", + "sig": "a2f9ad4f85bda8fc6ddbce28b2773e8938a747964dc278b2e89553666f777e9d27cbf37d6c69a30223dd8e2e2ace3155d31711ebb9b0b7cd0c5d1c92f8ecefec" + }, + "routing": { + "relays": [ + "wss://relay.laantungir.net" + ], + "delay": 10, + "audit": "ab35d9f342afd9541dac9679dae7a68d79eb52d01e35743d108ce0b4c56facd7", + "p": "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + } +} + + +# Process at first bounce pubkey:e295a831c9a3ae50da2ca3115f8b13b035805a5ebbbd68d95a1e0caf13bff440 + +7:49:44 AM Successfully processed event 1fa0869733fa1178... +7:49:44 AM Forwarded event with audit tag ab35d9f342afd954... +7:49:44 AM +Full published event: +{ + "kind": 22222, + "content": "AtcPM...GGA==", + "tags": [ + [ + "p", + "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + ], + [ + "p", + "ab35d9f342afd9541dac9679dae7a68d79eb52d01e35743d108ce0b4c56facd7" + ] + ], + "created_at": 1758196183, + "pubkey": "c2ff758233b1099682d99687e096859e4f930ea20e73905756166ffdcab9279c", + "id": "843d662891c497365d514b995c4135fb0cf080531cda4cef842d396cf0d1c1db", + "sig": "765c822b75fe7772e6c10c96e159bc1e64534571d3cd9e6daf25e5276e2a5efdd6dbb3523db75aa1483ca3b1062d8444145b69d0cd0823579980fe5074f8354b" +} +7:49:44 AM Published to relays: wss://relay.laantungir.net +7:49:43 AM +Rewrapped event to publish: +{ + "kind": 22222, + "content": "AtcPM...GGA==", + "tags": [ + [ + "p", + "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + ], + [ + "p", + "ab35d9f342afd9541dac9679dae7a68d79eb52d01e35743d108ce0b4c56facd7" + ] + ], + "created_at": 1758196183, + "pubkey": "c2ff758233b1099682d99687e096859e4f930ea20e73905756166ffdcab9279c", + "id": "843d662891c497365d514b995c4135fb0cf080531cda4cef842d396cf0d1c1db", + "sig": "765c822b75fe7772e6c10c96e159bc1e64534571d3cd9e6daf25e5276e2a5efdd6dbb3523db75aa1483ca3b1062d8444145b69d0cd0823579980fe5074f8354b" +} +7:49:43 AM +New payload to encrypt: +{ + "event": { + "kind": 22222, + "content": "Aob44...w2at6", + "tags": [ + [ + "p", + "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + ], + [ + "p", + "3413dc76625f59dc88b4ff4c290643110f21208c9f4f99a678a75730fd35cef3" + ] + ], + "created_at": 1758196144, + "pubkey": "acb7a5fe35eb69a0f30e58dc790558a86143d5c489749fe9c4db52bdb3f7b310", + "id": "00d7536a425b2d955084f920c45c1d5ddf86b5503374daa02922af60f536b953", + "sig": "a2f9ad4f85bda8fc6ddbce28b2773e8938a747964dc278b2e89553666f777e9d27cbf37d6c69a30223dd8e2e2ace3155d31711ebb9b0b7cd0c5d1c92f8ecefec" + }, + "routing": { + "relays": [ + "wss://relay.laantungir.net" + ], + "delay": 10, + "audit": "ab35d9f342afd9541dac9679dae7a68d79eb52d01e35743d108ce0b4c56facd7", + "p": "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + } +} +7:49:43 AM DEBUG Rewrapping: newRouting.p = "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" (string) +7:49:43 AM Forwarding to next Superball: 03f857567fc96b47... +7:49:43 AM DEBUG Will FORWARD event +7:49:43 AM DEBUG Decision point - routing.p = "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" (string) +7:49:43 AM Processing event 1fa0869733fa1178... +7:49:33 AM Event queued for processing in 10s: 1fa0869733fa1178... +7:49:33 AM DEBUG !routing.p: false +7:49:33 AM DEBUG routing.p === null: false +7:49:33 AM DEBUG routing.p === undefined: false +7:49:33 AM DEBUG routing.p = "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" (type: string) +7:49:33 AM +Decrypted payload: +{ + "event": { + "kind": 22222, + "content": "Aob44...w2at6", + "tags": [ + [ + "p", + "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + ], + [ + "p", + "3413dc76625f59dc88b4ff4c290643110f21208c9f4f99a678a75730fd35cef3" + ] + ], + "created_at": 1758196144, + "pubkey": "acb7a5fe35eb69a0f30e58dc790558a86143d5c489749fe9c4db52bdb3f7b310", + "id": "00d7536a425b2d955084f920c45c1d5ddf86b5503374daa02922af60f536b953", + "sig": "a2f9ad4f85bda8fc6ddbce28b2773e8938a747964dc278b2e89553666f777e9d27cbf37d6c69a30223dd8e2e2ace3155d31711ebb9b0b7cd0c5d1c92f8ecefec" + }, + "routing": { + "relays": [ + "wss://relay.laantungir.net" + ], + "delay": 10, + "audit": "ab35d9f342afd9541dac9679dae7a68d79eb52d01e35743d108ce0b4c56facd7", + "p": "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + } +} +7:49:33 AM Successfully decrypted routing event 1fa0869733fa1178... +7:49:33 AM Received routing event: 1fa0869733fa1178... +7:49:33 AM +Full received event: +{ + "content": "AnMH3...Piw==", + "created_at": 1758196166, + "id": "1fa0869733fa11781b9130f8392b672e452e5025d7604182f83551542de21517", + "kind": 22222, + "pubkey": "ef4663112f67ab47450605fc33e03f9cf19ddcd4d8ecf83c13bf3ecc9215461d", + "sig": "9a0a9deee3971e62deb9ff6974251c8599c4e6fd822bca12f28fe3df1646845fa0537ae5c3764e4227ee76874c233813896ee08a1b11e8b9f6e03192577891e6", + "tags": [ + [ + "p", + "e295a831c9a3ae50da2ca3115f8b13b035805a5ebbbd68d95a1e0caf13bff440" + ], + [ + "p", + "ab35d9f342afd9541dac9679dae7a68d79eb52d01e35743d108ce0b4c56facd7" + ] + ] +} +7:49:33 AM Received EVENT from wss://relay.laantungir.net: 1fa0869733fa1178... + + +# Process at second bounce pubkey: 03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25 + +7:49:54 AM Forwarded event with audit tag ab35d9f342afd954... +7:49:54 AM +Full published event: +{ + "kind": 22222, + "content": "ArEYH...cAw==", + "tags": [ + [ + "p", + "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + ], + [ + "p", + "ab35d9f342afd9541dac9679dae7a68d79eb52d01e35743d108ce0b4c56facd7" + ] + ], + "created_at": 1758196194, + "pubkey": "76c213037af4de471d6af7fa996f0b02882ccf2606e871f49ebb6dccef7f7642", + "id": "ddeebcc4091610ec5c7246a06ebfe31f3539492e273c920e9da5e10816b0d0d1", + "sig": "a3845cca91cfa79eb184499350b8f1b2486ee65313371a7b9ca61506cd1abcb684673eefecf9b7b0c17e50a70f4976d750310ef393a77cd69cbe923fa351aa37" +} +7:49:54 AM Published to relays: wss://relay.laantungir.net +7:49:54 AM +Rewrapped event to publish: +{ + "kind": 22222, + "content": "ArEYH...cAw==", + "tags": [ + [ + "p", + "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + ], + [ + "p", + "ab35d9f342afd9541dac9679dae7a68d79eb52d01e35743d108ce0b4c56facd7" + ] + ], + "created_at": 1758196194, + "pubkey": "76c213037af4de471d6af7fa996f0b02882ccf2606e871f49ebb6dccef7f7642", + "id": "ddeebcc4091610ec5c7246a06ebfe31f3539492e273c920e9da5e10816b0d0d1", + "sig": "a3845cca91cfa79eb184499350b8f1b2486ee65313371a7b9ca61506cd1abcb684673eefecf9b7b0c17e50a70f4976d750310ef393a77cd69cbe923fa351aa37" +} +7:49:54 AM +New payload to encrypt: +{ + "event": { + "kind": 22222, + "content": "Aob44...w2at6", + "tags": [ + [ + "p", + "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + ], + [ + "p", + "3413dc76625f59dc88b4ff4c290643110f21208c9f4f99a678a75730fd35cef3" + ] + ], + "created_at": 1758196144, + "pubkey": "acb7a5fe35eb69a0f30e58dc790558a86143d5c489749fe9c4db52bdb3f7b310", + "id": "00d7536a425b2d955084f920c45c1d5ddf86b5503374daa02922af60f536b953", + "sig": "a2f9ad4f85bda8fc6ddbce28b2773e8938a747964dc278b2e89553666f777e9d27cbf37d6c69a30223dd8e2e2ace3155d31711ebb9b0b7cd0c5d1c92f8ecefec" + }, + "routing": { + "relays": [ + "wss://relay.laantungir.net" + ], + "delay": 10, + "audit": "ab35d9f342afd9541dac9679dae7a68d79eb52d01e35743d108ce0b4c56facd7", + "p": "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + } +} +7:49:54 AM DEBUG Rewrapping: newRouting.p = "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" (string) +7:49:54 AM Forwarding to next Superball: 03f857567fc96b47... +7:49:54 AM DEBUG Will FORWARD event +7:49:54 AM DEBUG Decision point - routing.p = "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" (string) +7:49:54 AM Processing event 843d662891c49736... +7:49:44 AM Event queued for processing in 10s: 843d662891c49736... +7:49:44 AM DEBUG !routing.p: false +7:49:44 AM DEBUG routing.p === null: false +7:49:44 AM DEBUG routing.p === undefined: false +7:49:44 AM DEBUG routing.p = "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" (type: string) +7:49:44 AM +Decrypted payload: +{ + "event": { + "kind": 22222, + "content": "Aob44...w2at6", + "tags": [ + [ + "p", + "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + ], + [ + "p", + "3413dc76625f59dc88b4ff4c290643110f21208c9f4f99a678a75730fd35cef3" + ] + ], + "created_at": 1758196144, + "pubkey": "acb7a5fe35eb69a0f30e58dc790558a86143d5c489749fe9c4db52bdb3f7b310", + "id": "00d7536a425b2d955084f920c45c1d5ddf86b5503374daa02922af60f536b953", + "sig": "a2f9ad4f85bda8fc6ddbce28b2773e8938a747964dc278b2e89553666f777e9d27cbf37d6c69a30223dd8e2e2ace3155d31711ebb9b0b7cd0c5d1c92f8ecefec" + }, + "routing": { + "relays": [ + "wss://relay.laantungir.net" + ], + "delay": 10, + "audit": "ab35d9f342afd9541dac9679dae7a68d79eb52d01e35743d108ce0b4c56facd7", + "p": "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + } +} +7:49:44 AM Successfully decrypted routing event 843d662891c49736... +7:49:44 AM Received routing event: 843d662891c49736... +7:49:44 AM +Full received event: +{ + "content": "AtcPM...GGA==", + "created_at": 1758196183, + "id": "843d662891c497365d514b995c4135fb0cf080531cda4cef842d396cf0d1c1db", + "kind": 22222, + "pubkey": "c2ff758233b1099682d99687e096859e4f930ea20e73905756166ffdcab9279c", + "sig": "765c822b75fe7772e6c10c96e159bc1e64534571d3cd9e6daf25e5276e2a5efdd6dbb3523db75aa1483ca3b1062d8444145b69d0cd0823579980fe5074f8354b", + "tags": [ + [ + "p", + "03f857567fc96b47b68632457a818563c53e09aaf0028ac1081450afe3352e25" + ], + [ + "p", + "ab35d9f342afd9541dac9679dae7a68d79eb52d01e35743d108ce0b4c56facd7" + ] + ] +} +7:49:44 AM Received EVENT from wss://relay.laantungir.net: 843d662891c49736... \ No newline at end of file diff --git a/web/superball-builder.html b/web/superball-builder.html index 21668ec..6e969b4 100644 --- a/web/superball-builder.html +++ b/web/superball-builder.html @@ -478,7 +478,7 @@ bounceSection.id = `bounce-${bounceId}`; bounceSection.innerHTML = ` -

🏀 Bounce ${bounceId} (Kind 22222 Routing Event)

+

🏀 Bounce ${bounceId} (Kind 22222 Routing Event)

@@ -506,7 +506,7 @@
- +
@@ -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'); diff --git a/web/superball.html b/web/superball.html index 3997f77..f2c1ddd 100644 --- a/web/superball.html +++ b/web/superball.html @@ -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 = ` - ${entry.timestamp} ${entry.message} - `; + // Handle multiline messages (like JSON) with proper formatting + const messageContent = entry.message.includes('\n') ? + `${entry.timestamp}
${entry.message}
` : + `${entry.timestamp} ${entry.message}`; + div.innerHTML = messageContent; container.appendChild(div); });