Skip to content

Commit 9e262c7

Browse files
committed
Merge branch 'feat/adkg-cli' into feat/refactor
2 parents cca13e8 + 712bcee commit 9e262c7

74 files changed

Lines changed: 11448 additions & 946 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 811 additions & 915 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
[workspace]
22
resolver = "3"
33
members = [
4+
"crates/adkg",
5+
"crates/adkg-cli",
6+
"crates/network",
47
"crates/blocklock-weft",
58
"crates/blocklock-warp",
69
"crates/fulfiller-core",
710
"crates/payment-warp",
811
"crates/randomness-warp",
912
"crates/dsigner",
10-
"crates/marshmallow-crypto/adkg",
11-
"crates/marshmallow-crypto/pairing_utils",
12-
"crates/marshmallow-crypto/silent-threshold-encryption",
1313
"crates/network",
1414
"crates/omnievent",
1515
"crates/onlyswaps-verifier",
1616
"crates/randomness-weft",
1717
"crates/signer",
1818
"crates/superalloy",
1919
"crates/contracts-core",
20+
"crates/utils",
2021
]
2122

2223
[workspace.package]
@@ -25,6 +26,7 @@ edition = "2024"
2526

2627
[workspace.dependencies]
2728
# workspace crates
29+
adkg = { path = "./crates/adkg" }
2830
contracts-core = { path = "./crates/contracts-core" }
2931
fulfiller-core = { path = "./crates/fulfiller-core" }
3032
payment-warp = { path = "./crates/payment-warp" }
@@ -39,10 +41,12 @@ alloy = { version = "1.0", default-features = false }
3941

4042
# crypto
4143
ark-bn254 = "0.4.0"
44+
ark-bls12-381 = "0.4.0"
4245
ark-ec = "0.4.2"
4346
ark-ff = "0.4.2"
47+
ark-poly = "0.4.2"
4448
ark-std = "0.4.0"
45-
pairing_utils = { path = "./crates/marshmallow-crypto/pairing_utils" }
49+
utils = { path = "./crates/utils", features = ["bn254"] }
4650

4751
# hashes
4852
digest = "0.10"
@@ -88,6 +92,7 @@ clap = { version = "4.5", features = ["derive", "env"] }
8892
figment = { version = "0.10" }
8993
hex = "0.4"
9094
itertools = "0.14"
95+
rand = "0.8.0"
9196
thiserror = "2.0"
9297

9398
[patch.crates-io]

crates/adkg-cli/Cargo.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[package]
2+
name = "adkg-cli"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
dcipher-network = { workspace = true, features = ["libp2p"] }
8+
adkg = { workspace = true, features = ["bn254"] }
9+
utils = { workspace = true, features = ["bn254", "sha3"] }
10+
ark-bn254.workspace = true
11+
ark-ec.workspace = true
12+
ark-std.workspace = true
13+
clap = { workspace = true, features = ["derive", "env"] }
14+
itertools.workspace = true
15+
libp2p = { workspace = true, features = ["serde"] }
16+
rand.workspace = true
17+
serde.workspace = true
18+
serde_json.workspace = true
19+
toml = "0.9"
20+
tokio = { workspace = true, features = ["fs", "net", "rt", "sync", "macros", "io-util", "time", "tracing"] }
21+
tokio-util.workspace = true
22+
thiserror.workspace = true
23+
tracing.workspace = true
24+
tracing-subscriber = { workspace = true, features = ["env-filter"] }
25+
base64 = "0.22"
26+
anyhow = "1.0.86"
27+
humantime = "2.2.0"
28+
chrono = { version = "0.4.41", features = ["serde"] }

crates/adkg-cli/Dockerfile

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef
2+
WORKDIR /app
3+
4+
FROM chef AS planner
5+
COPY . .
6+
RUN cargo chef prepare --recipe-path recipe.json
7+
8+
FROM chef AS builder
9+
COPY --from=planner /app/recipe.json recipe.json
10+
# Build dependencies - this is the caching Docker layer!
11+
RUN cargo chef cook --release --recipe-path recipe.json
12+
# Build application
13+
COPY . .
14+
RUN cargo build --release -p adkg-cli
15+
RUN strip target/release/adkg-cli
16+
17+
# We do not need the Rust toolchain to run the binary!
18+
FROM debian:bookworm-slim AS runtime
19+
WORKDIR /app
20+
COPY --from=builder /app/target/release/adkg-cli /usr/local/bin/adkg-cli
21+
CMD ["adkg-cli"]

crates/adkg-cli/README.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# adkg-cli
2+
CLI tool used to prepare and execute asynchronous distributed key generation ceremonies using the ADKG described in [Practical Asynchronous Distributed Key Generation](https://eprint.iacr.org/2021/1591.pdf) by Das et al.
3+
4+
A ceremony is a three steps process: creation of a scheme specification, long-term key generation, and the execution of the ADKG.
5+
6+
## Networking
7+
The ADKG relies on libp2p to exchange messages between the various nodes.
8+
The current implementation requires all nodes to use tcp communication, the traffic is secured using [Noise](https://docs.libp2p.io/concepts/secure-comm/noise/), and multiplexed with [Yamux](https://docs.libp2p.io/concepts/multiplex/yamux/).
9+
The node must allow incoming traffic on its libp2p tcp port, and tcp outbound traffic must be allowed to the rest of the nodes.
10+
11+
## Specifying a scheme
12+
A scheme is used to describe an instance of the ADKG.
13+
It contains various parameters, such as the curve, the number of parties and the malicious threshold.
14+
Here is an example of a scheme specification used in the context of `dcipher`:
15+
```toml
16+
app_name = "dcipher"
17+
curve_id = "Bn254G1"
18+
hash_id = "Keccak256"
19+
adkg_version = "v0.1"
20+
adkg_scheme_name = "DYX20-Bn254G1-Keccak256"
21+
generator_g = "qB3/U8RDVn4aF2tUTlmeDQbV0PvHJ8IB0QL/k1Z+5WI="
22+
generator_h = "gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE="
23+
```
24+
25+
The `new-scheme` command can be used to obtain a scheme specification as follows:
26+
```bash
27+
adkg-cli new-scheme --app-name dcipher --scheme-out scheme.toml
28+
```
29+
30+
By default, we use the `DYX20-Bn254G1-Keccak256` scheme.
31+
This represents the aforementioned ADKG, using the bn254 curve on group G1.
32+
In this configuration, the long-term public keys use a deterministic generator, `H`, obtained by hashing `ADKG_GENERATOR_G` with the DST `ADKG-%adkg_version%-%app_name%_BN254G1_XMD:KECCAK-256_SVDW_RO_GENERATORS_` using [rfc9380](https://datatracker.ietf.org/doc/html/rfc9380).
33+
The public key after the ADKG, however, are output with respect to the standard generator of bn254, the point `(1, 2)`.
34+
35+
It is preferable that a single participant executes the `new-scheme` command, and sends the generated file to the rest of the participants.
36+
37+
## Generating long-term keys
38+
With the scheme in hand, the next step consists of generating long-term keys that can be used to execute multiple instances of the ADKG.
39+
We require the generation of two keypairs, an elliptic curve keypair `(sk, [sk] H)`, and an ed25519 libp2p keypair.
40+
41+
The former is used for the ADKG, while the latter to authenticate and encrypt network communications.
42+
43+
The `generate` command is used as follows and requires a `scheme.toml` file:
44+
```bash
45+
adkg-cli generate --scheme scheme.toml --priv-out adkg.priv --pub-out adkg.pub
46+
```
47+
48+
The command writes the private key material to the `adkg.priv` file, and the public key material to `adkg.pub`.
49+
Only the public key material must be forwarded to the rest of the participants.
50+
As explained above, `adkg.pub` contains two different values:
51+
```toml
52+
adkg_pk = "6xBXHRXOlmPcfV2LqeEKEDAOGOoAH2pIYBnuG/h1w8s="
53+
peer_id = "12D3KooWRLzVJSS2EYpc9Tm4BfV5HEdXc8DiKvQkFqWhEwXcJ8eP"
54+
```
55+
56+
Once the public key material of each of the participant has been gather, a group configuration in the following format must be built and sent to the participants:
57+
```toml
58+
n = 7
59+
t = 2
60+
start_time = "2025-07-24T16:25:30Z"
61+
62+
[[nodes]]
63+
id = 1
64+
multiaddr = "/ip4/127.0.0.1/tcp/9991"
65+
adkg_pk = "71zT8Vib8vHG3vX2cktMyFl5VngXFi8RLOkIw9a5ulI="
66+
peer_id = "12D3KooWMSH17hbmMBSbEtCeyYBkFT7phd6PVaA2fdxakaTAuXMx"
67+
68+
[[nodes]]
69+
id = 2
70+
multiaddr = "/dns4/my.domain.com/tcp/1005"
71+
adkg_pk = "354LSCLd/rCf8bX/vN+nWNfO2G2ZoLs/v054IAgiDFk="
72+
peer_id = "12D3KooWMAADWEWNMFCNBadQfNFLeHHcYeZr9tNJBekY4opdCzDM"
73+
74+
[...]
75+
76+
[[nodes]]
77+
id = 7
78+
multiaddr = "/ip4/127.0.0.1/tcp/7777"
79+
adkg_pk = "7dpgwvtWiLAw/TweCrzBeRuWahRbxqMwACwiiulYkfA="
80+
peer_id = "12D3KooWGjQdQ6B3LazUw2EVbhakN3P5931e1UV76vJUNoV73Dd4"
81+
```
82+
83+
This file contains the group configuration, which includes the number of parties (`n`), a threshold (`t`), and an agreed upon starting time, alongside a list of nodes.
84+
85+
Note that we use the malicious threshold here, i.e., the maximum number of parties that may be malicious.
86+
The reconstruction threshold, i.e., the number of partials required to obtain a threshold signature, is obtained by adding one.
87+
88+
Each node is specified by its unique identifier, its public key material, and a libp2p multiaddress that can be used to communicate with the node.
89+
90+
Once this group file has been obtained and save, we can proceed to the final step.
91+
92+
## Executing the ADKG
93+
Finally, to execute the ADKG, we must gather various piece of information:
94+
- the scheme configuration file (`scheme.toml`)
95+
- the long-term private key file (`adkg.priv`)
96+
- the node's identifier (`1`)
97+
- the group configuration (`group.toml`)
98+
- the libp2p listen address (`/ip4/0.0.0.0/tcp/7777`)
99+
100+
With those details, we can use the `run` command as follows:
101+
```bash
102+
adkg-cli run \
103+
--scheme ./scheme.toml \
104+
--group ./group.toml \
105+
--priv adkg.priv \
106+
--id 1 \
107+
--listen-address "/ip4/0.0.0.0/tcp/7777" \
108+
--priv-out adkg.ceremony.priv \
109+
--pub-out adkg.ceremony.pub
110+
```
111+
112+
Notice that we also include two output files used to store the private and public output of the ADKG.

crates/adkg-cli/src/adkg_dyx20.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use crate::GroupConfig;
2+
use adkg::adkg::AdkgOutput;
3+
use adkg::helpers::PartyId;
4+
use adkg::rand::AdkgRng;
5+
use adkg::scheme::bn254::DYX20Bn254G1Keccak256;
6+
use adkg::scheme::{AdkgScheme, AdkgSchemeConfig};
7+
use anyhow::{Context, anyhow};
8+
use ark_ec::Group;
9+
use dcipher_network::topic::TopicBasedTransport;
10+
use std::sync::Arc;
11+
use std::time::Duration;
12+
use utils::serialize::fq::FqDeserialize;
13+
use utils::serialize::point::PointDeserializeCompressed;
14+
15+
#[allow(clippy::too_many_arguments)]
16+
pub async fn adkg_dyx20_bn254_g1_keccak256<TBT>(
17+
id: PartyId,
18+
adkg_sk: &str,
19+
group_config: &GroupConfig,
20+
scheme_config: AdkgSchemeConfig,
21+
adkg_grace_period: Duration,
22+
adkg_timeout: Duration,
23+
topic_transport: Arc<TBT>,
24+
rng: &mut impl AdkgRng,
25+
) -> anyhow::Result<AdkgOutput<<DYX20Bn254G1Keccak256 as AdkgScheme>::Curve>>
26+
where
27+
TBT: TopicBasedTransport<Identity = PartyId>,
28+
{
29+
let scheme = DYX20Bn254G1Keccak256::try_from(scheme_config)?;
30+
let sk = <<DYX20Bn254G1Keccak256 as AdkgScheme>::Curve as Group>::ScalarField::deser_base64(
31+
adkg_sk,
32+
)?;
33+
let pks = group_config
34+
.nodes
35+
.iter()
36+
.map(|p| {
37+
<DYX20Bn254G1Keccak256 as AdkgScheme>::Curve::deser_base64(
38+
&p.public_key_material.adkg_pk,
39+
)
40+
})
41+
.collect::<Result<_, _>>()?;
42+
43+
let mut adkg = scheme.new_adkg(id, group_config.n, group_config.t, sk, pks)?;
44+
45+
// Calculate time to sleep before actively executing the adkg
46+
let sleep_duration = (group_config.start_time - chrono::Utc::now())
47+
.to_std() // TimeDelta to positive duration
48+
.context("start_time cannot be in the past")?;
49+
50+
tracing::info!(
51+
"Sleeping for {} before starting ADKG at {}",
52+
humantime::format_duration(sleep_duration),
53+
humantime::format_rfc3339(group_config.start_time.into()),
54+
);
55+
tokio::time::sleep(sleep_duration).await;
56+
57+
// Start the ADKG and wait until we obtain a share, or the timeout occurs
58+
tracing::info!(
59+
"Executing ADKG with a timeout of {}",
60+
humantime::format_duration(adkg_timeout)
61+
);
62+
63+
let res = tokio::select! {
64+
output = adkg.start(rng, topic_transport) => {
65+
match &output {
66+
Ok(_) => {
67+
tracing::info!("ADKG has terminated with an Ok output");
68+
tracing::info!("Running ADKG until grace period of {}", humantime::format_duration(adkg_grace_period));
69+
tokio::time::sleep(adkg_grace_period).await;
70+
}
71+
Err(e) => {
72+
tracing::error!("failed to obtain output from ADKG: {e:?}");
73+
}
74+
}
75+
76+
Ok(output)
77+
}
78+
79+
_ = tokio::time::sleep(adkg_timeout) => {
80+
println!("Aborting ADKG due to timeout");
81+
Err(anyhow!("ADKG has timed out"))
82+
}
83+
};
84+
85+
tracing::warn!("Stopping ADKG...");
86+
adkg.stop().await;
87+
88+
Ok(res??) // unwrap both errors (timeout + adkg error)
89+
}

0 commit comments

Comments
 (0)