dkls23/
lib.rs

1// Copyright (c) Silence Laboratories Pte. Ltd. All Rights Reserved.
2// This software is licensed under the Silence Laboratories License Agreement.
3
4//! A rust  threshold ECDSA signatures library implementing DKLs23 protocol.
5//!
6//! ## Functionality
7//! - Distributed Key Generation (DKG)
8//! - Distributed Signature Generation (DSG)
9//! - Key refresh protocol that refreshes the secret key shares without changing the common public key.
10//! - Import a singleton key and distribute it among parties
11//! - Export a threshold key to a singleton one
12//! - Quorum Change: change dynamically the set of participants by adding or removing nodes
13//! - Migration: Migrate from compatible curve protocols like: GG** or CMP to DKLs23
14//!
15//! ## Examples
16//! The mod common module can be replicated  from the dkls23 github [repo](https://github.com/silence-laboratories/dkls23/examples/common.rs) under examples folder
17//! ### KeyGen
18//! ```
19//! use dkls23::keygen;
20//! use k256::elliptic_curve::group::GroupEncoding;
21//! use rand::Rng;
22//! use rand_chacha::ChaCha20Rng;
23//! use rand_core::SeedableRng;
24//! use std::sync::Arc;
25//!
26//! mod common;
27//!
28//! #[tokio::main]
29//! pub async fn main() {
30//!     let t: u8 = 2;
31//!     let n: u8 = 3;
32//!     let coord = sl_mpc_mate::coord::SimpleMessageRelay::new();
33//!
34//!     let mut parties = tokio::task::JoinSet::new();
35//!
36//!     for setup in common::shared::setup_keygen(t, n, None) {
37//!         parties.spawn({
38//!             let relay = coord.connect();
39//!             let mut rng = ChaCha20Rng::from_entropy();
40//!             keygen::run(setup, rng.gen(), relay)
41//!         });
42//!     }
43//!     let mut shares = vec![];
44//!     while let Some(fini) = parties.join_next().await {
45//!         if let Err(ref err) = fini {
46//!             println!("error {err:?}");
47//!         } else {
48//!             match fini.unwrap() {
49//!                 Err(err) => panic!("err {:?}", err),
50//!                 Ok(share) => shares.push(Arc::new(share)),
51//!            }
52//!         }
53//!     }
54//!
55//!     for keyshare in shares.iter() {
56//!         println!("PK{}", hex::encode(keyshare.public_key().to_bytes()));
57//!     }
58//! }
59//! ```
60//! ### Key Refresh
61//! ```
62//! use dkls23::keygen::key_refresh::KeyshareForRefresh;
63//! use k256::elliptic_curve::group::GroupEncoding;
64//! use rand::Rng;
65//! use rand_chacha::ChaCha20Rng;
66//! use rand_core::SeedableRng;
67//! use sl_mpc_mate::coord::SimpleMessageRelay;
68//! use std::sync::Arc;
69//! use tokio::task::JoinSet;
70//!
71//! mod common;
72//!
73//! #[tokio::main]
74//! pub async fn main() {
75//!     let old_shares = common::shared::gen_keyshares(2, 3).await;
76//!     let coord = SimpleMessageRelay::new();
77//!     let mut parties = JoinSet::new();
78//!
79//!     let key_shares_for_refresh: Vec<KeyshareForRefresh> = old_shares
80//!         .iter()
81//!         .map(|share| KeyshareForRefresh::from_keyshare(share, None))
82//!         .collect();
83//!
84//!     let mut rng = ChaCha20Rng::from_entropy();
85//!     for (setup, share) in common::shared::setup_keygen(2, 3, None)
86//!         .into_iter()
87//!         .zip(key_shares_for_refresh)
88//!         .collect::<Vec<_>>()
89//!     {
90//!         // run the keyrefresh protocol for each node
91//!         parties.spawn(dkls23::keygen::key_refresh::run(
92//!             setup,
93//!             rng.gen(),
94//!             coord.connect(),
95//!             share,
96//!         ));
97//!     }
98//!
99//!     let mut new_shares = vec![];
100//!     while let Some(fini) = parties.join_next().await {
101//!         let fini = fini.unwrap();
102//!
103//!         if let Err(ref err) = fini {
104//!             println!("error {}", err);
105//!         }
106//!
107//!        assert!(fini.is_ok());
108//!
109//!         // Print all the new PK of the refreshed share
110//!         let new_share = fini.unwrap();
111//!         let pk = hex::encode(new_share.public_key().to_bytes());
112//!
113//!         new_shares.push(Arc::new(new_share));
114//!
115//!         println!("PK {}", pk);
116//!     }
117//!
118//!     //check that this is equal the old key share public key
119//!     println!(
120//!         "Old PK{}",
121//!         hex::encode(old_shares[0].public_key().to_bytes())
122//!     );
123//!
124//! }
125//! ```
126//!
127//! ### Sign
128//! ```
129//! use tokio::task::JoinSet;
130//!
131//! use rand::Rng;
132//! use rand_chacha::ChaCha20Rng;
133//! use rand_core::SeedableRng;
134//!
135//! use k256::ecdsa::{RecoveryId, VerifyingKey};
136//!
137//! use dkls23::sign;
138//! use sl_mpc_mate::coord::SimpleMessageRelay;
139//!
140//! mod common;
141//!
142//! #[tokio::main]
143//! async fn main() {
144//!     let coord = SimpleMessageRelay::new();
145//!
146//!     // We locally generate some key shares in order to test the signing procedure.
147//!     let shares = common::shared::gen_keyshares(2, 3).await;
148//!
149//!     //fetch the public verification key from one of the keyshares
150//!     let vk = VerifyingKey::from_affine(shares[0].public_key().to_affine()).unwrap();
151//!
152//!     //define a chain path for the signature: m is the default one
153//!     let chain_path = "m";
154//!
155//!     //Here the parties are simulated as in a real world example but locally as a set of rust async tasks:
156//!     let mut parties = JoinSet::new();
157//!
158//!     for setup in common::shared::setup_dsg(&shares[0..2], chain_path) {
159//!         let mut rng = ChaCha20Rng::from_entropy();
160//!         let relay = coord.connect();
161//!
162//!         parties.spawn(sign::run(setup, rng.gen(), relay));
163//!     }
164//!
165//!     // After all the tasks have finished we extract the signature and verify it against the public key
166//!     while let Some(fini) = parties.join_next().await {
167//!         let fini = fini.unwrap();
168//!
169//!         if let Err(ref err) = fini {
170//!             println!("error {err:?}");
171//!         }
172//!
173//!         let (sign, recid) = fini.unwrap();
174//!
175//!         let hash = [1u8; 32];
176//!
177//!         let recid2 = RecoveryId::trial_recovery_from_prehash(&vk, &hash, &sign).unwrap();
178//!
179//!         assert_eq!(recid, recid2);
180//!     }
181//! }
182//!```
183//!
184//! ## Networking
185//! Communication between nodes  is happening through a relayer in a pull messaging mode:
186//! Everything is posted on the relayer and the receiver knows when and what to ask. That was a design
187//! decision that maps best the nature of MPC protocols whereby any mpc node depending on the protocol knows what type of messages to expect and from
188//! where.
189//!
190//! The relayer follows the Actor model: It spawns from the caller task, does the assigned task
191//! independently and return the result in the main task. The library itself does not expose networking stack
192//! ,but instead a generic combination of shared state between rust tasks and message channel passing where
193//! receiver and sender channels are interleaved for p2p and broadcast communication. That is a local `SimpleMessageRelay`.
194//! In a real setup the relayer can be an independent network entity, where all the nodes can talk to. It
195//! can be implemented with a variety of existing  networking protocols such as websockets; as long as it follows the
196//! underlying pull logic : Each receiver knows what message to subscribe for and so it asks the relayer to deliver it
197//! as long as it arrives from the expected sender.
198//!
199//!
200//!
201//! ## Data Serialization
202//! The library implements zero-copy message serialization. All messages sent between parties
203//! and their components are defined as arrays of bytes. This transformation enables us to safely cast a byte
204//! slice `&[u8]` into a reference to some message structure if the sizes
205//! are equal.
206//!
207//! This allows to implement in-place message construction:  Allocate
208//! a memory buffer of an appropriate size, take a mutable reference to
209//! some message structure, and pass it to a message constructor. Then
210//! calculate the message signature or encrypt the message in place
211//! without any extra memory copying.
212//! This provides not only memory efficiency but also more secure code
213//! because there is exactly one copy of secret material in memory and
214//! overwrite it with in-place encryption.
215//! Key share representation also uses the same technique. Allocates a
216//! memory buffer for the key share at the beginning of the key generation
217//! execution and fill it piece by piece. Thus, memory copies are not happening
218#![deny(missing_docs, unsafe_code)]
219
220use rand::SeedableRng;
221use rand_chacha::ChaCha20Rng;
222
223/// DKLs23 keygen.
224pub mod keygen;
225
226/// DKLs23 sign.
227pub mod sign;
228
229/// Setup messages.
230pub mod setup;
231
232/// Misc helper functions.
233pub mod proto;
234
235/// Seed for our RNG.
236pub type Seed = <ChaCha20Rng as SeedableRng>::Seed;
237
238/// Exports a threshold key to a singleton one by consolidating all shares of other nodes.
239pub mod key_export;
240/// Imports a singleton external key and secret shares it among parties to use dkls23 related mpc protocols.
241pub mod key_import;
242
243pub(crate) mod pairs;
244
245/// Version of domain labels
246pub const VERSION: u16 = 1;
247
248pub use k256;
249pub use sl_mpc_mate::coord::{MessageSendError, Relay};
250pub use sl_mpc_mate::message::{InstanceId, MsgId};