diff --git a/.github/changelog.sh b/.github/changelog.sh index fa3c35a..f89ec47 100755 --- a/.github/changelog.sh +++ b/.github/changelog.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash m_branch=m; -changelog_file=CHANGELOG.md; +changelog_file=changelog.md; # fetch master since we might be in a shallow clone git fetch origin "$m_branch:$m_branch" --depth=1 diff --git a/Cargo.lock b/Cargo.lock index aabd145..2d9cb8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addchain" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e33f6a175ec6a9e0aca777567f9ff7c3deefc255660df887e7fa3585e9801d8" +dependencies = [ + "num-bigint 0.3.3", + "num-integer", + "num-traits", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -202,7 +213,7 @@ version = "3.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf1fa3f06bbff1ea5b1a9c7b14aa992a39657db60a2759457328d7e058f49ee" dependencies = [ - "num-bigint", + "num-bigint 0.4.6", "num-traits", "proc-macro2", "quote", @@ -215,10 +226,27 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ + "byteorder", + "ff_derive", "rand_core", "subtle", ] +[[package]] +name = "ff_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f10d12652036b0e99197587c6ba87a8fc3031986499973c030d8b44fcc151b60" +dependencies = [ + "addchain", + "num-bigint 0.3.3", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -231,12 +259,65 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -255,11 +336,16 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -319,8 +405,10 @@ dependencies = [ "generic-ec-core", "group", "k256", + "p256", "rand_core", "sha2", + "stark-curve", "subtle", "zeroize", ] @@ -368,6 +456,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + [[package]] name = "ident_case" version = "1.0.1" @@ -403,6 +497,23 @@ version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -431,6 +542,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "elliptic-curve", + "primeorder", +] + [[package]] name = "phantom-type" version = "0.3.1" @@ -470,6 +591,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.91" @@ -645,6 +775,25 @@ dependencies = [ "digest", ] +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "stark-curve" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad5e4452423e6bceebf2ecb2c4680f7159a62196e3e6b0ffb824b8d59c3fc90" +dependencies = [ + "ff", + "hex-literal", + "primeorder", + "subtle", + "zeroize", +] + [[package]] name = "strsim" version = "0.11.1" @@ -681,9 +830,10 @@ dependencies = [ [[package]] name = "tecdh" -version = "0.1.0" +version = "0.2.0" dependencies = [ "digest", + "futures", "generic-ec", "generic-ec-zkp", "key-share", diff --git a/Cargo.toml b/Cargo.toml index fd22f9c..9a806d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,20 @@ [package] name = "tecdh" -version = "0.1.0" +version = "0.2.0" edition = "2021" [dependencies] digest = "0.10" -generic-ec = { version = "0.4.5", features = ["serde", "curve-secp256k1", "hash-to-scalar"] } +generic-ec = { version = "0.4.5", features = ["serde", "hash-to-scalar"] } generic-ec-zkp = { version = "0.4.4", features = ["serde", "udigest"] } key-share = "0.6" rand_core = { version = "0.6", default-features = false } round-based = { version = "0.4", features = ["derive"] } -serde = "1" thiserror = { version = "2" } udigest = { version = "0.2", default-features = false, features = ["inline-struct", "derive"] } +serde = { version = "1", optional = true } + [dev-dependencies] generic-ec = { version = "0.4.5", features = ["curve-ed25519"] } key-share = { version = "0.6", features = ["spof"] } @@ -22,3 +23,15 @@ rand_dev = "0.1" round-based = { version = "0.4", features = ["sim"] } sha2 = "0.10" test-case = "3" +# doctests only +futures = "0.3" + +[features] +default = ["serde"] + +serde = ["dep:serde", "generic-ec/serde", "generic-ec-zkp/serde"] + +curve-secp256k1 = ["generic-ec/curve-secp256k1"] +curve-secp256r1 = ["generic-ec/curve-secp256r1"] +curve-ed25519 = ["generic-ec/curve-ed25519"] +curve-stark = ["generic-ec/curve-stark"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..15f7318 --- /dev/null +++ b/README.md @@ -0,0 +1,118 @@ +# tECDH - threshold elliptic-curve Diffie-Hellman key exchange + +This crate implements tECDH - when one of the Diffie-Hellman keys is kept as +key shares. Multiple parties, when activated with a common +`counterparty_public_key`, will execute the protocol to obtain a DH session +key. + +The procedure for running this protocol resembles tBLS signatures, and in +fact was directly adpated from . A big +difference from BLS is that since we don't need signature verification, we +don't need efficient pairings and can use any curve we want. + +## Cargo features + +This crate has the following feature flags: + +- `serde` - enable serde support for the data types exported by this crate. + **Enabled** by default +- `curve-secp256k1` - enable Secp256k1 curve in `generic_ec::curves` +- `curve-secp256r1` - enable Secp256r1 curve in `generic_ec::curves` +- `curve-ed25519` - enable Ed25519 curve in `generic_ec::curves` +- `curve-stark` - enable Stark curve in `generic_ec::curves` + +## How to use the library + +In short, you will need to load the key shares, establish network connection +between parties, obtain a shared execution id, match the party indicies, and +then commence the protocol. + +```rust,no_run +use tecdh::{round_based, generic_ec}; +// Curve of your choice +type E = generic_ec::curves::Ed25519; + +async fn example() -> Result, tecdh::mpc::Error> { + // The unique execution ID which the parties agree to before starting + let execution_id: Vec = todo!(); + // The key of the counterparty you're performing the ECDH handshake with + let counterparty: generic_ec::NonZero> = todo!(); + // The key share that was generated in advance and is now loaded from + // persistent storage + let key_share: tecdh::key_share::CoreKeyShare = todo!(); + // The network setup using `round-based` and `futures` + let incoming = futures::stream::pending::>, std::io::Error>>(); + let outgoing = futures::sink::drain::>>(); + let party = round_based::MpcParty::connected((incoming, outgoing)); + // Match party indicies from keygen to the current participants (not used for + // additive key shares) + let participant_indicies: &[u16] = todo!(); + let this_party_index: u16 = todo!(); + + // Run the protocol + let session_key = tecdh::start::( + &execution_id, + counterparty, + this_party_index, + &key_share, + participant_indicies, + party, + &mut rand::rngs::OsRng, + ).await?; + + // Use the generated session key + Ok(session_key) +} +``` + +### Distributed key generation + +First of all, you will need to generate a key that the distributed parties will +use. For that purpose, you can use any secure DKG protocol. We recommend using +[cggmp21-keygen](https://docs.rs/cggmp21-keygen/0.5.0/cggmp21_keygen/) - it's +an interactive protocol built on the same foundations of `round-based` and +`generic-ec`, and outputs the key share of the same type and format as expected +by this library. + +### Networking + +This library relies on [`round-based`](https://crates.io/crates/round-based) +for the MPC backend, which means the networking interface should be constructed +using its primitives, which are in practice the `Sink` and `Stream` types from +`futures_core`. + +The exact underlying mechanism behind the sink and stream depend on the +application and are not provided here. Some application will use libp2p, others +may want to use a message broker like kafka or postgres. +No matter the implementation, all messages need to be authenticated - when +one party receives a message from another, it needs to be able to verify that +the message comes from the claimed sender. + +### Signer indicies + +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. These indicies must match between the +keygen an the protocol execution. + +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. + +### Execution ID + +Execution of our protocols requires all participants to agree on unique +execution ID (aka session identifier) that is assumed never to repeat. This +string provides context separation between different executions of the protocol +to ensure that an adversary attack the protocol by replaying messages from one +execution to another. + +## Join us in Discord! +Feel free to reach out to us [in Discord](https://discordapp.com/channels/905194001349627914/1285268686147424388)! diff --git a/changelog.md b/changelog.md index e1ce5d4..e6bf931 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,7 @@ +# v0.2.0 + +Serde support is now behind a feature flag (that is enabled by default) + # v0.1.0 Initial implementation diff --git a/src/lib.rs b/src/lib.rs index e20df1a..0be6298 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,4 @@ -//! This crate implements Threshold Elliptic Curve Diffie-Hellman key -//! exchange. -//! -//! Threshold means that the private key is shared between multiple parties. -//! Only when a threshold amount of parties run the protocol together, can -//! they form the diffie-hellman session key -//! -//! The procedure for running this protocol resembles tBLS signatures, and in -//! fact was directly adpated from . A big -//! difference from BLS is that since we don't need signature verification, we -//! don't need efficient pairings and can use any curve we want. - +#![doc = include_str!("../README.md")] #![warn(missing_docs, unsafe_code, unused_crate_dependencies)] #![cfg_attr( not(test), @@ -17,11 +6,18 @@ )] /// Functions to perform low-level operations. This can be misused, so they are -/// not recommended unless you know how tECDH works +/// ⚠️not recommended⚠️ unless you know how tECDH works pub mod lowlevel; /// Helper types for the MPC execution pub mod mpc; +/// Reexport for convenience +pub use generic_ec; +/// Reexport for convenience +pub use key_share; +/// Reexport for convenience +pub use round_based; + /// Start an MPC protocol that performs threshold ECDH with shared private key. /// Returns the session key /// @@ -96,6 +92,9 @@ pub enum AggregateFailed { #[cfg(test)] mod test { + // Used in doctests only + use futures as _; + type E = generic_ec::curves::Secp256k1; #[test_case::test_case(3, 5; "t3n5")] diff --git a/src/lowlevel.rs b/src/lowlevel.rs index 44914f2..98797e3 100644 --- a/src/lowlevel.rs +++ b/src/lowlevel.rs @@ -11,8 +11,12 @@ pub fn ecdh( /// Evaluation of partial ECDH as outputted by parties, computed by /// [`partial_ecdh`]. `t` partials can be aggregated with [`aggregate`] -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -#[serde(bound = "")] +#[derive(Clone, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(bound = "") +)] pub struct PartialEvaluation { /// Index of evaluating party pub i: u16, @@ -79,8 +83,8 @@ pub fn partial_ecdh( /// the same order by participant as `partials` /// /// Partials, public shares and share preimages each should correspond to the -/// same party, that is all be ordered in the same way by the index of the party -/// they come from. +/// same party, that is, they should all be ordered in the same way by the index +/// of the party they come from. /// /// In paper this function is called `Combine(pk, VK, x, E)`, section IV.A pub fn aggregate( diff --git a/src/mpc.rs b/src/mpc.rs index f70c01a..5d2de1a 100644 --- a/src/mpc.rs +++ b/src/mpc.rs @@ -1,16 +1,24 @@ use crate::lowlevel; /// Protocol message -#[derive(round_based::ProtocolMessage, Clone, serde::Serialize, serde::Deserialize)] -#[serde(bound = "")] +#[derive(round_based::ProtocolMessage, Clone)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(bound = "") +)] pub enum Msg { /// The only round Partial(MsgPartial), } /// Protocol message -#[derive(Clone, serde::Serialize, serde::Deserialize)] -#[serde(bound = "")] +#[derive(Clone)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(bound = "") +)] pub struct MsgPartial { /// Partial evaluation of party pub evaluation: lowlevel::PartialEvaluation,