elastic_elgamal/sharing/
participant.rs

1//! Types representing participant state.
2
3// TODO: Use a publicly verifiable scheme, e.g. Schoenmakers?
4// https://www.win.tue.nl/~berry/papers/crypto99.pdf
5
6use merlin::Transcript;
7use rand_core::{CryptoRng, RngCore};
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11use core::iter;
12
13use crate::{
14    alloc::Vec,
15    group::Group,
16    proofs::{LogEqualityProof, ProofOfPossession},
17    sharing::{Error, Params, PublicKeySet},
18    Ciphertext, Keypair, PublicKey, SecretKey, VerifiableDecryption,
19};
20
21/// Dealer in a [Feldman verifiable secret sharing][feldman-vss] scheme.
22///
23/// [feldman-vss]: https://www.cs.umd.edu/~gasarch/TOPICS/secretsharing/feldmanVSS.pdf
24#[derive(Debug, Clone)]
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26#[cfg_attr(feature = "serde", serde(bound = ""))]
27pub struct Dealer<G: Group> {
28    params: Params,
29    polynomial: Vec<Keypair<G>>,
30    proof_of_possession: ProofOfPossession<G>,
31}
32
33impl<G: Group> Dealer<G> {
34    /// Instantiates a dealer.
35    pub fn new<R: CryptoRng + RngCore>(params: Params, rng: &mut R) -> Self {
36        let polynomial: Vec<_> = (0..params.threshold)
37            .map(|_| Keypair::<G>::generate(rng))
38            .collect();
39
40        let mut transcript = Transcript::new(b"elgamal_share_poly");
41        transcript.append_u64(b"n", params.shares as u64);
42        transcript.append_u64(b"t", params.threshold as u64);
43
44        let proof_of_possession = ProofOfPossession::new(&polynomial, &mut transcript, rng);
45
46        Self {
47            params,
48            polynomial,
49            proof_of_possession,
50        }
51    }
52
53    /// Returns public participant information: dealer's public polynomial and proof
54    /// of possession for the corresponding secret polynomial.
55    pub fn public_info(&self) -> (Vec<G::Element>, &ProofOfPossession<G>) {
56        let public_polynomial = self
57            .polynomial
58            .iter()
59            .map(|pair| pair.public().as_element())
60            .collect();
61        (public_polynomial, &self.proof_of_possession)
62    }
63
64    /// Returns a secret share for a participant with the specified `index`.
65    ///
66    /// # Panics
67    ///
68    /// Panics if `index` is out of allowed bounds.
69    pub fn secret_share_for_participant(&self, index: usize) -> SecretKey<G> {
70        assert!(
71            index < self.params.shares,
72            "participant index {index} out of bounds, expected a value in 0..{}",
73            self.params.shares
74        );
75
76        let power = G::Scalar::from(index as u64 + 1);
77        let mut poly_value = SecretKey::new(G::Scalar::from(0));
78        for keypair in self.polynomial.iter().rev() {
79            poly_value = poly_value * &power + keypair.secret().clone();
80        }
81        poly_value
82    }
83}
84
85/// Personalized state of a participant of a threshold ElGamal encryption scheme
86/// once the participant receives the secret share from the [`Dealer`].
87/// At this point, the participant can produce [`VerifiableDecryption`]s.
88#[derive(Debug, Clone)]
89#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
90#[cfg_attr(feature = "serde", serde(bound = ""))]
91pub struct ActiveParticipant<G: Group> {
92    key_set: PublicKeySet<G>,
93    index: usize,
94    secret_share: SecretKey<G>,
95}
96
97impl<G: Group> ActiveParticipant<G> {
98    /// Creates the participant state based on readily available components.
99    ///
100    /// # Errors
101    ///
102    /// Returns an error if `secret_share` does not correspond to the participant's public key share
103    /// in `key_set`.
104    ///
105    /// # Panics
106    ///
107    /// Panics if `index` is greater or equal than the number of participants in `key_set`.
108    pub fn new(
109        key_set: PublicKeySet<G>,
110        index: usize,
111        secret_share: SecretKey<G>,
112    ) -> Result<Self, Error> {
113        let expected_element = key_set.participant_keys()[index].as_element();
114        if G::mul_generator(secret_share.expose_scalar()) == expected_element {
115            Ok(Self {
116                key_set,
117                index,
118                secret_share,
119            })
120        } else {
121            Err(Error::InvalidSecret)
122        }
123    }
124
125    /// Returns the public key set for the threshold ElGamal encryption scheme this participant
126    /// is a part of.
127    pub fn key_set(&self) -> &PublicKeySet<G> {
128        &self.key_set
129    }
130
131    /// Returns 0-based index of this participant.
132    pub fn index(&self) -> usize {
133        self.index
134    }
135
136    /// Returns share of the secret key for this participant. This is secret information that
137    /// must not be shared.
138    pub fn secret_share(&self) -> &SecretKey<G> {
139        &self.secret_share
140    }
141
142    /// Returns share of the public key for this participant.
143    pub fn public_key_share(&self) -> &PublicKey<G> {
144        &self.key_set.participant_keys()[self.index]
145    }
146
147    /// Generates a [`ProofOfPossession`] of the participant's
148    /// [`secret_share`](Self::secret_share()).
149    pub fn proof_of_possession<R: CryptoRng + RngCore>(&self, rng: &mut R) -> ProofOfPossession<G> {
150        let mut transcript = Transcript::new(b"elgamal_participant_pop");
151        self.key_set.commit(&mut transcript);
152        transcript.append_u64(b"i", self.index as u64);
153        ProofOfPossession::from_keys(
154            iter::once(&self.secret_share),
155            iter::once(self.public_key_share()),
156            &mut transcript,
157            rng,
158        )
159    }
160
161    /// Creates a [`VerifiableDecryption`] for the specified `ciphertext` together with a proof
162    /// of its validity. `rng` is used to generate the proof.
163    pub fn decrypt_share<R>(
164        &self,
165        ciphertext: Ciphertext<G>,
166        rng: &mut R,
167    ) -> (VerifiableDecryption<G>, LogEqualityProof<G>)
168    where
169        R: CryptoRng + RngCore,
170    {
171        let dh_element = ciphertext.random_element * self.secret_share.expose_scalar();
172        let our_public_key = self.public_key_share().as_element();
173        let mut transcript = Transcript::new(b"elgamal_decryption_share");
174        self.key_set.commit(&mut transcript);
175        transcript.append_u64(b"i", self.index as u64);
176
177        let proof = LogEqualityProof::new(
178            &PublicKey::from_element(ciphertext.random_element),
179            &self.secret_share,
180            (our_public_key, dh_element),
181            &mut transcript,
182            rng,
183        );
184        (VerifiableDecryption::from_element(dh_element), proof)
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use crate::{curve25519::scalar::Scalar as Scalar25519, group::Ristretto};
192
193    #[test]
194    fn shared_2_of_3_key() {
195        let mut rng = rand::rng();
196        let params = Params::new(3, 2);
197
198        let dealer = Dealer::<Ristretto>::new(params, &mut rng);
199        let (public_poly, public_poly_proof) = dealer.public_info();
200        let key_set = PublicKeySet::new(params, public_poly, public_poly_proof).unwrap();
201
202        let alice_share = dealer.secret_share_for_participant(0);
203        let alice = ActiveParticipant::new(key_set.clone(), 0, alice_share).unwrap();
204        let bob_share = dealer.secret_share_for_participant(1);
205        let bob = ActiveParticipant::new(key_set.clone(), 1, bob_share).unwrap();
206        let carol_share = dealer.secret_share_for_participant(2);
207        let carol = ActiveParticipant::new(key_set.clone(), 2, carol_share).unwrap();
208
209        key_set
210            .verify_participant(0, &alice.proof_of_possession(&mut rng))
211            .unwrap();
212        key_set
213            .verify_participant(1, &bob.proof_of_possession(&mut rng))
214            .unwrap();
215        key_set
216            .verify_participant(2, &carol.proof_of_possession(&mut rng))
217            .unwrap();
218        assert!(key_set
219            .verify_participant(1, &alice.proof_of_possession(&mut rng))
220            .is_err());
221
222        let ciphertext = key_set.shared_key().encrypt(15_u64, &mut rng);
223        let (alice_share, proof) = alice.decrypt_share(ciphertext, &mut rng);
224        key_set
225            .verify_share(alice_share.into(), ciphertext, 0, &proof)
226            .unwrap();
227
228        let (bob_share, proof) = bob.decrypt_share(ciphertext, &mut rng);
229        key_set
230            .verify_share(bob_share.into(), ciphertext, 1, &proof)
231            .unwrap();
232
233        // We need to find `a0` from the following equations:
234        // a0 +   a1 = alice_share.dh_element;
235        // a0 + 2*a1 = bob_share.dh_element;
236        let composite_dh_element =
237            *alice_share.as_element() * Scalar25519::from(2_u64) - *bob_share.as_element();
238        let message = Ristretto::mul_generator(&Scalar25519::from(15_u64));
239        assert_eq!(composite_dh_element, ciphertext.blinded_element - message);
240    }
241}