Skip to content

Commit 464cc05

Browse files
book: add FROST Server section (#811)
* book: add FROST Server section * book: update Ywallet demo section * fill TODOs * Apply suggestions from code review Co-authored-by: natalie <[email protected]> * Apply suggestions from code review --------- Co-authored-by: natalie <[email protected]>
1 parent 602157a commit 464cc05

File tree

3 files changed

+563
-97
lines changed

3 files changed

+563
-97
lines changed

book/src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- [FROST with Zcash](zcash.md)
1414
- [Technical Details](zcash/technical-details.md)
1515
- [Ywallet Demo](zcash/ywallet-demo.md)
16+
- [FROST Server](zcash/server.md)
1617
- [Terminology](terminology.md)
1718
- [Developer Documentation](dev.md)
1819
- [List of Dependencies for Audit](dev/frost-dependencies-for-audit.md)

book/src/zcash/server.md

+363
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
# ZF FROST Server (frostd)
2+
3+
One challenge for using FROST is allowing participants to communicate securely
4+
with one another. Devices are usually behind firewalls and NATs, which make
5+
direct connections hard.
6+
7+
To mitigate this issue and to make it easier to use FROST, the ZF FROST Server
8+
(frostd) was created. It is a JSON-HTTP server with a small API to allow
9+
participants to create signing sessions and to communicate with one another.
10+
11+
It works like this:
12+
13+
- Clients (coordinator or participants) authenticate to the server using a key
14+
pair, which will likely be the same key pair they use to end-to-end encrypt
15+
messages.
16+
- The Coordinator creates a session, specifying the public keys of the
17+
participants.
18+
- Participants list sessions they're participating in, and choose the proceed
19+
with the signing session.
20+
- Coordinator and Participants run the FROST protocol, end-to-end encrypting
21+
messages and sending them to the server.
22+
- The Coordinator closes the session.
23+
24+
Note that the server doesn't really care about the particular key pair being
25+
used; it is only used to enforce who can send messages to who.
26+
27+
## Compiling, Running and Deploying
28+
29+
You will need to have [Rust and
30+
Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html)
31+
installed. Run:
32+
33+
```
34+
cargo install --git https://github.com/ZcashFoundation/frost-zcash-demo.git --locked frostd
35+
```
36+
37+
The `frostd` binary will be installed [per `cargo`
38+
config](https://doc.rust-lang.org/cargo/commands/cargo-install.html#description)
39+
and it will likely be in your `$PATH`, so you can run by simply running
40+
`frostd`.
41+
42+
To deploy the FROST Server, **you need TLS/HTTPS certificates**. We strongly
43+
recommend using a reverse proxy such as `nginx` to handle TLS and to also add
44+
denial of service protections. In that case, use the `--no-tls-very-insecure`
45+
flag in `frostd` and make `nginx` connect to it (see example config below).
46+
47+
If you want to expose `frostd` directly, use the `--tls-cert` and
48+
`--tls-key` to specify the paths of the PEM-encoded certificate and key. You can
49+
use [Let's Encrypt](https://letsencrypt.org/) to get a free certificate.
50+
51+
52+
### Local Testing
53+
54+
For local testing, you can use the [`mkcert`
55+
tool](https://github.com/FiloSottile/mkcert). Install it and run:
56+
57+
```
58+
mkcert -install
59+
mkcert localhost 127.0.0.1 ::1
60+
```
61+
62+
Then start the server with:
63+
64+
```
65+
frostd --tls-cert localhost+2.pem --tls-key localhost+2-key.pem
66+
```
67+
68+
69+
### Sample nginx Config
70+
71+
This is a sample nginx config file tested in a Ubuntu deployment (i.e. it
72+
assumes it's in a `http` block and it's included by `/etc/nginx/nginx.conf`);
73+
copy it to `/etc/nginx/sites-enabled/frostd` and run `sudo service nginx
74+
restart`.
75+
76+
The config assumes the certificates were copied to `/etc/ssl`.
77+
78+
79+
```
80+
limit_req_zone $binary_remote_addr zone=challenge:10m rate=30r/m;
81+
limit_req_zone $binary_remote_addr zone=create:10m rate=10r/m;
82+
limit_req_zone $binary_remote_addr zone=other:10m rate=240r/m;
83+
limit_conn_zone $binary_remote_addr zone=addr:10m;
84+
85+
server {
86+
listen 443 ssl;
87+
listen [::]:443 ssl;
88+
ssl_certificate /etc/ssl/localhost+2.pem;
89+
ssl_certificate_key /etc/ssl/localhost+2-key.pem;
90+
ssl_protocols TLSv1.3;
91+
ssl_ecdh_curve X25519:prime256v1:secp384r1;
92+
ssl_prefer_server_ciphers off;
93+
94+
server_name localhost;
95+
96+
client_body_timeout 5s;
97+
client_header_timeout 5s;
98+
99+
location / {
100+
proxy_pass http://127.0.0.1:2744;
101+
limit_req zone=other burst=5;
102+
limit_conn addr 10;
103+
}
104+
location /challenge {
105+
proxy_pass http://127.0.0.1:2744/challenge;
106+
limit_req zone=challenge burst=3;
107+
limit_conn addr 10;
108+
}
109+
location /create_new_session {
110+
proxy_pass http://127.0.0.1:2744/create_new_session;
111+
limit_req zone=create burst=3;
112+
limit_conn addr 10;
113+
}
114+
}
115+
```
116+
117+
## API
118+
119+
The API uses JSON/HTTP. All requests should have `Content-Type:
120+
application/json`. Errors are returned with status code 500 and the content
121+
body will have a JSON such as:
122+
123+
```
124+
{ code: 1, msg: "error message" }
125+
```
126+
127+
The
128+
[codes](https://github.com/ZcashFoundation/frost-zcash-demo/blob/548a8a7329c6eed8180464662f430d12cd71dfcc/frostd/src/lib.rs#L95-L98)
129+
are:
130+
131+
```
132+
pub const INVALID_ARGUMENT: usize = 1;
133+
pub const UNAUTHORIZED: usize = 2;
134+
pub const SESSION_NOT_FOUND: usize = 3;
135+
pub const NOT_COORDINATOR: usize = 4;
136+
```
137+
138+
139+
### Usage flow
140+
141+
For the Coordinator:
142+
143+
- Log in with `/challenge` and `/login`
144+
- Create a new signing session with `/create_new_session`
145+
- Wait for round 1 messages by repeatedly polling `/receive` each 2 seconds or longer
146+
- Send round 2 messages by using `/send`
147+
- Wait for round 2 message by repeatedly polling `/receive` each 2 seconds or longer
148+
- Close the session with `/close_session`
149+
150+
For Participants:
151+
152+
- Log in with `/challenge` and `/login`
153+
- Wait for signing sessions with `/list_sessions`, either by the user's request or by repeatedly
154+
polling each 10 seconds or longer
155+
- Get the session information with `/get_session_info`
156+
- Show the user the session information (who the participants are) to select which
157+
session (if more than one)
158+
- Send round 1 message by using `/send`
159+
- Wait for round 2 message by repeatedly polling `/receive` each 2 seconds or longer
160+
- Send round 2 message by using `/send`
161+
162+
```admonish info
163+
**Polling** is not optimal. The server will support a better mechanism in the
164+
future.
165+
```
166+
167+
```admonish info
168+
Selecting sessions is tricky. Ideally, the user should select what session
169+
to proceed by checking the message being signed; however, that is usually
170+
sent in Round 2. There are multiple ways to handle this:
171+
172+
- Simply show the users who are participants, hoping that is enough to
173+
disambiguate (we assume that concurrent signing sessions won't be that common)
174+
- Quietly proceed with all sessions, and only prompt the user after the message
175+
is received. (It's harmless to do round 1 of FROST even if the user might
176+
not have agreed to sign the message yet.)
177+
- Change the application so that the message is sent to the participants first
178+
(the server does not really care how the protocol is run).
179+
```
180+
181+
```admonish critical
182+
Always gather consent from the user by showing them the message before
183+
signing it.
184+
```
185+
186+
### `/challenge`
187+
188+
Input: empty
189+
190+
Sample output:
191+
192+
```
193+
{"challenge":"2c5cdb6d-a7db-470e-9e6f-2a7062532825"}
194+
```
195+
196+
Returns a challenge that the client will need to sign in order to authenticate.
197+
198+
### `/login`
199+
200+
To call `/login`, you will need to sign the challenge with XEdDSA, see
201+
[example](https://github.com/ZcashFoundation/frost-zcash-demo/blob/548a8a7329c6eed8180464662f430d12cd71dfcc/frostd/tests/integration_tests.rs#L443-L476).
202+
Sign the challenge UUID, converted to bytes.
203+
204+
205+
Input sample:
206+
207+
```
208+
{
209+
"challenge":"b771757e-085a-4a88-ab8f-28bd4ba67f3a",
210+
"pubkey":"f5bf1b8194e20ebdd28e662b1efcf1c5cd2aaade5d5dd83cf89b246b5492726b",
211+
"signature":"bba398d0963ab88e28134ad41c127eeee816a219838db01dd7bcd9d7fcd975f082330c134e6f7238580ba8434652aa116891495452d9048f5615e07f4ad6b204"
212+
}
213+
```
214+
215+
Output sample:
216+
217+
```
218+
{"access_token":"061a18ba-2c3c-4685-a79e-2c0c93000af5"}
219+
```
220+
221+
The returned access token must be included as a bearer token in an
222+
`Authorization` header; e.g. `Authorization: Bearer
223+
061a18ba-2c3c-4685-a79e-2c0c93000af5`.
224+
225+
Access tokens are currently valid for 1 hour. It's recommended to login at the
226+
beginning of each FROST session; log in again if it needs to take longer.
227+
228+
### `/logout`
229+
230+
Input: empty (it will logout the authenticated user)
231+
232+
Output: empty
233+
234+
Logs out, invalidating the access token. Note that access tokens expire after
235+
1 hour anyway.
236+
237+
### `/create_new_session`
238+
239+
Input sample:
240+
241+
```
242+
{
243+
"pubkeys": [
244+
"3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c",
245+
"edbd661dec0a9d0468b4a166a4afa80560d769f6bcb152fb8f4224059329a518"
246+
],
247+
message_count: 1,
248+
}
249+
```
250+
251+
Output sample:
252+
253+
```
254+
{"session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825"}
255+
```
256+
257+
Creates a new session. The requesting user will be the Coordinator, and the
258+
users with the hex-encoded public keys given in `pubkeys` will be the
259+
participants (which might or might not include the Coordinator itself).
260+
261+
The `message_count` parameter allows signing more than one message in the same
262+
signing session, which will save roundtrips. This does not impacts the server
263+
itself and is used to signal the participants (via `/get_session_info`).
264+
265+
### `/list_sessions`
266+
267+
Input: empty (it will list for the authenticated user)
268+
269+
Output sample:
270+
271+
```
272+
{"session_ids": ["2c5cdb6d-a7db-470e-9e6f-2a7062532825"]}
273+
```
274+
275+
List the sessions IDs of the session a participant is in.
276+
277+
### `/get_session_info`
278+
279+
Input sample:
280+
281+
```{"session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825"}```
282+
283+
Output sample:
284+
285+
```
286+
{
287+
"message_count": 1,
288+
"pubkeys": [
289+
"3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c",
290+
"edbd661dec0a9d0468b4a166a4afa80560d769f6bcb152fb8f4224059329a518"
291+
],
292+
"coordinator_pubkey": "3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c",
293+
}
294+
```
295+
296+
Returns information about the given session.
297+
298+
### `/send`
299+
300+
Input sample:
301+
302+
```
303+
{
304+
"session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825",
305+
"recipients": ["3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c"],
306+
"msg": "000102",
307+
}
308+
```
309+
310+
Output: empty
311+
312+
Sends a (hex-encoded) message to one or more participants. To send to the
313+
Coordinator, pass an empty list in `recipients` (**do not** use the
314+
Coordinator's public key, because that might be ambiguous if they're also a
315+
Participant).
316+
317+
```admonish critical
318+
Messages **MUST** be end-to-end encrypted between recipients. The server can't
319+
enforce this and if you fail to encrypt them then the server could read
320+
all the messages.
321+
```
322+
323+
### `/receive`
324+
325+
Input sample:
326+
327+
```
328+
{
329+
"session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825",
330+
"as_coordinator": false,
331+
}
332+
```
333+
334+
Output sample:
335+
336+
```
337+
{
338+
"msgs":[
339+
{
340+
"sender": "3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c",
341+
"msg": "000102",
342+
}
343+
]
344+
}
345+
```
346+
347+
Receives messages sent to the requesting user. Note that if a user is both a
348+
Coordinator and a Participant, it is not possible to distinguish if a message
349+
received from them was sent as Coordinator or as a Participant. This does not
350+
matter in FROST since this ambiguity never arises (Participants always receive
351+
messages from the Coordinator, and vice-versa, except during DKG where there is
352+
no Coordinator anyway).
353+
354+
### `/close_session`
355+
356+
Input sample:
357+
358+
```{"session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825"}```
359+
360+
Output: empty
361+
362+
Closes the given session. Only the Coordinator who created the session can close
363+
it. Sessions also expire by default after 24 hours.

0 commit comments

Comments
 (0)