Crates.io | givre |
lib.rs | givre |
version | |
source | src |
created_at | 2024-02-15 11:56:22.281933 |
updated_at | 2024-12-06 12:04:56.907561 |
description | TSS Schnorr/EdDSA implementation based on FROST |
homepage | |
repository | https://github.com/LFDT-Lockness/givre |
max_upload_size | |
id | 1141007 |
Cargo.toml error: | TOML parse error at line 17, column 1 | 17 | autolib = false | ^^^^^^^ unknown field `autolib`, expected one of `name`, `version`, `edition`, `authors`, `description`, `readme`, `license`, `repository`, `homepage`, `documentation`, `build`, `resolver`, `links`, `default-run`, `default_dash_run`, `rust-version`, `rust_dash_version`, `rust_version`, `license-file`, `license_dash_file`, `license_file`, `licenseFile`, `license_capital_file`, `forced-target`, `forced_dash_target`, `autobins`, `autotests`, `autoexamples`, `autobenches`, `publish`, `metadata`, `keywords`, `categories`, `exclude`, `include` |
size | 0 |
FROST is state of art protocol for Threshold Schnorr Signatures that supports 1-round signing (requires signers to commit nonces ahead of time), and identifiable abort.
This crate provides:
cggmp21-keygen
feature is enabled, which is a fairly reasonable choice as it's proven to be UC-secure.
Alternatively, you can use any other UC-secure DKG protocol.signing
module for details.This crate doesn't support (currently):
The crate is wasm and no_std friendly.
First of all, you need to generate a key. For that purpose, you can use any secure
(preferably, UC-secure) DKG protocol. FROST IETF Draft does not define any DKG
protocol or requirements it needs to meet, so the choice is up to you. This library
re-exports CGGMP21 DKG from cggmp21-keygen
crate when cggmp21-keygen
feature
is enabled which is proven to be UC-secure and should be a reasonable default.
CGGMP21 DKG is an interactive protocol built on round_based
framework. In order
to carry it out, you need to define the transport layer (i.e. how the signers can
communicate with each other). It's simply a pair of stream and sink:
let incoming: impl Stream<Item = Result<Incoming<Msg>>>;
let outgoing: impl Sink<Outgoing<Msg>>;
where:
Msg
is a protocol message (e.g., keygen::msg::threshold::Msg
)
round_based::Incoming
and round_based::Outgoing
wrap Msg
and provide additional data (e.g., sender/recipient)
futures::Stream
and futures::Sink
are well-known async primitives.
Transport layer implementation needs to meet requirements:
Then, construct an MpcParty:
let delivery = (incoming, outgoing);
let party = round_based::MpcParty::connected(delivery);
Now, you can finally execute the DKG protocol. The protocol involves all signers who will co-share a key. All signers need to agree on some basic parameters including the participants’ indices, the execution ID, and the threshold value (i.e., t).
use givre::ciphersuite::{Ciphersuite, Secp256k1};
let eid = givre::keygen::ExecutionId::new(b"execution id, unique per protocol execution");
let i = /* signer index (0 <= i < n) */;
let n = /* number of signers taking part in key generation */;
let t = /* threshold */;
let key_share = givre::keygen::<<Secp256k1 as Ciphersuite>::Curve>(eid, i, n)
.set_threshold(t)
.start(&mut OsRng, party)
.await?;
FROST signing can be carried out either interactively with the help of round_based
framework, or manually.
In the manual signing, as the name suggests, you manually construct all messages
and drive the protocol. It gives you better control over protocol execution and
you can benefit from better performance (e.g. by having 1 round signing). However,
it also gives a greater chance of misusing the protocol and violating security.
When opting for manual signing, make sure you're familiar with the FROST IETF Draft.
Refer to signing
module docs for the instructions.
full-signing
feature)Interactive Signing has more user-friendly interface and harder-to-misuse design.
It works on top of round_based
framework similarly to DKG described above.
As before, you need to define a secure transport layer and construct MpcParty.
Then, you need to assign each signer a unique index, in range from 0 to t-1. The
signers also need to know which index each of them occupied at the time of keygen.
use givre::ciphersuite::Secp256k1;
let i = /* signer index (0 <= i < min_signers) */;
let parties_indexes_at_keygen: [u16; MIN_SIGNERS] =
/* parties_indexes_at_keygen[i] is the index the i-th party had at keygen */;
let key_share = /* key share */;
let data_to_sign = b"data to be signed";
let signature = givre::signing::<Secp256k1>(i, &key_share, &parties_indexes_at_keygen, data_to_sign)
.sign(&mut OsRng, party)
.await?;
We use indices to uniquely refer to particular signers sharing a key. Each
index i
is an unsigned integer u16
with 0 ≤ i < n
where n
is the
total number of participants in the protocol.
All signers should have the same view about each others’ indices. For instance, if Signer A holds index 2, then all other signers must agree that i=2 corresponds to Signer A.
Assuming some sort of PKI (which would anyway likely be used to ensure secure communication, as described above), each signer has a public key that uniquely identifies that signer. It is then possible to assign unique indices to the signers by lexicographically sorting the signers’ public keys, and letting the index of a signer be the position of that signer’s public key in the sorted list.
no_std
supportThis crate is compatible with wasm32-unknown-unknown
target and no_std
. Requires
disabling std
feature which is on by default.
Feel free to reach out to us in Discord!