This commit is contained in:
Your Name 2025-09-27 06:35:41 -04:00
parent 26c0a71292
commit 6191b29e49
13 changed files with 158 additions and 189 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,7 +1,7 @@
# Superball # Superball
![superball](super_ball.jpg) ![superball](super_ball.jpg)
Superball provides Tor-like location privacy for Nostr users. A **Superball** is a wrapped nostr event, similar to a TOR onion packet, that bounces between nostr relays and **Throwers**. Superball provides remailer-like location privacy for Nostr users. A **Superball** is a wrapped nostr event, similar to a TOR onion packet, that bounces between nostr relays and **Throwers**.
A **Thrower** is a nostr-capable daemon that monitors nostr nodes, looks for superballs posted for them, grabs them, cryptographically unwraps them, rewraps them potentially with padding, then throws the superball to relay(s) that it is instructed to, when it is instructed to. A **Thrower** is a nostr-capable daemon that monitors nostr nodes, looks for superballs posted for them, grabs them, cryptographically unwraps them, rewraps them potentially with padding, then throws the superball to relay(s) that it is instructed to, when it is instructed to.

View File

@ -131,13 +131,10 @@ I am a Thrower - an anonymizing node that provides location privacy for Nostr us
1. **Never log sensitive data** - Don't store decrypted content or routing info 1. **Never log sensitive data** - Don't store decrypted content or routing info
2. **Generate new keys** - Use fresh ephemeral keys for each forward 2. **Generate new keys** - Use fresh ephemeral keys for each forward
3. **Validate everything** - Check signatures, event structure, relay URLs 3. **Validate everything** - Check signatures, event structure, relay URLs
4. **Rate limiting** - Don't process more than X events per minute from same source
### Privacy Rules ### Privacy Rules
1. **No correlation** - Don't link input events to output events in logs 1. **No correlation** - Don't link input events to output events in logs
2. **Clear memory** - Immediately clear decrypted data after processing 2. **Clear memory** - Immediately clear decrypted data after processing
3. **Random timing** - Add jitter to specified delays
4. **Mix traffic** - Send decoy traffic when idle (optional)
### Processing Rules ### Processing Rules
1. **First come, first served** - Process events in order received 1. **First come, first served** - Process events in order received

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@ -1,3 +1,3 @@
#!/bin/bash #!/bin/bash
rsync -avz --chmod=644 --progress web/{superball.html,thrower.html,superball-shared.css} ubuntu@laantungir.net:WWW/superball/ rsync -avz --chmod=D755,F644 --progress web/ ubuntu@laantungir.net:WWW/superball/

80
nostr_post.md Normal file
View File

@ -0,0 +1,80 @@
# Superball: Anonymous Nostr Posting
![](https://laantungir.github.io/img_repo/0246a1c67289367573fcc552dffdc54867c15cca200572e12499db2d907df66b.jpg)
This is an experiment I call #Superball, which I believe could become the most anonymous way to post on social media.
Superballs are a way to post nostr events that help preserve the anonymity and location of the sender. Superballs use timing delays and add or subtract event padding to help defeat timing and size analysis, such as is used to help defeat TOR anonymity.
A Superball is a wrapped nostr event that is posted to a relay and picked up by a "Thrower". A Thrower is a program that watches relays for Superballs that are marked for them. The Thrower grabs the Superball, decrypts the outer shell of the Superball and reads the instructions inside, which tell the Thrower:
1. What relays to throw the superball to next.
2. How long to wait before throwing the superball.
3. Whether to add or remove padding to the superball to obscure its size.
When you create a Superball, you can chain throwers together. Depending on where the Thrower is in the chain, the Thrower will either rewrap the Superball with optional padding and post it to relays where it can be picked up by the next Thrower, or post the final event.
But enough talk. It's best understood by using it.
I have implemented the protocol in two web pages, which you can use to test it out:
This page lets you create and throw a superball. You can log in as yourself if you want, and just throw a superball. There are already throwers up. [https://laantungir.net/superball/superball.html](https://laantungir.net/superball/superball.html)
This page lets you create a thrower. You can open as many of these pages as you want to create as many throwers as you want. You can watch your superballs propogate through your throwers. Don't log in as yourself. Log in as a new account to create a thrower. [https://laantungir.net/superball/thrower.html](https://laantungir.net/superball/thrower.html)
If you don't like reading instructions, go ahead and dive in. You can probably figure it out if you are nostr savy.
If you like instructions, keep reading:
## QUICK START ONE- Throw a single bounce Superball
Go here to create a superball and throw it:
[https://laantungir.net/superball/superball.html](https://laantungir.net/superball/superball.html)
1. Sign in with your favorite nostr method, or create a new throwaway test key.
![](https://laantungir.net/superball/img/sb1.png)
2. Create a type 1 event that will eventually be posted.
![](https://laantungir.net/superball/img/sb2.png)
3. Click on the Add Bounce button.
![](https://laantungir.net/superball/img/sb3.png)
4. You have to, at a minimum, choose a thrower, along with what relays you want the thrower to throw your event to. The default delay is 30 seconds.
5. Click on Throw Superball, with the blue outline.
6. Scroll down to Event Flow Visualization. This will show you how your Superball will bounce around NOSTR.
![](https://laantungir.net/superball/img/sb4.png)
7. Click on Throw Superball.
8. Click on Reset Builder to start again.
## Quick Start Two: Throw a Double Bounce Superball
1. Click on Reset Builder on the bottom right of the page.
2. Repeat steps 1-6
3. Click on Add Bounce, and add another bounce. Note that with 2 or more bounces, you can now specify that the thrower add padding to the Superball, to defeat any size analysis of your superball.
## Quick Start Three: Create and Use Your Own Thrower
1. In a separate browser window, go to [https://laantungir.net/superball/thrower.html](https://laantungir.net/superball/thrower.html)
2. Log in not with your own key, but with a key you want to use for your Thrower that you are going to create. Jot down the key.
![](https://laantungir.net/superball/img/thrower01.png)
3. Click on Edit Thrower Info, and give your thrower a fun name and description. Save the info. You may have to log back in for that information to display properly on this page.
4. Go to Edit Relays to add relays that you want your Thrower to be able to read from and write to. Save Relay Configuration.
5. Test Relays. Because your thrower will be publishing someone else's notes, it can't publish to relays that require authentication. That is what this button does.
6. Click on Start Thrower. Your thrower will then publish a kind 12222 thrower information document. This lets the world know your thrower is alive and active.
7. Go back to [https://laantungir.net/superball/superball.html](https://laantungir.net/superball/superball.html). Under Available Throwers, click on Refresh List and your thrower should appear. The account you logged into on the Superball Builder page has to follow at least one relay that the Thrower publishes to, or you won't see it.
8. Go ahead and create a superball, select your thrower, and throw it.
9. When the Thrower receives the Superball, it will show it in the Event Queue. You can follow the Processing Log if you encounter problems.
## Project Link
[https://git.laantungir.net/laantungir/super_ball](https://git.laantungir.net/laantungir/super_ball)
## Inspiration
When I was a kid, I used to buy superballs from gumball machines, go into parking lots, and throw my superballs down as hard as I could onto the pavement. Hours of fun.
The early cypherpunks used Anonymous Remailers. I believe this may have been one of the most anonymous ways to message ever created. Tor and I2P are great, and can't be replaced, but their immediate nature means that they will always be vulnerable to timing attacks. Remailers helped solve this, and I think Superball can do the same on nostr.

76
nostr_post.txt Normal file
View File

@ -0,0 +1,76 @@
Superball: Anonymous Nostr Posting
This is an experiment I call #Superball, which I believe could become the most anonymous way to post on social media.
Superballs are a way to post nostr events that help preserve the anonymity and location of the sender. Superballs use timing delays and add or subtract event padding to help defeat timing and size analysis, such as is used to help defeat TOR anonymity.
A Superball is a wrapped nostr event that is posted to a relay and picked up by a "Thrower". A Thrower is a program that watches relays for Superballs that are marked for them. The Thrower grabs the Superball, decrypts the outer shell of the Superball and reads the instructions inside, which tell the Thrower:
1. What relays to throw the superball to next.
2. How long to wait before throwing the superball.
3. Whether to add or remove padding to the superball to obscure its size.
When you create a Superball, you can chain throwers together. Depending on where the Thrower is in the chain, the Thrower will either rewrap the Superball with optional padding and post it to relays where it can be picked up by the next Thrower, or post the final event.
But enough talk. It's best understood by using it.
I have implemented the protocol in two web pages, which you can use to test it out:
This page lets you create and throw a superball. You can log in as yourself if you want, and just throw a superball. There are already throwers up.
https://laantungir.net/superball/superball.html
This page lets you create a thrower. You can open as many of these pages as you want to create as many throwers as you want. You can watch your superballs propogate through your throwers. Don't log in as yourself. Log in as a new account to create a thrower. https://laantungir.net/superball/thrower.html
If you don't like reading instructions, go ahead and dive in. You can probably figure it out if you are nostr savy.
If you like instructions, keep reading:
QUICK START ONE- Throw a single bounce Superball
Go here to create a superball and throw it:
https://laantungir.net/superball/superball.html
1. Sign in with your favorite nostr method, or create a new throwaway test key.
https://laantungir.net/superball/img/sb1.png
2. Create a type 1 event that will eventually be posted.
https://laantungir.net/superball/img/sb2.png
3. Click on the Add Bounce button.
https://laantungir.net/superball/img/sb3.png
4. You have to, at a minimum, choose a thrower, along with what relays you want the thrower to throw your event to. The default delay is 30 seconds.
5. Click on Throw Superball, with the blue outline.
6. Scroll down to Event Flow Visualization. This will show you how your Superball will bounce around NOSTR.
https://laantungir.net/superball/img/sb4.png
7. Click on Throw Superball.
8. Click on Reset Builder to start again.
Quick Start Two: Throw a Double Bounce Superball
1. Click on Reset Builder on the bottom right of the page.
2. Repeat steps 1-6
3. Click on Add Bounce, and add another bounce. Note that with 2 or more bounces, you can now specify that the thrower add padding to the Superball, to defeat any size analysis of your superball.
Quick Start Three: Create and Use Your Own Thrower
1. In a separate browser window, go to https://laantungir.net/superball/thrower.html
2. Log in not with your own key, but with a key you want to use for your Thrower that you are going to create. Jot down the key.
https://laantungir.net/superball/img/thrower01.png
3. Click on Edit Thrower Info, and give your thrower a fun name and description. Save the info. You may have to log back in for that information to display properly on this page.
4. Go to Edit Relays to add relays that you want your Thrower to be able to read from and write to. Save Relay Configuration.
5. Test Relays. Because your thrower will be publishing someone else's notes, it can't publish to relays that require authentication. That is what this button does.
6. Click on Start Thrower. Your thrower will then publish a kind 12222 thrower information document. This lets the world know your thrower is alive and active.
7. Go back to https://laantungir.net/superball/superball.html. Under Available Throwers, click on Refresh List and your thrower should appear. The account you logged into on the Superball Builder page has to follow at least one relay that the Thrower publishes to, or you won't see it.
8. Go ahead and create a superball, select your thrower, and throw it.
9. When the Thrower receives the Superball, it will show it in the Event Queue. You can follow the Processing Log if you encounter problems.
Project Link
https://git.laantungir.net/laantungir/super_ball
Inspiration
When I was a kid, I used to buy superballs from gumball machines, go into parking lots, and throw my superballs down as hard as I could onto the pavement. Hours of fun.
The early cypherpunks used Anonymous Remailers. I believe this may have been one of the most anonymous ways to post online ever created. Tor and I2P are great, and can't be replaced, but their immediate nature means that they will always be vulnerable to timing attacks. Remailers solved this, and I think Superball can do the same on nostr.

BIN
web/img/sb1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
web/img/sb2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
web/img/sb3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
web/img/sb4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

BIN
web/img/thrower01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

View File

@ -1,184 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NIP-07 Signing Test</title>
</head>
<body>
<div>
<div id="status"></div>
<div id="test-section" style="display:none;">
<button id="sign-button">Sign Event</button>
<button id="encrypt-button">Test NIP-04 Encrypt</button>
<button id="decrypt-button">Test NIP-04 Decrypt</button>
<div id="results"></div>
</div>
</div>
<script src="../lite/nostr.bundle.js"></script>
<script src="../lite/nostr-lite.js"></script>
<script>
let testPubkey = 'npub1damus9dqe7g7jqn45kjcjgsv0vxjqnk8cxjkf8gqjwm8t8qjm7cqm3z7l';
let ciphertext = '';
document.addEventListener('DOMContentLoaded', async () => {
await window.NOSTR_LOGIN_LITE.init({
theme: 'default',
methods: {
extension: true,
local: true,
readonly: true,
connect: true,
remote: true,
otp: true
},
floatingTab: {
enabled: true,
hPosition: 1, // 0.0-1.0 or '95%' from left
vPosition: 0, // 0.0-1.0 or '50%' from top
appearance: {
style: 'pill', // 'pill', 'square', 'circle', 'minimal'
icon: '', // Clean display without icon placeholders
text: 'Login'
},
behavior: {
hideWhenAuthenticated: false,
showUserInfo: true,
autoSlide: true
},
getUserInfo: true, // Enable profile fetching
getUserRelay: [ // Specific relays for profile fetching
'wss://relay.laantungir.net'
]
}});
// document.getElementById('login-button').addEventListener('click', () => {
// window.NOSTR_LOGIN_LITE.launch('login');
// });
window.addEventListener('nlMethodSelected', (event) => {
document.getElementById('status').textContent = `Authenticated with: ${event.detail.method}`;
document.getElementById('test-section').style.display = 'block';
});
document.getElementById('sign-button').addEventListener('click', testSigning);
document.getElementById('encrypt-button').addEventListener('click', testEncryption);
document.getElementById('decrypt-button').addEventListener('click', testDecryption);
});
async function testSigning() {
try {
console.log('=== DEBUGGING SIGN EVENT START ===');
console.log('testSigning: window.nostr is:', window.nostr);
console.log('testSigning: window.nostr constructor:', window.nostr?.constructor?.name);
console.log('testSigning: window.nostr === our facade?', window.nostr?.constructor?.name === 'WindowNostr');
// Get user public key for comparison
const userPubkey = await window.nostr.getPublicKey();
console.log('User public key:', userPubkey);
// Check auth state if our facade
if (window.nostr?.constructor?.name === 'WindowNostr') {
console.log('WindowNostr authState:', window.nostr.authState);
console.log('WindowNostr authenticatedExtension:', window.nostr.authenticatedExtension);
console.log('WindowNostr existingNostr:', window.nostr.existingNostr);
}
const event = {
kind: 1,
content: 'Hello from NIP-07!',
tags: [],
created_at: Math.floor(Date.now() / 1000)
};
console.log('=== EVENT BEING SENT TO EXTENSION ===');
console.log('Event object:', JSON.stringify(event, null, 2));
console.log('Event keys:', Object.keys(event));
console.log('Event kind type:', typeof event.kind, event.kind);
console.log('Event content type:', typeof event.content, event.content);
console.log('Event tags type:', typeof event.tags, event.tags);
console.log('Event created_at type:', typeof event.created_at, event.created_at);
console.log('Event created_at value:', event.created_at);
// Check if created_at is within reasonable bounds
const now = Math.floor(Date.now() / 1000);
const timeDiff = Math.abs(event.created_at - now);
console.log('Time difference from now (seconds):', timeDiff);
console.log('Event timestamp as Date:', new Date(event.created_at * 1000));
// Additional debugging for user-specific issues
console.log('=== USER-SPECIFIC DEBUG INFO ===');
console.log('User pubkey length:', userPubkey?.length);
console.log('User pubkey format check (hex):', /^[a-fA-F0-9]{64}$/.test(userPubkey));
// Try to get user profile info if available
try {
const profileEvent = {
kinds: [0],
authors: [userPubkey],
limit: 1
};
console.log('Would query profile with filter:', profileEvent);
} catch (profileErr) {
console.log('Profile query setup failed:', profileErr);
}
console.log('=== ABOUT TO CALL EXTENSION SIGN EVENT ===');
const signedEvent = await window.nostr.signEvent(event);
console.log('=== SIGN EVENT SUCCESSFUL ===');
console.log('Signed event:', JSON.stringify(signedEvent, null, 2));
console.log('Signed event keys:', Object.keys(signedEvent));
console.log('Signature present:', !!signedEvent.sig);
console.log('ID present:', !!signedEvent.id);
console.log('Pubkey matches user:', signedEvent.pubkey === userPubkey);
document.getElementById('results').innerHTML = `<h3>Signed Event:</h3><pre>${JSON.stringify(signedEvent, null, 2)}</pre>`;
console.log('=== DEBUGGING SIGN EVENT END ===');
} catch (error) {
console.error('=== SIGN EVENT ERROR ===');
console.error('Error message:', error.message);
console.error('Error stack:', error.stack);
console.error('Error object:', error);
document.getElementById('results').innerHTML = `<h3>Sign Error:</h3><pre>${error.message}</pre><pre>${error.stack}</pre>`;
}
}
async function testEncryption() {
try {
const plaintext = 'Secret message for testing';
const pubkey = await window.nostr.getPublicKey();
ciphertext = await window.nostr.nip04.encrypt(pubkey, plaintext);
document.getElementById('results').innerHTML = `<h3>Encrypted:</h3><pre>${ciphertext}</pre>`;
} catch (error) {
document.getElementById('results').innerHTML = `<h3>Encrypt Error:</h3><pre>${error.message}</pre>`;
}
}
async function testDecryption() {
try {
if (!ciphertext) {
document.getElementById('results').innerHTML = `<h3>Decrypt Error:</h3><pre>No ciphertext available. Run encrypt first.</pre>`;
return;
}
const pubkey = await window.nostr.getPublicKey();
const decrypted = await window.nostr.nip04.decrypt(pubkey, ciphertext);
document.getElementById('results').innerHTML = `<h3>Decrypted:</h3><pre>${decrypted}</pre>`;
} catch (error) {
document.getElementById('results').innerHTML = `<h3>Decrypt Error:</h3><pre>${error.message}</pre>`;
}
}
</script>
</body>
</html>