From 37bfcb737ba5bbb5fb37906090ed204ed5adc70e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 15 Feb 2018 13:12:51 +0300 Subject: [PATCH] SecretStore: threshold ECDSA PoC (#7615) * SecretStore: ECDSA PoC * SecretStore: fixed ECDSA serialization + cleanup * removed unused param * removed unused method * removed debug unwrap * 1/x -> inv(x) * SecretStore: merged fixes from ECDSA session branch * once again 1/* -> inv(*) * fixed grumbles --- ethkey/src/secret.rs | 97 +++-- secret_store/src/key_server_cluster/math.rs | 373 ++++++++++++++++++-- 2 files changed, 424 insertions(+), 46 deletions(-) diff --git a/ethkey/src/secret.rs b/ethkey/src/secret.rs index 569f58d49..6273671b7 100644 --- a/ethkey/src/secret.rs +++ b/ethkey/src/secret.rs @@ -48,6 +48,11 @@ impl Secret { Secret { inner: h } } + /// Creates zero key, which is invalid for crypto operations, but valid for math operation. + pub fn zero() -> Self { + Secret { inner: Default::default() } + } + /// Imports and validates the key. pub fn from_unsafe_slice(key: &[u8]) -> Result { let secret = key::SecretKey::from_slice(&super::SECP256K1, key)?; @@ -61,51 +66,91 @@ impl Secret { /// Inplace add one secret key to another (scalar + scalar) pub fn add(&mut self, other: &Secret) -> Result<(), Error> { - let mut key_secret = self.to_secp256k1_secret()?; - let other_secret = other.to_secp256k1_secret()?; - key_secret.add_assign(&SECP256K1, &other_secret)?; + match (self.is_zero(), other.is_zero()) { + (true, true) | (false, true) => Ok(()), + (true, false) => { + *self = other.clone(); + Ok(()) + }, + (false, false) => { + let mut key_secret = self.to_secp256k1_secret()?; + let other_secret = other.to_secp256k1_secret()?; + key_secret.add_assign(&SECP256K1, &other_secret)?; - *self = key_secret.into(); - Ok(()) + *self = key_secret.into(); + Ok(()) + }, + } } /// Inplace subtract one secret key from another (scalar - scalar) pub fn sub(&mut self, other: &Secret) -> Result<(), Error> { - let mut key_secret = self.to_secp256k1_secret()?; - let mut other_secret = other.to_secp256k1_secret()?; - other_secret.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?; - key_secret.add_assign(&SECP256K1, &other_secret)?; + match (self.is_zero(), other.is_zero()) { + (true, true) | (false, true) => Ok(()), + (true, false) => { + *self = other.clone(); + self.neg() + }, + (false, false) => { + let mut key_secret = self.to_secp256k1_secret()?; + let mut other_secret = other.to_secp256k1_secret()?; + other_secret.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?; + key_secret.add_assign(&SECP256K1, &other_secret)?; - *self = key_secret.into(); - Ok(()) + *self = key_secret.into(); + Ok(()) + }, + } } /// Inplace decrease secret key (scalar - 1) pub fn dec(&mut self) -> Result<(), Error> { - let mut key_secret = self.to_secp256k1_secret()?; - key_secret.add_assign(&SECP256K1, &key::MINUS_ONE_KEY)?; + match self.is_zero() { + true => { + *self = key::MINUS_ONE_KEY.into(); + Ok(()) + }, + false => { + let mut key_secret = self.to_secp256k1_secret()?; + key_secret.add_assign(&SECP256K1, &key::MINUS_ONE_KEY)?; - *self = key_secret.into(); - Ok(()) + *self = key_secret.into(); + Ok(()) + }, + } } /// Inplace multiply one secret key to another (scalar * scalar) pub fn mul(&mut self, other: &Secret) -> Result<(), Error> { - let mut key_secret = self.to_secp256k1_secret()?; - let other_secret = other.to_secp256k1_secret()?; - key_secret.mul_assign(&SECP256K1, &other_secret)?; + match (self.is_zero(), other.is_zero()) { + (true, true) | (true, false) => Ok(()), + (false, true) => { + *self = Self::zero(); + Ok(()) + }, + (false, false) => { + let mut key_secret = self.to_secp256k1_secret()?; + let other_secret = other.to_secp256k1_secret()?; + key_secret.mul_assign(&SECP256K1, &other_secret)?; - *self = key_secret.into(); - Ok(()) + *self = key_secret.into(); + Ok(()) + }, + } } /// Inplace negate secret key (-scalar) pub fn neg(&mut self) -> Result<(), Error> { - let mut key_secret = self.to_secp256k1_secret()?; - key_secret.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?; + match self.is_zero() { + true => Ok(()), + false => { + let mut key_secret = self.to_secp256k1_secret()?; + key_secret.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?; - *self = key_secret.into(); - Ok(()) + *self = key_secret.into(); + Ok(()) + }, + } } /// Inplace inverse secret key (1 / scalar) @@ -120,6 +165,10 @@ impl Secret { /// Compute power of secret key inplace (secret ^ pow). /// This function is not intended to be used with large powers. pub fn pow(&mut self, pow: usize) -> Result<(), Error> { + if self.is_zero() { + return Ok(()); + } + match pow { 0 => *self = key::ONE_KEY.into(), 1 => (), diff --git a/secret_store/src/key_server_cluster/math.rs b/secret_store/src/key_server_cluster/math.rs index 85e164082..178033762 100644 --- a/secret_store/src/key_server_cluster/math.rs +++ b/secret_store/src/key_server_cluster/math.rs @@ -18,6 +18,7 @@ use ethkey::{Public, Secret, Random, Generator, math}; use ethereum_types::{H256, U256}; use hash::keccak; use key_server_cluster::Error; +#[cfg(test)] use ethkey::Signature; /// Encryption result. #[derive(Debug)] @@ -28,16 +29,43 @@ pub struct EncryptedSecret { pub encrypted_point: Public, } -/// Generate random scalar +/// Create zero scalar. +#[cfg(test)] +pub fn zero_scalar() -> Secret { + Secret::zero() +} + +/// Convert hash to EC scalar (modulo curve order). +pub fn to_scalar(hash: H256) -> Result { + let scalar: U256 = hash.into(); + let scalar: H256 = (scalar % math::curve_order()).into(); + let scalar = Secret::from_slice(&*scalar); + scalar.check_validity()?; + Ok(scalar) +} + +/// Generate random scalar. pub fn generate_random_scalar() -> Result { Ok(Random.generate()?.secret().clone()) } -/// Generate random point +/// Generate random point. pub fn generate_random_point() -> Result { Ok(Random.generate()?.public().clone()) } +/// Get X coordinate of point. +#[cfg(test)] +fn public_x(public: &Public) -> H256 { + public[0..32].into() +} + +/// Get Y coordinate of point. +#[cfg(test)] +fn public_y(public: &Public) -> H256 { + public[32..64].into() +} + /// Compute publics sum. pub fn compute_public_sum<'a, I>(mut publics: I) -> Result where I: Iterator { let mut sum = publics.next().expect("compute_public_sum is called when there's at least one public; qed").clone(); @@ -342,15 +370,10 @@ pub fn combine_message_hash_with_public(message_hash: &H256, public: &Public) -> let hash = keccak(&buffer[..]); // map hash to EC finite field value - let hash: U256 = hash.into(); - let hash: H256 = (hash % math::curve_order()).into(); - let hash = Secret::from_slice(&*hash); - hash.check_validity()?; - - Ok(hash) + to_scalar(hash) } -/// Compute signature share. +/// Compute Schnorr signature share. pub fn compute_signature_share<'a, I>(threshold: usize, combined_hash: &Secret, one_time_secret_coeff: &Secret, node_secret_share: &Secret, node_number: &Secret, other_nodes_numbers: I) -> Result where I: Iterator { let mut sum = one_time_secret_coeff.clone(); @@ -364,7 +387,7 @@ pub fn compute_signature_share<'a, I>(threshold: usize, combined_hash: &Secret, Ok(sum) } -/// Check signature share. +/// Check Schnorr signature share. pub fn _check_signature_share<'a, I>(_combined_hash: &Secret, _signature_share: &Secret, _public_share: &Public, _one_time_public_share: &Public, _node_numbers: I) -> Result where I: Iterator { // TODO [Trust]: in paper partial signature is checked using comparison: @@ -384,7 +407,7 @@ pub fn _check_signature_share<'a, I>(_combined_hash: &Secret, _signature_share: Ok(true) } -/// Compute signature. +/// Compute Schnorr signature. pub fn compute_signature<'a, I>(signature_shares: I) -> Result where I: Iterator { compute_secret_sum(signature_shares) } @@ -405,7 +428,7 @@ pub fn local_compute_signature(nonce: &Secret, secret: &Secret, message_hash: &S Ok((combined_hash, sig)) } -/// Verify signature as described in https://en.wikipedia.org/wiki/Schnorr_signature#Verifying. +/// Verify Schnorr signature as described in https://en.wikipedia.org/wiki/Schnorr_signature#Verifying. #[cfg(test)] pub fn verify_signature(public: &Public, signature: &(Secret, Secret), message_hash: &H256) -> Result { let mut addendum = math::generation_point(); @@ -418,10 +441,104 @@ pub fn verify_signature(public: &Public, signature: &(Secret, Secret), message_h Ok(combined_hash == signature.0) } +/// Compute R part of ECDSA signature. +#[cfg(test)] +pub fn compute_ecdsa_r(nonce_public: &Public) -> Result { + to_scalar(public_x(nonce_public)) +} + +/// Compute share of S part of ECDSA signature. +#[cfg(test)] +pub fn compute_ecdsa_s_share(inv_nonce_share: &Secret, inv_nonce_mul_secret: &Secret, signature_r: &Secret, message_hash: &Secret) -> Result { + let mut nonce_inv_share_mul_message_hash = inv_nonce_share.clone(); + nonce_inv_share_mul_message_hash.mul(&message_hash.clone().into())?; + + let mut nonce_inv_share_mul_secret_share_mul_r = inv_nonce_mul_secret.clone(); + nonce_inv_share_mul_secret_share_mul_r.mul(signature_r)?; + + let mut signature_s_share = nonce_inv_share_mul_message_hash; + signature_s_share.add(&nonce_inv_share_mul_secret_share_mul_r)?; + + Ok(signature_s_share) +} + +/// Compute S part of ECDSA signature from shares. +#[cfg(test)] +pub fn compute_ecdsa_s(t: usize, signature_s_shares: &[Secret], id_numbers: &[Secret]) -> Result { + let double_t = t * 2; + debug_assert!(id_numbers.len() >= double_t + 1); + debug_assert_eq!(signature_s_shares.len(), id_numbers.len()); + + compute_joint_secret_from_shares(double_t, + &signature_s_shares.iter().take(double_t + 1).collect::>(), + &id_numbers.iter().take(double_t + 1).collect::>()) +} + +/// Serialize ECDSA signature to [r][s]v form. +#[cfg(test)] +pub fn serialize_ecdsa_signature(nonce_public: &Public, signature_r: Secret, mut signature_s: Secret) -> Signature { + // compute recvery param + let mut signature_v = { + let nonce_public_x = public_x(nonce_public); + let nonce_public_y: U256 = public_y(nonce_public).into(); + let nonce_public_y_is_odd = !(nonce_public_y % 2.into()).is_zero(); + let bit0 = if nonce_public_y_is_odd { 1u8 } else { 0u8 }; + let bit1 = if nonce_public_x != *signature_r { 2u8 } else { 0u8 }; + bit0 | bit1 + }; + + // fix high S + let curve_order = math::curve_order(); + let curve_order_half = curve_order / 2.into(); + let s_numeric: U256 = (*signature_s).into(); + if s_numeric > curve_order_half { + let signature_s_hash: H256 = (curve_order - s_numeric).into(); + signature_s = signature_s_hash.into(); + signature_v ^= 1; + } + + // serialize as [r][s]v + let mut signature = [0u8; 65]; + signature[..32].copy_from_slice(&**signature_r); + signature[32..64].copy_from_slice(&**signature_s); + signature[64] = signature_v; + + signature.into() +} + +/// Compute share of ECDSA reversed-nonce coefficient. Result of this_coeff * secret_share gives us a share of inv(nonce). +#[cfg(test)] +pub fn compute_ecdsa_inversed_secret_coeff_share(secret_share: &Secret, nonce_share: &Secret, zero_share: &Secret) -> Result { + let mut coeff = secret_share.clone(); + coeff.mul(nonce_share).unwrap(); + coeff.add(zero_share).unwrap(); + Ok(coeff) +} + +/// Compute ECDSA reversed-nonce coefficient from its shares. Result of this_coeff * secret_share gives us a share of inv(nonce). +#[cfg(test)] +pub fn compute_ecdsa_inversed_secret_coeff_from_shares(t: usize, id_numbers: &[Secret], shares: &[Secret]) -> Result { + debug_assert_eq!(shares.len(), 2 * t + 1); + debug_assert_eq!(shares.len(), id_numbers.len()); + + let u_shares = (0..2*t+1).map(|i| compute_shadow_mul(&shares[i], &id_numbers[i], id_numbers.iter().enumerate() + .filter(|&(j, _)| i != j) + .map(|(_, id)| id) + .take(2 * t))).collect::, _>>()?; + + // compute u + let u = compute_secret_sum(u_shares.iter())?; + + // compute inv(u) + let mut u_inv = u; + u_inv.inv()?; + Ok(u_inv) +} + #[cfg(test)] pub mod tests { use std::iter::once; - use ethkey::KeyPair; + use ethkey::{KeyPair, recover, verify_public}; use super::*; #[derive(Clone)] @@ -434,7 +551,27 @@ pub mod tests { joint_public: Public, } - fn run_key_generation(t: usize, n: usize, id_numbers: Option>) -> KeyGenerationArtifacts { + struct ZeroGenerationArtifacts { + polynoms1: Vec>, + secret_shares: Vec, + } + + fn prepare_polynoms1(t: usize, n: usize, secret_required: Option) -> Vec> { + let mut polynoms1: Vec<_> = (0..n).map(|_| generate_random_polynom(t).unwrap()).collect(); + // if we need specific secret to be shared, update polynoms so that sum of their free terms = required secret + if let Some(mut secret_required) = secret_required { + for polynom1 in polynoms1.iter_mut().take(n - 1) { + let secret_coeff1 = generate_random_scalar().unwrap(); + secret_required.sub(&secret_coeff1).unwrap(); + polynom1[0] = secret_coeff1; + } + + polynoms1[n - 1][0] = secret_required; + } + polynoms1 + } + + fn run_key_generation(t: usize, n: usize, id_numbers: Option>, secret_required: Option) -> KeyGenerationArtifacts { // === PART1: DKG === // data, gathered during initialization @@ -445,8 +582,9 @@ pub mod tests { }; // data, generated during keys dissemination - let polynoms1: Vec<_> = (0..n).map(|_| generate_random_polynom(t).unwrap()).collect(); + let polynoms1 = prepare_polynoms1(t, n, secret_required); let secrets1: Vec<_> = (0..n).map(|i| (0..n).map(|j| compute_polynom(&polynoms1[i], &id_numbers[j]).unwrap()).collect::>()).collect(); + // following data is used only on verification step let polynoms2: Vec<_> = (0..n).map(|_| generate_random_polynom(t).unwrap()).collect(); let secrets2: Vec<_> = (0..n).map(|i| (0..n).map(|j| compute_polynom(&polynoms2[i], &id_numbers[j]).unwrap()).collect::>()).collect(); @@ -474,6 +612,20 @@ pub mod tests { } } + fn run_zero_key_generation(t: usize, n: usize, id_numbers: &[Secret]) -> ZeroGenerationArtifacts { + // data, generated during keys dissemination + let polynoms1 = prepare_polynoms1(t, n, Some(zero_scalar())); + let secrets1: Vec<_> = (0..n).map(|i| (0..n).map(|j| compute_polynom(&polynoms1[i], &id_numbers[j]).unwrap()).collect::>()).collect(); + + // data, generated during keys generation + let secret_shares: Vec<_> = (0..n).map(|i| compute_secret_share(secrets1.iter().map(|s| &s[i])).unwrap()).collect(); + + ZeroGenerationArtifacts { + polynoms1: polynoms1, + secret_shares: secret_shares, + } + } + fn run_key_share_refreshing(old_t: usize, new_t: usize, new_n: usize, old_artifacts: &KeyGenerationArtifacts) -> KeyGenerationArtifacts { // === share refreshing protocol from // === based on "Verifiable Secret Redistribution for Threshold Sharing Schemes" @@ -528,6 +680,56 @@ pub mod tests { result } + fn run_multiplication_protocol(t: usize, secret_shares1: &[Secret], secret_shares2: &[Secret]) -> Vec { + let n = secret_shares1.len(); + assert!(t * 2 + 1 <= n); + + // shares of secrets multiplication = multiplication of secrets shares + let mul_shares: Vec<_> = (0..n).map(|i| { + let share1 = secret_shares1[i].clone(); + let share2 = secret_shares2[i].clone(); + let mut mul_share = share1; + mul_share.mul(&share2).unwrap(); + mul_share + }).collect(); + + mul_shares + } + + fn run_reciprocal_protocol(t: usize, artifacts: &KeyGenerationArtifacts) -> Vec { + // === Given a secret x mod r which is shared among n players, it is + // === required to generate shares of inv(x) mod r with out revealing + // === any information about x or inv(x). + // === https://www.researchgate.net/publication/280531698_Robust_Threshold_Elliptic_Curve_Digital_Signature + + // generate shared random secret e for given t + let n = artifacts.id_numbers.len(); + assert!(t * 2 + 1 <= n); + let e_artifacts = run_key_generation(t, n, Some(artifacts.id_numbers.clone()), None); + + // generate shares of zero for 2 * t threshold + let z_artifacts = run_zero_key_generation(2 * t, n, &artifacts.id_numbers); + + // each player computes && broadcast u[i] = x[i] * e[i] + z[i] + let ui: Vec<_> = (0..n).map(|i| compute_ecdsa_inversed_secret_coeff_share(&artifacts.secret_shares[i], + &e_artifacts.secret_shares[i], + &z_artifacts.secret_shares[i]).unwrap()).collect(); + + // players can interpolate the polynomial of degree 2t and compute u && inv(u): + let u_inv = compute_ecdsa_inversed_secret_coeff_from_shares(t, + &artifacts.id_numbers.iter().take(2*t + 1).cloned().collect::>(), + &ui.iter().take(2*t + 1).cloned().collect::>()).unwrap(); + + // each player Pi computes his share of inv(x) as e[i] * inv(u) + let x_inv_shares: Vec<_> = (0..n).map(|i| { + let mut x_inv_share = e_artifacts.secret_shares[i].clone(); + x_inv_share.mul(&u_inv).unwrap(); + x_inv_share + }).collect(); + + x_inv_shares + } + pub fn do_encryption_and_decryption(t: usize, joint_public: &Public, id_numbers: &[Secret], secret_shares: &[Secret], joint_secret: Option<&Secret>, document_secret_plain: Public) -> (Public, Public) { // === PART2: encryption using joint public key === @@ -576,7 +778,7 @@ pub mod tests { let test_cases = [(0, 2), (1, 2), (1, 3), (2, 3), (1, 4), (2, 4), (3, 4), (1, 5), (2, 5), (3, 5), (4, 5), (1, 10), (2, 10), (3, 10), (4, 10), (5, 10), (6, 10), (7, 10), (8, 10), (9, 10)]; for &(t, n) in &test_cases { - let artifacts = run_key_generation(t, n, None); + let artifacts = run_key_generation(t, n, None, None); // compute joint private key [just for test] let joint_secret = compute_joint_secret(artifacts.polynoms1.iter().map(|p| &p[0])).unwrap(); @@ -608,7 +810,7 @@ pub mod tests { } #[test] - fn full_signature_math_session() { + fn full_schnorr_signature_math_session() { let test_cases = [(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (1, 4), (2, 4), (3, 4), (1, 5), (2, 5), (3, 5), (4, 5), (1, 10), (2, 10), (3, 10), (4, 10), (5, 10), (6, 10), (7, 10), (8, 10), (9, 10)]; for &(t, n) in &test_cases { @@ -617,7 +819,7 @@ pub mod tests { // === MiDS-S algorithm === // setup: all nodes share master secret key && every node knows master public key - let artifacts = run_key_generation(t, n, None); + let artifacts = run_key_generation(t, n, None, None); // in this gap (not related to math): // master node should ask every other node if it is able to do a signing @@ -628,7 +830,7 @@ pub mod tests { // step 1: run DKG to generate one-time secret key (nonce) let id_numbers = artifacts.id_numbers.iter().cloned().take(n).collect(); - let one_time_artifacts = run_key_generation(t, n, Some(id_numbers)); + let one_time_artifacts = run_key_generation(t, n, Some(id_numbers), None); // step 2: message hash && x coordinate of one-time public value are combined let combined_hash = combine_message_hash_with_public(&message_hash, &one_time_artifacts.joint_public).unwrap(); @@ -681,12 +883,67 @@ pub mod tests { } } + #[test] + fn full_ecdsa_signature_math_session() { + let test_cases = [(2, 5), (2, 6), (3, 11), (4, 11)]; + for &(t, n) in &test_cases { + // values that can be hardcoded + let joint_secret: Secret = Random.generate().unwrap().secret().clone(); + let joint_nonce: Secret = Random.generate().unwrap().secret().clone(); + let message_hash: H256 = H256::random(); + + // convert message hash to EC scalar + let message_hash_scalar = to_scalar(message_hash.clone()).unwrap(); + + // generate secret key shares + let artifacts = run_key_generation(t, n, None, Some(joint_secret)); + + // generate nonce shares + let nonce_artifacts = run_key_generation(t, n, Some(artifacts.id_numbers.clone()), Some(joint_nonce)); + + // compute nonce public + // x coordinate (mapped to EC field) of this public is the r-portion of signature + let nonce_public_shares: Vec<_> = (0..n).map(|i| compute_public_share(&nonce_artifacts.polynoms1[i][0]).unwrap()).collect(); + let nonce_public = compute_joint_public(nonce_public_shares.iter()).unwrap(); + let signature_r = compute_ecdsa_r(&nonce_public).unwrap(); + + // compute shares of inv(nonce) so that both nonce && inv(nonce) are still unknown to all nodes + let nonce_inv_shares = run_reciprocal_protocol(t, &nonce_artifacts); + + // compute multiplication of secret-shares * inv-nonce-shares + let mul_shares = run_multiplication_protocol(t, &artifacts.secret_shares, &nonce_inv_shares); + + // compute shares for s portion of signature: nonce_inv * (message_hash + secret * signature_r) + // every node broadcasts this share + let double_t = 2 * t; + let signature_s_shares: Vec<_> = (0..double_t+1).map(|i| compute_ecdsa_s_share( + &nonce_inv_shares[i], + &mul_shares[i], + &signature_r, + &message_hash_scalar + ).unwrap()).collect(); + + // compute signature_s from received shares + let signature_s = compute_ecdsa_s(t, + &signature_s_shares, + &artifacts.id_numbers.iter().take(double_t + 1).cloned().collect::>() + ).unwrap(); + + // check signature + let signature_actual = serialize_ecdsa_signature(&nonce_public, signature_r, signature_s); + let joint_secret = compute_joint_secret(artifacts.polynoms1.iter().map(|p| &p[0])).unwrap(); + let joint_secret_pair = KeyPair::from_secret(joint_secret).unwrap(); + assert_eq!(recover(&signature_actual, &message_hash).unwrap(), *joint_secret_pair.public()); + assert!(verify_public(joint_secret_pair.public(), &signature_actual, &message_hash).unwrap()); + } + } + #[test] fn full_generation_math_session_with_refreshing_shares() { let test_cases = vec![(1, 4), (6, 10)]; for (t, n) in test_cases { // generate key using t-of-n session - let artifacts1 = run_key_generation(t, n, None); + let artifacts1 = run_key_generation(t, n, None, None); let joint_secret1 = compute_joint_secret(artifacts1.polynoms1.iter().map(|p1| &p1[0])).unwrap(); // let's say we want to refresh existing secret shares @@ -710,7 +967,7 @@ pub mod tests { let test_cases = vec![(1, 3), (1, 4), (6, 10)]; for (t, n) in test_cases { // generate key using t-of-n session - let artifacts1 = run_key_generation(t, n, None); + let artifacts1 = run_key_generation(t, n, None, None); let joint_secret1 = compute_joint_secret(artifacts1.polynoms1.iter().map(|p1| &p1[0])).unwrap(); // let's say we want to include additional couple of servers to the set @@ -733,7 +990,8 @@ pub mod tests { let (t, n) = (3, 5); // generate key using t-of-n session - let artifacts1 = run_key_generation(t, n, None); + let artifacts1 = run_key_generation(t, n, None, None); + let joint_secret1 = compute_joint_secret(artifacts1.polynoms1.iter().map(|p1| &p1[0])).unwrap(); // let's say we want to decrease threshold so that it becames (t-1)-of-n @@ -751,4 +1009,75 @@ pub mod tests { &artifacts3.id_numbers.iter().take(new_t + 1).collect::>()).unwrap(); assert_eq!(joint_secret1, joint_secret3); } + + #[test] + fn full_zero_secret_generation_math_session() { + let test_cases = vec![(1, 4), (2, 4)]; + for (t, n) in test_cases { + // run joint zero generation session + let id_numbers: Vec<_> = (0..n).map(|_| generate_random_scalar().unwrap()).collect(); + let artifacts = run_zero_key_generation(t, n, &id_numbers); + + // check that zero secret is generated + // we can't compute secrets sum here, because result will be zero (invalid secret, unsupported by SECP256k1) + // so just use complement trick: x + (-x) = 0 + // TODO [Refac]: switch to SECP256K1-free scalar EC arithmetic + let partial_joint_secret = compute_secret_sum(artifacts.polynoms1.iter().take(n - 1).map(|p| &p[0])).unwrap(); + let mut partial_joint_secret_complement = artifacts.polynoms1[n - 1][0].clone(); + partial_joint_secret_complement.neg().unwrap(); + assert_eq!(partial_joint_secret, partial_joint_secret_complement); + } + } + + #[test] + fn full_generation_with_multiplication() { + let test_cases = vec![(1, 3), (2, 5), (2, 7), (3, 8)]; + for (t, n) in test_cases { + // generate two shared secrets + let artifacts1 = run_key_generation(t, n, None, None); + let artifacts2 = run_key_generation(t, n, Some(artifacts1.id_numbers.clone()), None); + + // multiplicate original secrets + let joint_secret1 = compute_joint_secret(artifacts1.polynoms1.iter().map(|p| &p[0])).unwrap(); + let joint_secret2 = compute_joint_secret(artifacts2.polynoms1.iter().map(|p| &p[0])).unwrap(); + let mut expected_joint_secret_mul = joint_secret1; + expected_joint_secret_mul.mul(&joint_secret2).unwrap(); + + // run multiplication protocol + let joint_secret_mul_shares = run_multiplication_protocol(t, &artifacts1.secret_shares, &artifacts2.secret_shares); + + // calculate actual secrets multiplication + let double_t = t * 2; + let actual_joint_secret_mul = compute_joint_secret_from_shares(double_t, + &joint_secret_mul_shares.iter().take(double_t + 1).collect::>(), + &artifacts1.id_numbers.iter().take(double_t + 1).collect::>()).unwrap(); + + assert_eq!(actual_joint_secret_mul, expected_joint_secret_mul); + } + } + + #[test] + fn full_generation_with_reciprocal() { + let test_cases = vec![(1, 3), (2, 5), (2, 7), (2, 7), (3, 8)]; + for (t, n) in test_cases { + // generate shared secret + let artifacts = run_key_generation(t, n, None, None); + + // calculate inversion of original shared secret + let joint_secret = compute_joint_secret(artifacts.polynoms1.iter().map(|p| &p[0])).unwrap(); + let mut expected_joint_secret_inv = joint_secret.clone(); + expected_joint_secret_inv.inv().unwrap(); + + // run inversion protocol + let reciprocal_shares = run_reciprocal_protocol(t, &artifacts); + + // calculate actual secret inversion + let double_t = t * 2; + let actual_joint_secret_inv = compute_joint_secret_from_shares(double_t, + &reciprocal_shares.iter().take(double_t + 1).collect::>(), + &artifacts.id_numbers.iter().take(double_t + 1).collect::>()).unwrap(); + + assert_eq!(actual_joint_secret_inv, expected_joint_secret_inv); + } + } }