Oddbean new post about | logout
 For now... It doesn't need to.  
 Its going to be pretty gay trying to identify instances by numbers....even ipv6 numbers lol

Wont ssl not work if we use numbers ?  
 You can do SSL with IP only just fine. There is no need for domain names. 

Also, you will identify relay instances and blossom servers by pubkey, not by IP. Relays and servers will be on a marketplace of their own. You just need to search, select one you like and add the pubkey to your relay list. Clients will figure out which IP the pubkey is currently using to host the relay. 

Super easy.  
 We bitch about ipv6 raw addresses while we go around cutting, pasting and trusting npubs? 
 I know, right? npubs are easy. nprofiles are massive. 
 Honestly I love how it scales wonderfully, but the chances of IPv6 not ending up geo'ed is less than a Planck length. And it would all be public, 100%. No thanks. 
 I completely agree with you. The scalability of IPv6 is impressive, but the potential for it to be geo'ed and public is definitely a concern. Thanks for sharing your thoughts! 
 Exactly 👍  
 Ah. Cool. Every time I try to use Ip with ssl it fails to get validation and errors are given. Many libs dont like self-signed either.

I cant wait for blossom to develop more.mayvr nextcloud replacement one day? Hehe

 
 Yeah, the server needs to offer IP-based SSL. They usually don't though :(  
 I smell a new npub-based ssl certificate replacement for relays coming 
 This is how we win.
nostr:nevent1qqst8v7whsm84hg5rd5lut8s5x6qu2t2an3pfxr3a6mkjd5ttxlxtugpr3mhxue69uhkummnw3ezucnfw33k76twv4ezuum0vd5kzmqzyrq7n2e62632km9yh6l5f6nykt76gzkxxy0gs6agddr9y95uk445xqcyqqqqqqgafdhsn 
 Nostr be-like: 

forget what you know, open your mind to what's next 

nostr:nevent1qqst8v7whsm84hg5rd5lut8s5x6qu2t2an3pfxr3a6mkjd5ttxlxtugpzpmhxue69uhkummnw3ezumt0d5hsygxpax4n544z4dk2f04lgn4xfvha5s9vvvg73p46s66x2gtfedttgvpsgqqqqqqsqkly4n 
 The idea here is to identify the relay with a uri something like thus:

wsn://npub1234....7890@[1001:1003:1003:1004::abcd]:8443

wsn://npub1234....7890@22.33.44.55:8443

wsn://npub1234....7890@centralizeddomainname.com:8443

wsn://npub1234...7890@torelayabcd...efg.onion:8443

Where the format is
wsn://<public key>@<IP addr>:<port>

wsn: Web Sockets Nostr

The @ symbol could be changed to something that doesen't have an alternative meaning. Maybe #, %, &, ~ or whatnot

The only downside is it wouldn't play nice with reverse websocket proxies 
 nostr:nevent1qqs97npkm0dknd3a4zp68zeg0c4x48smcpkm3tvskfn6kafnyt07fyqpz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsygxpax4n544z4dk2f04lgn4xfvha5s9vvvg73p46s66x2gtfedttgvpsgqqqqqqs7fxkyt 
 This is how @simplex works basically. 
 Chatgpt:
Using Elliptic Curve Cryptography (ECC) is a great way to align with NOSTR's encryption and signing schemes. Here’s how you can create a secure WebSocket communication using ECC for encryption and signing, similar to how NOSTR handles notes.

### Step-by-Step Guide

#### 1. Key Generation

Generate ECC key pairs for the server and client using `cryptography` library in Python.

#### 2. Implement the Relay Server

The relay server will use its private key to decrypt the initial connection request and then use symmetric encryption (AES) for the WebSocket communication.

#### 3. Implement the Client

The client will use the server's public key to encrypt the initial connection request.

### Example Code

#### 1. Key Generation

Use the `cryptography` library to generate ECC key pairs.

```python
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import serialization

# Generate server private key
server_private_key = ec.generate_private_key(ec.SECP256R1())
server_public_key = server_private_key.public_key()

# Save server private key
with open("server_private_key.pem", "wb") as f:
    f.write(server_private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
    ))

# Save server public key
with open("server_public_key.pem", "wb") as f:
    f.write(server_public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    ))
```

#### 2. Relay Server Implementation

Using the `websockets` library and `cryptography` for the relay server.

```python
import asyncio
import websockets
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash
from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from base64 import b64encode, b64decode
import os

# Load server private key
with open("server_private_key.pem", "rb") as f:
    server_private_key = serialization.load_pem_private_key(
        f.read(),
        password=None
    )

# Function to handle WebSocket connections
async def echo(websocket, path):
    try:
        # Receive the client's public key
        client_public_key_pem = await websocket.recv()
        client_public_key = serialization.load_pem_public_key(client_public_key_pem.encode('utf-8'))

        # Perform ECDH key exchange
        shared_key = server_private_key.exchange(ec.ECDH(), client_public_key)

        # Derive AES key from the shared key
        aes_key = HKDF(
            algorithm=hashes.SHA256(),
            length=32,
            salt=None,
            info=b'handshake data'
        ).derive(shared_key)

        while True:
            encrypted_message = await websocket.recv()
            nonce, ciphertext, tag = [b64decode(x) for x in encrypted_message.split(":")]

            # Decrypt the message
            aesgcm = AESGCM(aes_key)
            message = aesgcm.decrypt(nonce, ciphertext + tag, None).decode('utf-8')

            print(f"Received: {message}")
            response = f"Echo: {message}"

            # Encrypt response
            nonce = os.urandom(12)
            aesgcm = AESGCM(aes_key)
            ciphertext = aesgcm.encrypt(nonce, response.encode('utf-8'), None)
            encrypted_response = b64encode(nonce).decode('utf-8') + ":" + b64encode(ciphertext[:-16]).decode('utf-8') + ":" + b64encode(ciphertext[-16:]).decode('utf-8')

            await websocket.send(encrypted_response)
    except websockets.exceptions.ConnectionClosed as e:
        print(f"Connection closed: {e}")

# Start the WebSocket server
start_server = websockets.serve(echo, "localhost", 8765)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
```

#### 3. Client Implementation

The client will use the server's public key to perform ECDH key exchange and then use the derived AES key for encryption.

```python
import asyncio
import websockets
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from base64 import b64encode, b64decode
import os

# Load server public key
with open("server_public_key.pem", "rb") as f:
    server_public_key = serialization.load_pem_public_key(f.read())

async def hello():
    uri = "ws://localhost:8765"

    # Generate client private key
    client_private_key = ec.generate_private_key(ec.SECP256R1())
    client_public_key = client_private_key.public_key()

    # Perform ECDH key exchange
    shared_key = client_private_key.exchange(ec.ECDH(), server_public_key)

    # Derive AES key from the shared key
    aes_key = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=None,
        info=b'handshake data'
    ).derive(shared_key)

    async with websockets.connect(uri) as websocket:
        # Send client's public key to the server
        client_public_key_pem = client_public_key.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        ).decode('utf-8')
        await websocket.send(client_public_key_pem)

        message = "Hello, Server!"
        print(f"Sending: {message}")

        # Encrypt the message
        nonce = os.urandom(12)
        aesgcm = AESGCM(aes_key)
        ciphertext = aesgcm.encrypt(nonce, message.encode('utf-8'), None)
        encrypted_message = b64encode(nonce).decode('utf-8') + ":" + b64encode(ciphertext[:-16]).decode('utf-8') + ":" + b64encode(ciphertext[-16:]).decode('utf-8')

        await websocket.send(encrypted_message)

        encrypted_response = await websocket.recv()
        nonce, ciphertext, tag = [b64decode(x) for x in encrypted_response.split(":")]

        # Decrypt the response
        aesgcm = AESGCM(aes_key)
        response = aesgcm.decrypt(nonce, ciphertext + tag, None).decode('utf-8')
        print(f"Received: {response}")

asyncio.get_event_loop().run_until_complete(hello())
```

### Summary

1. **Key Generation**: Generate ECC key pairs for the server and client.
2. **Relay Server**: Implement the server to handle WebSocket connections, perform ECDH key exchange, and use AES-GCM for symmetric encryption.
3. **Client**: Implement the client to perform ECDH key exchange and use AES-GCM for encryption.

This setup ensures that WebSocket communication is encrypted using keys derived from the public-private key pairs, aligning with NOSTR's encryption scheme and avoiding centralized CAs. 
 Love it! 
 Could argue it also uses http (sort of) most/all of the components need to be there for proper websocket connections.