Skip to content
Open

V2 #19

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
53b5ea4
Start of v2
iffy Oct 24, 2025
e246ca1
Auth and notes work
iffy Oct 27, 2025
bbc8e6b
Message sending works
iffy Oct 27, 2025
80de203
Multiple recipients
iffy Oct 28, 2025
6826bdb
Back to single recipient messages
iffy Oct 28, 2025
fe21a75
Chunks work
iffy Oct 28, 2025
f924394
Remove known-pubkey concept
iffy Oct 28, 2025
2a2cb4b
Finish serialization/deserialization
iffy Oct 28, 2025
ef76093
Remove v1
iffy Oct 28, 2025
1f33915
Better changelog entry
iffy Oct 28, 2025
cf5c9e2
CI
iffy Oct 28, 2025
9daadac
Fix template path
iffy Oct 28, 2025
3df2815
CI
iffy Oct 28, 2025
9553ea7
Add credentials field for future use
iffy Oct 29, 2025
ed8c21d
duplicate note keys
iffy Oct 29, 2025
922230c
Start of functional tests, but there's a SIGSEGV
iffy Oct 29, 2025
4ddde1c
Before reply-returning
iffy Oct 29, 2025
883c7d9
It's not pretty, but it works
iffy Oct 29, 2025
d16bc3a
Run functional tests
iffy Oct 29, 2025
b2e6f3e
Fix test runner
iffy Oct 29, 2025
4f95e3a
Not sure cli is worth the time
iffy Oct 30, 2025
9d1c6fd
Functional tests work
iffy Oct 30, 2025
24328bf
Fix test running
iffy Oct 30, 2025
8fc7642
Add hashing challenge to clients
iffy Oct 31, 2025
837647c
Add a timestamp to the challenge
iffy Oct 31, 2025
38733ac
Add sending/storage limits, ChunksPresent command, stats page
iffy Nov 4, 2025
662a773
Add password auth to stats page
iffy Nov 4, 2025
b883746
Fix docker build
iffy Nov 4, 2025
fba5f23
Test same key
iffy Nov 4, 2025
45507e8
cli
iffy Nov 4, 2025
6811102
ADMIN_USERNAME and ADMIN_PWHASH fully supported now
iffy Nov 21, 2025
a165d02
Format stats numbers with commas
iffy Nov 21, 2025
7272d36
Fix error on empty stats
iffy Nov 21, 2025
33fcac4
work on mounted volumes
iffy Nov 21, 2025
e7a4db2
More stats
iffy Nov 21, 2025
19192e9
A little effort given to refusing invalid public keys
iffy Nov 21, 2025
e23623e
Move to subdir for better importing
iffy Nov 25, 2025
b753c0c
Bring v1 back
iffy Nov 25, 2025
1f9b065
RelayMessages have a resp_id to associate them with RelayCommand that…
iffy Dec 3, 2025
7e2558f
Merge pull request #21 from buckets/add-ids
iffy Dec 3, 2025
8c6f7a3
Notes must exist before you can fetch them, now.
iffy Dec 3, 2025
48c1eac
Make sure fetch error has resp_id
iffy Dec 3, 2025
0360dff
Merge pull request #22 from buckets/fetch-must-exist
iffy Dec 3, 2025
76fa446
Rename public/secret
iffy Dec 3, 2025
b560ba2
Chunks are gone in favor of overwriteable Data messages
iffy Dec 10, 2025
b320c9e
Merge pull request #23 from buckets/v2-keyed-msg-instead-of-chunks
iffy Dec 10, 2025
1652944
Improved data storage efficiency of multi-recipient messages
iffy Dec 12, 2025
56b91ed
Fix stats
iffy Dec 12, 2025
69d868f
Fix test assertions
iffy Dec 12, 2025
a26f42b
Merge pull request #24 from buckets/v2-efficient-storage
iffy Dec 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 12 additions & 28 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,32 @@ jobs:
strategy:
matrix:
version:
- binary:1.6.18
- binary:2.2.4
os:
- ubuntu-latest
# - macOS-latest
steps:
- uses: actions/checkout@v1
- uses: iffy/install-nim@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
version: ${{ matrix.version }}
- name: Update nimble
run: nimble install -y nimble
#----------------------------------------
# multi user mode (default)
#----------------------------------------
- name: Install
run: nimble install -y
- name: Build bins
run: nimble multiuserbins
- name: Run tests
run: |
export PATH="${PATH}:$(pwd)/bin"
nimble test
- name: Command-line tests
- name: Install libsodium
run: |
export PATH="${PATH}:$(pwd)/bin"
tests/func1.sh
#----------------------------------------
# single user mode
#----------------------------------------
- name: Install (single user mode)
run: nimble singleuserbins
- name: Run tests (single user mode)
sudo apt update -q
sudo apt install -y libsodium-dev
- name: Install pkger
run: nimble install -y https://github.com/iffy/pkger/
- name: Install deps
run: pkger fetch
- name: Run tests
run: |
export PATH="${PATH}:$(pwd)/bin"
nimble -d:relaysingleusermode test
export SHOW_LOGS=1
tests/all.sh

docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: docker build --file docker/singleuser.Dockerfile .
- run: docker build --file docker/multiuser.Dockerfile .
- run: docker build --file docker/Dockerfile .

6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ bucketsrelay.sqlite
_tests
!TODO
fly.toml
relay.sqlite
libs
*.keys
*.sqlite
tests/bin
*.sqlite-journal
217 changes: 107 additions & 110 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,132 +6,79 @@

This repository contains the open source code for the [Buckets](https://www.budgetwithbuckets.com) relay server, which allows users to share budget data between their devices in an end-to-end encrypted way.

## Quickstart - single user mode
You can use the publicly available relay at <https://relay.budgetwithbuckets.com>

If you want to run the relay on your own computer with only one user account, do the following:
## Quickstart w/ Docker/Podman

1. Install [Nim](https://nim-lang.org/)
2. Build the relay:
If you want to run the relay on with docker:

1. Get the code:

```
git clone https://github.com/buckets/relay.git buckets-relay.git
cd buckets-relay.git
nimble singleuserbins
```

3. Run the server:
2. Build the image

```sh
RELAY_USERNAME=someusername
RELAY_PASSWORD=somepassword
bin/brelay server
```
docker build -f docker/Dockerfile -t buckets/relay .
```

This will launch the relay on the default port. Run `brelay --help` for more options.

## Multi-user mode

If instead of `nimble singleuserbins` you run `nimble multiuserbins` the server will be built in multi-user mode.

Register users via `brelay adduser ...` or through the web interface.
3. Run it:

Registration-related emails are sent through [Postmark](https://postmarkapp.com/). Set `POSTMARK_API_KEY` to your Postmark key to use it. Otherwise, disable emails with `-d:nopostmark`.
```
docker run -it --rm -p 8080:8080 buckets/relay
```

Users can authenticate with their Buckets license if you set an environment variable `AUTH_LICENSE_PUBKEY=<A PEM FORMATTED PUBKEY>`
Read `docker/Dockerfile` to get an idea of how to build it yourself if you'd like.

## Security

- You should ensure that connections to this relay server are made with TLS.
- This relay server can see all traffic, so clients should encrypt data intended for other clients.
- Clients should also authenticate each other through the relay and not trust the authentication done by this server.

## Development

To run the server locally:

```sh
nimble run brelay server
```

## Deployment to fly.io

If you'd like to run a relay server on [fly.io](https://fly.io/), sign up for the service then do one of the following. If you'd like to host somewhere else, you could use the Dockerfiles in [docker/](./docker/) as a starting point.

### Single-user mode

```sh
fly launch --dockerfile docker/singleuser.Dockerfile
fly secrets set RELAY_USERNAME='someusername' RELAY_PASSWORD='somepassword'
```

| Variable | Description |
|---|---|
| `RELAY_USERNAME` | Username or email you'll use to authenticate to the relay. |
| `RELAY_PASSWORD` | Password you'll use to authenticate to the relay. |

### Multi-user mode

```sh
fly launch --dockerfile docker/multiuser.Dockerfile
fly secrets set POSTMARK_API_KEY='your key' AUTH_LICENSE_PUBKEY='the key' LICENSE_HASH_SALT='choose something here'
```

| Variable | Description |
|---|---|
| `POSTMARK_API_KEY` | API key from [Postmark](https://postmarkapp.com/) |
| `AUTH_LICENSE_PUBKEY` | RSA public key of Buckets licenses. If empty, license authentication is disabled. |
| `LICENSE_HASH_SALT` | A hashing salt for the case when a license needs to be disabled. Any random, but consistent value is fine. |

## Protocol

Relay clients communicate with the relay server using the following protocol. See [./src/bucketsrelay/proto.nim](./src/bucketsrelay/proto.nim) for more information, and [./src/bucketsrelay/stringproto.nim](./src/bucketsrelay/stringproto.nim) for encoding details.
Relay clients communicate with the relay server using the following protocol. See [./src/proto2.nim](./src/proto2.nim) for more information.

In summary, devices connect with websockets and exchange messages. Messages sent from client to server are called commands. Messages sent from server to client are called events.
In summary, devices connect with websockets and exchange messages. Messages sent from client to server are called *commands*. Messages sent from server to client are called *events*.

### Authentication

Clients authenticate with the server in two ways:

1. With a relay account via HTTP Basic authentication. This is used to group together a user's various clients and prevent abuse.
2. With a public/private key. This is used to identify and connect individual clients.

A single relay account can have multiple public/private keys; typically one for each device.
Clients authenticate with the server using a public/private key. A single person may have multiple public/private keys; typically one for each device.

### Client Commands

Clients send the following commands:

| Command | Description |
|--------------|-------------|
| `Iam` | In response to a `Who` event, proves that this client has the private key for their public key. |
| `Connect` | Asks the server for a connection to another client identified by the client's public key. |
| `Disconnect` | Asks the server to disconnect a connection to another client. |
| `SendData` | Sends bytes to another client. |
| Command | Description |
|----------------|-------------|
| `Iam` | In response to a `Who` event, proves that this client has the private key and does some spam mitigation |
| `PublishNote` | Send a few bytes to another client addressed by topic (good for key exchange) |
| `FetchNote` | Request a note addressed by topic |
| `SendData` | Store/forward bytes to other clients, addressed by relay-authenticated public keys |

### Server Events

The relay server sends the following events:

| Event | Description |
|-----------------|-------------|
| `Who` | Challenge for authenticating a client's public/private keys |
| `Authenticated` | Sent when a client successfully completes authentication |
| `Connected` | Sent when a client has connected to another client |
| `Disconnected` | Sent when a client has been disconnected from another client |
| `Data` | Data payload from another, connected client |
| `Entered` | Sent when a client within the same user account has authenticated to the relay |
| `Exited` | Sent when a client within the same user account has disconnected from the relay |
| `ErrorEvent` | Sent when errors happen with authentication, connection or message sending |
| Event | Description |
|-----------------|------------------------------------|
| `Okay` | Sent when certain commands succeed |
| `Error` | Sent when commands fail |
| `Who` | Challenge for authenticating a client's public/private keys and spam mitigation |
| `Note` | Data payload of a note requested by `FetchNote` |
| `Data` | Data payload from another client, addressed by relay-authenticated public key |

### Sequences and Usage

#### Authentication
### Authentication

Authentication happens like this:

1. On connection, server sends `Who(challenge=ABCD...)`
2. Client responds with `Iam(pubkey=MYPK..., signature=SIGN...)`
3. If the signature is correct, server sends `Authenticated`
2. Client responds with a signed PoW hash `Iam(pubkey=MYPK..., signature=SIGN...)`
3. If the signature is correct, server sends `Okay(cmd=Iam)`

```
Client Relay
Expand All @@ -142,49 +89,99 @@ Client Relay
│ Iam │
├────────────────►│
│ │
Authenticated
Okay
│◄────────────────┤
│ │
```

#### Client-to-client connection
### Data

There are 2 ways clients can exchange data:

1. Notes - public notes that are accessed by knowing the note *topic*. Notes are a good way to do key exchange. Notes expire after a short time.
2. Messages - ordered, stored-and-forwarded messages sent from one client to another client. These are automatically sent to a client upon connection, and deleted when sent. Messages expire after a while. Messages with a non-blank key will overwrite undelivered messages with the same key.

Build relay clients with the understanding that all forms of exchanging data through this relay are unreliable.

#### Notes

After authenticating, clients connect to each other and send data like this:
1. Alice sends `PublishNote(topic=apple, data=something)`
2. Bob sends `FetchNote(topic=apple)`
3. Server sends to Bob `Note(topic=apple, data=something)`

1. Alice sends `Connect(pubkey=BOBPK)`
2. Bob sends `Connect(pubkey=ALICEPK)`
3. Server sends Alice `Connected(pubkey=BOBPK)`
4. Server sends Bob `Connected(pubkey=ALICEPK)`
5. Alice sends data with `SendData(data=hello, pubkey=BOBPK)`
6. Server sends Bob data with `Data(data=hello, sender=ALICEPK)`
```
Alice Relay Bob
│ │ │
├───────Authenticated─|─Authenticated──────┤
│ │ │
│ PublishNote(apple) | │
├────────────────────►│ FetchNote(apple) |
│ │◄───────────────────┤
│ │ │
│ │ Note(apple) │
│ │───────────────────►│
│ │ │
```

#### Messages

1. Alice sends `SendData(dst=BOBPK, data=hello)`
2. Server sends to Bob `Data(src=ALICEPK, data=hello)`

```
Alice Relay Bob
│ │ │
├───Authenticated─┼─Authenticated───┤
│ │ │
│Connect(Bob) │ │
├────────────────►│ Connect(Alice) │
│ │◄────────────────┤
│ │ │
│ Connected(Bob) │ Connected(Alice)│
│◄────────────────┼────────────────►│
│ │ │
│SendData(Bob) │ │
│ SendData(Bob) │ │
├────────────────►│ Data(Alice) │
│ ├────────────────►│
│ │ │
```

#### Same-user presence notifications
#### Messages with keys

The relay server will announce client presence to all clients that use the same HTTP Auth credentials. For example, if both Alice and Bob signed in as `alicenbob@example.com` the following would happen:
1. Alice sends `SendData(dst=BOBPK, key=apple, data=core)`
2. Alice sends `SendData(dst=BOBPK, key=banana, data=boat)`
3. Alice sends `SendData(dst=BOBPK, key=apple, data=pie)` (replacing prior `key=apple` message)
4. Bob connects
5. Server sends to Bob `Data(src=ALICEPK, key=banana, data=boat)`
6. Server sends to Bob `Data(src=ALICEPK, key=apple, data=pie)`

1. Alice finishes authenticating
2. Bob finishes authenticating
3. Server sends Alice `Entered(pubkey=BOBPK)`
4. Server sends Bob `Entered(pubkey=ALICEPK)`
5. Alice disconnects
6. Server sends Bob `Exited(pubkey=ALICEPK)`
```
Alice Relay Bob
│ │ │
├───Authenticated─┤ │
│ │ │
│ SendData(Bob,1) │ │
├────────────────►│ │
│ SendData(Bob,2) │ │
├────────────────►│ │
│ SendData(Bob,1) │ │
├────────────────►│ │
│ │ Data(Alice, 2) │
│ ├────────────────►│
│ │ Data(Alice, 1) │
│ ├────────────────►│
│ │ │
```

#### Chunks

1. Alice sends `StoreChunk(dst=[BOBPK], key=apple, val=seed)`
2. Bob sends `GetChunks(src=ALICEPK, keys=[apple])`
3. Server sends `Chunk(src=ALICEPK, key=apple, val=seed)`

```
Alice Relay Bob
│ │ │
├───────Authenticated─|─Authenticated──────┤
│ │ │
│ StoreChunk(apple) | │
├────────────────────►│ GetChunks([apple]) |
│ │◄───────────────────┤
│ │ │
│ │ Chunk(apple) │
│ │───────────────────►│
│ │ │
```
Loading