This commit is contained in:
Your Name 2025-09-18 10:17:34 -04:00
parent 1b28f78f44
commit ecf248dfc2
4 changed files with 669 additions and 129 deletions

121
DAEMON.md
View File

@ -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", "<audit_tag>"]` 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": "<my_ephemeral_key>", // Generate fresh ephemeral key
"content": "<encrypted_inner_event>", // Re-encrypt with my key
"tags": [
["p", "<next_hop_or_final_destination>"],
["p", "<audit_tag_from_routing>"], // Always include audit tag as p tag
["padding", "<random_data_1>"], // Adjusted padding
["padding", "<random_data_2>"] // 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.

420
debug.md Normal file
View File

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

View File

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

View File

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