Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add docs #50

Merged
merged 7 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/readme.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Check README

on:
pull_request:
branches: [ "*" ]

jobs:
check_readme:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install cargo-hakari
uses: baptiste0928/cargo-install@v1
with:
crate: cargo-readme
- name: Check that readme matches lib.rs
run: |
cp README.md README-copy.md
make readme
diff README.md README-copy.md
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: docs docs-open
.PHONY: docs docs-open docs-private readme readme-check

docs:
RUSTDOCFLAGS="--html-in-header katex-header.html --cfg docsrs" cargo +nightly doc --no-deps
Expand All @@ -8,3 +8,12 @@ docs-open:

docs-private:
RUSTDOCFLAGS="--html-in-header katex-header.html --cfg docsrs" cargo +nightly doc --no-deps --document-private-items

readme:
cargo readme -i src/lib.rs -r cggmp21/ -t ../docs/README.tpl --no-indent-headings \
| sed -E 's/(\/\*.+\*\/)/\1;/' \
| sed -E '/^\[`.+`\]:/d' \
| sed -E 's/\[`([^`]*)`\]/`\1`/g' \
| sed -E 's/\[([^\]+)\]\([^)]+\)/\1/g' \
| sed -E '/^#$$/d' \
> README.md
155 changes: 153 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,153 @@
# cggmp21
State-of-art threshold ECDSA in Rust
# Threshold ECDSA based on CGGMP21 paper

[CGGMP21] is a state-of-art ECDSA TSS protocol that supports 1-round signing (requires preprocessing),
identifiable abort, provides two signing protocols (3+1 and 5+1 rounds with different complexity
of abort identification) and key refresh protocol out of the box.

This crate implements:
* Threshold and non-threshold key generation
* 3+1 rounds threshold and non-threshold signing
* Auxiliary info generation protocol
* Key refresh for non-threshold keys

We also provide auxiliary tools like:
* Secret key reconstruction (exporting key from TSS)
* Trusted dealer (importing key into TSS)

This crate **does not** support (currently):
* Threshold key refresh
* Identifiable abort
* 5+1 rounds signing protocol

## Running protocol

### Networking
In order to run protocol, you need to define how signer can communicate with other signers. We
use `round_based` framework that handles network part. Basically, you need to define: a stream
of `incoming` messages and sink of `outgoing` messages:

```rust
let incoming: impl Stream<Item = Result<Incoming<Msg>>>;
let outgoing: impl Sink<Outgoing<Msg>>;
```

where:
* `Msg` is protocol message (e.g., `signing::msg::Msg`)
* `round_based::Incoming` and `round_based::Outgoing` wrap `Msg` and provide additional data (e.g., sender/recepient)
* `futures::Stream` and `futures::Sink` are well-known async primitives.

Then, construct a `round_based::MpcParty`:
```rust
let delivery = (incoming, outgoing);
let party = round_based::MpcParty::connected(delivery);
```

#### Signers indexes
Each signer in protocol execution (keygen/signing/etc.) occupies a unique index $i$ ($0 \le i < n$,
where $n$ is amount of parties in the protocol). For instance, if Signer A occupies index `2`, then all
other signers must acknowledge that `i=2` corresponds to Signer A.

Assuming you have some sort of PKI (which you need to comply with [security requirements]) and each signer
has a public key which uniqely idenitifies that signer, you can easily assign unique indexes to the signers:
1. Make a list of signers public keys
2. Sort the list of public keys
3. Assign each signer index `i` such that `i` corresponds to position of signer public key in sorted list of
public keys

[security requirements]: #security

#### Security
Make sure that communication layer complies with security requirements:
* All messages sent between parties must be authenticated
* All p2p messages must be encrypted

### Execution ID
Final step of preparation, all the signers need to agree on unique identifier of protocol execution `ExecutionId`.
Execution ID needs to be unique per protocol execution (keygen/signing/etc.), otherwise it may compromise security.
Execution ID needs to be the same for all signers taking part in the protocol, otherwise protocol will abort.
Execution ID **doesn't** need to be secret.

Now that signers can talk to each other and they have an execution ID, they're ready to generate a key!

### Distributed Key Generation
```rust
use cggmp21::supported_curves::Secp256k1;

let eid = cggmp21::ExecutionId::new(b"execution id, unique per protocol execution");
let i = /* signer index (0 <= i < n) */;
let n = /* amount of signers taking part in key generation */;
let t = /* threshold */;

let incomplete_key_share = cggmp21::keygen::<Secp256k1>(eid, i, n)
.set_threshold(t)
.start(&mut OsRng, party)
.await?;
```
This code outputs `IncompleteKeyShare`. Note that this key share is not ready yet to do signing. You need to “complete” it
by generating auxiliary info (see below).

### Auxiliary info generation
After key generation, all signers need to take part in auxiliary information generation. Make sure all signers occupy exactly
the same indexes as at keygen.
```rust
// Primes generation can take a while
let pregenerated_primes = cggmp21::PregeneratedPrimes::generate(&mut OsRng);

let eid = cggmp21::ExecutionId::new(b"execution id, unique per protocol execution");
let i = /* signer index, same as at keygen */;
let n = /* amount of signers */;

let aux_info = cggmp21::aux_info_gen(eid, i, n, pregenerated_primes)
.start(&mut OsRng, party)
.await?;
```

After keygen and aux info gen are done, you can make a “complete” key share that can be used for signing:
```rust
let key_share = cggmp21::KeyShare::make(incomplete_key_share, aux_info)?;
```

### Signing
Once completed key share is obtained, signers can do signing or generate presignatures. In either case, threshold amount of
signers must take part in the protocol. Similar to previous protocols, at signing each signer needs to be assigned an index
`0 <= i < min_signers`, but we also need to know which index each signer occupied at keygen.

In the example below, we do a full signing:
```rust
let eid = cggmp21::ExecutionId::new(b"execution id, unique per protocol execution");

let i = /* signer index (0 <= i < min_signers) */;
let parties_indexes_at_keygen: [u16; MIN_SIGNERS] =
/* parties_indexes_at_keygen[i] is index which i-th party occupied at keygen */;
let key_share = /* completed key share */;

let data_to_sign = cggmp21::DataToSign::digest::<Sha256>(b"data to be signed");

let signature = cggmp21::signing(eid, i, &parties_indexes_at_keygen, &key_share)
.sign(&mut OsRng, party, data_to_sign)
.await?;
```

Alternatively, you can generate presignature and use it to sign data:
1. Use `SigningBuilder::generate_presignature` to run presignature generation protocol
2. Once signing request is received, each signer issues a partial signature using
`Presignature::issue_partial_signature`
3. Combine threshold amount of partial signatures using `PartialSignature::combine` to
obtain a regular signature

**Never reuse presignatures!** If you use the same presignature to sign two different messages,
it leaks private key to anyone who can observe the signatures.

## Implementation vs CGGMP21 paper differences
Original CGGMP21 paper only defines non-threshold (n-out-of-n) protocol. To support threshold
(t-out-of-n) signing, we defined our own CGGMP21-like key generation and threshold signing
protocol which works based on original non-threshold signing protocol. However, we keep both
threshold and non-threshold versions of the protocols in the crate, so if you opt for non-threshold
protocol, you will be running original protocol defined in the paper.

There are other differences in the implementation compared to original paper (mostly typo fixes),
they are all documented in [the spec].

[CGGMP21]: https://ia.cr/2021/060
[the spec]: https://github.com/dfns-labs/cggmp21/tree/m/docs/spec.pdf
[security guidelines]: #security-guidelines
4 changes: 4 additions & 0 deletions cggmp21/src/key_refresh.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Key refresh & aux info generation protocols

/// Auxiliary info (re)generation protocol specific types
mod aux_only;
/// Non-threshold key refresh specific types
Expand Down Expand Up @@ -229,6 +231,7 @@ where
}
}

/// Sets a tracer that tracks progress of protocol execution
pub fn set_progress_tracer(mut self, tracer: &'a mut dyn Tracer) -> Self {
self.tracer = Some(tracer);
self
Expand All @@ -243,6 +246,7 @@ where
}
}

/// Error of key refresh and aux info generation protocols
#[derive(Debug, Error)]
#[error("key refresh protocol failed to complete")]
pub struct KeyRefreshError(#[source] Reason);
Expand Down
34 changes: 21 additions & 13 deletions cggmp21/src/key_refresh/aux_only.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,43 +31,51 @@ use super::{Bug, KeyRefreshError, PregeneratedPrimes, ProtocolAborted};
// 3 kilobytes for the largest option, and 2.5 kilobytes for second largest
#[allow(clippy::large_enum_variant)]
pub enum Msg<D: Digest, L: SecurityLevel> {
/// Round 1 message
Round1(MsgRound1<D>),
/// Round 2 message
Round2(MsgRound2<D, L>),
/// Round 3 message
Round3(MsgRound3),
/// Reliability check message (optional additional round)
ReliabilityCheck(MsgReliabilityCheck<D>),
}

/// Message from round 1
#[derive(Clone, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct MsgRound1<D: Digest> {
commitment: HashCommit<D>,
/// $V_i$
pub commitment: HashCommit<D>,
}
/// Message from round 2
#[derive(Clone, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct MsgRound2<D: Digest, L: SecurityLevel> {
N: BigNumber,
s: BigNumber,
t: BigNumber,
/// psi_circonflexe_i in paper
/// $N_i$
pub N: BigNumber,
/// $s_i$
pub s: BigNumber,
/// $t_i$
pub t: BigNumber,
/// $\hat \psi_i$
// this should be L::M instead, but no rustc support yet
params_proof: π_prm::Proof<{ π_prm::SECURITY }>,
/// rho_i in paper
pub params_proof: π_prm::Proof<{ π_prm::SECURITY }>,
/// $\rho_i$
// ideally it would be [u8; L::SECURITY_BYTES], but no rustc support yet
#[serde(with = "hex")]
pub rho_bytes: L::Rid,
/// u_i in paper
decommit: hash_commitment::DecommitNonce<D>,
/// $u_i$
pub decommit: hash_commitment::DecommitNonce<D>,
}
/// Unicast message of round 3, sent to each participant
#[derive(Clone, Serialize, Deserialize)]
pub struct MsgRound3 {
/// psi_i in paper
/// $\psi_i$
// this should be L::M instead, but no rustc support yet
mod_proof: (π_mod::Commitment, π_mod::Proof<{ π_prm::SECURITY }>),
/// phi_i^j in paper
fac_proof: π_fac::Proof,
pub mod_proof: (π_mod::Commitment, π_mod::Proof<{ π_prm::SECURITY }>),
/// $\phi_i^j$
pub fac_proof: π_fac::Proof,
}

/// Message from an optional round that enforces reliability check
Expand Down
26 changes: 17 additions & 9 deletions cggmp21/src/key_refresh/non_threshold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,51 +41,59 @@ use crate::{
// 3 kilobytes for the largest option, and 2.5 kilobytes for second largest
#[allow(clippy::large_enum_variant)]
pub enum Msg<E: Curve, D: Digest, L: SecurityLevel> {
/// Round 1 message
Round1(MsgRound1<D>),
/// Round 2 message
Round2(MsgRound2<E, D, L>),
/// Round 3 message
Round3(MsgRound3<E>),
/// Reliability check message (optional additional round)
ReliabilityCheck(MsgReliabilityCheck<D>),
}

/// Message from round 1
#[derive(Clone, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct MsgRound1<D: Digest> {
/// $V_i$
pub commitment: HashCommit<D>,
}
/// Message from round 2
#[derive(Clone, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct MsgRound2<E: Curve, D: Digest, L: SecurityLevel> {
/// **X_i** in paper
/// $\vec X_i$
pub Xs: Vec<Point<E>>,
/// **A_i** in paper
/// $\vec A_i$
pub sch_commits_a: Vec<schnorr_pok::Commit<E>>,
/// $N_i$
pub N: BigNumber,
/// $s_i$
pub s: BigNumber,
/// $t_i$
pub t: BigNumber,
/// psi_circonflexe_i in paper
/// $\hat \psi_i$
// this should be L::M instead, but no rustc support yet
pub params_proof: π_prm::Proof<{ π_prm::SECURITY }>,
/// rho_i in paper
/// $\rho_i$
// ideally it would be [u8; L::SECURITY_BYTES], but no rustc support yet
#[serde(with = "hex")]
pub rho_bytes: L::Rid,
/// u_i in paper
/// $u_i$
pub decommit: hash_commitment::DecommitNonce<D>,
}
/// Unicast message of round 3, sent to each participant
#[derive(Clone, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct MsgRound3<E: Curve> {
/// psi_i in paper
/// $\psi_i$
// this should be L::M instead, but no rustc support yet
pub mod_proof: (π_mod::Commitment, π_mod::Proof<{ π_prm::SECURITY }>),
/// phi_i^j in paper
/// $\phi_i^j$
pub fac_proof: π_fac::Proof,
/// C_i^j in paper
/// $C_i^j$
pub C: BigNumber,
/// psi_i_j in paper
/// $\psi_i^k$
///
/// Here in the paper you only send one proof, but later they require you to
/// verify by all the other proofs, that are never sent. We fix this here
Expand Down
Loading