This project implements a decentralized threshold signing service leveraging the Olaf protocol. The service operates off-chain, providing a secure and cost-effective alternative to on-chain threshold signature mechanisms within the Stellar ecosystem.
Each participant in the threshold signing group runs a browser-based application that performs decentralized key generation and signing operations through the Olaf threshold signature protocol, compiled to WebAssembly (WASM). The networking layer is built using JavaScript and libp2p for peer discovery and communication.
The service implements proof of possession mechanisms to ensure that only the legitimate owner of a Stellar address can register it with the relay server and establish connections with other peers. This prevents address spoofing and ensures secure peer-to-peer communication.
Transport: Peers connect to a relay server using WebSockets.
Discovery:
- When a peer connects, it must prove ownership of its Stellar address through a cryptographic challenge-response protocol
- The peer requests a challenge from the relay server, signs it with their private key, and submits the proof
- The relay server verifies the signature using the address's public key and stores the mapping: Address β Peer ID
- Peers can query the relay server with a known Stellar address to obtain the corresponding Peer ID
Direct Peer Communication: Once a Peer ID is obtained, the peer establishes a WebRTC connection using libp2p. Before communication begins, both peers perform mutual proof of possession to verify each other's identity. All protocol messages are exchanged via this secure, direct P2P channel.
The cryptographic logic is written in Rust and compiled to WebAssembly (WASM) for browser use.
Core Functionality:
- Distributed Key Generation (DKG) to derive a shared threshold public key
- Threshold Signing for signing Stellar transactions
State Management: Key shares and protocol state are stored in browser-local storage (e.g., IndexedDB).
The service implements proof of possession mechanisms to ensure secure peer-to-peer communication and prevent address spoofing attacks.
When a peer registers their Stellar address with the relay server:
- Challenge Generation: The peer requests a cryptographic challenge from the relay server
- Challenge Signing: The peer signs the challenge using their private key corresponding to the Stellar address
- Signature Verification: The relay server verifies the signature using the address's public key
- Registration: Only upon successful verification is the address registered and mapped to a Peer ID
This ensures that only the legitimate owner of a Stellar address can register it with the relay server.
When two peers establish a direct connection:
- Initiator Challenge: The connecting peer requests a challenge from the target peer
- Initiator Response: The connecting peer signs the challenge and sends their response
- Mutual Challenge: The target peer generates their own challenge for the initiator
- Mutual Verification: Both peers verify each other's signatures
- Connection Established: Only after mutual verification is the connection considered secure
This mutual verification process ensures that both parties can confirm each other's identity before any sensitive protocol messages are exchanged.
- Signature Algorithm: Uses Ed25519 signatures for compatibility with Stellar
- Challenge Format: Random 32-byte challenges generated using cryptographically secure random number generation
- Signature Verification: Leverages the
stellar-baselibrary for signature verification - Expiration: Challenges expire after 5 minutes to prevent replay attacks
This milestone establishes the foundational networking layer where two browsers can connect to a relay server, register with a Stellar address, discover each other, and exchange messages directly over WebRTC using libp2p.
- β LibP2P relay server with WebSocket transport
- β Peer discovery system using Stellar addresses with proof of possession
- β Cryptographic challenge-response protocol for address registration
- β Address β Peer ID mapping and storage in relay server with verification
- β Browser-based LibP2P client with WebRTC transport
- β Direct peer communication via WebRTC using libp2p
- β Mutual proof of possession for peer-to-peer connections
- β Peer-to-peer message exchange protocol with identity verification
- β Docker containerization for relay server and client
- β Comprehensive automated tests using Playwright
- β Inline documentation and testing guide
Two browsers will exchange messages and successfully produce a shared threshold public key using the Olaf DKG protocol compiled to WASM.
Planned Deliverables:
- Rust to WebAssembly compilation of Olaf DKG protocol
- Integration of DKG protocol with browser client
- Shared threshold public key generation
- Browser-local storage for key shares and protocol state
Two browsers will exchange messages and produce a valid threshold signature over a given Stellar transaction using the Olaf protocol compiled to WASM.
Planned Deliverables:
- Threshold signature generation for Stellar transactions
- Complete tutorial and article explaining the service
- Production-ready implementation
- Node.js installed
- npm or yarn package manager
npm test-
Start the relay server:
npm run relay
-
Start the first client application on port 5173:
npm start
-
Start the second client application on port 5174 (in a new terminal):
npm start
-
Follow the manual testing steps below (same process for both Docker and non-Docker)
- Docker and Docker Compose installed
This project provides four Docker services:
-
relay-server- The LibP2P relay server- Runs on port 8080
- Handles peer discovery and key-value storage
- Must be started before the clients
-
client-a- The first client server- Runs on port 5173
- Serves the browser-based client application for the first participant
- Depends on the relay server being available
-
client-b- The second client server- Runs on port 5174
- Serves the browser-based client application for the second participant
- Depends on the relay server being available
-
test- The automated test runner- Uses Playwright for browser automation
- Runs integration tests against the relay and client services
- Exits after test completion
Before running the test scenario, ensure Docker is properly set up:
-
Install Docker Desktop:
- Download from: https://www.docker.com/products/docker-desktop/
- Follow the installation instructions for your operating system
-
Start Docker Desktop:
- On macOS: Open Docker Desktop from Applications folder or run
open -a Dockercommand from the terminal - On Windows: Start Docker Desktop from Start menu
- On Linux: Start Docker daemon:
sudo systemctl start docker
- On macOS: Open Docker Desktop from Applications folder or run
-
Verify Docker is running:
docker ps
You should see Docker version information and an empty container list.
npm run test:dockerStart the relay-server and the two clients:
docker compose up -d-
Open the first browser window/tab:
- Navigate to
http://localhost:5173 - Wait for the "Connected to relay" message
- In the "Stellar Address" input field, enter a Stellar address. For example:
GDHMW6QZOL73SHKG2JG4MFD7X5Z6Y6Q67H5OONPKUQYRCHCLKGT5BAJZ - In the "Secret Key" input field, enter the corresponding secret key (Stellar format starting with S, or hex 32 bytes). For example:
SDVX4LJGH7FJ5RLYMXKQTKG3G4NZ2F7A3BW3NV6HS4LQMQI6EQN6Z2QF - Click "Store Stellar Address with Proof of Possession"
- Verify you see: "Address registered with proof of possession!"
- Navigate to
-
Open a second browser window/tab:
- Navigate to
http://localhost:5174 - Wait for the "Connected to relay" message
- In the "Stellar Address" input field, enter a Stellar address. For example:
GBQNR2WMGKP45RIKZTJLGKDT4OHK5CQFRR5FUIZQMQVWYFB4UI5JJM2V - In the "Secret Key" input field, enter the corresponding secret key (Stellar format starting with S, or hex 32 bytes). For example:
SC4VZJFT3QKBCFROB6GGQCH6S27ECEGQMK5XQHY6JJNQQ6QQH25HN2Z4 - Click "Store Stellar Address with Proof of Possession"
- Verify you see: "Address registered with proof of possession!"
- Navigate to
-
Connect to the first peer (in the second browser):
- In the "Stellar Address" input field, enter:
GDHMW6QZOL73SHKG2JG4MFD7X5Z6Y6Q67H5OONPKUQYRCHCLKGT5BAJZ - Click "Find Peer & Connect"
- Wait for the other peer to accept the connection
- In the "Stellar Address" input field, enter:
-
Accept the connection (in the first browser):
- In the first browser window, you should see a connection permission request
- Click "Accept" to allow the connection
-
Autenticate the connection:
- Verify you see the message "Mutual connection proof of possession completed!" or "Mutual connection challenge verified - connection established!"
- Both browser windows should show the peer connection in "Active Connections"
- The "Message" section should now be visible in both windows
-
Send a message from the first browser:
- In the first browser window, type a message in the "Message" field
- Click "Send"
- Verify the message is received in the second browser window
-
Send a message from the second browser:
- In the second browser window, type a different message
- Click "Send"
- Verify the message is received in first browser window
-
Shutdown the relay server:
Ctrl+Cin the relay server terminal (non Docker) ordocker compose stop relay-server- Verify that both peers are still able to exchange messages
docker compose down --rmi all --volumes --remove-orphansSimply stop the processes with Ctrl+C in the terminal windows where they are running.
This project is licensed under the GPLv3 License - see the LICENSE file for details.