Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

book: add FROST Server section #811

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [FROST with Zcash](zcash.md)
- [Technical Details](zcash/technical-details.md)
- [Ywallet Demo](zcash/ywallet-demo.md)
- [FROST Server](zcash/server.md)
- [Terminology](terminology.md)
- [Developer Documentation](dev.md)
- [List of Dependencies for Audit](dev/frost-dependencies-for-audit.md)
Expand Down
361 changes: 361 additions & 0 deletions book/src/zcash/server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,361 @@
# ZF FROST Server (frostd)

One challenge for using FROST is allowing participants to communicate securely
with one another. Devices are usually behind firewalls and NATs, which make
direct connections hard.

To mitigate this issue and to make it easier to use FROST, the ZF FROST Server
(frostd) was created. It is a JSON-HTTP server with a small API to allow
participants to create signing sessions and to communicate with one another.

It works like this:

- Clients (coordinator or participants) authenticate to the server using a key
pair, which will likely be the same key pair they use to end-to-end encrypt
messages.
- The Coordinator creates a session, specifying the public keys of the
participants.
- Participants list sessions they're participating in, and choose the proceed
with the signing session.
- Coordinator and Participants run the FROST protocol, end-to-end encrypting
messages and sending them to the server.
- The Coordinator closes the session.

Note that the server doesn't really care about the particular key pair being
used; it is only used to enforce who can send messages to who.

## Compiling, Running and Deploying

You will need to have [Rust and
Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html)
installed. Run:

```
cargo install --git https://github.com/ZcashFoundation/frost-zcash-demo.git --locked frostd
```

The `frostd` binary will be installed [per `cargo`
config](https://doc.rust-lang.org/cargo/commands/cargo-install.html#description)
and it will likely be in your `$PATH`, so you can run by simply running
`frostd`.

To deploy the FROST Server, **you need TLS/HTTPS certificates**. We strongly
recommend using a reverse proxy such as `nginx` to handle TLS and to also add
denial of service protections. In that case, use the `--no-tls-very-insecure`
flag in `frostd` and make `nginx` connect to it (see example config below).

If you want to expose `frostd` directly, use the `--tls-cert` and
`--tls-key` to specify the paths of the PEM-encoded certificate and key. You can
use [Let's Encrypt](https://letsencrypt.org/) to get a free certificate.


### Local Testing

For local testing, you can use the [`mkcert`
tool](https://github.com/FiloSottile/mkcert). Install it and run:

```
mkcert -install
mkcert localhost 127.0.0.1 ::1
```

Then start the server with:

```
frostd --tls-cert localhost+2.pem --tls-key localhost+2-key.pem
```


### Sample nginx Config

This is a sample nginx config file tested in a Ubuntu deployment (i.e. it
assumes it's in a `http` block and it's included by `/etc/nginx/nginx.conf`);
copy it to `/etc/nginx/sites-enabled/frostd` and run `sudo service nginx
restart`.

The config assumes the certificates were copied to `/etc/ssl`.


```
limit_req_zone $binary_remote_addr zone=challenge:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=create:10m rate=10r/m;
limit_req_zone $binary_remote_addr zone=other:10m rate=240r/m;
limit_conn_zone $binary_remote_addr zone=addr:10m;

server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/ssl/localhost+2.pem;
ssl_certificate_key /etc/ssl/localhost+2-key.pem;
ssl_protocols TLSv1.3;
ssl_ecdh_curve X25519:prime256v1:secp384r1;
ssl_prefer_server_ciphers off;

server_name localhost;

client_body_timeout 5s;
client_header_timeout 5s;

location / {
proxy_pass http://127.0.0.1:2744;
limit_req zone=other burst=5;
limit_conn addr 10;
}
location /challenge {
proxy_pass http://127.0.0.1:2744/challenge;
limit_req zone=challenge burst=3;
limit_conn addr 10;
}
location /create_new_session {
proxy_pass http://127.0.0.1:2744/create_new_session;
limit_req zone=create burst=3;
limit_conn addr 10;
}
}
```

## API

The API uses JSON/HTTP. All requests should have `Content-Type:
application/json`. Errors are returned with status code 500 and the content
body will have a JSON such as:

```
{ code: 1, msg: "error message" }
```

The codes are: (TODO: link to source code?)

```
pub const INVALID_ARGUMENT: usize = 1;
pub const UNAUTHORIZED: usize = 2;
pub const SESSION_NOT_FOUND: usize = 3;
pub const NOT_COORDINATOR: usize = 4;
```


### Usage flow

For the Coordinator:

- Log in with `/challenge` and `/login`
- Create a new signing session with `/create_new_session`
- Wait for round 1 messages by repeatedly polling `/receive` each 2 seconds or longer
- Send round 2 messages by using `/send`
- Wait for round 2 message by repeatedly polling `/receive` each 2 seconds or longer
- Close the session with `/close_session`

For Participants:

- Log in with `/challenge` and `/login`
- Wait for signing sessions with `/list_sessions`, either by the user's request or by repeatedly
polling each 10 seconds or longer
- Get the session information with `/get_session_info`
- Show the user the session information (who the participants are) to select which
session (if more than one)
- Send round 1 message by using `/send`
- Wait for round 2 message by repeatedly polling `/receive` each 2 seconds or longer
- Send round 2 message by using `/send`

```admonish info
**Polling** is not optimal. The server will support a better mechanism in the
future.
```

```admonish info
Selecting sessions is tricky. Ideally, the user should select what session
to proceed by checking the message being signed; however, that is usually
sent in Round 2. There are multiple ways to handle this:

- Simply show the users who are participant, hoping that is enough to
disambiguate (we assume that concurrent signing sessions won't be that common)
- Quietly proceed with all sessions, and only prompt the user after the message
is received. (It's harmless to do round 1 of FROST even if the user might
not have agreed to sign the message yet.)
- Change the application so that the message is sent to the participants first
(the server does not really care how the protocol is run).
```

```admonish critical
Always gather consent from the user by showing them the message before
signing it.
```

### `/challenge`

Input: empty

Sample output:

```
{"challenge":"2c5cdb6d-a7db-470e-9e6f-2a7062532825"}
```

Returns a challenge that the client will need to sign in order to authenticate.

### `/login`

To call `/login`, you will need to sign the challenge with XEdDSA, see example
(TODO: link). Sign the challenge UUID, converted to bytes.

TODO: fill valid signature

Input sample:

```
{
"challenge":"2c5cdb6d-a7db-470e-9e6f-2a7062532825",
"pubkey":"3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c",
"signature":""
}
```

Output sample:

```
{"access_token":"5a21ebf4-3f28-4198-bf3d-6174851adbb9"}
```

The returned access token must be included as a bearer token in an
`Authorization` header; e.g. `Authorization: Bearer
5a21ebf4-3f28-4198-bf3d-6174851adbb9`.

Access token are currently valid for 1 hour. It's recommended to login at the
beginning of each FROST session; log in again if it needs to take longer.

### `/logout`

Input: empty (it will logout the authenticated user)

Output: empty

Logs out, invalidating the access token. Note that access tokens expire after
1 hour anyway.

### `/create_new_session`

Input sample:

```
{
"pubkeys": [
"3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c",
"edbd661dec0a9d0468b4a166a4afa80560d769f6bcb152fb8f4224059329a518"
],
message_count: 1,
}
```

Output sample:

```
{"session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825"}
```

Creates a new session. The requesting user will be the Coordinator, and the
users with the hex-encoded public keys given in `pubkeys` will be the
participants (which might or might not include the Coordinator itself).

The `message_count` parameter allows signing more than one message in the same
signing session, which will save roundtrips. This does not impacts the server
itself and is used to signal the participants (via `/get_session_info`).

### `/list_sessions`

Input: empty (it will list for the authenticated user)

Output sample:

```
{"session_ids": ["2c5cdb6d-a7db-470e-9e6f-2a7062532825"]}
```

List the sessions IDs of the session a participant is in.

### `/get_session_info`

Input sample:

```{"session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825"}```

Output sample:

```
{
"message_count": 1,
"pubkeys": [
"3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c",
"edbd661dec0a9d0468b4a166a4afa80560d769f6bcb152fb8f4224059329a518"
],
"coordinator_pubkey": "3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c",
}
```

Returns information about the given session.

### `/send`

Input sample:

```
{
"session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825",
"recipients": ["3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c"],
"msg": "000102",
}
```

Output: empty

Sends a (hex-encoded) message to one or more participants. To send to the
Coordinator, pass an empty list in `recipients` (**do not** use the
Coordinator's public key, because that might be ambiguous if they're also a
Participant).

```admonish critical
Messages **MUST** be end-to-end encrypted to their recipients. The server can't
enforce this and if you will fail to encrypt them then the server could read
all the messages.
```

### `/receive`

Input sample:

```
{
"session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825",
"as_coordinator": false,
}
```

Output sample:

```
{
"msgs":[
{
"sender": "3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c",
"msg": "000102",
}
]
}
```

Receives messages sent to the requesting user. Note that if a user is both a
Coordinator and a Participant, it is not possible to distinguish if a message
received from them was sent as Coordinator or as a Participant. This does not
matter in FROST since this ambiguity never arises (Participants always receive
messages from the Coordinator, and vice-versa, except during DKG where there is
no Coordinator anyway).

### `/close_session`

Input sample:

```{"session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825"}```

Output: empty

Closes the given session. Only the Coordinator who created the session can close
it. Sessions also expire, by default after 24 hours.
Loading
Loading