expand docs (#371)

* expand docs

* Apply suggestions from code review

Co-authored-by: Deirdre Connolly <durumcrustulum@gmail.com>

* address comments, reflow some paragraphs

* tutorial mostly done

also cleaned up README example for extracting snippets; changed tests for consitency

* docs: add DKG; organize sections; remove stale docs

* run gencode

* Apply suggestions from code review

Co-authored-by: Pili Guerra <mpguerra@users.noreply.github.com>

---------

Co-authored-by: Deirdre Connolly <durumcrustulum@gmail.com>
Co-authored-by: Pili Guerra <mpguerra@users.noreply.github.com>
This commit is contained in:
Conrado Gouvea 2023-06-21 05:22:45 -03:00 committed by GitHub
parent 75aedce792
commit 30433ce029
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1193 additions and 675 deletions

View File

@ -10,7 +10,7 @@ concurrency:
on:
workflow_dispatch:
pull_request:
branches:
branches:
- main
paths:
# doc source files
@ -20,9 +20,9 @@ on:
# workflow definitions
- '.github/workflows/docs.yml'
push:
branches:
branches:
- main
env:
RUST_LOG: info
RUST_BACKTRACE: full
@ -62,14 +62,15 @@ jobs:
mdbook-version: '0.4.18'
# TODO: actions-mdbook does not yet have an option to install mdbook-mermaid https://github.com/peaceiris/actions-mdbook/issues/426
- name: Install mdbook
- name: Install plugins
run: |
cargo install mdbook-mermaid
cargo install mdbook-admonish
- name: Build FROST book
run: |
mdbook build book/
- name: Deploy FROST book to Firebase preview channel
uses: FirebaseExtended/action-hosting-deploy@v0
if: ${{ github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' }}

View File

@ -1,4 +1,4 @@
# FROST (Flexible Round-Optimised Schnorr Threshold signatures)
# ZF FROST (Flexible Round-Optimised Schnorr Threshold signatures)
Rust implementations of ['Two-Round Threshold Schnorr Signatures with FROST'](https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/).
@ -17,6 +17,10 @@ to compute a signature, and implements signing efficiency improvements described
[Schnorr21](https://eprint.iacr.org/2021/1375.pdf). Single-round signing with FROST is not
implemented here.
## Getting Started
Refer to the [ZF FROST book](https://frost.zfnd.org/).
## Status ⚠
The FROST specification is not yet finalized, and this codebase has not yet been audited or

View File

@ -3,4 +3,15 @@ authors = ["Zcash Foundation <frost@zfnd.org>"]
language = "en"
multilingual = false
src = "src"
title = "The FROST Book"
title = "The ZF FROST Book"
[preprocessor]
[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "2.0.1" # do not edit: managed by `mdbook-admonish install`
[output]
[output.html]
additional-css = ["./mdbook-admonish.css"]

353
book/mdbook-admonish.css Normal file
View File

@ -0,0 +1,353 @@
@charset "UTF-8";
:root {
--md-admonition-icon--note:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
--md-admonition-icon--abstract:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>");
--md-admonition-icon--info:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");
--md-admonition-icon--tip:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");
--md-admonition-icon--success:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>");
--md-admonition-icon--question:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");
--md-admonition-icon--warning:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");
--md-admonition-icon--failure:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>");
--md-admonition-icon--danger:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>");
--md-admonition-icon--bug:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");
--md-admonition-icon--example:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
--md-admonition-icon--quote:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
--md-details-icon:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
}
:is(.admonition) {
display: flow-root;
margin: 1.5625em 0;
padding: 0 1.2rem;
color: var(--fg);
page-break-inside: avoid;
background-color: var(--bg);
border: 0 solid black;
border-inline-start-width: 0.4rem;
border-radius: 0.2rem;
box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1);
}
@media print {
:is(.admonition) {
box-shadow: none;
}
}
:is(.admonition) > * {
box-sizing: border-box;
}
:is(.admonition) :is(.admonition) {
margin-top: 1em;
margin-bottom: 1em;
}
:is(.admonition) > .tabbed-set:only-child {
margin-top: 0;
}
html :is(.admonition) > :last-child {
margin-bottom: 1.2rem;
}
a.admonition-anchor-link {
display: none;
position: absolute;
left: -1.2rem;
padding-right: 1rem;
}
a.admonition-anchor-link:link, a.admonition-anchor-link:visited {
color: var(--fg);
}
a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover {
text-decoration: none;
}
a.admonition-anchor-link::before {
content: "§";
}
:is(.admonition-title, summary) {
position: relative;
min-height: 4rem;
margin-block: 0;
margin-inline: -1.6rem -1.2rem;
padding-block: 0.8rem;
padding-inline: 4.4rem 1.2rem;
font-weight: 700;
background-color: rgba(68, 138, 255, 0.1);
display: flex;
}
:is(.admonition-title, summary) p {
margin: 0;
}
html :is(.admonition-title, summary):last-child {
margin-bottom: 0;
}
:is(.admonition-title, summary)::before {
position: absolute;
top: 0.625em;
inset-inline-start: 1.6rem;
width: 2rem;
height: 2rem;
background-color: #448aff;
mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
-webkit-mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-size: contain;
content: "";
}
:is(.admonition-title, summary):hover a.admonition-anchor-link {
display: initial;
}
details.admonition > summary.admonition-title::after {
position: absolute;
top: 0.625em;
inset-inline-end: 1.6rem;
height: 2rem;
width: 2rem;
background-color: currentcolor;
mask-image: var(--md-details-icon);
-webkit-mask-image: var(--md-details-icon);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-size: contain;
content: "";
transform: rotate(0deg);
transition: transform 0.25s;
}
details[open].admonition > summary.admonition-title::after {
transform: rotate(90deg);
}
:is(.admonition):is(.note) {
border-color: #448aff;
}
:is(.note) > :is(.admonition-title, summary) {
background-color: rgba(68, 138, 255, 0.1);
}
:is(.note) > :is(.admonition-title, summary)::before {
background-color: #448aff;
mask-image: var(--md-admonition-icon--note);
-webkit-mask-image: var(--md-admonition-icon--note);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.abstract, .summary, .tldr) {
border-color: #00b0ff;
}
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) {
background-color: rgba(0, 176, 255, 0.1);
}
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before {
background-color: #00b0ff;
mask-image: var(--md-admonition-icon--abstract);
-webkit-mask-image: var(--md-admonition-icon--abstract);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.info, .todo) {
border-color: #00b8d4;
}
:is(.info, .todo) > :is(.admonition-title, summary) {
background-color: rgba(0, 184, 212, 0.1);
}
:is(.info, .todo) > :is(.admonition-title, summary)::before {
background-color: #00b8d4;
mask-image: var(--md-admonition-icon--info);
-webkit-mask-image: var(--md-admonition-icon--info);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.tip, .hint, .important) {
border-color: #00bfa5;
}
:is(.tip, .hint, .important) > :is(.admonition-title, summary) {
background-color: rgba(0, 191, 165, 0.1);
}
:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before {
background-color: #00bfa5;
mask-image: var(--md-admonition-icon--tip);
-webkit-mask-image: var(--md-admonition-icon--tip);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.success, .check, .done) {
border-color: #00c853;
}
:is(.success, .check, .done) > :is(.admonition-title, summary) {
background-color: rgba(0, 200, 83, 0.1);
}
:is(.success, .check, .done) > :is(.admonition-title, summary)::before {
background-color: #00c853;
mask-image: var(--md-admonition-icon--success);
-webkit-mask-image: var(--md-admonition-icon--success);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.question, .help, .faq) {
border-color: #64dd17;
}
:is(.question, .help, .faq) > :is(.admonition-title, summary) {
background-color: rgba(100, 221, 23, 0.1);
}
:is(.question, .help, .faq) > :is(.admonition-title, summary)::before {
background-color: #64dd17;
mask-image: var(--md-admonition-icon--question);
-webkit-mask-image: var(--md-admonition-icon--question);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.warning, .caution, .attention) {
border-color: #ff9100;
}
:is(.warning, .caution, .attention) > :is(.admonition-title, summary) {
background-color: rgba(255, 145, 0, 0.1);
}
:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before {
background-color: #ff9100;
mask-image: var(--md-admonition-icon--warning);
-webkit-mask-image: var(--md-admonition-icon--warning);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.failure, .fail, .missing) {
border-color: #ff5252;
}
:is(.failure, .fail, .missing) > :is(.admonition-title, summary) {
background-color: rgba(255, 82, 82, 0.1);
}
:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before {
background-color: #ff5252;
mask-image: var(--md-admonition-icon--failure);
-webkit-mask-image: var(--md-admonition-icon--failure);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.danger, .error) {
border-color: #ff1744;
}
:is(.danger, .error) > :is(.admonition-title, summary) {
background-color: rgba(255, 23, 68, 0.1);
}
:is(.danger, .error) > :is(.admonition-title, summary)::before {
background-color: #ff1744;
mask-image: var(--md-admonition-icon--danger);
-webkit-mask-image: var(--md-admonition-icon--danger);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.bug) {
border-color: #f50057;
}
:is(.bug) > :is(.admonition-title, summary) {
background-color: rgba(245, 0, 87, 0.1);
}
:is(.bug) > :is(.admonition-title, summary)::before {
background-color: #f50057;
mask-image: var(--md-admonition-icon--bug);
-webkit-mask-image: var(--md-admonition-icon--bug);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.example) {
border-color: #7c4dff;
}
:is(.example) > :is(.admonition-title, summary) {
background-color: rgba(124, 77, 255, 0.1);
}
:is(.example) > :is(.admonition-title, summary)::before {
background-color: #7c4dff;
mask-image: var(--md-admonition-icon--example);
-webkit-mask-image: var(--md-admonition-icon--example);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.quote, .cite) {
border-color: #9e9e9e;
}
:is(.quote, .cite) > :is(.admonition-title, summary) {
background-color: rgba(158, 158, 158, 0.1);
}
:is(.quote, .cite) > :is(.admonition-title, summary)::before {
background-color: #9e9e9e;
mask-image: var(--md-admonition-icon--quote);
-webkit-mask-image: var(--md-admonition-icon--quote);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
.navy :is(.admonition) {
background-color: var(--sidebar-bg);
}
.ayu :is(.admonition), .coal :is(.admonition) {
background-color: var(--theme-hover);
}
.rust :is(.admonition) {
background-color: var(--sidebar-bg);
color: var(--sidebar-fg);
}
.rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited {
color: var(--sidebar-fg);
}

View File

@ -1,21 +1,10 @@
# Summary
[FROST](README.md)
[ZF FROST](index.md)
- [Understanding FROST](frost.md)
- [Tutorial](tutorial.md)
- [Distributed Key Generation](tutorial/dkg.md)
- [User Documentation](user.md)
- [frost-core](user/frost-core.md)
- [frost-rerandomized](user/frost-rerandomized.md)
- [frost-ed25519](user/frost-ed25519.md)
- [DKG](user/frost-ed25519/dkg.md)
- [frost-ed448](user/frost-ed448.md)
- [DKG](user/frost-ed448/dkg.md)
- [frost-p256](user/frost-p256.md)
- [DKG](user/frost-p256/dkg.md)
- [frost-ristretto255](user/frost-ristretto255.md)
- [DKG](user/frost-ristretto255/dkg.md)
- [frost-secp256k1](user/frost-secp256k1.md)
- [DKG](user/frost-secp256k1/dkg.md)
- [Terminology](terminology.md)
- [Developer Documentation](dev.md)
- [FROST RFCs](dev/rfcs.md)
- [FROST messages](dev/rfcs/0001-messages.md )
- [List of Dependencies for Audit](dev/frost-dependencies-for-audit.md)

View File

@ -1 +1,4 @@
# Developer Documentation
This section contains information only relevant to ZF FROST developers or
contributors.

View File

@ -1 +0,0 @@
# FROST RFCs

View File

@ -1 +0,0 @@
{{#include ../../../../rfcs/0001-messages.md}}

92
book/src/frost.md Normal file
View File

@ -0,0 +1,92 @@
# Understanding FROST
This explains the main concepts and flows of FROST in a generic manner. These
are important to understand how to use the library, but rest assured that the
[Tutorial](tutorial.md) will have more concrete information.
FROST is a threshold signature scheme. It allows splitting a Schnorr signing key
into `n` shares for a threshold `t`, such that `t` (or more) participants can
together generate a signature that can be validated by the corresponding verifying
key. One important aspect is that the resulting signature is indistinguishable from a
non-threshold signature from the point of view of signature verifiers.
```admonish note
FROST only supports Schnorr signatures. Therefore it can't produce
ECDSA signatures.
```
## Key Generation
There are two options for generating FROST key shares. In both cases, after the
key generation procedure, each participant will get:
- a **secret share**;
- a **verifying share** (which can be used by other participants to verify the
signature shares the participant produces);
- a **group verifying key**, which is the public key matching the private key that was
split into shares; it is used to verify the final signature generated with FROST.
### Trusted Dealer Generation
An existing key (which can be freshly generated) is split into shares. It's the
simplest approach, but it has the downside of requiring the entire key to exist
in memory at some point in time, which may not be desired in high security
applications. However, it is much simpler to set up. It requires an
[authenticated and confidential communication
channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) to
distribute each share to their respective participants.
[Learn how to do Trusted Dealer Generation with the ZF FROST library](tutorial.md#generating-key-shares-with-a-trusted-dealer).
### Distribtuted Key Generation
A two-round protocol after which each participant will have their share of the
secret, without the secret being ever present in its entirety in any
participant's memory. Its downside is that it requires a [broadcast
channel](https://frost.zfnd.org/terminology.html#broadcast-channel) as well as
an [authenticated and confidential communication
channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) between
each pair of participants, which may be difficult to deploy in practice.
[Learn how to do Distributed Key Generation with the ZF FROST
library](tutorial/dkg.md).
## Signing
Signing with FROST starts with a Coordinator (which can be one of the
share holders, or not) which selects the message to be signed and
the participants that will generate the signature.
Each participant sends fresh nonce commitments to the Coordinator, which then
consolidates them and sends them to each participant. Each one will then produce
a signature share, which is sent to the Coordinator who finally aggregates them
and produces the final signature.
```admonish note
If having a single coordinator is not desired, then all participants
can act as coordinators. Refer to the
[spec](https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#removing-the-coordinator-role-no-coordinator)
for more information.
```
```admonish warning
ALL participants who are selected for generating the signature need
to produce their share, even if there are more than `t` of them.
For example, in 2-of-3 signing, if 3 participants are selected,
them all 3 must produce signature shares in order for the Coordinator
be able to produce the final signature. Of course, the Coordinator
is still free to start the process with only 2 participants if they wish.
```
## Verifying
Signature verification is carried out as normal with single-party signatures,
along with the signed message and the group verifying key as inputs.
## Ciphersuites
FROST is a generic protocol that works with any adequate prime-order group,
which in practice are constructed from elliptic curves. The spec specifies
five ciphersuites with the Ristretto255, Ed25519, Ed448, P-256 and secp256k1
groups. It's possible (though not recommended) to use your own ciphersuite.

11
book/src/index.md Normal file
View File

@ -0,0 +1,11 @@
# The ZF FROST Book
This is a guide-level reference for the [ZF FROST library](https://github.com/ZcashFoundation/frost/).
## Getting Started
If you're not familiar with FROST, first read [Understanding FROST](frost.md).
Then read the [Tutorial](tutorial.md), and use the [Rust
docs](user.md) as
reference.

View File

@ -16,6 +16,16 @@ Possible deployment options:
- Posting commitments to an authenticated centralized server that is trusted to
provide a single view to all participants (also known as 'public bulletin board')
### _Identifier_
An identifier is a non-zero scalar (i.e. a number in a range specific to the
ciphersuite) which identifies a specific party. There are no restrictions to
them other than being unique for each participant and being in the valid range.
In the ZF FROST library, they are either automatically generated incrementally
during key generation or can be instantiated from a byte array using
[`Identifier::deserialize()`](https://docs.rs/frost-core/latest/frost_core/frost/struct.Identifier.html#method.deserialize).
### _Peer to peer channel_
Peer-to-peer channels are authenticated, reliable, and unordered, per the

242
book/src/tutorial.md Normal file
View File

@ -0,0 +1,242 @@
# Tutorial
The ZF FROST suite consists of multiple crates. `frost-core` contains
a generic implementation of the protocol, which can't be used directly
without a concrete instantiation.
The ciphersuite crates (`frost-ristretto255`, `frost-ed25519`, `frost-ed448`,
`frost-p256`, and `frost-secp256k1`) provide ciphersuites to use with
`frost-core`, but also re-expose the `frost-core` functions without
generics. If you will only use a single ciphersuite, then we recommend
using those functions, and this tutorial will follow this approach.
If you need to support multiple ciphersuites then feel free to use
`frost-core` along with the ciphersuite types.
This tutorial will use the `frost-ristretto255` crate, but changing
to another ciphersuite should be a matter of simply changing the import.
## Including `frost-ristretto255`
Add to your `Cargo.toml` file:
```
[dependencies]
frost-ristretto255 = "0.3.0"
```
## Handling errors
Most crate functions mentioned below return `Result`s with
[`Error`](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/type.Error.html)s.
All errors should be considered fatal and should lead to aborting the key
generation or signing procedure.
## Serializing structures
FROST is a distributed protocol and thus it requires sending messages between
participants. However, the ZF FROST library does not handle communication nor
encoding, which is the application's responsibility. For this reason, all
structures that need to be transmitted have public fields allowing the
application to encode and decode them as it wishes. (Note that fields like
`Scalar` and `Element` do have standard encodings; only the serialization of the
structure itself and things like maps and lists need to be handled by the
application.)
The ZF FROST library will also support `serde` in the future, which will make
this process simpler.
## Generating key shares with a trusted dealer
The diagram below shows the trusted dealer key generation process. Dashed lines
represent data being sent through an [authenticated and confidential communication
channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel).
![Diagram of Trusted Dealer Key Generation, illustrating what is explained in the text](tutorial/tkg.png)
To generate the key shares, the dealer calls
[`generate_with_dealer()`](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/keys/fn.generate_with_dealer.html).
It returns a `HashMap` mapping the (automatically generated) `Identifier`s to
their respective `SecretShare`s, and a `PublicKeyPackage` which contains the
`VerifyingShare` for each participant and the group public key (`VerifyingKey`).
```rust,no_run,noplayground
{{#include ../../frost-ristretto255/README.md:tkg_gen}}
```
Each `SecretShare` must then be sent via an [**authenticated** and
**confidential** channel
](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) for each
participant, who must verify the package to obtain a `KeyPackage` which contains
their signing share, verifying share and group verifying key. This is done with
[`KeyPackage::try_from()`](https://docs.rs/frost-core/latest/frost_core/frost/keys/struct.KeyPackage.html#method.try_from):
```rust,no_run,noplayground
{{#include ../../frost-ristretto255/README.md:tkg_verify}}
```
```admonish info
Currently there is no way to specify which identifiers to use. This will
likely be supported in the future.
[More information on how to handle Identifiers](https://frost.zfnd.org/terminology.html#identifier).
```
```admonish danger
Which [**authenticated** and **confidential** channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel)
to use is up to the application. Some examples:
- Manually require the dealer to sent the `SecretShare`s to the
partipants using some secure messenger such as Signal;
- Use a TLS connection, authenticating the server with a certificate
and the client with some user/password or another suitable authentication
mechanism;
Refer to the [Terminology page](https://frost.zfnd.org/terminology.html#peer-to-peer-channel)
for more details.
Failure of using a **confidential** channel may lead to the shares being
stolen and possibly allowing signature forgeries if a threshold number of
them are stolen.
Failure of using an **authenticated** channel may lead to shares being
sent to the wrong person, possibly allowing unintended parties
to generate signatures.
```
```admonish danger
The `SecretPackage` contents must be stored securely. For example:
- Make sure other users in the system can't read it;
- If possible, use the OS secure storage such that the package
contents can only be opened with the user's password or biometrics.
```
```admonish warning
The participants may wish to not fully trust the dealer. While **the dealer
has access to the original secret and can forge signatures
by simply using the secret to sign** (and this can't be
possibly avoided with this method; use Distributed Key Generation
if that's an issue), the dealer could also tamper with the `SecretShare`s
in a way that the participants will never be able to generate a valid
signature in the future (denial of service). Participants can detect
such tampering by comparing the `VerifiableSecretSharingCommitment`
values from their `SecretShare`s (either by some manual process, or
by using a [broadcast channel](https://frost.zfnd.org/terminology.html#broadcast-channel))
to make sure they are all equal.
```
## Signing
The diagram below shows the signing process. Dashed lines represent data being
sent through an [authenticated communication
channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel).
![Diagram of Signing, illustrating what is explained in the text](tutorial/signing.png)
### Coordinator, Round 1
To sign, the
[Coordinator](file:///home/conrado/zfnd/frost/book/book/frost.html#signing) must
select which participants are going to generate the signature, and must signal
to start the process. This needs to be implemented by users of the ZF FROST library and will depend on
the communication channel being used.
### Participants, Round 1
Each selected participant will then generate the nonces (a `SigningNonces`) and
their commitments (a `SigningCommitments`) by calling
[`round1::commit()`](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/round1/fn.commit.html):
```rust,no_run,noplayground
{{#include ../../frost-ristretto255/README.md:round1_commit}}
```
The `SigningNonces` must be kept by the participant to use in Round 2, while the
`SigningCommitments` must be sent to the Coordinator using an [authenticated
channel](https://frost.zfnd.org/terminology.html#broadcast-channel).
### Coordinator, Round 2
The Coordinator will get all `SigningCommitments` from the participants and the
message to be signed, and then build a `SigningPackage` by calling
[`SigningPackage::new()`](https://docs.rs/frost-core/latest/frost_core/frost/struct.SigningPackage.html#method.new).
```rust,no_run,noplayground
{{#include ../../frost-ristretto255/README.md:round2_package}}
```
The `SigningPackage` must then be sent to all the participants using an
[authenticated
channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). (Of course,
if the message is confidential, then the channel must also be confidential.)
```admonish warning
In all of the main FROST ciphersuites, the entire message must
be sent to participants. In some cases, where the message is too big, it may be
necessary to send a hash of the message instead. We strongly suggest creating a
specific ciphersuite for this, and not just sending the hash as if it were the
message. For reference, see [how RFC 8032 handles
"pre-hashing"](https://datatracker.ietf.org/doc/html/rfc8032).
```
### Participants, Round 2
Upon receiving the `SigningPackage`, each participant will then produce their
signature share using their `KeyPackage` from the key generation process and
their `SigningNonces` from Round 1, by calling
[`round2::sign()`](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/round2/fn.sign.html):
```rust,no_run,noplayground
{{#include ../../frost-ristretto255/README.md:round2_sign}}
```
The resulting `SignatureShare` must then be sent back to the Coordinator using
an [authenticated
channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel).
```admonish important
In most applications, it is important that the participant must be aware of what
they are signing. Thus the application should show the message to the
participant and obtain their consent to proceed before producing the signature
share.
```
### Coordinator, Aggregate
Upon receiving the `SignatureShare`s from the participants, the Coordinator can
finally produce the final signature by calling
[`aggregate()`](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/fn.aggregate.html)
with the same `SigningPackage` sent to the participants and the
`PublicKeyPackage` from the key generation (which is used to validate each
`SignatureShare`).
```rust,no_run,noplayground
{{#include ../../frost-ristretto255/README.md:aggregate}}
```
The returned signature, a `Signature`, will be a valid signature for the message
chosen in Round 2 for the group verifying key in the `PublicKeyPackage`.
```admonish note
FROST supports identifiable abort: if a participant misbehaves and produces an
invalid signature share, then aggregation will fail and the returned error
will have the identifier of the misbehaving participant. (If multiple participants
misbehave, only the first one detected will be returned.)
What should be done in that case is up to the application. The misbehaving participant
could be excluded from future signing sessions, for example.
```
## Verifying signatures
The Coordinator could verify the signature with:
```rust,no_run,noplayground
{{#include ../../frost-ristretto255/README.md:verify}}
```
(There is no need for the Coordinator to verify the signature since that already
happens inside `aggregate()`. This just shows how the signature can be
verified.)

82
book/src/tutorial/dkg.md Normal file
View File

@ -0,0 +1,82 @@
# Distributed Key Generation
The diagram below shows the distributed key generation process. Dashed lines
represent data being sent through an [authenticated and confidential
communication
channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). Note
that the first dashed line requires a [**broadcast
channel**](https://frost.zfnd.org/terminology.html#broadcast-channel)
![Diagram of Distribtuted Key Generation, illustrating what is explained in the text](dkg/dkg.png)
## Part 1
To start the DKG, each participant calls
[`dkg::part1()`](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/keys/dkg/fn.part1.html)
passing its identifier, the desired threshold and total number of participants.
(Thus, they need to agree on those parameters via some mechanism which is up to
the application.) It returns a `round1::SecretPackage` and a `round1::Package`:
```rust,no_run,noplayground
{{#include ../../../frost-ristretto255/dkg.md:dkg_import}}
// create `participant_identifier` somehow
{{#include ../../../frost-ristretto255/dkg.md:dkg_part1}}
```
The `round1::SecretPackage` must be kept in memory to use in the next round. The
`round1::Package` must be sent to all other participants using a [**broadcast
channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) to ensure
that all participants receive the same value.
```admonish danger
A [**broadcast
channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) in this
context is not simply broadcasting the value to all participants. It requires
running a protocol to ensure that all participants have the same value or that
the protocol is aborted. Check the linked [Terminology
section](https://frost.zfnd.org/terminology.html#broadcast-channel) for more
details.
**Failure in using a proper broadcast channel will make the key generation
insecure.**
```
## Part 2
Upon receiving the other participants' `round1::Package`s, each participant then
calls
[`dkg::part2()`](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/keys/dkg/fn.part2.html)
passing their own previously created `round1::SecretPackage` and the list of
received `round1::Packages`. It returns a `round2::SecretPackage` and a
`HashMap` mapping other participants's `Identifier`s to `round2::Package`s:
```rust,no_run,noplayground
{{#include ../../../frost-ristretto255/dkg.md:dkg_part2}}
```
The `round2::SecretPackage` must be kept in memory for the next part; the
`round1::SecretPackage` is consumed and is not required anymore.
The `round2::Package`s must be sent to their respective participants with the
given `Identifier`s, using an [authenticated and confidential communication
channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel).
## Part 3
Finally, upon receiving the other participant's `round2::Package`, the DKG is
concluded by calling
[`dkg::part3()`](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/keys/dkg/fn.part3.html)
passing the same `round1::Package`s received in Part 2, the `round2::Package`s
just received, and the previously stored `round2::SecretPackage` for the
participant. It returns a `KeyPackage`, with the participant's secret share,
and a `PublicKeyPackage` containing the group verifying key:
```rust,no_run,noplayground
{{#include ../../../frost-ristretto255/dkg.md:dkg_part3}}
```
```admonish note
All participants will generate the same `PublicKeyPackage`.
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
book/src/tutorial/tkg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -1 +1,9 @@
# User Documentation
- [frost-core](https://docs.rs/frost-core/)
- [frost-rerandomized](https://docs.rs/frost-rerandomized/)
- [frost-ed25519](https://docs.rs/frost-ristretto255/)
- [frost-ed448](https://docs.rs/frost-ed448/)
- [frost-p256](https://docs.rs/frost-p256/)
- [frost-ristretto255](https://docs.rs/frost-ristretto255/)
- [frost-secp256k1](https://docs.rs/frost-secp256k1/)

View File

@ -81,10 +81,11 @@ fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
min_signers: u16,
key_packages: HashMap<frost::Identifier<C>, frost::keys::KeyPackage<C>>,
mut rng: R,
pubkeys: frost::keys::PublicKeyPackage<C>,
pubkey_package: frost::keys::PublicKeyPackage<C>,
) -> (Vec<u8>, Signature<C>, VerifyingKey<C>) {
let mut nonces: HashMap<frost::Identifier<C>, frost::round1::SigningNonces<C>> = HashMap::new();
let mut commitments: HashMap<frost::Identifier<C>, frost::round1::SigningCommitments<C>> =
let mut nonces_map: HashMap<frost::Identifier<C>, frost::round1::SigningNonces<C>> =
HashMap::new();
let mut commitments_map: HashMap<frost::Identifier<C>, frost::round1::SigningCommitments<C>> =
HashMap::new();
////////////////////////////////////////////////////////////////////////////
@ -95,7 +96,7 @@ fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
let participant_identifier = participant_index.try_into().expect("should be nonzero");
// Generate one (1) nonce and one SigningCommitments instance for each
// participant, up to _min_signers_.
let (nonce, commitment) = frost::round1::commit(
let (nonces, commitments) = frost::round1::commit(
participant_identifier,
key_packages
.get(&participant_identifier)
@ -103,8 +104,8 @@ fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
.secret_share(),
&mut rng,
);
nonces.insert(participant_identifier, nonce);
commitments.insert(participant_identifier, commitment);
nonces_map.insert(participant_identifier, nonces);
commitments_map.insert(participant_identifier, commitments);
}
// This is what the signature aggregator / coordinator needs to do:
@ -112,17 +113,17 @@ fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
// - take one (unused) commitment per signing participant
let mut signature_shares = Vec::new();
let message = "message to sign".as_bytes();
let comms = commitments.clone().into_values().collect();
let comms = commitments_map.clone().into_values().collect();
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
////////////////////////////////////////////////////////////////////////////
// Round 2: each participant generates their signature share
////////////////////////////////////////////////////////////////////////////
for participant_identifier in nonces.keys() {
for participant_identifier in nonces_map.keys() {
let key_package = key_packages.get(participant_identifier).unwrap();
let nonces_to_use = &nonces.get(participant_identifier).unwrap();
let nonces_to_use = &nonces_map.get(participant_identifier).unwrap();
// Each participant generates their signature share.
let signature_share =
@ -136,22 +137,20 @@ fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
////////////////////////////////////////////////////////////////////////////
// Aggregate (also verifies the signature shares)
let group_signature_res = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys);
assert!(group_signature_res.is_ok());
let group_signature = group_signature_res.unwrap();
let group_signature =
frost::aggregate(&signing_package, &signature_shares[..], &pubkey_package).unwrap();
// Check that the threshold signature can be verified by the group public
// key (the verification key).
assert!(pubkeys
let is_signature_valid = pubkey_package
.group_public
.verify(message, &group_signature)
.is_ok());
.is_ok();
assert!(is_signature_valid);
// Check that the threshold signature can be verified by the group public
// key (the verification key) from KeyPackage.group_public
for (participant_identifier, _) in nonces.clone() {
for (participant_identifier, _) in nonces_map.clone() {
let key_package = key_packages.get(&participant_identifier).unwrap();
assert!(key_package
@ -160,7 +159,11 @@ fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
.is_ok());
}
(message.to_owned(), group_signature, pubkeys.group_public)
(
message.to_owned(),
group_signature,
pubkey_package.group_public,
)
}
/// Test FROST signing with trusted dealer with a Ciphersuite.
@ -197,13 +200,13 @@ where
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (secret_package, round1_package) =
let (round1_secret_package, round1_package) =
frost::keys::dkg::part1(participant_identifier, max_signers, min_signers, &mut rng)
.unwrap();
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round1_secret_packages.insert(participant_identifier, secret_package);
round1_secret_packages.insert(participant_identifier, round1_secret_package);
// "Send" the round 1 package to all other participants. In this
// test this is simulated using a HashMap; in practice this will be
@ -240,15 +243,12 @@ where
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (round2_secret_package, round2_packages) = frost::keys::dkg::part2(
round1_secret_packages
.remove(&participant_identifier)
.unwrap(),
received_round1_packages
.get(&participant_identifier)
.unwrap(),
)
.expect("should work");
let round1_secret_package = round1_secret_packages
.remove(&participant_identifier)
.unwrap();
let round1_packages = &received_round1_packages[&participant_identifier];
let (round2_secret_package, round2_packages) =
frost::keys::dkg::part2(round1_secret_package, round1_packages).expect("should work");
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.

View File

@ -9,6 +9,7 @@ scenario in a single thread and it abstracts away any communication between peer
```rust
# // ANCHOR: tkg_gen
use frost_ed25519 as frost;
use rand::thread_rng;
use std::collections::HashMap;
@ -16,20 +17,23 @@ use std::collections::HashMap;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
let (shares, pubkeys) = frost::keys::generate_with_dealer(max_signers, min_signers, &mut rng)?;
let (shares, pubkey_package) = frost::keys::generate_with_dealer(max_signers, min_signers, &mut rng)?;
# // ANCHOR_END: tkg_gen
// Verifies the secret shares from the dealer and store them in a HashMap.
// In practice, the KeyPackages must be sent to its respective participants
// through a confidential and authenticated channel.
let mut key_packages: HashMap<_, _> = HashMap::new();
for (k, v) in shares {
let key_package = frost::keys::KeyPackage::try_from(v)?;
key_packages.insert(k, key_package);
for (identifier, secret_share) in shares {
# // ANCHOR: tkg_verify
let key_package = frost::keys::KeyPackage::try_from(secret_share)?;
# // ANCHOR_END: tkg_verify
key_packages.insert(identifier, key_package);
}
let mut nonces = HashMap::new();
let mut commitments = HashMap::new();
let mut nonces_map = HashMap::new();
let mut commitments_map = HashMap::new();
////////////////////////////////////////////////////////////////////////////
// Round 1: generating nonces and signing commitments for each participant
@ -38,43 +42,51 @@ let mut commitments = HashMap::new();
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_index in 1..(min_signers as u16 + 1) {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let key_package = &key_packages[&participant_identifier];
// Generate one (1) nonce and one SigningCommitments instance for each
// participant, up to _threshold_.
let (nonce, commitment) = frost::round1::commit(
# // ANCHOR: round1_commit
let (nonces, commitments) = frost::round1::commit(
participant_identifier,
key_packages[&participant_identifier].secret_share(),
key_package.secret_share(),
&mut rng,
);
// In practice, the nonces and commitment must be sent to the coordinator
# // ANCHOR_END: round1_commit
// In practice, the nonces must be kept by the participant to use in the
// next round, while the commitment must be sent to the coordinator
// (or to every other participant if there is no coordinator) using
// an authenticated channel.
nonces.insert(participant_identifier, nonce);
commitments.insert(participant_identifier, commitment);
nonces_map.insert(participant_identifier, nonces);
commitments_map.insert(participant_identifier, commitments);
}
// This is what the signature aggregator / coordinator needs to do:
// - decide what message to sign
// - take one (unused) commitment per signing participant
let mut signature_shares = Vec::new();
let commitments_received = commitments_map.clone().into_values().collect();
# // ANCHOR: round2_package
let message = "message to sign".as_bytes();
let comms = commitments.clone().into_values().collect();
// In practice, the SigningPackage must be sent to all participants
// involved in the current signing (at least min_signers participants),
// using an authenticate channel (and confidential if the message is secret).
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
# // In practice, the SigningPackage must be sent to all participants
# // involved in the current signing (at least min_signers participants),
# // using an authenticate channel (and confidential if the message is secret).
let signing_package = frost::SigningPackage::new(commitments_received, message.to_vec());
# // ANCHOR_END: round2_package
////////////////////////////////////////////////////////////////////////////
// Round 2: each participant generates their signature share
////////////////////////////////////////////////////////////////////////////
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_identifier in nonces.keys() {
for participant_identifier in nonces_map.keys() {
let key_package = &key_packages[participant_identifier];
let nonces_to_use = &nonces[participant_identifier];
let nonces = &nonces_map[participant_identifier];
// Each participant generates their signature share.
let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?;
# // ANCHOR: round2_sign
let signature_share = frost::round2::sign(&signing_package, nonces, key_package)?;
# // ANCHOR_END: round2_sign
// In practice, the signature share must be sent to the Coordinator
// using an authenticated channel.
@ -87,14 +99,20 @@ for participant_identifier in nonces.keys() {
////////////////////////////////////////////////////////////////////////////
// Aggregate (also verifies the signature shares)
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?;
# // ANCHOR: aggregate
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkey_package)?;
# // ANCHOR_END: aggregate
// Check that the threshold signature can be verified by the group public
// key (the verification key).
assert!(pubkeys
# // ANCHOR: verify
let is_signature_valid = pubkey_package
.group_public
.verify(message, &group_signature)
.is_ok());
.is_ok();
# // ANCHOR_END: verify
assert!(is_signature_valid);
# Ok::<(), frost::Error>(())
```

View File

@ -25,6 +25,7 @@ they can proceed to sign messages with FROST.
## Example
```rust
# // ANCHOR: dkg_import
use rand::thread_rng;
use std::collections::HashMap;
@ -32,13 +33,14 @@ use frost_ed25519 as frost;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
# // ANCHOR_END: dkg_import
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 1
////////////////////////////////////////////////////////////////////////////
let max_signers = 5;
let min_signers = 3;
// Keep track of each participant's round 1 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
@ -53,16 +55,18 @@ let mut received_round1_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (secret_package, round1_package) = frost::keys::dkg::part1(
# // ANCHOR: dkg_part1
let (round1_secret_package, round1_package) = frost::keys::dkg::part1(
participant_identifier,
max_signers,
min_signers,
&mut rng,
)?;
# // ANCHOR_END: dkg_part1
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round1_secret_packages.insert(participant_identifier, secret_package);
round1_secret_packages.insert(participant_identifier, round1_secret_package);
// "Send" the round 1 package to all other participants. In this
// test this is simulated using a HashMap; in practice this will be
@ -99,12 +103,14 @@ let mut received_round2_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (round2_secret_package, round2_packages) = frost::keys::dkg::part2(
round1_secret_packages
.remove(&participant_identifier)
.unwrap(),
&received_round1_packages[&participant_identifier],
)?;
let round1_secret_package = round1_secret_packages
.remove(&participant_identifier)
.unwrap();
let round1_packages = &received_round1_packages[&participant_identifier];
# // ANCHOR: dkg_part2
let (round2_secret_package, round2_packages) =
frost::keys::dkg::part2(round1_secret_package, round1_packages)?;
# // ANCHOR_END: dkg_part2
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
@ -142,13 +148,18 @@ let mut pubkey_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (key_package, pubkey_package_for_participant) = frost::keys::dkg::part3(
&round2_secret_packages[&participant_identifier],
&received_round1_packages[&participant_identifier],
&received_round2_packages[&participant_identifier],
let round2_secret_package = &round2_secret_packages[&participant_identifier];
let round1_packages = &received_round1_packages[&participant_identifier];
let round2_packages = &received_round2_packages[&participant_identifier];
# // ANCHOR: dkg_part3
let (key_package, pubkey_package) = frost::keys::dkg::part3(
round2_secret_package,
round1_packages,
round2_packages,
)?;
# // ANCHOR_END: dkg_part3
key_packages.insert(participant_identifier, key_package);
pubkey_packages.insert(participant_identifier, pubkey_package_for_participant);
pubkey_packages.insert(participant_identifier, pubkey_package);
}
// With its own key package and the pubkey package, each participant can now proceed

View File

@ -9,6 +9,7 @@ scenario in a single thread and it abstracts away any communication between peer
```rust
# // ANCHOR: tkg_gen
use frost_ed448 as frost;
use rand::thread_rng;
use std::collections::HashMap;
@ -16,20 +17,23 @@ use std::collections::HashMap;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
let (shares, pubkeys) = frost::keys::generate_with_dealer(max_signers, min_signers, &mut rng)?;
let (shares, pubkey_package) = frost::keys::generate_with_dealer(max_signers, min_signers, &mut rng)?;
# // ANCHOR_END: tkg_gen
// Verifies the secret shares from the dealer and store them in a HashMap.
// In practice, the KeyPackages must be sent to its respective participants
// through a confidential and authenticated channel.
let mut key_packages: HashMap<_, _> = HashMap::new();
for (k, v) in shares {
let key_package = frost::keys::KeyPackage::try_from(v)?;
key_packages.insert(k, key_package);
for (identifier, secret_share) in shares {
# // ANCHOR: tkg_verify
let key_package = frost::keys::KeyPackage::try_from(secret_share)?;
# // ANCHOR_END: tkg_verify
key_packages.insert(identifier, key_package);
}
let mut nonces = HashMap::new();
let mut commitments = HashMap::new();
let mut nonces_map = HashMap::new();
let mut commitments_map = HashMap::new();
////////////////////////////////////////////////////////////////////////////
// Round 1: generating nonces and signing commitments for each participant
@ -38,43 +42,51 @@ let mut commitments = HashMap::new();
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_index in 1..(min_signers as u16 + 1) {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let key_package = &key_packages[&participant_identifier];
// Generate one (1) nonce and one SigningCommitments instance for each
// participant, up to _threshold_.
let (nonce, commitment) = frost::round1::commit(
# // ANCHOR: round1_commit
let (nonces, commitments) = frost::round1::commit(
participant_identifier,
key_packages[&participant_identifier].secret_share(),
key_package.secret_share(),
&mut rng,
);
// In practice, the nonces and commitment must be sent to the coordinator
# // ANCHOR_END: round1_commit
// In practice, the nonces must be kept by the participant to use in the
// next round, while the commitment must be sent to the coordinator
// (or to every other participant if there is no coordinator) using
// an authenticated channel.
nonces.insert(participant_identifier, nonce);
commitments.insert(participant_identifier, commitment);
nonces_map.insert(participant_identifier, nonces);
commitments_map.insert(participant_identifier, commitments);
}
// This is what the signature aggregator / coordinator needs to do:
// - decide what message to sign
// - take one (unused) commitment per signing participant
let mut signature_shares = Vec::new();
let commitments_received = commitments_map.clone().into_values().collect();
# // ANCHOR: round2_package
let message = "message to sign".as_bytes();
let comms = commitments.clone().into_values().collect();
// In practice, the SigningPackage must be sent to all participants
// involved in the current signing (at least min_signers participants),
// using an authenticate channel (and confidential if the message is secret).
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
# // In practice, the SigningPackage must be sent to all participants
# // involved in the current signing (at least min_signers participants),
# // using an authenticate channel (and confidential if the message is secret).
let signing_package = frost::SigningPackage::new(commitments_received, message.to_vec());
# // ANCHOR_END: round2_package
////////////////////////////////////////////////////////////////////////////
// Round 2: each participant generates their signature share
////////////////////////////////////////////////////////////////////////////
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_identifier in nonces.keys() {
for participant_identifier in nonces_map.keys() {
let key_package = &key_packages[participant_identifier];
let nonces_to_use = &nonces[participant_identifier];
let nonces = &nonces_map[participant_identifier];
// Each participant generates their signature share.
let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?;
# // ANCHOR: round2_sign
let signature_share = frost::round2::sign(&signing_package, nonces, key_package)?;
# // ANCHOR_END: round2_sign
// In practice, the signature share must be sent to the Coordinator
// using an authenticated channel.
@ -87,14 +99,20 @@ for participant_identifier in nonces.keys() {
////////////////////////////////////////////////////////////////////////////
// Aggregate (also verifies the signature shares)
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?;
# // ANCHOR: aggregate
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkey_package)?;
# // ANCHOR_END: aggregate
// Check that the threshold signature can be verified by the group public
// key (the verification key).
assert!(pubkeys
# // ANCHOR: verify
let is_signature_valid = pubkey_package
.group_public
.verify(message, &group_signature)
.is_ok());
.is_ok();
# // ANCHOR_END: verify
assert!(is_signature_valid);
# Ok::<(), frost::Error>(())
```

View File

@ -25,6 +25,7 @@ they can proceed to sign messages with FROST.
## Example
```rust
# // ANCHOR: dkg_import
use rand::thread_rng;
use std::collections::HashMap;
@ -32,13 +33,14 @@ use frost_ed448 as frost;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
# // ANCHOR_END: dkg_import
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 1
////////////////////////////////////////////////////////////////////////////
let max_signers = 5;
let min_signers = 3;
// Keep track of each participant's round 1 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
@ -53,16 +55,18 @@ let mut received_round1_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (secret_package, round1_package) = frost::keys::dkg::part1(
# // ANCHOR: dkg_part1
let (round1_secret_package, round1_package) = frost::keys::dkg::part1(
participant_identifier,
max_signers,
min_signers,
&mut rng,
)?;
# // ANCHOR_END: dkg_part1
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round1_secret_packages.insert(participant_identifier, secret_package);
round1_secret_packages.insert(participant_identifier, round1_secret_package);
// "Send" the round 1 package to all other participants. In this
// test this is simulated using a HashMap; in practice this will be
@ -99,12 +103,14 @@ let mut received_round2_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (round2_secret_package, round2_packages) = frost::keys::dkg::part2(
round1_secret_packages
.remove(&participant_identifier)
.unwrap(),
&received_round1_packages[&participant_identifier],
)?;
let round1_secret_package = round1_secret_packages
.remove(&participant_identifier)
.unwrap();
let round1_packages = &received_round1_packages[&participant_identifier];
# // ANCHOR: dkg_part2
let (round2_secret_package, round2_packages) =
frost::keys::dkg::part2(round1_secret_package, round1_packages)?;
# // ANCHOR_END: dkg_part2
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
@ -142,13 +148,18 @@ let mut pubkey_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (key_package, pubkey_package_for_participant) = frost::keys::dkg::part3(
&round2_secret_packages[&participant_identifier],
&received_round1_packages[&participant_identifier],
&received_round2_packages[&participant_identifier],
let round2_secret_package = &round2_secret_packages[&participant_identifier];
let round1_packages = &received_round1_packages[&participant_identifier];
let round2_packages = &received_round2_packages[&participant_identifier];
# // ANCHOR: dkg_part3
let (key_package, pubkey_package) = frost::keys::dkg::part3(
round2_secret_package,
round1_packages,
round2_packages,
)?;
# // ANCHOR_END: dkg_part3
key_packages.insert(participant_identifier, key_package);
pubkey_packages.insert(participant_identifier, pubkey_package_for_participant);
pubkey_packages.insert(participant_identifier, pubkey_package);
}
// With its own key package and the pubkey package, each participant can now proceed

View File

@ -9,6 +9,7 @@ scenario in a single thread and it abstracts away any communication between peer
```rust
# // ANCHOR: tkg_gen
use frost_p256 as frost;
use rand::thread_rng;
use std::collections::HashMap;
@ -16,20 +17,23 @@ use std::collections::HashMap;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
let (shares, pubkeys) = frost::keys::generate_with_dealer(max_signers, min_signers, &mut rng)?;
let (shares, pubkey_package) = frost::keys::generate_with_dealer(max_signers, min_signers, &mut rng)?;
# // ANCHOR_END: tkg_gen
// Verifies the secret shares from the dealer and store them in a HashMap.
// In practice, the KeyPackages must be sent to its respective participants
// through a confidential and authenticated channel.
let mut key_packages: HashMap<_, _> = HashMap::new();
for (k, v) in shares {
let key_package = frost::keys::KeyPackage::try_from(v)?;
key_packages.insert(k, key_package);
for (identifier, secret_share) in shares {
# // ANCHOR: tkg_verify
let key_package = frost::keys::KeyPackage::try_from(secret_share)?;
# // ANCHOR_END: tkg_verify
key_packages.insert(identifier, key_package);
}
let mut nonces = HashMap::new();
let mut commitments = HashMap::new();
let mut nonces_map = HashMap::new();
let mut commitments_map = HashMap::new();
////////////////////////////////////////////////////////////////////////////
// Round 1: generating nonces and signing commitments for each participant
@ -38,43 +42,51 @@ let mut commitments = HashMap::new();
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_index in 1..(min_signers as u16 + 1) {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let key_package = &key_packages[&participant_identifier];
// Generate one (1) nonce and one SigningCommitments instance for each
// participant, up to _threshold_.
let (nonce, commitment) = frost::round1::commit(
# // ANCHOR: round1_commit
let (nonces, commitments) = frost::round1::commit(
participant_identifier,
key_packages[&participant_identifier].secret_share(),
key_package.secret_share(),
&mut rng,
);
// In practice, the nonces and commitment must be sent to the coordinator
# // ANCHOR_END: round1_commit
// In practice, the nonces must be kept by the participant to use in the
// next round, while the commitment must be sent to the coordinator
// (or to every other participant if there is no coordinator) using
// an authenticated channel.
nonces.insert(participant_identifier, nonce);
commitments.insert(participant_identifier, commitment);
nonces_map.insert(participant_identifier, nonces);
commitments_map.insert(participant_identifier, commitments);
}
// This is what the signature aggregator / coordinator needs to do:
// - decide what message to sign
// - take one (unused) commitment per signing participant
let mut signature_shares = Vec::new();
let commitments_received = commitments_map.clone().into_values().collect();
# // ANCHOR: round2_package
let message = "message to sign".as_bytes();
let comms = commitments.clone().into_values().collect();
// In practice, the SigningPackage must be sent to all participants
// involved in the current signing (at least min_signers participants),
// using an authenticate channel (and confidential if the message is secret).
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
# // In practice, the SigningPackage must be sent to all participants
# // involved in the current signing (at least min_signers participants),
# // using an authenticate channel (and confidential if the message is secret).
let signing_package = frost::SigningPackage::new(commitments_received, message.to_vec());
# // ANCHOR_END: round2_package
////////////////////////////////////////////////////////////////////////////
// Round 2: each participant generates their signature share
////////////////////////////////////////////////////////////////////////////
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_identifier in nonces.keys() {
for participant_identifier in nonces_map.keys() {
let key_package = &key_packages[participant_identifier];
let nonces_to_use = &nonces[participant_identifier];
let nonces = &nonces_map[participant_identifier];
// Each participant generates their signature share.
let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?;
# // ANCHOR: round2_sign
let signature_share = frost::round2::sign(&signing_package, nonces, key_package)?;
# // ANCHOR_END: round2_sign
// In practice, the signature share must be sent to the Coordinator
// using an authenticated channel.
@ -87,14 +99,20 @@ for participant_identifier in nonces.keys() {
////////////////////////////////////////////////////////////////////////////
// Aggregate (also verifies the signature shares)
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?;
# // ANCHOR: aggregate
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkey_package)?;
# // ANCHOR_END: aggregate
// Check that the threshold signature can be verified by the group public
// key (the verification key).
assert!(pubkeys
# // ANCHOR: verify
let is_signature_valid = pubkey_package
.group_public
.verify(message, &group_signature)
.is_ok());
.is_ok();
# // ANCHOR_END: verify
assert!(is_signature_valid);
# Ok::<(), frost::Error>(())
```

View File

@ -25,6 +25,7 @@ they can proceed to sign messages with FROST.
## Example
```rust
# // ANCHOR: dkg_import
use rand::thread_rng;
use std::collections::HashMap;
@ -32,13 +33,14 @@ use frost_p256 as frost;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
# // ANCHOR_END: dkg_import
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 1
////////////////////////////////////////////////////////////////////////////
let max_signers = 5;
let min_signers = 3;
// Keep track of each participant's round 1 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
@ -53,16 +55,18 @@ let mut received_round1_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (secret_package, round1_package) = frost::keys::dkg::part1(
# // ANCHOR: dkg_part1
let (round1_secret_package, round1_package) = frost::keys::dkg::part1(
participant_identifier,
max_signers,
min_signers,
&mut rng,
)?;
# // ANCHOR_END: dkg_part1
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round1_secret_packages.insert(participant_identifier, secret_package);
round1_secret_packages.insert(participant_identifier, round1_secret_package);
// "Send" the round 1 package to all other participants. In this
// test this is simulated using a HashMap; in practice this will be
@ -99,12 +103,14 @@ let mut received_round2_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (round2_secret_package, round2_packages) = frost::keys::dkg::part2(
round1_secret_packages
.remove(&participant_identifier)
.unwrap(),
&received_round1_packages[&participant_identifier],
)?;
let round1_secret_package = round1_secret_packages
.remove(&participant_identifier)
.unwrap();
let round1_packages = &received_round1_packages[&participant_identifier];
# // ANCHOR: dkg_part2
let (round2_secret_package, round2_packages) =
frost::keys::dkg::part2(round1_secret_package, round1_packages)?;
# // ANCHOR_END: dkg_part2
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
@ -142,13 +148,18 @@ let mut pubkey_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (key_package, pubkey_package_for_participant) = frost::keys::dkg::part3(
&round2_secret_packages[&participant_identifier],
&received_round1_packages[&participant_identifier],
&received_round2_packages[&participant_identifier],
let round2_secret_package = &round2_secret_packages[&participant_identifier];
let round1_packages = &received_round1_packages[&participant_identifier];
let round2_packages = &received_round2_packages[&participant_identifier];
# // ANCHOR: dkg_part3
let (key_package, pubkey_package) = frost::keys::dkg::part3(
round2_secret_package,
round1_packages,
round2_packages,
)?;
# // ANCHOR_END: dkg_part3
key_packages.insert(participant_identifier, key_package);
pubkey_packages.insert(participant_identifier, pubkey_package_for_participant);
pubkey_packages.insert(participant_identifier, pubkey_package);
}
// With its own key package and the pubkey package, each participant can now proceed

View File

@ -9,6 +9,7 @@ scenario in a single thread and it abstracts away any communication between peer
```rust
# // ANCHOR: tkg_gen
use frost_ristretto255 as frost;
use rand::thread_rng;
use std::collections::HashMap;
@ -16,20 +17,23 @@ use std::collections::HashMap;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
let (shares, pubkeys) = frost::keys::generate_with_dealer(max_signers, min_signers, &mut rng)?;
let (shares, pubkey_package) = frost::keys::generate_with_dealer(max_signers, min_signers, &mut rng)?;
# // ANCHOR_END: tkg_gen
// Verifies the secret shares from the dealer and store them in a HashMap.
// In practice, the KeyPackages must be sent to its respective participants
// through a confidential and authenticated channel.
let mut key_packages: HashMap<_, _> = HashMap::new();
for (k, v) in shares {
let key_package = frost::keys::KeyPackage::try_from(v)?;
key_packages.insert(k, key_package);
for (identifier, secret_share) in shares {
# // ANCHOR: tkg_verify
let key_package = frost::keys::KeyPackage::try_from(secret_share)?;
# // ANCHOR_END: tkg_verify
key_packages.insert(identifier, key_package);
}
let mut nonces = HashMap::new();
let mut commitments = HashMap::new();
let mut nonces_map = HashMap::new();
let mut commitments_map = HashMap::new();
////////////////////////////////////////////////////////////////////////////
// Round 1: generating nonces and signing commitments for each participant
@ -38,43 +42,51 @@ let mut commitments = HashMap::new();
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_index in 1..(min_signers as u16 + 1) {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let key_package = &key_packages[&participant_identifier];
// Generate one (1) nonce and one SigningCommitments instance for each
// participant, up to _threshold_.
let (nonce, commitment) = frost::round1::commit(
# // ANCHOR: round1_commit
let (nonces, commitments) = frost::round1::commit(
participant_identifier,
key_packages[&participant_identifier].secret_share(),
key_package.secret_share(),
&mut rng,
);
// In practice, the nonces and commitment must be sent to the coordinator
# // ANCHOR_END: round1_commit
// In practice, the nonces must be kept by the participant to use in the
// next round, while the commitment must be sent to the coordinator
// (or to every other participant if there is no coordinator) using
// an authenticated channel.
nonces.insert(participant_identifier, nonce);
commitments.insert(participant_identifier, commitment);
nonces_map.insert(participant_identifier, nonces);
commitments_map.insert(participant_identifier, commitments);
}
// This is what the signature aggregator / coordinator needs to do:
// - decide what message to sign
// - take one (unused) commitment per signing participant
let mut signature_shares = Vec::new();
let commitments_received = commitments_map.clone().into_values().collect();
# // ANCHOR: round2_package
let message = "message to sign".as_bytes();
let comms = commitments.clone().into_values().collect();
// In practice, the SigningPackage must be sent to all participants
// involved in the current signing (at least min_signers participants),
// using an authenticate channel (and confidential if the message is secret).
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
# // In practice, the SigningPackage must be sent to all participants
# // involved in the current signing (at least min_signers participants),
# // using an authenticate channel (and confidential if the message is secret).
let signing_package = frost::SigningPackage::new(commitments_received, message.to_vec());
# // ANCHOR_END: round2_package
////////////////////////////////////////////////////////////////////////////
// Round 2: each participant generates their signature share
////////////////////////////////////////////////////////////////////////////
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_identifier in nonces.keys() {
for participant_identifier in nonces_map.keys() {
let key_package = &key_packages[participant_identifier];
let nonces_to_use = &nonces[participant_identifier];
let nonces = &nonces_map[participant_identifier];
// Each participant generates their signature share.
let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?;
# // ANCHOR: round2_sign
let signature_share = frost::round2::sign(&signing_package, nonces, key_package)?;
# // ANCHOR_END: round2_sign
// In practice, the signature share must be sent to the Coordinator
// using an authenticated channel.
@ -87,14 +99,20 @@ for participant_identifier in nonces.keys() {
////////////////////////////////////////////////////////////////////////////
// Aggregate (also verifies the signature shares)
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?;
# // ANCHOR: aggregate
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkey_package)?;
# // ANCHOR_END: aggregate
// Check that the threshold signature can be verified by the group public
// key (the verification key).
assert!(pubkeys
# // ANCHOR: verify
let is_signature_valid = pubkey_package
.group_public
.verify(message, &group_signature)
.is_ok());
.is_ok();
# // ANCHOR_END: verify
assert!(is_signature_valid);
# Ok::<(), frost::Error>(())
```

View File

@ -25,6 +25,7 @@ they can proceed to sign messages with FROST.
## Example
```rust
# // ANCHOR: dkg_import
use rand::thread_rng;
use std::collections::HashMap;
@ -32,13 +33,14 @@ use frost_ristretto255 as frost;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
# // ANCHOR_END: dkg_import
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 1
////////////////////////////////////////////////////////////////////////////
let max_signers = 5;
let min_signers = 3;
// Keep track of each participant's round 1 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
@ -53,16 +55,18 @@ let mut received_round1_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (secret_package, round1_package) = frost::keys::dkg::part1(
# // ANCHOR: dkg_part1
let (round1_secret_package, round1_package) = frost::keys::dkg::part1(
participant_identifier,
max_signers,
min_signers,
&mut rng,
)?;
# // ANCHOR_END: dkg_part1
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round1_secret_packages.insert(participant_identifier, secret_package);
round1_secret_packages.insert(participant_identifier, round1_secret_package);
// "Send" the round 1 package to all other participants. In this
// test this is simulated using a HashMap; in practice this will be
@ -99,12 +103,14 @@ let mut received_round2_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (round2_secret_package, round2_packages) = frost::keys::dkg::part2(
round1_secret_packages
.remove(&participant_identifier)
.unwrap(),
&received_round1_packages[&participant_identifier],
)?;
let round1_secret_package = round1_secret_packages
.remove(&participant_identifier)
.unwrap();
let round1_packages = &received_round1_packages[&participant_identifier];
# // ANCHOR: dkg_part2
let (round2_secret_package, round2_packages) =
frost::keys::dkg::part2(round1_secret_package, round1_packages)?;
# // ANCHOR_END: dkg_part2
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
@ -142,13 +148,18 @@ let mut pubkey_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (key_package, pubkey_package_for_participant) = frost::keys::dkg::part3(
&round2_secret_packages[&participant_identifier],
&received_round1_packages[&participant_identifier],
&received_round2_packages[&participant_identifier],
let round2_secret_package = &round2_secret_packages[&participant_identifier];
let round1_packages = &received_round1_packages[&participant_identifier];
let round2_packages = &received_round2_packages[&participant_identifier];
# // ANCHOR: dkg_part3
let (key_package, pubkey_package) = frost::keys::dkg::part3(
round2_secret_package,
round1_packages,
round2_packages,
)?;
# // ANCHOR_END: dkg_part3
key_packages.insert(participant_identifier, key_package);
pubkey_packages.insert(participant_identifier, pubkey_package_for_participant);
pubkey_packages.insert(participant_identifier, pubkey_package);
}
// With its own key package and the pubkey package, each participant can now proceed

View File

@ -9,6 +9,7 @@ scenario in a single thread and it abstracts away any communication between peer
```rust
# // ANCHOR: tkg_gen
use frost_secp256k1 as frost;
use rand::thread_rng;
use std::collections::HashMap;
@ -16,20 +17,23 @@ use std::collections::HashMap;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
let (shares, pubkeys) = frost::keys::generate_with_dealer(max_signers, min_signers, &mut rng)?;
let (shares, pubkey_package) = frost::keys::generate_with_dealer(max_signers, min_signers, &mut rng)?;
# // ANCHOR_END: tkg_gen
// Verifies the secret shares from the dealer and store them in a HashMap.
// In practice, the KeyPackages must be sent to its respective participants
// through a confidential and authenticated channel.
let mut key_packages: HashMap<_, _> = HashMap::new();
for (k, v) in shares {
let key_package = frost::keys::KeyPackage::try_from(v)?;
key_packages.insert(k, key_package);
for (identifier, secret_share) in shares {
# // ANCHOR: tkg_verify
let key_package = frost::keys::KeyPackage::try_from(secret_share)?;
# // ANCHOR_END: tkg_verify
key_packages.insert(identifier, key_package);
}
let mut nonces = HashMap::new();
let mut commitments = HashMap::new();
let mut nonces_map = HashMap::new();
let mut commitments_map = HashMap::new();
////////////////////////////////////////////////////////////////////////////
// Round 1: generating nonces and signing commitments for each participant
@ -38,43 +42,51 @@ let mut commitments = HashMap::new();
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_index in 1..(min_signers as u16 + 1) {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let key_package = &key_packages[&participant_identifier];
// Generate one (1) nonce and one SigningCommitments instance for each
// participant, up to _threshold_.
let (nonce, commitment) = frost::round1::commit(
# // ANCHOR: round1_commit
let (nonces, commitments) = frost::round1::commit(
participant_identifier,
key_packages[&participant_identifier].secret_share(),
key_package.secret_share(),
&mut rng,
);
// In practice, the nonces and commitment must be sent to the coordinator
# // ANCHOR_END: round1_commit
// In practice, the nonces must be kept by the participant to use in the
// next round, while the commitment must be sent to the coordinator
// (or to every other participant if there is no coordinator) using
// an authenticated channel.
nonces.insert(participant_identifier, nonce);
commitments.insert(participant_identifier, commitment);
nonces_map.insert(participant_identifier, nonces);
commitments_map.insert(participant_identifier, commitments);
}
// This is what the signature aggregator / coordinator needs to do:
// - decide what message to sign
// - take one (unused) commitment per signing participant
let mut signature_shares = Vec::new();
let commitments_received = commitments_map.clone().into_values().collect();
# // ANCHOR: round2_package
let message = "message to sign".as_bytes();
let comms = commitments.clone().into_values().collect();
// In practice, the SigningPackage must be sent to all participants
// involved in the current signing (at least min_signers participants),
// using an authenticate channel (and confidential if the message is secret).
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
# // In practice, the SigningPackage must be sent to all participants
# // involved in the current signing (at least min_signers participants),
# // using an authenticate channel (and confidential if the message is secret).
let signing_package = frost::SigningPackage::new(commitments_received, message.to_vec());
# // ANCHOR_END: round2_package
////////////////////////////////////////////////////////////////////////////
// Round 2: each participant generates their signature share
////////////////////////////////////////////////////////////////////////////
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_identifier in nonces.keys() {
for participant_identifier in nonces_map.keys() {
let key_package = &key_packages[participant_identifier];
let nonces_to_use = &nonces[participant_identifier];
let nonces = &nonces_map[participant_identifier];
// Each participant generates their signature share.
let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?;
# // ANCHOR: round2_sign
let signature_share = frost::round2::sign(&signing_package, nonces, key_package)?;
# // ANCHOR_END: round2_sign
// In practice, the signature share must be sent to the Coordinator
// using an authenticated channel.
@ -87,14 +99,20 @@ for participant_identifier in nonces.keys() {
////////////////////////////////////////////////////////////////////////////
// Aggregate (also verifies the signature shares)
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?;
# // ANCHOR: aggregate
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkey_package)?;
# // ANCHOR_END: aggregate
// Check that the threshold signature can be verified by the group public
// key (the verification key).
assert!(pubkeys
# // ANCHOR: verify
let is_signature_valid = pubkey_package
.group_public
.verify(message, &group_signature)
.is_ok());
.is_ok();
# // ANCHOR_END: verify
assert!(is_signature_valid);
# Ok::<(), frost::Error>(())
```

View File

@ -25,6 +25,7 @@ they can proceed to sign messages with FROST.
## Example
```rust
# // ANCHOR: dkg_import
use rand::thread_rng;
use std::collections::HashMap;
@ -32,13 +33,14 @@ use frost_secp256k1 as frost;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
# // ANCHOR_END: dkg_import
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 1
////////////////////////////////////////////////////////////////////////////
let max_signers = 5;
let min_signers = 3;
// Keep track of each participant's round 1 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
@ -53,16 +55,18 @@ let mut received_round1_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (secret_package, round1_package) = frost::keys::dkg::part1(
# // ANCHOR: dkg_part1
let (round1_secret_package, round1_package) = frost::keys::dkg::part1(
participant_identifier,
max_signers,
min_signers,
&mut rng,
)?;
# // ANCHOR_END: dkg_part1
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round1_secret_packages.insert(participant_identifier, secret_package);
round1_secret_packages.insert(participant_identifier, round1_secret_package);
// "Send" the round 1 package to all other participants. In this
// test this is simulated using a HashMap; in practice this will be
@ -99,12 +103,14 @@ let mut received_round2_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (round2_secret_package, round2_packages) = frost::keys::dkg::part2(
round1_secret_packages
.remove(&participant_identifier)
.unwrap(),
&received_round1_packages[&participant_identifier],
)?;
let round1_secret_package = round1_secret_packages
.remove(&participant_identifier)
.unwrap();
let round1_packages = &received_round1_packages[&participant_identifier];
# // ANCHOR: dkg_part2
let (round2_secret_package, round2_packages) =
frost::keys::dkg::part2(round1_secret_package, round1_packages)?;
# // ANCHOR_END: dkg_part2
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
@ -142,13 +148,18 @@ let mut pubkey_packages = HashMap::new();
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (key_package, pubkey_package_for_participant) = frost::keys::dkg::part3(
&round2_secret_packages[&participant_identifier],
&received_round1_packages[&participant_identifier],
&received_round2_packages[&participant_identifier],
let round2_secret_package = &round2_secret_packages[&participant_identifier];
let round1_packages = &received_round1_packages[&participant_identifier];
let round2_packages = &received_round2_packages[&participant_identifier];
# // ANCHOR: dkg_part3
let (key_package, pubkey_package) = frost::keys::dkg::part3(
round2_secret_package,
round1_packages,
round2_packages,
)?;
# // ANCHOR_END: dkg_part3
key_packages.insert(participant_identifier, key_package);
pubkey_packages.insert(participant_identifier, pubkey_package_for_participant);
pubkey_packages.insert(participant_identifier, pubkey_package);
}
// With its own key package and the pubkey package, each participant can now proceed

View File

@ -1,431 +0,0 @@
# FROST messages
Proposes a message layout to exchange information between participants of a FROST setup using the [jubjub](https://github.com/zkcrypto/jubjub) curve.
## Motivation
Currently FROST library is complete for 2 round signatures with a dealer/aggregator setup.
This proposal acknowledges that specific features, additions and upgrades will need to be made when DKG is implemented.
Assuming all participants have a FROST library available, we need to define message structures in a way that data can be exchanged between participants. The proposal is a collection of data types so each side can do all the actions needed for a real life situation.
## Definitions
- `dealer` - Participant who distributes the initial package to all the other participants.
- `aggregator` - Participant in charge of collecting all the signatures from the other participants and generating the final group signature.
- `signer` - Participant that will receive the initial package, sign and send the signature to the aggregator to receive the final group signature.
Note: In this RFC we consider the above 3 participants to be different. `dealer` and `aggregator` have specific hard coded `ParticipantId`s, so for example a `dealer` can't be a `signer`. This is not a protocol limitation but a specific rule introduced in this document.
## Guide-level explanation
We propose a message separated in 2 parts, a header and a payload:
```rust
/// The data required to serialize a frost message.
struct Message {
header: Header,
payload: Payload,
}
```
`Header` will look as follows:
```rust
/// The data required to serialize the common header fields for every message.
///
/// Note: the `msg_type` is derived from the `payload` enum variant.
struct Header {
version: MsgVersion,
sender: ParticipantId,
receiver: ParticipantId,
}
```
While `Payload` will be defined as:
```rust
/// The data required to serialize the payload for a message.
enum Payload {
SharePackage(messages::SharePackage),
SigningCommitments(messages::SigningCommitments),
SigningPackage(messages::SigningPackage),
SignatureShare(messages::SignatureShare),
AggregateSignature(messages::AggregateSignature),
}
```
All the messages and new types will be defined in a new file `src/frost/messages.rs`
## Reference-level explanation
Here we explore in detail the header types and all the message payloads.
### Header
Fields of the header define new types. Proposed implementation for them is as follows:
```rust
/// The numeric values used to identify each `Payload` variant during serialization.
#[repr(u8)]
#[non_exhaustive]
enum MsgType {
SharePackage,
SigningCommitments,
SigningPackage,
SignatureShare,
AggregateSignature,
}
/// The numeric values used to identify the protocol version during serialization.
struct MsgVersion(u8);
const BASIC_FROST_SERIALIZATION: MsgVersion = MsgVersion(0);
/// The numeric values used to identify each participant during serialization.
///
/// In the `frost` module, participant ID `0` should be invalid.
/// But in serialization, we want participants to be indexed from `0..n`,
/// where `n` is the number of participants.
/// This helps us look up their shares and commitments in serialized arrays.
/// So in serialization, we assign the dealer and aggregator the highest IDs,
/// and mark those IDs as invalid for signers. Then we serialize the
/// participants in numeric order of their FROST IDs.
///
/// "When performing Shamir secret sharing, a polynomial `f(x)` is used to generate
/// each partys share of the secret. The actual secret is `f(0)` and the party with
/// ID `i` will be given a share with value `f(i)`.
/// Since a DKG may be implemented in the future, we recommend that the ID `0` be declared invalid."
/// https://raw.githubusercontent.com/ZcashFoundation/redjubjub/main/zcash-frost-audit-report-20210323.pdf#d
enum ParticipantId {
/// A serialized participant ID for a signer.
///
/// Must be less than or equal to `MAX_SIGNER_PARTICIPANT_ID`.
Signer(u64),
/// The fixed participant ID for the dealer.
Dealer,
/// The fixed participant ID for the aggregator.
Aggregator,
}
/// The fixed participant ID for the dealer.
const DEALER_PARTICIPANT_ID: u64 = u64::MAX - 1;
/// The fixed participant ID for the aggregator.
const AGGREGATOR_PARTICIPANT_ID: u64 = u64::MAX;
/// The maximum `ParticipantId::Signer` in this serialization format.
///
/// We reserve two participant IDs for the dealer and aggregator.
const MAX_SIGNER_PARTICIPANT_ID: u64 = u64::MAX - 2;
```
### Payloads
Each payload defines a new message:
```rust
/// The data required to serialize `frost::SharePackage`.
///
/// The dealer sends this message to each signer for this round.
/// With this, the signer should be able to build a `SharePackage` and use
/// the `sign()` function.
///
/// Note: `frost::SharePackage.public` can be calculated from `secret_share`.
struct messages::SharePackage {
/// The public signing key that represents the entire group:
/// `frost::SharePackage.group_public`.
group_public: VerificationKey<SpendAuth>,
/// This participant's secret key share: `frost::SharePackage.share.value`.
secret_share: frost::Secret,
/// The commitments to the coefficients for our secret polynomial _f_,
/// used to generate participants' key shares. Participants use these to perform
/// verifiable secret sharing.
/// Share packages that contain duplicate or missing `ParticipantId`s are invalid.
/// `ParticipantId`s must be serialized in ascending numeric order.
share_commitment: BTreeMap<ParticipantId, frost::Commitment>,
}
/// The data required to serialize `frost::SigningCommitments`.
///
/// Each signer must send this message to the aggregator.
/// A signing commitment from the first round of the signing protocol.
struct messages::SigningCommitments {
/// The hiding point: `frost::SigningCommitments.hiding`
hiding: frost::Commitment,
/// The binding point: `frost::SigningCommitments.binding`
binding: frost::Commitment,
}
/// The data required to serialize `frost::SigningPackage`.
///
/// The aggregator decides what message is going to be signed and
/// sends it to each signer with all the commitments collected.
struct messages::SigningPackage {
/// The collected commitments for each signer as an ordered map of
/// unique participant identifiers: `frost::SigningPackage.signing_commitments`
///
/// Signing packages that contain duplicate or missing `ParticipantId`s are invalid.
/// `ParticipantId`s must be serialized in ascending numeric order.
signing_commitments: BTreeMap<ParticipantId, SigningCommitments>,
/// The message to be signed: `frost::SigningPackage.message`.
///
/// Each signer should perform protocol-specific verification on the message.
message: Vec<u8>,
}
/// The data required to serialize `frost::SignatureShare`.
///
/// Each signer sends their signatures to the aggregator who is going to collect them
/// and generate a final spend signature.
struct messages::SignatureShare {
/// This participant's signature over the message: `frost::SignatureShare.signature`
signature: frost::SignatureResponse,
}
/// The data required to serialize a successful output from `frost::aggregate()`.
///
/// The final signature is broadcasted by the aggregator to all signers.
struct messages::AggregateSignature {
/// The aggregated group commitment: `Signature<SpendAuth>.r_bytes` returned by `frost::aggregate`
group_commitment: frost::GroupCommitment,
/// A plain Schnorr signature created by summing all the signature shares:
/// `Signature<SpendAuth>.s_bytes` returned by `frost::aggregate`
schnorr_signature: frost::SignatureResponse,
}
```
## Validation
Validation is implemented to each new data type as needed. This will ensure the creation of valid messages before they are send and right after they are received. We create a trait for this as follows:
```rust
pub trait Validate {
fn validate(&self) -> Result<&Self, MsgErr>;
}
```
And we implement where needed. For example, in the header, sender and receiver can't be the same:
```rust
impl Validate for Header {
fn validate(&self) -> Result<&Self, MsgErr> {
if self.sender.0 == self.receiver.0 {
return Err(MsgErr::SameSenderAndReceiver);
}
Ok(self)
}
}
```
This will require to have validation error messages as:
```rust
use thiserror::Error;
#[derive(Clone, Error, Debug)]
pub enum MsgErr {
#[error("sender and receiver are the same")]
SameSenderAndReceiver,
}
```
Then to create a valid `Header` in the sender side we call:
```rust
let header = Validate::validate(&Header {
..
}).expect("a valid header");
```
The receiver side will validate the header using the same method. Instead of panicking the error can be ignored to don't crash and keep waiting for other (potentially valid) messages.
```rust
if let Ok(header) = msg.header.validate() {
..
}
```
### Rules
The following rules must be implemented:
#### Header
- `version` must be a supported version.
- `sender` and `receiver` can't be the same.
- The `ParticipantId` variants of `sender` and `receiver` must match the message type.
#### Payloads
- Each jubjub type must be validated during deserialization.
- `share_commitments`:
- Length must be less than or equal to `MAX_SIGNER_PARTICIPANT_ID`.
- Length must be at least `MIN_SIGNERS` (`2` signers).
- Duplicate `ParticipantId`s are invalid. This is implicit in the use of `BTreeMap` during serialization, but must be checked during deserialization.
- Commitments must be serialized in ascending numeric `ParticipantId` order. This is the order of `BTreeMap.iter` during serialization, but must be checked during deserialization.
- `signing_commitments`:
- Length must be less than or equal to `MAX_SIGNER_PARTICIPANT_ID`.
- Length must be at least `MIN_THRESHOLD` (`2` required signers).
- Signing packages that contain duplicate `ParticipantId`s are invalid. This is implicit in the use of `BTreeMap` during serialization, but must be checked during deserialization.
- Signing packages must serialize in ascending numeric `ParticipantId` order. This is the order of `BTreeMap.iter` during serialization, but must be checked during deserialization..
- `message`: signed messages have a protocol-specific length limit. For Zcash, that limit is the maximum network protocol message length: `2^21` bytes (2 MB).
## Serialization/Deserialization
Each message struct needs to serialize to bytes representation before it is sent through the wire and must deserialize to the same struct (round trip) on the receiver side. We use `serde` and macro derivations (`Serialize` and `Deserialize`) to automatically implement where possible.
This will require deriving serde in several types defined in `frost.rs`.
Manual implementation of serialization/deserialization will be located at a new mod `src/frost/serialize.rs`.
### Byte order
Each byte chunk specified below is in little-endian order unless is specified otherwise.
Multi-byte integers **must not** be used for serialization, because they have different byte orders on different platforms.
### Header
The `Header` part of the message is 18 bytes total:
Bytes | Field name | Data type
------|------------|-----------
1 | version | u8
1 | msg_type | u8
8 | sender | u64
8 | receiver | u64
### Frost types
The FROST types we will be using in the messages can be represented always as a primitive type. For serialization/deserialization purposes:
- `Commitment` = `AffinePoint`
- `Secret` = `Scalar`
- `GroupCommitment` = `AffinePoint`
- `SignatureResponse` = `Scalar`
### Primitive types
`Payload`s use data types that we need to specify first. We have 3 primitive types inside the payload messages:
#### `Scalar`
`jubjub::Scalar` is a an alias for `jubjub::Fr`. We use `Scalar::to_bytes` and `Scalar::from_bytes` to get a 32-byte little-endian canonical representation. See https://github.com/zkcrypto/bls12_381/blob/main/src/scalar.rs#L260 and https://github.com/zkcrypto/bls12_381/blob/main/src/scalar.rs#L232
#### `AffinePoint`
Much of the math in FROST is done using `jubjub::ExtendedPoint`. But for message exchange `jubjub::AffinePoint`s are a better choice, as their byte representation is smaller.
Conversion from one type to the other is trivial:
https://docs.rs/jubjub/0.6.0/jubjub/struct.AffinePoint.html#impl-From%3CExtendedPoint%3E
https://docs.rs/jubjub/0.6.0/jubjub/struct.ExtendedPoint.html#impl-From%3CAffinePoint%3E
We use `AffinePoint::to_bytes` and `AffinePoint::from_bytes` to get a 32-byte little-endian canonical representation. See https://github.com/zkcrypto/jubjub/blob/main/src/lib.rs#L443
#### VerificationKey
`redjubjub::VerificationKey<SpendAuth>`s can be serialized and deserialized using `<[u8; 32]>::from` and `VerificationKey::from`. See https://github.com/ZcashFoundation/redjubjub/blob/main/src/verification_key.rs#L80-L90 and https://github.com/ZcashFoundation/redjubjub/blob/main/src/verification_key.rs#L114-L121.
### Payload
Payload part of the message is variable in size and depends on message type.
#### `SharePackage`
Bytes | Field name | Data type
----------------|------------------|-----------
32 | group_public | VerificationKey<SpendAuth>
32 | secret_share | Share
1 | participants | u8
(8+32)*participants | share_commitment | BTreeMap<ParticipantId, Commitment>
#### `SigningCommitments`
Bytes | Field name | Data type
--------|---------------------|-----------
32 | hiding | Commitment
32 | binding | Commitment
#### `SigningPackage`
Bytes | Field name | Data type
-----------------------|--------------------|-----------
1 | participants | u8
(8+32+32)*participants | signing_commitments| BTreeMap<ParticipantId, SigningCommitments>
8 | message_length | u64
message_length | message | Vec\<u8\>
#### `SignatureShare`
Bytes | Field name | Data type
------|------------|-----------
32 | signature | SignatureResponse
#### `AggregateSignature`
Bytes | Field name | Data type
------|------------------|-----------
32 | group_commitment | GroupCommitment
32 | schnorr_signature| SignatureResponse
## Not included
The following are a few things this RFC is not considering:
- The RFC does not describe implementation-specific issues - it is focused on message structure and serialization.
- Implementations using this serialization should handle missing messages using timeouts or similar protocol-specific mechanisms.
- This is particularly important for `SigningPackage`s, which only need a threshold of participants to continue.
- Messages larger than 4 GB are not supported on 32-bit platforms.
- Implementations should validate that message lengths are lower than a protocol-specific maximum length, then allocate message memory.
- Implementations should distinguish between FROST messages from different signature schemes using implementation-specific mechanisms.
### State-Based Validation
The following validation rules should be checked by the implementation:
- `share_commitments`: The number of participants in each round is set by the length of `share_commitments`.
- If `sender` and `receiver` are a `ParticipantId::Signer`, they must be less than the number of participants in this round.
- The length of `signing_commitments` must be less than or equal to the number of participants in this round.
- `signing_commitments`: Signing packages that contain missing `ParticipantId`s are invalid
- Note: missing participants are supported by this serialization format.
But implementations can require all participants to fully participate in each round.
If the implementation knows the number of key shares, it should re-check all the validation rules involving `MAX_SIGNER_PARTICIPANT_ID` using that lower limit.
## Testing plan
### Test Vectors
#### Conversion on Test Vectors
- Test conversion from `frost` to `message` on a test vector
1. Implement the Rust `message` struct
2. Implement conversion from and to the `frost` type
3. Do a round-trip test from `frost` to `message` on a test vector
- Test conversion from `message` to bytes on a test vector
1. Implement conversion from and to the `message` type
2. Do a round-trip test from `message` to bytes on a test vector
#### Signing Rounds on Test Vectors
- Test signing using `frost` types on a test vector
1. Implement a single round of `frost` signing using a test vector
- Test signing using `message` types on a test vector
- Test signing using byte vectors on a test vector
### Property Tests
#### Conversion Property Tests
- Create property tests for each message
- Test round-trip conversion from `frost` to `message` types
- Test round-trip serialization and deserialization for each `message` type
#### Signing Round Property Tests
- Create property tests for signing rounds
- Test a signing round with `frost` types
- Test a signing round with `message` types
- Test a signing round with byte vectors