use num_bigint::{BigInt, BigUint, ToBigInt};
use num_integer::Integer;
use num_traits::identities::{One, Zero};
use sha2::{Digest, Sha256};
use std::collections::{BTreeMap, HashMap};
use std::sync::Arc;
use crate::dleq::DLEQ;
use crate::group::Group;
use crate::groups::ModpGroup;
use crate::polynomial::Polynomial;
use crate::sharebox::{DistributionSharesBox, ShareBox};
use crate::groups::Secp256k1Group;
use k256::elliptic_curve::FieldBytes;
use k256::elliptic_curve::ff::PrimeField;
use k256::{AffinePoint, Scalar};
use crate::groups::Ristretto255Group;
use curve25519_dalek::ristretto::RistrettoPoint;
use curve25519_dalek::scalar::Scalar as RistrettoScalar;
#[derive(Debug)]
pub struct Participant<G: Group> {
group: Arc<G>,
pub privatekey: G::Scalar,
pub publickey: G::Element,
}
impl<G: Group> Clone for Participant<G>
where
G::Scalar: Clone,
G::Element: Clone,
{
fn clone(&self) -> Self {
Participant {
group: Arc::clone(&self.group),
privatekey: self.privatekey.clone(),
publickey: self.publickey.clone(),
}
}
}
impl<G: Group> Participant<G> {
pub fn with_arc(group: Arc<G>) -> Self
where
G::Scalar: Default,
G::Element: Default,
{
Participant {
group,
privatekey: Default::default(),
publickey: Default::default(),
}
}
pub fn new(group: G) -> Self
where
G::Scalar: Default,
G::Element: Default,
{
Participant {
group: Arc::new(group),
privatekey: Default::default(),
publickey: Default::default(),
}
}
pub fn initialize(&mut self)
where
G::Scalar: Default,
G::Element: Default,
{
self.privatekey = self.group.generate_private_key();
self.publickey = self.group.generate_public_key(&self.privatekey);
}
}
impl Participant<ModpGroup> {
pub fn distribute_secret(
&mut self,
secret: &BigInt,
publickeys: &[BigInt],
threshold: u32,
) -> DistributionSharesBox<ModpGroup> {
assert!(threshold <= publickeys.len() as u32);
let subgroup_gen = self.group.subgroup_generator();
let main_gen = self.group.generator();
let group_order = self.group.order();
let mut polynomial = Polynomial::new();
polynomial.init((threshold - 1) as i32, group_order);
let mut commitments: Vec<BigInt> = Vec::new();
let mut positions: HashMap<Vec<u8>, i64> = HashMap::new();
let mut x: HashMap<Vec<u8>, BigInt> = HashMap::new();
let mut shares: HashMap<Vec<u8>, BigInt> = HashMap::new();
let mut challenge_hasher = Sha256::new();
let mut sampling_points: HashMap<Vec<u8>, BigInt> = HashMap::new();
let mut dleq_w: HashMap<Vec<u8>, BigInt> = HashMap::new();
let mut position: i64 = 1;
for j in 0..threshold {
let coeff = &polynomial.coefficients[j as usize];
let commitment = self.group.exp(&subgroup_gen, coeff);
commitments.push(commitment);
}
for pubkey in publickeys {
let pubkey_bytes = self.group.element_to_bytes(pubkey);
positions.insert(pubkey_bytes.clone(), position);
let pos_scalar = &BigInt::from(position);
let secret_share = polynomial.get_value(pos_scalar) % group_order;
sampling_points.insert(pubkey_bytes.clone(), secret_share.clone());
let mut x_val = self.group.identity();
let mut exponent = BigInt::one();
for j in 0..threshold {
let c_j_pow =
self.group.exp(&commitments[j as usize], &exponent);
x_val = self.group.mul(&x_val, &c_j_pow);
exponent =
self.group.scalar_mul(&exponent, pos_scalar) % group_order;
}
x.insert(pubkey_bytes.clone(), x_val.clone());
let encrypted_secret_share = self.group.exp(pubkey, &secret_share);
shares.insert(pubkey_bytes.clone(), encrypted_secret_share.clone());
let witness = self.group.generate_private_key();
let mut dleq = DLEQ::new(self.group.clone());
dleq.init(
subgroup_gen.clone(),
x_val.clone(),
pubkey.clone(),
encrypted_secret_share.clone(),
secret_share.clone(),
witness.clone(),
);
dleq_w.insert(pubkey_bytes.clone(), witness);
let a1 = dleq.get_a1();
let a2 = dleq.get_a2();
DLEQ::<ModpGroup>::append_transcript_hash(
self.group.as_ref(),
&x_val,
&encrypted_secret_share,
&a1,
&a2,
&mut challenge_hasher,
);
position += 1;
}
let challenge_hash = challenge_hasher.finalize();
let challenge = self.group.hash_to_scalar(&challenge_hash);
let mut responses: HashMap<Vec<u8>, BigInt> = HashMap::new();
for pubkey in publickeys {
let pubkey_bytes = self.group.element_to_bytes(pubkey);
let alpha = sampling_points.get(&pubkey_bytes).unwrap();
let w_i = dleq_w.get(&pubkey_bytes).unwrap();
let alpha_c =
self.group.scalar_mul(alpha, &challenge) % group_order;
let response = self.group.scalar_sub(w_i, &alpha_c) % group_order;
responses.insert(pubkey_bytes, response);
}
let s = polynomial.get_value(&BigInt::zero()) % group_order;
let g_s = self.group.exp(&main_gen, &s);
let sha256_hash = Sha256::digest(self.group.element_to_bytes(&g_s));
let hash_biguint = BigUint::from_bytes_be(&sha256_hash[..])
.mod_floor(&self.group.modulus().to_biguint().unwrap());
let u = secret.to_biguint().unwrap() ^ hash_biguint;
let mut shares_box = DistributionSharesBox::new();
shares_box.init(
&commitments,
positions,
shares,
publickeys,
&challenge,
responses,
&u.to_bigint().unwrap(),
);
shares_box
}
pub fn extract_secret_share(
&self,
shares_box: &DistributionSharesBox<ModpGroup>,
private_key: &BigInt,
w: &BigInt,
) -> Option<ShareBox<ModpGroup>> {
use crate::util::Util;
let main_gen = self.group.generator();
let group_order = self.group.order();
let public_key = self.group.generate_public_key(private_key);
let pubkey_bytes = self.group.element_to_bytes(&public_key);
let encrypted_secret_share = shares_box.shares.get(&pubkey_bytes)?;
let privkey_inverse = Util::mod_inverse(private_key, group_order)?;
let decrypted_share =
self.group.exp(encrypted_secret_share, &privkey_inverse);
let mut dleq = DLEQ::new(self.group.clone());
dleq.init(
main_gen.clone(),
public_key.clone(),
decrypted_share.clone(),
encrypted_secret_share.clone(),
private_key.clone(),
w.clone(),
);
let mut challenge_hasher = Sha256::new();
let a1 = dleq.get_a1();
let a2 = dleq.get_a2();
DLEQ::<ModpGroup>::append_transcript_hash(
self.group.as_ref(),
&public_key,
encrypted_secret_share,
&a1,
&a2,
&mut challenge_hasher,
);
let challenge_hash = challenge_hasher.finalize();
let challenge = self.group.hash_to_scalar(&challenge_hash);
dleq.c = Some(challenge.clone());
let response = dleq.get_r()?;
let mut share_box = ShareBox::new();
share_box.init(public_key, decrypted_share, challenge, response);
Some(share_box)
}
pub fn verify_share(
&self,
sharebox: &ShareBox<ModpGroup>,
distribution_sharebox: &DistributionSharesBox<ModpGroup>,
publickey: &BigInt,
) -> bool {
let main_gen = self.group.generator();
let pubkey_bytes = self.group.element_to_bytes(publickey);
let encrypted_share =
match distribution_sharebox.shares.get(&pubkey_bytes) {
Some(s) => s,
None => return false,
};
let mut dleq = DLEQ::<ModpGroup>::new(self.group.clone());
dleq.g1 = main_gen;
dleq.h1 = publickey.clone();
dleq.g2 = sharebox.share.clone();
dleq.h2 = encrypted_share.clone();
dleq.c = Some(sharebox.challenge.clone());
dleq.r = Some(sharebox.response.clone());
dleq.verify()
}
pub fn verify_distribution_shares(
&self,
distribute_sharesbox: &DistributionSharesBox<ModpGroup>,
) -> bool {
let subgroup_gen = self.group.subgroup_generator();
let group_order = self.group.order();
let mut challenge_hasher = Sha256::new();
for publickey in &distribute_sharesbox.publickeys {
let pubkey_bytes = self.group.element_to_bytes(publickey);
let position = distribute_sharesbox.positions.get(&pubkey_bytes);
let response = distribute_sharesbox.responses.get(&pubkey_bytes);
let encrypted_share =
distribute_sharesbox.shares.get(&pubkey_bytes);
if position.is_none()
|| response.is_none()
|| encrypted_share.is_none()
{
return false;
}
let mut x_val = self.group.identity();
let mut exponent = BigInt::one();
for j in 0..distribute_sharesbox.commitments.len() {
let c_j_pow = self
.group
.exp(&distribute_sharesbox.commitments[j], &exponent);
x_val = self.group.mul(&x_val, &c_j_pow);
exponent = self
.group
.scalar_mul(&exponent, &BigInt::from(*position.unwrap()))
% group_order;
}
let _ = DLEQ::<ModpGroup>::verifier_update_hash(
self.group.as_ref(),
&subgroup_gen,
&x_val,
publickey,
encrypted_share.unwrap(),
response.unwrap(),
&distribute_sharesbox.challenge,
&mut challenge_hasher,
);
}
let challenge_hash = challenge_hasher.finalize();
let computed_challenge = self.group.hash_to_scalar(&challenge_hash);
computed_challenge == distribute_sharesbox.challenge
}
pub fn reconstruct(
&self,
share_boxes: &[ShareBox<ModpGroup>],
distribute_share_box: &DistributionSharesBox<ModpGroup>,
) -> Option<BigInt> {
use rayon::prelude::*;
if share_boxes.len() < distribute_share_box.commitments.len() {
return None;
}
let subgroup_order = self.group.subgroup_order();
let mut shares: BTreeMap<i64, BigInt> = BTreeMap::new();
for share_box in share_boxes.iter() {
let pubkey_bytes =
self.group.element_to_bytes(&share_box.publickey);
let position = distribute_share_box.positions.get(&pubkey_bytes)?;
shares.insert(*position, share_box.share.clone());
}
let mut secret = self.group.identity();
let values: Vec<i64> = shares.keys().copied().collect();
let shares_vec: Vec<(i64, BigInt)> = shares.into_iter().collect();
let shares_slice = shares_vec.as_slice();
let factor_options: Vec<Option<BigInt>> = shares_slice
.par_iter()
.map(|(position, share)| {
self.compute_lagrange_factor(
*position,
share,
&values,
subgroup_order,
)
})
.collect();
let mut factors: Vec<BigInt> = Vec::with_capacity(factor_options.len());
for factor in factor_options {
factors.push(factor?);
}
secret = factors
.into_iter()
.fold(secret, |acc, factor| self.group.mul(&acc, &factor));
let secret_hash = Sha256::digest(self.group.element_to_bytes(&secret));
let hash_biguint = BigUint::from_bytes_be(&secret_hash[..])
.mod_floor(&self.group.modulus().to_biguint().unwrap());
let decrypted_secret =
hash_biguint ^ distribute_share_box.U.to_biguint().unwrap();
Some(decrypted_secret.to_bigint().unwrap())
}
fn compute_lagrange_factor(
&self,
position: i64,
share: &BigInt,
values: &[i64],
subgroup_order: &BigInt,
) -> Option<BigInt> {
use crate::util::Util;
let lagrange_coefficient =
Util::lagrange_coefficient(&position, values);
let is_negative = lagrange_coefficient.0.clone()
* lagrange_coefficient.1.clone()
< BigInt::zero();
let mut numerator = Util::abs(&lagrange_coefficient.0);
let mut denominator = Util::abs(&lagrange_coefficient.1);
let gcd = numerator.gcd(&denominator);
numerator /= &gcd;
denominator /= &gcd;
let denominator_inverse =
Util::mod_inverse(&denominator, subgroup_order)?;
let exponent =
(numerator * denominator_inverse).mod_floor(subgroup_order);
let mut factor = self.group.exp(share, &exponent);
if is_negative {
factor = self.group.element_inverse(&factor)?;
}
Some(factor)
}
}
pub type ModpParticipant = Participant<ModpGroup>;
#[cfg(test)]
mod tests {
use super::*;
use crate::groups::ModpGroup;
use crate::participant::Participant;
use num_bigint::RandBigInt;
#[test]
fn test_generic_modp_participant_new() {
let group = ModpGroup::new();
let participant = Participant::with_arc(group);
assert_eq!(participant.publickey, Default::default());
}
#[test]
fn test_generic_modp_participant_initialize() {
let group = ModpGroup::new();
let mut participant = Participant::with_arc(group);
participant.initialize();
let _ = &participant.privatekey;
let _ = &participant.publickey;
}
#[test]
fn test_end_to_end_modp() {
use num_bigint::{BigUint, ToBigInt};
let group = ModpGroup::new();
let mut dealer = Participant::with_arc(group.clone());
dealer.initialize();
let mut p1 = Participant::with_arc(group.clone());
let mut p2 = Participant::with_arc(group.clone());
let mut p3 = Participant::with_arc(group.clone());
p1.initialize();
p2.initialize();
p3.initialize();
let secret_message = String::from("Hello MPVSS End-to-End Test!");
let secret = BigUint::from_bytes_be(secret_message.as_bytes());
let publickeys = vec![
p1.publickey.clone(),
p2.publickey.clone(),
p3.publickey.clone(),
];
let threshold = 3;
let dist_box = dealer.distribute_secret(
&secret.to_bigint().unwrap(),
&publickeys,
threshold,
);
let verified_by_p1 = dealer.verify_distribution_shares(&dist_box);
let verified_by_p2 = dealer.verify_distribution_shares(&dist_box);
let verified_by_p3 = dealer.verify_distribution_shares(&dist_box);
assert!(verified_by_p1, "P1 should verify distribution as valid");
assert!(verified_by_p2, "P2 should verify distribution as valid");
assert!(verified_by_p3, "P3 should verify distribution as valid");
assert_eq!(dist_box.publickeys.len(), 3, "Should have 3 public keys");
assert_eq!(dist_box.commitments.len(), 3, "Should have 3 commitments");
assert_eq!(dist_box.shares.len(), 3, "Should have 3 shares");
assert_ne!(dist_box.U, BigInt::zero(), "U should not be zero");
let mut rng = rand::thread_rng();
let w: BigInt = rng
.gen_biguint_below(&group.modulus().to_biguint().unwrap())
.to_bigint()
.unwrap();
let s1 = p1
.extract_secret_share(&dist_box, &p1.privatekey, &w)
.unwrap();
let s2 = p2
.extract_secret_share(&dist_box, &p2.privatekey, &w)
.unwrap();
let s3 = p3
.extract_secret_share(&dist_box, &p3.privatekey, &w)
.unwrap();
assert_eq!(s1.publickey, p1.publickey, "P1 publickey should match");
assert_ne!(s1.share, BigInt::zero(), "P1 share should not be zero");
assert_eq!(s2.publickey, p2.publickey, "P2 publickey should match");
assert_ne!(s2.share, BigInt::zero(), "P2 share should not be zero");
assert_eq!(s3.publickey, p3.publickey, "P3 publickey should match");
assert_ne!(s3.share, BigInt::zero(), "P3 share should not be zero");
let p1_verifies_s2 = dealer.verify_share(&s2, &dist_box, &p2.publickey);
let p1_verifies_s3 = dealer.verify_share(&s3, &dist_box, &p3.publickey);
assert!(p1_verifies_s2, "P1 should verify P2's share as valid");
assert!(p1_verifies_s3, "P1 should verify P3's share as valid");
let p2_verifies_s1 = dealer.verify_share(&s1, &dist_box, &p1.publickey);
let p2_verifies_s3 = dealer.verify_share(&s3, &dist_box, &p3.publickey);
assert!(p2_verifies_s1, "P2 should verify P1's share as valid");
assert!(p2_verifies_s3, "P2 should verify P3's share as valid");
let p3_verifies_s1 = dealer.verify_share(&s1, &dist_box, &p1.publickey);
let p3_verifies_s2 = dealer.verify_share(&s2, &dist_box, &p2.publickey);
assert!(p3_verifies_s1, "P3 should verify P1's share as valid");
assert!(p3_verifies_s2, "P3 should verify P2's share as valid");
let shares = vec![s1, s2, s3];
let reconstructed = dealer.reconstruct(&shares, &dist_box).unwrap();
let reconstructed_message = String::from_utf8(
reconstructed.to_biguint().unwrap().to_bytes_be(),
)
.unwrap();
assert_eq!(
reconstructed_message, secret_message,
"Reconstructed message should match original"
);
}
#[test]
fn test_threshold_subset_modp_positions_1_and_3() {
use num_bigint::{BigUint, ToBigInt};
let group = ModpGroup::new();
let mut dealer = Participant::with_arc(group.clone());
dealer.initialize();
let mut p1 = Participant::with_arc(group.clone());
let mut p2 = Participant::with_arc(group.clone());
let mut p3 = Participant::with_arc(group.clone());
p1.initialize();
p2.initialize();
p3.initialize();
let secret = BigUint::from(123456u32).to_bigint().unwrap();
let publickeys = vec![
p1.publickey.clone(),
p2.publickey.clone(),
p3.publickey.clone(),
];
let dist_box = dealer.distribute_secret(&secret, &publickeys, 2);
let mut rng = rand::thread_rng();
let w: BigInt = rng
.gen_biguint_below(&group.modulus().to_biguint().unwrap())
.to_bigint()
.unwrap();
let s1 = p1
.extract_secret_share(&dist_box, &p1.privatekey, &w)
.unwrap();
let s3 = p3
.extract_secret_share(&dist_box, &p3.privatekey, &w)
.unwrap();
let reconstructed = dealer.reconstruct(&[s1, s3], &dist_box).unwrap();
assert_eq!(
reconstructed, secret,
"Threshold-2 reconstruction from positions 1 and 3 should recover original secret"
);
}
#[test]
fn test_end_to_end_secp256k1() {
use num_bigint::{BigUint, ToBigInt};
let group = Secp256k1Group::new();
let mut dealer = Participant::with_arc(group.clone());
dealer.initialize();
let mut p1 = Participant::with_arc(group.clone());
let mut p2 = Participant::with_arc(group.clone());
let mut p3 = Participant::with_arc(group.clone());
p1.initialize();
p2.initialize();
p3.initialize();
let secret_message = String::from("Hello secp256k1 PVSS!");
let secret = BigUint::from_bytes_be(secret_message.as_bytes());
let publickeys: Vec<k256::AffinePoint> = vec![
p1.publickey.clone(),
p2.publickey.clone(),
p3.publickey.clone(),
];
let threshold = 3;
let dist_box = dealer.distribute_secret(
&secret.to_bigint().unwrap(),
&publickeys,
threshold,
);
assert!(
dealer.verify_distribution_shares(&dist_box),
"Distribution should be valid"
);
let w = group.generate_private_key();
let s1 = p1
.extract_secret_share(&dist_box, &p1.privatekey, &w)
.unwrap();
let s2 = p2
.extract_secret_share(&dist_box, &p2.privatekey, &w)
.unwrap();
let s3 = p3
.extract_secret_share(&dist_box, &p3.privatekey, &w)
.unwrap();
assert!(
dealer.verify_share(&s1, &dist_box, &p1.publickey),
"P1's share should be valid"
);
assert!(
dealer.verify_share(&s3, &dist_box, &p3.publickey),
"P3's share should be valid"
);
let shares = vec![s1, s2, s3];
let reconstructed = dealer.reconstruct(&shares, &dist_box).unwrap();
let reconstructed_message = String::from_utf8(
reconstructed.to_biguint().unwrap().to_bytes_be(),
)
.unwrap();
assert_eq!(
reconstructed_message, secret_message,
"Reconstructed message should match original"
);
}
#[test]
fn test_threshold_secp256k1() {
use num_bigint::{BigUint, ToBigInt};
let group = Secp256k1Group::new();
let mut dealer = Participant::with_arc(group.clone());
dealer.initialize();
let mut p1 = Participant::with_arc(group.clone());
let mut p2 = Participant::with_arc(group.clone());
let mut p3 = Participant::with_arc(group.clone());
let mut p4 = Participant::with_arc(group.clone());
let mut p5 = Participant::with_arc(group.clone());
p1.initialize();
p2.initialize();
p3.initialize();
p4.initialize();
p5.initialize();
let secret_message = String::from("Threshold test secp256k1!");
let secret = BigUint::from_bytes_be(secret_message.as_bytes());
let publickeys: Vec<k256::AffinePoint> = vec![
p1.publickey.clone(),
p2.publickey.clone(),
p3.publickey.clone(),
p4.publickey.clone(),
p5.publickey.clone(),
];
let threshold = 3;
let dist_box = dealer.distribute_secret(
&secret.to_bigint().unwrap(),
&publickeys,
threshold,
);
assert!(
dealer.verify_distribution_shares(&dist_box),
"Distribution should be valid"
);
let w = group.generate_private_key();
let s1 = p1
.extract_secret_share(&dist_box, &p1.privatekey, &w)
.unwrap();
let s3 = p3
.extract_secret_share(&dist_box, &p3.privatekey, &w)
.unwrap();
let s5 = p5
.extract_secret_share(&dist_box, &p5.privatekey, &w)
.unwrap();
let shares = vec![s1, s3, s5];
let reconstructed = dealer.reconstruct(&shares, &dist_box).unwrap();
let reconstructed_message = String::from_utf8(
reconstructed.to_biguint().unwrap().to_bytes_be(),
)
.unwrap();
assert_eq!(
reconstructed_message, secret_message,
"Reconstructed message should match original"
);
}
#[test]
fn test_scalar_arithmetic_secp256k1() {
use num_bigint::BigInt;
let group = Secp256k1Group::new();
let a_bigint = BigInt::from(5u32);
let b_bigint = BigInt::from(7u32);
let s_bigint = &a_bigint + &b_bigint;
let a = Scalar::from_repr({
let mut fb = FieldBytes::<k256::Secp256k1>::default();
let b = a_bigint.to_bytes_be().1;
if b.len() < 32 {
fb[32 - b.len()..].copy_from_slice(&b);
} else {
fb.copy_from_slice(&b[..32]);
}
fb.into()
})
.unwrap();
let b = Scalar::from_repr({
let mut fb = FieldBytes::<k256::Secp256k1>::default();
let b = b_bigint.to_bytes_be().1;
if b.len() < 32 {
fb[32 - b.len()..].copy_from_slice(&b);
} else {
fb.copy_from_slice(&b[..32]);
}
fb.into()
})
.unwrap();
let s = Scalar::from_repr({
let mut fb = FieldBytes::<k256::Secp256k1>::default();
let b = s_bigint.to_bytes_be().1;
if b.len() < 32 {
fb[32 - b.len()..].copy_from_slice(&b);
} else {
fb.copy_from_slice(&b[..32]);
}
fb.into()
})
.unwrap();
let g = group.generator();
let a_times_g = group.exp(&g, &a);
let b_times_g = group.exp(&g, &b);
let s_times_g = group.exp(&g, &s);
let sum_ab_g = group.mul(&a_times_g, &b_times_g);
assert_eq!(
sum_ab_g, s_times_g,
"Scalar arithmetic: (a+b)*g should equal a*g + b*g"
);
}
#[test]
fn test_dleq_basic_secp256k1() {
let group = Secp256k1Group::new();
let mut dealer = Participant::with_arc(group.clone());
dealer.initialize();
let alpha = group.generate_private_key();
let w = group.generate_private_key();
let g1 = group.generator();
let h1 = group.exp(&g1, &alpha);
let mut p2 = Participant::with_arc(group.clone());
p2.initialize();
let g2 = p2.publickey;
let h2 = group.exp(&g2, &alpha);
let mut dleq = DLEQ::new(group.clone());
dleq.init(g1, h1, g2, h2, alpha, w);
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
dleq.update_hash(&mut hasher);
let hash = hasher.finalize();
let challenge = group.hash_to_scalar(&hash);
dleq.c = Some(challenge.clone());
let response = dleq.get_r().unwrap();
dleq.r = Some(response);
assert!(dleq.verify(), "Basic DLEQ proof should verify");
}
#[test]
fn test_dleq_proofs_secp256k1() {
use num_bigint::{BigUint, ToBigInt};
let group = Secp256k1Group::new();
let mut dealer = Participant::with_arc(group.clone());
dealer.initialize();
let mut p1 = Participant::with_arc(group.clone());
let mut p2 = Participant::with_arc(group.clone());
p1.initialize();
p2.initialize();
let secret = BigUint::from_bytes_be(b"DLEQ test secp256k1");
let publickeys: Vec<k256::AffinePoint> =
vec![p1.publickey.clone(), p2.publickey.clone()];
let threshold = 2;
let dist_box = dealer.distribute_secret(
&secret.to_bigint().unwrap(),
&publickeys,
threshold,
);
assert!(
dealer.verify_distribution_shares(&dist_box),
"Distribution DLEQ proofs should be valid"
);
let w = group.generate_private_key();
let s1 = p1
.extract_secret_share(&dist_box, &p1.privatekey, &w)
.unwrap();
let s2 = p2
.extract_secret_share(&dist_box, &p2.privatekey, &w)
.unwrap();
assert!(
dealer.verify_share(&s1, &dist_box, &p1.publickey),
"P1's DLEQ proof should be valid"
);
assert!(
dealer.verify_share(&s2, &dist_box, &p2.publickey),
"P2's DLEQ proof should be valid"
);
}
}
impl Participant<Secp256k1Group> {
pub fn distribute_secret(
&mut self,
secret: &BigInt,
publickeys: &[AffinePoint],
threshold: u32,
) -> DistributionSharesBox<Secp256k1Group> {
assert!(threshold <= publickeys.len() as u32);
let subgroup_gen = self.group.subgroup_generator();
let main_gen = self.group.generator();
let _group_order = self.group.order();
let mut polynomial = Polynomial::new();
let group_order_bigint = self.group.order_as_bigint().clone();
polynomial.init((threshold - 1) as i32, &group_order_bigint);
let mut commitments: Vec<AffinePoint> = Vec::new();
let mut positions: std::collections::HashMap<Vec<u8>, i64> =
std::collections::HashMap::new();
let mut shares: std::collections::HashMap<Vec<u8>, AffinePoint> =
std::collections::HashMap::new();
let mut challenge_hasher = Sha256::new();
let mut sampling_points: std::collections::HashMap<Vec<u8>, Scalar> =
std::collections::HashMap::new();
let mut dleq_w: std::collections::HashMap<Vec<u8>, Scalar> =
std::collections::HashMap::new();
let mut position: i64 = 1;
for j in 0..threshold {
let coeff_bigint = &polynomial.coefficients[j as usize];
let coeff_bytes = coeff_bigint.to_bytes_be().1;
let mut field_bytes = FieldBytes::<k256::Secp256k1>::default();
if coeff_bytes.len() < 32 {
field_bytes[32 - coeff_bytes.len()..]
.copy_from_slice(&coeff_bytes);
} else {
field_bytes.copy_from_slice(&coeff_bytes[..32]);
}
let coeff = Scalar::from_repr(field_bytes).unwrap();
let commitment = self.group.exp(&subgroup_gen, &coeff);
commitments.push(commitment);
}
for pubkey in publickeys.iter() {
let pubkey_bytes = self.group.element_to_bytes(pubkey);
positions.insert(pubkey_bytes.clone(), position);
let pos_scalar = BigInt::from(position);
let secret_share_bigint = polynomial.get_value(&pos_scalar);
let secret_share_mod = &secret_share_bigint % &group_order_bigint;
let secret_share_bytes = secret_share_mod.to_bytes_be().1;
let mut field_bytes = FieldBytes::<k256::Secp256k1>::default();
if secret_share_bytes.len() < 32 {
field_bytes[32 - secret_share_bytes.len()..]
.copy_from_slice(&secret_share_bytes);
} else {
field_bytes.copy_from_slice(&secret_share_bytes[..32]);
}
let secret_share = Scalar::from_repr(field_bytes).unwrap();
sampling_points.insert(pubkey_bytes.clone(), secret_share);
let witness = self.group.generate_private_key();
dleq_w.insert(pubkey_bytes.clone(), witness);
let mut x_val = self.group.identity();
let mut exponent = Scalar::ONE;
for j in 0..threshold {
let c_j_pow =
self.group.exp(&commitments[j as usize], &exponent);
x_val = self.group.mul(&x_val, &c_j_pow);
let pos_scalar = Scalar::from(position as u64);
exponent = self.group.scalar_mul(&exponent, &pos_scalar);
}
let encrypted_secret_share = self.group.exp(pubkey, &secret_share);
shares.insert(pubkey_bytes.clone(), encrypted_secret_share);
let mut dleq = DLEQ::new(self.group.clone());
dleq.init(
subgroup_gen,
x_val,
*pubkey,
encrypted_secret_share,
secret_share,
witness,
);
let a1 = dleq.get_a1();
let a2 = dleq.get_a2();
DLEQ::<Secp256k1Group>::append_transcript_hash(
self.group.as_ref(),
&x_val,
&encrypted_secret_share,
&a1,
&a2,
&mut challenge_hasher,
);
position += 1;
}
let challenge_hash = challenge_hasher.finalize();
let challenge = self.group.hash_to_scalar(&challenge_hash);
let mut responses: std::collections::HashMap<Vec<u8>, Scalar> =
std::collections::HashMap::new();
for pubkey in publickeys {
let pubkey_bytes = self.group.element_to_bytes(pubkey);
let alpha = sampling_points.get(&pubkey_bytes).unwrap();
let alpha_c = self.group.scalar_mul(alpha, &challenge);
let w_i = dleq_w.get(&pubkey_bytes).unwrap();
let response = self.group.scalar_sub(w_i, &alpha_c);
responses.insert(pubkey_bytes, response);
}
let s_bigint = polynomial.get_value(&BigInt::zero());
let s_bytes = s_bigint.to_bytes_be().1;
let mut field_bytes = FieldBytes::<k256::Secp256k1>::default();
if s_bytes.len() < 32 {
field_bytes[32 - s_bytes.len()..].copy_from_slice(&s_bytes);
} else {
field_bytes.copy_from_slice(&s_bytes[s_bytes.len() - 32..]);
}
let s = Scalar::from_repr(field_bytes).unwrap();
let g_s = self.group.exp(&main_gen, &s);
let sha256_hash = Sha256::digest(self.group.element_to_bytes(&g_s));
let mut field_bytes2 = FieldBytes::<k256::Secp256k1>::default();
let hash_len = sha256_hash.len().min(field_bytes2.len());
field_bytes2[32 - hash_len..].copy_from_slice(&sha256_hash[..hash_len]);
let hash_scalar = Scalar::from_repr(field_bytes2).unwrap();
let hash_bytes = hash_scalar.to_bytes();
let hash_biguint = BigUint::from_bytes_be(&hash_bytes);
let curve_order_bigint = BigUint::from_bytes_be(
&self.group.order_as_bigint().to_bytes_be().1,
);
let hash_reduced = hash_biguint % curve_order_bigint;
let u = secret.to_biguint().unwrap() ^ hash_reduced;
let mut shares_box = DistributionSharesBox::new();
shares_box.init(
&commitments,
positions,
shares,
publickeys,
&challenge,
responses,
&u.to_bigint().unwrap(),
);
shares_box
}
pub fn extract_secret_share(
&self,
shares_box: &DistributionSharesBox<Secp256k1Group>,
private_key: &Scalar,
w: &Scalar,
) -> Option<ShareBox<Secp256k1Group>> {
let main_gen = self.group.generator();
let public_key = self.group.generate_public_key(private_key);
let public_key_bytes = self.group.element_to_bytes(&public_key);
let encrypted_secret_share =
shares_box.shares.get(&public_key_bytes)?;
let privkey_inverse = self.group.scalar_inverse(private_key)?;
let decrypted_share =
self.group.exp(encrypted_secret_share, &privkey_inverse);
let mut dleq = DLEQ::new(self.group.clone());
dleq.init(
main_gen,
public_key,
decrypted_share,
*encrypted_secret_share,
*private_key,
*w,
);
let mut challenge_hasher = Sha256::new();
let a1 = dleq.get_a1();
let a2 = dleq.get_a2();
DLEQ::<Secp256k1Group>::append_transcript_hash(
self.group.as_ref(),
&public_key,
encrypted_secret_share,
&a1,
&a2,
&mut challenge_hasher,
);
let challenge_hash = challenge_hasher.finalize();
let challenge = self.group.hash_to_scalar(&challenge_hash);
dleq.c = Some(challenge);
let response = dleq.get_r()?;
let mut share_box = ShareBox::new();
share_box.init(public_key, decrypted_share, challenge, response);
Some(share_box)
}
pub fn verify_share(
&self,
sharebox: &ShareBox<Secp256k1Group>,
distribution_sharebox: &DistributionSharesBox<Secp256k1Group>,
publickey: &AffinePoint,
) -> bool {
let main_gen = self.group.generator();
let publickey_bytes = self.group.element_to_bytes(publickey);
let encrypted_share =
match distribution_sharebox.shares.get(&publickey_bytes) {
Some(s) => s,
None => return false,
};
let mut dleq = DLEQ::<Secp256k1Group>::new(self.group.clone());
dleq.g1 = main_gen;
dleq.h1 = *publickey;
dleq.g2 = sharebox.share;
dleq.h2 = *encrypted_share;
dleq.c = Some(sharebox.challenge);
dleq.r = Some(sharebox.response);
dleq.verify()
}
pub fn verify_distribution_shares(
&self,
distribute_sharesbox: &DistributionSharesBox<Secp256k1Group>,
) -> bool {
let subgroup_gen = self.group.subgroup_generator();
let mut challenge_hasher = Sha256::new();
for publickey in distribute_sharesbox.publickeys.iter() {
let publickey_bytes = self.group.element_to_bytes(publickey);
let position = distribute_sharesbox.positions.get(&publickey_bytes);
let response = distribute_sharesbox.responses.get(&publickey_bytes);
let encrypted_share =
distribute_sharesbox.shares.get(&publickey_bytes);
if position.is_none()
|| response.is_none()
|| encrypted_share.is_none()
{
return false;
}
let position = *position.unwrap();
let response = response.unwrap();
let encrypted_share = encrypted_share.unwrap();
let mut x_val = self.group.identity();
let mut exponent = Scalar::ONE;
for j in 0..distribute_sharesbox.commitments.len() {
let c_j_pow = self
.group
.exp(&distribute_sharesbox.commitments[j], &exponent);
x_val = self.group.mul(&x_val, &c_j_pow);
let pos_scalar = Scalar::from(position as u64);
exponent = self.group.scalar_mul(&exponent, &pos_scalar);
}
let _ = DLEQ::<Secp256k1Group>::verifier_update_hash(
self.group.as_ref(),
&subgroup_gen,
&x_val,
publickey,
encrypted_share,
response,
&distribute_sharesbox.challenge,
&mut challenge_hasher,
);
}
let challenge_hash = challenge_hasher.finalize();
let computed_challenge = self.group.hash_to_scalar(&challenge_hash);
computed_challenge == distribute_sharesbox.challenge
}
pub fn reconstruct(
&self,
share_boxes: &[ShareBox<Secp256k1Group>],
distribute_share_box: &DistributionSharesBox<Secp256k1Group>,
) -> Option<BigInt> {
use rayon::prelude::*;
if share_boxes.len() < distribute_share_box.commitments.len() {
return None;
}
let mut shares: std::collections::HashMap<i64, AffinePoint> =
std::collections::HashMap::new();
for share_box in share_boxes.iter() {
let publickey_bytes =
self.group.element_to_bytes(&share_box.publickey);
let position =
distribute_share_box.positions.get(&publickey_bytes)?;
shares.insert(*position, share_box.share);
}
let secret = self.group.identity();
let values: Vec<i64> = shares.keys().copied().collect();
let shares_vec: Vec<(i64, AffinePoint)> = shares.into_iter().collect();
let shares_slice = shares_vec.as_slice();
let factors: Vec<AffinePoint> = shares_slice
.par_iter()
.map(|(position, share)| {
self.compute_lagrange_factor_secp256k1(
*position, share, &values,
)
})
.collect();
let final_secret = factors
.into_iter()
.fold(secret, |acc, factor| self.group.mul(&acc, &factor));
let secret_hash =
Sha256::digest(self.group.element_to_bytes(&final_secret));
let mut field_bytes = FieldBytes::<k256::Secp256k1>::default();
let hash_len = secret_hash.len().min(field_bytes.len());
field_bytes[32 - hash_len..].copy_from_slice(&secret_hash[..hash_len]);
let hash_scalar = Scalar::from_repr(field_bytes).unwrap();
let hash_bytes = hash_scalar.to_bytes();
let hash_biguint = BigUint::from_bytes_be(&hash_bytes);
let scalar_bytes = self.group.order_as_bigint().to_bytes_be().1;
let curve_order_bigint = BigUint::from_bytes_be(&scalar_bytes);
let hash_reduced = hash_biguint % curve_order_bigint;
let decrypted_secret =
hash_reduced ^ distribute_share_box.U.to_biguint().unwrap();
Some(decrypted_secret.to_bigint().unwrap())
}
fn compute_lagrange_factor_secp256k1(
&self,
position: i64,
share: &AffinePoint,
values: &[i64],
) -> AffinePoint {
let mut lambda_num = Scalar::ONE;
let mut lambda_den = Scalar::ONE;
let mut sign = 1i64;
for &j in values {
if j == position {
continue;
}
lambda_num *= Scalar::from(j as u64);
let diff = j - position;
if diff < 0 {
sign *= -1;
lambda_den *= Scalar::from((-diff) as u64);
} else {
lambda_den *= Scalar::from(diff as u64);
}
}
let lambda = lambda_num * lambda_den.invert().unwrap();
let mut factor = self.group.exp(share, &lambda);
if sign < 0
&& let Some(negated) = self.group.element_inverse(&factor)
{
factor = negated;
}
factor
}
}
impl Participant<Ristretto255Group> {
pub fn distribute_secret(
&mut self,
secret: &BigInt,
publickeys: &[RistrettoPoint],
threshold: u32,
) -> DistributionSharesBox<Ristretto255Group> {
assert!(threshold <= publickeys.len() as u32);
let subgroup_gen = self.group.subgroup_generator();
let main_gen = self.group.generator();
let _group_order = self.group.order();
let mut polynomial = Polynomial::new();
let group_order_bigint = self.group.order_as_bigint().clone();
polynomial.init((threshold - 1) as i32, &group_order_bigint);
let mut commitments: Vec<RistrettoPoint> = Vec::new();
let mut positions: HashMap<Vec<u8>, i64> = HashMap::new();
let mut shares: HashMap<Vec<u8>, RistrettoPoint> = HashMap::new();
let mut challenge_hasher = Sha256::new();
let mut sampling_points: HashMap<Vec<u8>, RistrettoScalar> =
HashMap::new();
let mut dleq_w: HashMap<Vec<u8>, RistrettoScalar> = HashMap::new();
let mut position: i64 = 1;
for j in 0..threshold {
let coeff_bigint = &polynomial.coefficients[j as usize];
let coeff = Ristretto255Group::bigint_to_scalar(coeff_bigint);
let commitment = self.group.exp(&subgroup_gen, &coeff);
commitments.push(commitment);
}
for pubkey in publickeys {
let pubkey_bytes = self.group.element_to_bytes(pubkey);
positions.insert(pubkey_bytes.clone(), position);
let pos_scalar = BigInt::from(position);
let secret_share_bigint = polynomial.get_value(&pos_scalar);
let secret_share_mod = &secret_share_bigint % &group_order_bigint;
let secret_share =
Ristretto255Group::bigint_to_scalar(&secret_share_mod);
sampling_points.insert(pubkey_bytes.clone(), secret_share);
let witness = self.group.generate_private_key();
dleq_w.insert(pubkey_bytes.clone(), witness);
let mut x_val = self.group.identity();
let mut exponent = RistrettoScalar::ONE;
for j in 0..threshold {
let c_j_pow =
self.group.exp(&commitments[j as usize], &exponent);
x_val = self.group.mul(&x_val, &c_j_pow);
let pos_scalar = RistrettoScalar::from(position as u64);
exponent = self.group.scalar_mul(&exponent, &pos_scalar);
}
let encrypted_secret_share = self.group.exp(pubkey, &secret_share);
shares.insert(pubkey_bytes.clone(), encrypted_secret_share);
let mut dleq = DLEQ::new(self.group.clone());
dleq.init(
subgroup_gen,
x_val,
*pubkey,
encrypted_secret_share,
secret_share,
witness,
);
let a1 = dleq.get_a1();
let a2 = dleq.get_a2();
DLEQ::<Ristretto255Group>::append_transcript_hash(
self.group.as_ref(),
&x_val,
&encrypted_secret_share,
&a1,
&a2,
&mut challenge_hasher,
);
position += 1;
}
let challenge_hash = challenge_hasher.finalize();
let challenge = self.group.hash_to_scalar(&challenge_hash);
let mut responses: HashMap<Vec<u8>, RistrettoScalar> = HashMap::new();
for pubkey in publickeys {
let pubkey_bytes = self.group.element_to_bytes(pubkey);
let alpha = sampling_points.get(&pubkey_bytes).unwrap();
let alpha_c = self.group.scalar_mul(alpha, &challenge);
let w_i = dleq_w.get(&pubkey_bytes).unwrap();
let response = self.group.scalar_sub(w_i, &alpha_c);
responses.insert(pubkey_bytes, response);
}
let s_bigint = polynomial.get_value(&BigInt::zero());
let s = Ristretto255Group::bigint_to_scalar(&s_bigint);
let g_s = self.group.exp(&main_gen, &s);
let sha256_hash = Sha256::digest(self.group.element_to_bytes(&g_s));
let hash_biguint = BigUint::from_bytes_be(&sha256_hash[..]);
let curve_order_bigint = BigUint::from_bytes_be(
&self.group.order_as_bigint().to_bytes_be().1,
);
let hash_reduced = hash_biguint % curve_order_bigint;
let u = secret.to_biguint().unwrap() ^ hash_reduced;
let mut shares_box = DistributionSharesBox::new();
shares_box.init(
&commitments,
positions,
shares,
publickeys,
&challenge,
responses,
&u.to_bigint().unwrap(),
);
shares_box
}
pub fn extract_secret_share(
&self,
shares_box: &DistributionSharesBox<Ristretto255Group>,
private_key: &RistrettoScalar,
w: &RistrettoScalar,
) -> Option<ShareBox<Ristretto255Group>> {
let main_gen = self.group.generator();
let public_key = self.group.generate_public_key(private_key);
let public_key_bytes = self.group.element_to_bytes(&public_key);
let encrypted_secret_share =
shares_box.shares.get(&public_key_bytes)?;
let privkey_inverse = self.group.scalar_inverse(private_key)?;
let decrypted_share =
self.group.exp(encrypted_secret_share, &privkey_inverse);
let mut dleq = DLEQ::new(self.group.clone());
dleq.init(
main_gen,
public_key,
decrypted_share,
*encrypted_secret_share,
*private_key,
*w,
);
let mut challenge_hasher = Sha256::new();
let a1 = dleq.get_a1();
let a2 = dleq.get_a2();
DLEQ::<Ristretto255Group>::append_transcript_hash(
self.group.as_ref(),
&public_key,
encrypted_secret_share,
&a1,
&a2,
&mut challenge_hasher,
);
let challenge_hash = challenge_hasher.finalize();
let challenge = self.group.hash_to_scalar(&challenge_hash);
dleq.c = Some(challenge);
let response = dleq.get_r()?;
let mut share_box = ShareBox::new();
share_box.init(public_key, decrypted_share, challenge, response);
Some(share_box)
}
pub fn verify_share(
&self,
sharebox: &ShareBox<Ristretto255Group>,
distribution_sharebox: &DistributionSharesBox<Ristretto255Group>,
publickey: &RistrettoPoint,
) -> bool {
let main_gen = self.group.generator();
let publickey_bytes = self.group.element_to_bytes(publickey);
let encrypted_share =
match distribution_sharebox.shares.get(&publickey_bytes) {
Some(s) => s,
None => return false,
};
let mut dleq = DLEQ::<Ristretto255Group>::new(self.group.clone());
dleq.g1 = main_gen;
dleq.h1 = *publickey;
dleq.g2 = sharebox.share;
dleq.h2 = *encrypted_share;
dleq.c = Some(sharebox.challenge);
dleq.r = Some(sharebox.response);
dleq.verify()
}
pub fn verify_distribution_shares(
&self,
distribute_sharesbox: &DistributionSharesBox<Ristretto255Group>,
) -> bool {
let subgroup_gen = self.group.subgroup_generator();
let mut challenge_hasher = Sha256::new();
for publickey in &distribute_sharesbox.publickeys {
let publickey_bytes = self.group.element_to_bytes(publickey);
let position = distribute_sharesbox.positions.get(&publickey_bytes);
let response = distribute_sharesbox.responses.get(&publickey_bytes);
let encrypted_share =
distribute_sharesbox.shares.get(&publickey_bytes);
if position.is_none()
|| response.is_none()
|| encrypted_share.is_none()
{
return false;
}
let position = *position.unwrap();
let response = response.unwrap();
let encrypted_share = encrypted_share.unwrap();
let mut x_val = self.group.identity();
let mut exponent = RistrettoScalar::ONE;
for j in 0..distribute_sharesbox.commitments.len() {
let c_j_pow = self
.group
.exp(&distribute_sharesbox.commitments[j], &exponent);
x_val = self.group.mul(&x_val, &c_j_pow);
let pos_scalar = RistrettoScalar::from(position as u64);
exponent = self.group.scalar_mul(&exponent, &pos_scalar);
}
let _ = DLEQ::<Ristretto255Group>::verifier_update_hash(
self.group.as_ref(),
&subgroup_gen,
&x_val,
publickey,
encrypted_share,
response,
&distribute_sharesbox.challenge,
&mut challenge_hasher,
);
}
let challenge_hash = challenge_hasher.finalize();
let computed_challenge = self.group.hash_to_scalar(&challenge_hash);
computed_challenge == distribute_sharesbox.challenge
}
pub fn reconstruct(
&self,
share_boxes: &[ShareBox<Ristretto255Group>],
distribute_share_box: &DistributionSharesBox<Ristretto255Group>,
) -> Option<BigInt> {
use rayon::prelude::*;
if share_boxes.len() < distribute_share_box.commitments.len() {
return None;
}
let mut shares: HashMap<i64, RistrettoPoint> = HashMap::new();
for share_box in share_boxes.iter() {
let publickey_bytes =
self.group.element_to_bytes(&share_box.publickey);
let position =
distribute_share_box.positions.get(&publickey_bytes)?;
shares.insert(*position, share_box.share);
}
let secret = self.group.identity();
let values: Vec<i64> = shares.keys().copied().collect();
let shares_vec: Vec<(i64, RistrettoPoint)> =
shares.into_iter().collect();
let shares_slice = shares_vec.as_slice();
let factors: Vec<RistrettoPoint> = shares_slice
.par_iter()
.map(|(position, share)| {
self.compute_lagrange_factor_ristretto(
*position, share, &values,
)
})
.collect();
let final_secret = factors
.into_iter()
.fold(secret, |acc, factor| self.group.mul(&acc, &factor));
let secret_hash =
Sha256::digest(self.group.element_to_bytes(&final_secret));
let hash_biguint = BigUint::from_bytes_be(&secret_hash[..]);
let curve_order_bigint = BigUint::from_bytes_be(
&self.group.order_as_bigint().to_bytes_be().1,
);
let hash_reduced = hash_biguint % curve_order_bigint;
let decrypted_secret =
hash_reduced ^ distribute_share_box.U.to_biguint().unwrap();
Some(decrypted_secret.to_bigint().unwrap())
}
fn compute_lagrange_factor_ristretto(
&self,
position: i64,
share: &RistrettoPoint,
values: &[i64],
) -> RistrettoPoint {
let mut lambda_num = RistrettoScalar::ONE;
let mut lambda_den = RistrettoScalar::ONE;
let mut sign = 1i64;
for &j in values {
if j == position {
continue;
}
lambda_num *= RistrettoScalar::from(j as u64);
let diff = j - position;
if diff < 0 {
sign *= -1;
lambda_den *= RistrettoScalar::from((-diff) as u64);
} else {
lambda_den *= RistrettoScalar::from(diff as u64);
}
}
let lambda_den_inv = Option::from(lambda_den.invert());
let lambda = match lambda_den_inv {
Some(inv) => lambda_num * inv,
None => {
RistrettoScalar::ZERO
}
};
let mut factor = self.group.exp(share, &lambda);
if sign < 0
&& let Some(negated) = self.group.element_inverse(&factor)
{
factor = negated;
}
factor
}
}