diff --git a/Cargo.toml b/Cargo.toml index 08795ca..9a564be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,8 @@ authors = [ "Gary " ] +edition="2018" + [lib] crate-type = ["rlib", "dylib"] @@ -17,11 +19,25 @@ serde_json = "1.0" serde_derive = "1.0" rand = "0.8" sha2 = "0.9" +zeroize = "1" + [dev-dependencies] ed25519-dalek = "1.0.1" rand_xoshiro = "0.6.0" itertools = "0.10" +rocket = { version = "0.5.0-rc.1", features = ["json"] } +reqwest = { version = "0.9", default-features = false } +aes-gcm = "0.9.4" +uuid = { version = "0.8", features = ["v4"] } +hmac = "0.11" + [features] default = ["curv/rust-gmp-kzen"] + +[[example]] +name = "sm_manager" + +[[example]] +name = "sign_client" \ No newline at end of file diff --git a/README.md b/README.md index 6d245ff..630d3dd 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,40 @@ Rust implementation of multiparty Ed25519 signature scheme. The above protocols are for Schnorr signature system. EdDSA is a variant of Schnorr signature system with (possibly twisted) Edwards curves. We adopt the multi party implementations to follow Ed25519 methods for private key and public key generation according to [RFC8032](https://tools.ietf.org/html/rfc8032#section-5.1) +## Run GG18 Demo + +The following steps are for setup, key generation with `n` parties and signing with `t+1` parties. + +### Setup + +1. We use shared state machine architecture (see [white city](https://github.com/KZen-networks/white-city)). The parameters `parties` and `threshold` can be configured by changing the file: `param.json`. A keygen will run with `parties` parties and signing will run with any subset of `threshold + 1` parties. `param.json` file should be located in the same path of the client software. + +2. Install [Rust](https://rustup.rs/). Run `cargo build --release --examples` (it will build into `/target/release/examples/`) + +3. Run the shared state machine: `./sm_manager`. By default, it's configured to be in `127.0.0.1:8000`, this can be changed in `Rocket.toml` file. The `Rocket.toml` file should be in the same folder you run `sm_manager` from. + +### KeyGen + +run `gg18_keygen_client` as follows: `./gg18_keygen_client http://127.0.0.1:8000 keys.store`. Replace IP and port with the ones configured in setup. Once `n` parties join the application will run till finish. At the end each party will get a local keys file `keys.store` (change filename in command line). This contains secret and public data of the party after keygen. The file therefore should remain private. + +### Sign + +Run `./sign_client`. The application takes three arguments: `IP:port` as in keygen, `filename` and message to be signed: `./sign_client http://127.0.0.1:8001 keys.store "KZen Networks"`. The same message should be used by all signers. Once `t+1` parties join the protocol will run and will output to screen signature (R,s). + +The `./sign_client` executable initially tries to unhex its input message (the third parameter). Before running ensure two things: + +1. If you want to pass a binary message to be signed - hex it. +2. If you want to pass a textual message in a non-hex form, make sure it can't be unhexed. + Simply put, the safest way to use the signing binary is to just always hex your messages before passing them to the `./sign_client` executable. + +#### Example +To sign the message `hello world`, first calculate its hexadecimal representation. This yields the `68656c6c6f20776f726c64`. +Then, run: +```bash +./sign_client http://127.0.0.1:8000 keys.store "68656c6c6f20776f726c64" +``` + + License ------- This library is released under the terms of the GPL-3.0 license. See [LICENSE](LICENSE) for more information. diff --git a/examples/common.rs b/examples/common.rs new file mode 100644 index 0000000..cf22fbd --- /dev/null +++ b/examples/common.rs @@ -0,0 +1,204 @@ +use std::{env, iter::repeat, thread, time, time::Duration}; + +use aes_gcm::{Aes256Gcm, Nonce}; +use aes_gcm::aead::{NewAead, Aead, Payload}; + +use reqwest::Client; +use serde::{Deserialize, Serialize}; + +pub type Key = String; + +#[allow(dead_code)] +pub const AES_KEY_BYTES_LEN: usize = 32; + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct AEAD { + pub ciphertext: Vec, + pub tag: Vec, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct PartySignup { + pub number: u16, + pub uuid: String, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Index { + pub key: Key, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Entry { + pub key: Key, + pub value: String, +} + +#[derive(Serialize, Deserialize)] +pub struct Params { + pub parties: String, + pub threshold: String, +} + +#[allow(dead_code)] +pub fn aes_encrypt(key: &[u8], plaintext: &[u8]) -> AEAD { + + let aes_key = aes_gcm::Key::from_slice(key); + let cipher = Aes256Gcm::new(aes_key); + + let nonce_vector: Vec = repeat(3).take(12).collect(); + let nonce = Nonce::from_slice(nonce_vector.as_slice()); + + let out_tag: Vec = repeat(0).take(16).collect(); + + let text_payload = Payload { + msg: plaintext, + aad: &out_tag.as_slice() + }; + + let ciphertext = cipher.encrypt(nonce, text_payload) + .expect("encryption failure!"); + + AEAD { + ciphertext: ciphertext, + tag: out_tag.to_vec(), + } +} + +#[allow(dead_code)] +pub fn aes_decrypt(key: &[u8], aead_pack: AEAD) -> Vec { + + let aes_key = aes_gcm::Key::from_slice(key); + + let nonce_vector: Vec = repeat(3).take(12).collect(); + let nonce = Nonce::from_slice(nonce_vector.as_slice()); + + let gcm = Aes256Gcm::new(aes_key); + + let text_payload = Payload { + msg: aead_pack.ciphertext.as_slice(), + aad: aead_pack.tag.as_slice() + }; + + let out = gcm.decrypt(nonce, text_payload); + out.unwrap() +} + +pub fn postb(client: &Client, path: &str, body: T) -> Option +where + T: serde::ser::Serialize, +{ + let addr = env::args() + .nth(1) + .unwrap_or_else(|| "http://127.0.0.1:8001".to_string()); + let retries = 3; + let retry_delay = time::Duration::from_millis(250); + for _i in 1..retries { + let res = client + .post(&format!("{}/{}", addr, path)) + .json(&body) + .send(); + + if let Ok(mut res) = res { + return Some(res.text().unwrap()); + } + thread::sleep(retry_delay); + } + None +} + +pub fn broadcast( + client: &Client, + party_num: u16, + round: &str, + data: String, + sender_uuid: String, +) -> Result<(), ()> { + let key = format!("{}-{}-{}", party_num, round, sender_uuid); + let entry = Entry { + key: key.clone(), + value: data, + }; + + let res_body = postb(&client, "set", entry).unwrap(); + serde_json::from_str(&res_body).unwrap() +} + +pub fn sendp2p( + client: &Client, + party_from: u16, + party_to: u16, + round: &str, + data: String, + sender_uuid: String, +) -> Result<(), ()> { + let key = format!("{}-{}-{}-{}", party_from, party_to, round, sender_uuid); + + let entry = Entry { + key: key.clone(), + value: data, + }; + + let res_body = postb(&client, "set", entry).unwrap(); + serde_json::from_str(&res_body).unwrap() +} + +pub fn poll_for_broadcasts( + client: &Client, + party_num: u16, + n: u16, + delay: Duration, + round: &str, + sender_uuid: String, +) -> Vec { + let mut ans_vec = Vec::new(); + for i in 1..=n { + if i != party_num { + let key = format!("{}-{}-{}", i, round, sender_uuid); + let index = Index { key }; + loop { + // add delay to allow the server to process request: + thread::sleep(delay); + let res_body = postb(client, "get", index.clone()).unwrap(); + let answer: Result = serde_json::from_str(&res_body).unwrap(); + if let Ok(answer) = answer { + ans_vec.push(answer.value); + println!("[{:?}] party {:?} => party {:?}", round, i, party_num); + break; + } + } + } + } + ans_vec +} + +pub fn poll_for_p2p( + client: &Client, + party_num: u16, + n: u16, + delay: Duration, + round: &str, + sender_uuid: String, +) -> Vec { + let mut ans_vec = Vec::new(); + for i in 1..=n { + if i != party_num { + let key = format!("{}-{}-{}-{}", i, party_num, round, sender_uuid); + let index = Index { key }; + loop { + // add delay to allow the server to process request: + thread::sleep(delay); + let res_body = postb(client, "get", index.clone()).unwrap(); + let answer: Result = serde_json::from_str(&res_body).unwrap(); + if let Ok(answer) = answer { + ans_vec.push(answer.value); + println!("[{:?}] party {:?} => party {:?}", round, i, party_num); + break; + } + } + } + } + ans_vec +} + +fn main(){} \ No newline at end of file diff --git a/examples/gg18_keygen_client.rs b/examples/gg18_keygen_client.rs new file mode 100644 index 0000000..38627d3 --- /dev/null +++ b/examples/gg18_keygen_client.rs @@ -0,0 +1,312 @@ +#![allow(non_snake_case)] +/// to run: +/// 1: go to rocket_server -> cargo run +/// 2: cargo run from PARTIES number of terminals +use curv::{arithmetic::traits::Converter, cryptographic_primitives::{ + proofs::sigma_dlog::DLogProof, secret_sharing::feldman_vss::VerifiableSS, +}, BigInt}; +use multi_party_eddsa::protocols::thresholdsig::{ + KeyGenBroadcastMessage1, KeyGenDecommitMessage1, Keys, Parameters, +}; +use reqwest::Client; +use std::{env, fs, time}; +use curv::elliptic::curves::{Ed25519, Point, Scalar}; +use sha2::Sha512; + +mod common; +use common::{ + aes_decrypt, aes_encrypt, broadcast, poll_for_broadcasts, poll_for_p2p, postb, sendp2p, Params, + PartySignup, AEAD, AES_KEY_BYTES_LEN, +}; +use multi_party_eddsa::Error; +use multi_party_eddsa::Error::InvalidKey; + +fn main() { + if env::args().nth(3).is_some() { + panic!("too many arguments") + } + if env::args().nth(2).is_none() { + panic!("too few arguments") + } + //read parameters: + let data = fs::read_to_string("params.json") + .expect("Unable to read params, make sure config file is present in the same folder "); + let params: Params = serde_json::from_str(&data).unwrap(); + + run_keygen(params); +} + +pub fn run_keygen(params: Params) { + let PARTIES: u16 = params.parties.parse::().unwrap(); + let THRESHOLD: u16 = params.threshold.parse::().unwrap(); + + let client = Client::new(); + + // delay: + let delay = time::Duration::from_millis(25); + let params = Parameters { + threshold: THRESHOLD, + share_count: PARTIES, + }; + + //signup: + let (party_num_int, uuid) = match signup(&client).unwrap() { + PartySignup { number, uuid } => (number, uuid), + }; + println!("number: {:?}, uuid: {:?}", party_num_int, uuid); + + let party_keys = Keys::phase1_create(party_num_int); + let (bc_i, decom_i) = party_keys.phase1_broadcast(); + + // send commitment to ephemeral public keys, get round 1 commitments of other parties + assert!(broadcast( + &client, + party_num_int, + "round1", + serde_json::to_string(&bc_i).unwrap(), + uuid.clone() + ) + .is_ok()); + let round1_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + PARTIES, + delay, + "round1", + uuid.clone(), + ); + + let mut bc1_vec = round1_ans_vec + .iter() + .map(|m| serde_json::from_str::(m).unwrap()) + .collect::>(); + + bc1_vec.insert(party_num_int as usize - 1, bc_i); + + // send ephemeral public keys and check commitments correctness + assert!(broadcast( + &client, + party_num_int, + "round2", + serde_json::to_string(&decom_i).unwrap(), + uuid.clone() + ) + .is_ok()); + let round2_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + PARTIES, + delay, + "round2", + uuid.clone(), + ); + + let mut j = 0; + let mut point_vec: Vec> = Vec::new(); + let mut blind_vec: Vec = Vec::new(); + let mut enc_keys: Vec> = Vec::new(); + + for i in 1..=PARTIES { + if i == party_num_int { + point_vec.push(decom_i.clone().y_i); + blind_vec.push(decom_i.clone().blind_factor); + } else { + let decom_j: KeyGenDecommitMessage1 = serde_json::from_str::(&round2_ans_vec[j]).unwrap(); + + point_vec.push(decom_j.clone().y_i); + blind_vec.push(decom_j.clone().blind_factor); + let key_bn: BigInt = (decom_j.clone().y_i * party_keys.keypair.expanded_private_key.private_key.clone()).x_coord().unwrap(); + let key_bytes = BigInt::to_bytes(&key_bn); + let mut template: Vec = vec![0u8; AES_KEY_BYTES_LEN - key_bytes.len()]; + template.extend_from_slice(&key_bytes[..]); + enc_keys.push(template); + j = j + 1; + } + } + + let (head, tail) = point_vec.split_at(1); + let y_sum = tail.iter().fold(head[0].clone(), |acc, x| acc + x); + /*let mut y_sum = &point_vec[0]; + for i in 1..=point_vec.len() { + y_sum = &(y_sum + &point_vec[i]); + }*/ + + let key_gen_parties_points_vec = (0..PARTIES) + .map(|i| i + 1) + .collect::>(); + + let (vss_scheme, secret_shares) = party_keys + .phase1_verify_com_phase2_distribute( + ¶ms, &blind_vec, &point_vec, &bc1_vec, &key_gen_parties_points_vec + ) + .expect("invalid key"); + + ////////////////////////////////////////////////////////////////////////////// + + let mut j = 0; + for (k, i) in (1..=PARTIES).enumerate() { + if i != party_num_int { + // prepare encrypted ss for party i: + let key_i = &enc_keys[j]; + let plaintext = BigInt::to_bytes(&secret_shares[k].to_bigint()); + let aead_pack_i = aes_encrypt(key_i, &plaintext); + assert!(sendp2p( + &client, + party_num_int, + i, + "round3", + serde_json::to_string(&aead_pack_i).unwrap(), + uuid.clone() + ) + .is_ok()); + j += 1; + } + } + + let round3_ans_vec = poll_for_p2p( + &client, + party_num_int, + PARTIES, + delay, + "round3", + uuid.clone(), + ); + + let mut j = 0; + let mut party_shares: Vec> = Vec::new(); + for i in 1..=PARTIES { + if i == party_num_int { + party_shares.push(secret_shares[(i - 1) as usize].clone()); + } else { + let aead_pack: AEAD = serde_json::from_str(&round3_ans_vec[j]).unwrap(); + let key_i = &enc_keys[j]; + let out = aes_decrypt(key_i, aead_pack); + let out_bn = BigInt::from_bytes(&out[..]); + let out_fe = Scalar::::from(&out_bn); + party_shares.push(out_fe); + + j += 1; + } + } + + // round 4: send vss commitments + assert!(broadcast( + &client, + party_num_int, + "round4", + serde_json::to_string(&vss_scheme).unwrap(), + uuid.clone() + ) + .is_ok()); + let round4_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + PARTIES, + delay, + "round4", + uuid.clone(), + ); + + let mut j = 0; + let mut vss_scheme_vec: Vec> = Vec::new(); + for i in 1..=PARTIES { + if i == party_num_int { + vss_scheme_vec.push(vss_scheme.clone()); + } else { + let vss_scheme_j: VerifiableSS = serde_json::from_str(&round4_ans_vec[j]).unwrap(); + + vss_scheme_vec.push(vss_scheme_j); + j += 1; + } + } + + let shared_keys = party_keys + .phase2_verify_vss_construct_keypair( + ¶ms, + &point_vec, + &party_shares, + &vss_scheme_vec, + party_num_int, + ) + .expect("invalid vss"); + + let dlog_proof = DLogProof::prove(&shared_keys.x_i); + + // round 5: send dlog proof + assert!(broadcast( + &client, + party_num_int, + "round5", + serde_json::to_string(&dlog_proof).unwrap(), + uuid.clone() + ) + .is_ok()); + let round5_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + PARTIES, + delay, + "round5", + uuid.clone(), + ); + + let mut j = 0; + let mut dlog_proof_vec: Vec> = Vec::new(); + for i in 1..=PARTIES { + if i == party_num_int { + dlog_proof_vec.push(dlog_proof.clone()); + } else { + let dlog_proof_j: DLogProof = serde_json::from_str(&round5_ans_vec[j]).unwrap(); + + dlog_proof_vec.push(dlog_proof_j); + j += 1; + } + } + verify_dlog_proofs(¶ms, &dlog_proof_vec, &point_vec) + .expect("bad dlog proof"); + + //save key to file: + /*let paillier_key_vec = (0..PARTIES) + .map(|i| bc1_vec[i as usize].e.clone()) + .collect::>(); + + */ + + let keygen_json = serde_json::to_string(&( + party_keys, + shared_keys, + party_num_int, + vss_scheme_vec, + //paillier_key_vec, + y_sum, + )) + .unwrap(); + fs::write(env::args().nth(2).unwrap(), keygen_json).expect("Unable to save !"); +} + +pub fn signup(client: &Client) -> Result { + let key = "signup-keygen".to_string(); + + let res_body = postb(&client, "signupkeygen", key).unwrap(); + serde_json::from_str(&res_body).unwrap() +} + + + +pub fn verify_dlog_proofs( + params: &Parameters, + dlog_proofs_vec: &[DLogProof], + y_vec: &[Point], +) -> Result<(), Error> { + assert_eq!(y_vec.len(), usize::from(params.share_count)); + assert_eq!(dlog_proofs_vec.len(), usize::from(params.share_count)); + + let xi_dlog_verify = + (0..y_vec.len()).all(|i| DLogProof::verify(&dlog_proofs_vec[i]).is_ok()); + + if xi_dlog_verify { + Ok(()) + } else { + Err(InvalidKey) + } +} \ No newline at end of file diff --git a/examples/sign_client.rs b/examples/sign_client.rs new file mode 100644 index 0000000..0a035ea --- /dev/null +++ b/examples/sign_client.rs @@ -0,0 +1,399 @@ +#![allow(non_snake_case)] + +use curv::{ + arithmetic::traits::*, + cryptographic_primitives::{ + secret_sharing::feldman_vss::VerifiableSS, + }, + BigInt, +}; + +use multi_party_eddsa::protocols::thresholdsig::{SharedKeys}; + +use reqwest::Client; +use std::{env, fs, time}; +use std::time::Duration; +use curv::elliptic::curves::{Ed25519, Point, Scalar}; + +mod common; +use common::{ + broadcast, poll_for_broadcasts, poll_for_p2p, postb, sendp2p, Params, PartySignup, +}; +use multi_party_eddsa::protocols::{Signature, thresholdsig}; +use multi_party_eddsa::protocols::thresholdsig::{EphemeralKey, EphemeralSharedKeys, Parameters, Keys, KeyGenBroadcastMessage1, LocalSig}; +use crate::common::{AEAD, aes_decrypt, aes_encrypt, AES_KEY_BYTES_LEN}; + +#[allow(clippy::cognitive_complexity)] +fn main() { + if env::args().nth(4).is_some() { + panic!("too many arguments") + } + if env::args().nth(3).is_none() { + panic!("too few arguments") + } + let message_str = env::args().nth(3).unwrap_or_else(|| "".to_string()); + // read key file + let key_file_path = env::args().nth(2).unwrap(); + + //read parameters: + let data = fs::read_to_string("params.json") + .expect("Unable to read params, make sure config file is present in the same folder "); + let params: Params = serde_json::from_str(&data).unwrap(); + + let (signature, y_sum) = run_signer(key_file_path, params, message_str); + let sign_json = serde_json::json!({ + "r": (BigInt::from_bytes(&(signature.R).to_bytes(false))).to_str_radix(16), + "s": (BigInt::from_bytes(&(signature.s).to_bytes())).to_str_radix(16), + "y": y_sum + }); + let sign_json = serde_json::to_string(&sign_json).unwrap(); + + /*let sign_json = serde_json::to_string(&( + "r", + (BigInt::from_bytes(&(signature.R).to_bytes(false))).to_str_radix(16), + "s", + (BigInt::from_bytes(&(signature.s).to_bytes())).to_str_radix(16), + )) + .unwrap();*/ + + fs::write("signature.json".to_string(), sign_json.clone()).expect("Unable to save !"); + + println!("{:?}", sign_json); +} + +fn run_signer(key_file_path: String, params: Params, message_str:String) -> (Signature, Point) { + // + // This function is written inspired from the + // test function: protocols::thresholdsig::test::tests::test_t2_n5_sign_with_4_internal() + //TODO Make sure this approach is valid for {t,n} multy party threshold EdDSA + let message = match hex::decode(message_str.clone()) { + Ok(x) => x, + Err(_e) => message_str.as_bytes().to_vec(), + }; + let message = &message[..]; + let client = Client::new(); + // delay: + let delay = time::Duration::from_millis(25); + + let data = fs::read_to_string(key_file_path) + .expect("Unable to load keys, did you run keygen first? "); + let (party_keys, shared_keys, _, vss_scheme_vec, Y): ( + Keys, + SharedKeys, + u16, + Vec>, + //Vec, + Point, + ) = serde_json::from_str(&data).unwrap(); + + let THRESHOLD = params.threshold.parse::().unwrap(); + //signup: + let (party_num_int, uuid) = match signup(&client).unwrap() { + PartySignup { number, uuid } => (number, uuid), + }; + println!("number: {:?}, uuid: {:?}", party_num_int, uuid); + + let (_eph_keys_vec, eph_shared_keys_vec, R, eph_vss_vec) = eph_keygen_t_n_parties( + client.clone(), + uuid.clone(), + delay, + THRESHOLD.clone(), + THRESHOLD.clone()+1, + party_num_int, + &party_keys, + &message, + ); + + let local_sig = LocalSig::compute( + &message, + &eph_shared_keys_vec[(party_num_int-1) as usize], + &shared_keys, + ); + + let local_sig_vec = exchange_data( + client.clone(), + party_num_int, + THRESHOLD.clone()+1, + uuid, + "round1_local_sig", + delay, + local_sig + ); + + let parties_index_vec = (0..THRESHOLD.clone()+1) + .map(|i| i as u16) + .collect::>(); + + let verify_local_sig = LocalSig::verify_local_sigs( + &local_sig_vec, + &parties_index_vec.as_slice(), + &vss_scheme_vec, + &eph_vss_vec, + ); + + assert!(verify_local_sig.is_ok()); + + let vss_sum_local_sigs = verify_local_sig.unwrap(); + + // each party / dealer can generate the signature + let signature = + thresholdsig::generate(&vss_sum_local_sigs, &local_sig_vec, &parties_index_vec, R); + let verify_sig = signature.verify(&message, &Y); + assert!(verify_sig.is_ok()); + + (signature, Y) +} + + +pub fn eph_keygen_t_n_parties( + client: Client, + uuid: String, + delay: Duration, + t: u16, // system threshold + n: u16, // number of signers + party_num_int: u16, + key_i: &Keys, + message: &[u8], +) -> ( + EphemeralKey, + Vec, + Point, + Vec>, +) { + let parties = (0..n) + .map(|i| (i + 1) as u16) + .collect::>(); + + let parames = Parameters { + threshold: t, + share_count: n.clone(), + }; + assert!(parties.len() as u16 > t && parties.len() as u16 <= n); + + let eph_party_key: EphemeralKey = EphemeralKey::ephermeral_key_create_from_deterministic_secret( + key_i, + message, + party_num_int, + ); + + let mut bc1_vec = Vec::new(); + let mut blind_vec = Vec::new(); + let mut R_vec = Vec::new(); + let (bc_i, blind) = eph_party_key.phase1_broadcast(); + + assert!(broadcast( + &client, + party_num_int, + "eph_keygen_round1", + serde_json::to_string(&(bc_i.clone(), blind.clone(), eph_party_key.R_i.clone())).unwrap(), + uuid.clone() + ) + .is_ok()); + let round1_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + n as u16, + delay, + "eph_keygen_round1", + uuid.clone(), + ); + + let mut j = 0; + let mut enc_keys: Vec> = Vec::new(); + for i in 1..=n { + if i == party_num_int { + bc1_vec.push(bc_i.clone()); + blind_vec.push(blind.clone()); + R_vec.push(eph_party_key.R_i.clone()); + } else { + let (bc1_j, blind_j, R_i_j) = serde_json::from_str::<(KeyGenBroadcastMessage1, BigInt, Point)>(&round1_ans_vec[j]).unwrap(); + bc1_vec.push(bc1_j); + blind_vec.push(blind_j); + R_vec.push(R_i_j.clone()); + let key_bn: BigInt = (R_i_j.clone() * eph_party_key.r_i.clone()).x_coord().unwrap(); + let key_bytes = BigInt::to_bytes(&key_bn); + let mut template: Vec = vec![0u8; AES_KEY_BYTES_LEN - key_bytes.len()]; + template.extend_from_slice(&key_bytes[..]); + enc_keys.push(template); + j += 1; + } + } + + let mut R_vec_iter = R_vec.iter(); + let head = R_vec_iter.next().unwrap(); + let tail = R_vec_iter; + let R_sum = tail.fold(head.clone(), |acc, x| acc + x); + let (vss_scheme, secret_shares) = eph_party_key + .phase1_verify_com_phase2_distribute( + ¶mes, &blind_vec, &R_vec, &bc1_vec, parties.as_slice(), + ) + .expect("invalid key"); + + // round 2: send vss commitments + assert!(broadcast( + &client, + party_num_int, + "eph_keygen_round2", + serde_json::to_string(&vss_scheme).unwrap(), + uuid.clone() + ) + .is_ok()); + let round2_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + n as u16, + delay, + "eph_keygen_round2", + uuid.clone(), + ); + + let mut j = 0; + let mut vss_scheme_vec: Vec> = Vec::new(); + for i in 1..=n { + if i == party_num_int { + vss_scheme_vec.push(vss_scheme.clone()); + } else { + let vss_scheme_j: VerifiableSS = serde_json::from_str(&round2_ans_vec[j]).unwrap(); + + vss_scheme_vec.push(vss_scheme_j); + j += 1; + } + } + + ////////////////////////////////////////////////////////////////////////////// + //I'm not sure if we need this phase in ephemeral mode or not? + let mut j = 0; + for (k, i) in (1..=n).enumerate() { + if i != party_num_int { + // prepare encrypted ss for party i: + let key_i = &enc_keys[j]; + let plaintext = BigInt::to_bytes(&secret_shares[k].to_bigint()); + let aead_pack_i = aes_encrypt(key_i, &plaintext); + assert!(sendp2p( + &client, + party_num_int, + i as u16, + "eph_keygen_round3", + serde_json::to_string(&aead_pack_i).unwrap(), + uuid.clone() + ) + .is_ok()); + j += 1; + } + } + + let round3_ans_vec = poll_for_p2p( + &client, + party_num_int, + n as u16, + delay, + "eph_keygen_round3", + uuid.clone(), + ); + + let mut j = 0; + let mut party_shares: Vec> = Vec::new(); + for i in 1..=n { + if i == party_num_int { + party_shares.push(secret_shares[(i - 1) as usize].clone()); + } else { + let aead_pack: AEAD = serde_json::from_str(&round3_ans_vec[j]).unwrap(); + let key_i = &enc_keys[j]; + let out = aes_decrypt(key_i, aead_pack); + let out_bn = BigInt::from_bytes(&out[..]); + let out_fe = Scalar::::from(&out_bn); + party_shares.push(out_fe); + j += 1; + } + } + ////////////////////////////////////////////////////////////////////////////// + + let mut shared_keys_vec = Vec::new(); + let eph_shared_key = eph_party_key + .phase2_verify_vss_construct_keypair( + ¶mes, + &R_vec, + &party_shares, + &vss_scheme_vec, + party_num_int, + ) + .expect("invalid vss"); + + // round 4: send shared key + assert!(broadcast( + &client, + party_num_int, + "eph_keygen_round4", + serde_json::to_string(&eph_shared_key).unwrap(), + uuid.clone() + ) + .is_ok()); + let round4_ans_vec = poll_for_broadcasts( + &client, + party_num_int, + n as u16, + delay, + "eph_keygen_round4", + uuid.clone(), + ); + + let mut j = 0; + for i in 1..=n { + if i == party_num_int { + shared_keys_vec.push(eph_shared_key.clone()); + } else { + let shared_key_j:EphemeralSharedKeys = serde_json::from_str(&round4_ans_vec[j]).unwrap(); + shared_keys_vec.push(shared_key_j); + j += 1; + } + } + + (eph_party_key, shared_keys_vec, R_sum, vss_scheme_vec) +} + +pub fn signup(client: &Client) -> Result { + let key = "signup-sign".to_string(); + + let res_body = postb(&client, "signupsign", key).unwrap(); + serde_json::from_str(&res_body).unwrap() +} + + + +pub fn exchange_data(client:Client, party_num:u16, n:u16, uuid:String, round: &str, delay: Duration, data:T) -> Vec +where + T: Clone + serde::de::DeserializeOwned + serde::Serialize, +{ + assert!(broadcast( + &client, + party_num, + &round, + serde_json::to_string(&data).unwrap(), + uuid.clone() + ) + .is_ok()); + let round_ans_vec = poll_for_broadcasts( + &client, + party_num, + n, + delay, + &round, + uuid.clone(), + ); + + let json_answers = round_ans_vec.clone(); + let mut j = 0; + let mut answers: Vec = Vec::new(); + for i in 1..=n { + if i == party_num { + answers.push(data.clone()); + } else { + let data_j: T = serde_json::from_str::(&json_answers[j].clone()).unwrap(); + answers.push(data_j); + j += 1; + } + } + + return answers; +} + diff --git a/examples/sm_manager.rs b/examples/sm_manager.rs new file mode 100644 index 0000000..6587d19 --- /dev/null +++ b/examples/sm_manager.rs @@ -0,0 +1,144 @@ + +use std::collections::HashMap; +use std::fs; +use std::sync::RwLock; + +use rocket::{post, routes, State, launch}; +use rocket::serde::json::Json; + +use uuid::Uuid; + +mod common; +use common::{Entry, Index, Key, Params, PartySignup}; + +#[post("/get", format = "json", data = "")] +fn get( + db_mtx: &State>>, + request: Json, +) -> Json> { + let index: Index = request.0; + let hm = db_mtx.read().unwrap(); + match hm.get(&index.key) { + Some(v) => { + let entry = Entry { + key: index.key, + value: v.clone().to_string(), + }; + Json(Ok(entry)) + } + None => Json(Err(())), + } +} + +#[post("/set", format = "json", data = "")] +fn set(db_mtx: &State>>, request: Json) -> Json> { + let entry: Entry = request.0; + let mut hm = db_mtx.write().unwrap(); + hm.insert(entry.key.clone(), entry.value.clone()); + Json(Ok(())) +} + +#[post("/signupkeygen", format = "json")] +fn signup_keygen(db_mtx: &State>>) -> Json> { + let data = fs::read_to_string("params.json") + .expect("Unable to read params, make sure config file is present in the same folder "); + let params: Params = serde_json::from_str(&data).unwrap(); + let parties = params.parties.parse::().unwrap(); + + let key = "signup-keygen".to_string(); + + let party_signup = { + let hm = db_mtx.read().unwrap(); + let value = hm.get(&key).unwrap(); + let client_signup: PartySignup = serde_json::from_str(&value).unwrap(); + if client_signup.number < parties { + PartySignup { + number: client_signup.number + 1, + uuid: client_signup.uuid, + } + } else { + PartySignup { + number: 1, + uuid: Uuid::new_v4().to_string(), + } + } + }; + + let mut hm = db_mtx.write().unwrap(); + hm.insert(key, serde_json::to_string(&party_signup).unwrap()); + Json(Ok(party_signup)) +} + +#[post("/signupsign", format = "json")] +fn signup_sign(db_mtx: &State>>) -> Json> { + //read parameters: + let data = fs::read_to_string("params.json") + .expect("Unable to read params, make sure config file is present in the same folder "); + let params: Params = serde_json::from_str(&data).unwrap(); + let threshold = params.threshold.parse::().unwrap(); + let key = "signup-sign".to_string(); + + let party_signup = { + let hm = db_mtx.read().unwrap(); + let value = hm.get(&key).unwrap(); + let client_signup: PartySignup = serde_json::from_str(&value).unwrap(); + if client_signup.number < threshold + 1 { + PartySignup { + number: client_signup.number + 1, + uuid: client_signup.uuid, + } + } else { + PartySignup { + number: 1, + uuid: Uuid::new_v4().to_string(), + } + } + }; + + let mut hm = db_mtx.write().unwrap(); + hm.insert(key, serde_json::to_string(&party_signup).unwrap()); + Json(Ok(party_signup)) +} + +//refcell, arc + +#[launch] +fn rocket() -> _ { + // let mut my_config = Config::development(); + // my_config.set_port(18001); + let db: HashMap = HashMap::new(); + let db_mtx = RwLock::new(db); + //rocket::custom(my_config).mount("/", routes![get, set]).manage(db_mtx).launch(); + + ///////////////////////////////////////////////////////////////// + //////////////////////////init signups:////////////////////////// + ///////////////////////////////////////////////////////////////// + + let keygen_key = "signup-keygen".to_string(); + let sign_key = "signup-sign".to_string(); + + let uuid_keygen = Uuid::new_v4().to_string(); + let uuid_sign = Uuid::new_v4().to_string(); + + let party1 = 0; + let party_signup_keygen = PartySignup { + number: party1, + uuid: uuid_keygen, + }; + let party_signup_sign = PartySignup { + number: party1, + uuid: uuid_sign, + }; + { + let mut hm = db_mtx.write().unwrap(); + hm.insert( + keygen_key, + serde_json::to_string(&party_signup_keygen).unwrap(), + ); + hm.insert(sign_key, serde_json::to_string(&party_signup_sign).unwrap()); + } + ///////////////////////////////////////////////////////////////// + rocket::build() + .mount("/", routes![get, set, signup_keygen, signup_sign]) + .manage(db_mtx) +} diff --git a/params.json b/params.json new file mode 100644 index 0000000..a03898e --- /dev/null +++ b/params.json @@ -0,0 +1 @@ +{"parties":"3", "threshold":"2"} diff --git a/src/protocols/aggsig/mod.rs b/src/protocols/aggsig/mod.rs index 114712e..bcaca9e 100644 --- a/src/protocols/aggsig/mod.rs +++ b/src/protocols/aggsig/mod.rs @@ -29,7 +29,7 @@ use curv::BigInt; pub use curv::arithmetic::traits::Converter; use curv::cryptographic_primitives::commitments::traits::Commitment; -use protocols::{ProofError, Signature}; +use crate::protocols::{ProofError, Signature}; use rand::{thread_rng, Rng}; use sha2::{digest::Digest, Sha512}; diff --git a/src/protocols/aggsig/test.rs b/src/protocols/aggsig/test.rs index 2b5e2b7..7fe5938 100644 --- a/src/protocols/aggsig/test.rs +++ b/src/protocols/aggsig/test.rs @@ -28,8 +28,8 @@ mod tests { use rand::{Rng, RngCore}; use sha2::Sha512; - use protocols::tests::deterministic_fast_rand; - use protocols::{ + use crate::protocols::tests::deterministic_fast_rand; + use crate::protocols::{ aggsig::{self, KeyAgg}, tests::verify_dalek, ExpandedKeyPair, Signature, diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs index 9849ec6..6eb2712 100644 --- a/src/protocols/mod.rs +++ b/src/protocols/mod.rs @@ -31,13 +31,13 @@ pub mod thresholdsig; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ExpandedPrivateKey { pub prefix: Scalar, - private_key: Scalar, + pub private_key: Scalar, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ExpandedKeyPair { pub public_key: Point, - expanded_private_key: ExpandedPrivateKey, + pub expanded_private_key: ExpandedPrivateKey, } impl ExpandedKeyPair { @@ -114,7 +114,7 @@ pub(crate) mod tests { use rand_xoshiro::rand_core::{RngCore, SeedableRng}; use rand_xoshiro::Xoshiro256PlusPlus; - use protocols::{ExpandedKeyPair, Signature}; + use crate::protocols::{ExpandedKeyPair, Signature}; pub fn verify_dalek(pk: &Point, sig: &Signature, msg: &[u8]) -> bool { let mut sig_bytes = [0u8; 64]; diff --git a/src/protocols/multisig/mod.rs b/src/protocols/multisig/mod.rs index 57f3dc5..447a374 100644 --- a/src/protocols/multisig/mod.rs +++ b/src/protocols/multisig/mod.rs @@ -23,7 +23,7 @@ use super::ExpandedKeyPair; use curv::cryptographic_primitives::hashing::DigestExt; use curv::elliptic::curves::{Ed25519, Point, Scalar}; use curv::BigInt; -use protocols::multisig; +use crate::protocols::multisig; use sha2::{digest::Digest, Sha512}; diff --git a/src/protocols/multisig/test.rs b/src/protocols/multisig/test.rs index a0b31b8..7b3a131 100644 --- a/src/protocols/multisig/test.rs +++ b/src/protocols/multisig/test.rs @@ -22,7 +22,7 @@ mod tests { use curv::cryptographic_primitives::hashing::DigestExt; use curv::elliptic::curves::Scalar; use curv::BigInt; - use protocols::multisig::{partial_sign, verify, EphKey, Keys, Signature}; + use crate::protocols::multisig::{partial_sign, verify, EphKey, Keys, Signature}; use sha2::{digest::Digest, Sha256}; #[test] diff --git a/src/protocols/musig2/mod.rs b/src/protocols/musig2/mod.rs index 34504b3..55864a6 100644 --- a/src/protocols/musig2/mod.rs +++ b/src/protocols/musig2/mod.rs @@ -10,7 +10,7 @@ use super::{ExpandedKeyPair, Signature}; use curv::arithmetic::Converter; use curv::elliptic::curves::{Ed25519, Point, Scalar}; use curv::BigInt; -use protocols::Rng; +use crate::protocols::Rng; use sha2::{digest::Digest, Sha512}; pub const NUMBER_OF_NONCES: usize = 2; diff --git a/src/protocols/thresholdsig/mod.rs b/src/protocols/thresholdsig/mod.rs index f4c7564..7b07aa8 100644 --- a/src/protocols/thresholdsig/mod.rs +++ b/src/protocols/thresholdsig/mod.rs @@ -10,7 +10,7 @@ version 3 of the License, or (at your option) any later version. @license GPL-3.0+ */ -use Error::{self, InvalidKey, InvalidSS}; +use crate::Error::{self, InvalidKey, InvalidSS}; use curv::arithmetic::traits::*; use curv::cryptographic_primitives::commitments::hash_commitment::HashCommitment; @@ -19,22 +19,30 @@ use curv::cryptographic_primitives::hashing::DigestExt; use curv::cryptographic_primitives::secret_sharing::feldman_vss::{SecretShares, VerifiableSS}; use curv::elliptic::curves::{Ed25519, Point, Scalar}; use curv::BigInt; -use protocols::{ExpandedKeyPair, Signature}; +use crate::protocols::{ExpandedKeyPair, Signature}; use rand::{thread_rng, Rng}; use sha2::{digest::Digest, Sha512}; const SECURITY: usize = 256; // u_i is private key and {u__i, prefix} are extended private key. +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Keys { pub keypair: ExpandedKeyPair, pub party_index: u16, } +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct KeyGenBroadcastMessage1 { com: BigInt, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct KeyGenDecommitMessage1 { + pub blind_factor: BigInt, + pub y_i: Point, +} + #[derive(Debug)] pub struct Parameters { pub threshold: u16, //t @@ -44,7 +52,7 @@ pub struct Parameters { pub struct SharedKeys { pub y: Point, pub x_i: Scalar, - prefix: Scalar, + pub prefix: Scalar, } pub struct EphemeralKey { @@ -59,6 +67,7 @@ pub struct EphemeralSharedKeys { pub r_i: Scalar, } +#[derive(Clone, Serialize, Deserialize)] pub struct LocalSig { gamma_i: Scalar, k: Scalar, @@ -79,11 +88,11 @@ impl Keys { } } - pub fn phase1_broadcast(&self) -> (KeyGenBroadcastMessage1, BigInt) { + pub fn phase1_broadcast(&self) -> (KeyGenBroadcastMessage1, KeyGenDecommitMessage1) { self.phase1_broadcast_rng(&mut thread_rng()) } - fn phase1_broadcast_rng(&self, rng: &mut impl Rng) -> (KeyGenBroadcastMessage1, BigInt) { + fn phase1_broadcast_rng(&self, rng: &mut impl Rng) -> (KeyGenBroadcastMessage1, KeyGenDecommitMessage1) { let blind_factor: [u8; SECURITY / 8] = rng.gen(); let blind_factor = BigInt::from_bytes(&blind_factor); let com = HashCommitment::::create_commitment_with_user_defined_randomness( @@ -91,7 +100,10 @@ impl Keys { &blind_factor, ); let bcm1 = KeyGenBroadcastMessage1 { com }; - (bcm1, blind_factor) + (bcm1, KeyGenDecommitMessage1 { + blind_factor, + y_i: self.keypair.public_key.clone() + }) } pub fn phase1_verify_com_phase2_distribute( @@ -120,7 +132,8 @@ impl Keys { if !correct_key_correct_decom_all { return Err(InvalidKey); } - Ok(VerifiableSS::share_at_indices( + + Ok(VerifiableSS::::share_at_indices( params.threshold, params.share_count, &self.keypair.expanded_private_key.private_key, diff --git a/src/protocols/thresholdsig/test.rs b/src/protocols/thresholdsig/test.rs index 1a1e84e..5ee1ad5 100644 --- a/src/protocols/thresholdsig/test.rs +++ b/src/protocols/thresholdsig/test.rs @@ -15,11 +15,12 @@ mod tests { use curv::cryptographic_primitives::secret_sharing::feldman_vss::VerifiableSS; use curv::elliptic::curves::{Ed25519, Point}; use itertools::{izip, Itertools}; - use protocols::tests::{deterministic_fast_rand, verify_dalek}; - use protocols::thresholdsig::{ + use crate::protocols::tests::{deterministic_fast_rand, verify_dalek}; + use crate::protocols::thresholdsig::{ self, EphemeralKey, EphemeralSharedKeys, Keys, LocalSig, Parameters, SharedKeys, }; use rand::{Rng, RngCore}; + use curv::BigInt; #[test] fn test_sign_threshold_verify_dalek_n1() { @@ -231,11 +232,16 @@ mod tests { assert_eq!(parties.len(), usize::from(n)); let keypairs: Vec<_> = parties.iter().copied().map(Keys::phase1_create).collect(); - let (first_msgs, first_msg_blinds): (Vec<_>, Vec<_>) = keypairs + let (first_msgs, first_msg_decommits): (Vec<_>, Vec<_>) = keypairs .iter() .map(|keypair| Keys::phase1_broadcast_rng(keypair, rng)) .unzip(); + let blind_vec = first_msg_decommits + .iter() + .map(|decommit_msg| decommit_msg.blind_factor.clone()) + .collect::>(); + let pubkeys_list: Vec<_> = keypairs .iter() .map(|k| k.keypair.public_key.clone()) @@ -252,7 +258,7 @@ mod tests { keypair .phase1_verify_com_phase2_distribute( ¶ms, - &first_msg_blinds, + &blind_vec, &pubkeys_list, &first_msgs, parties,