Skip to main content

multi_party_schnorr/common/
utils.rs

1// Copyright (c) Silence Laboratories Pte. Ltd. All Rights Reserved.
2// This software is licensed under the Silence Laboratories License Agreement.
3
4use crypto_box::{
5    aead::{Aead, AeadCore},
6    PublicKey, SalsaBox, SecretKey,
7};
8
9use crypto_bigint::{
10    generic_array::{typenum::Unsigned, GenericArray},
11    rand_core::CryptoRngCore,
12};
13
14use sha2::{Digest, Sha256};
15
16// Encryption is done inplace, so the size of the ciphertext is the size of the message plus the tag size.
17pub const SCALAR_CIPHERTEXT_SIZE: usize = 64 + <SalsaBox as AeadCore>::TagSize::USIZE;
18
19// Custom serde serializer
20#[cfg(feature = "serde")]
21pub mod serde_point {
22    use std::marker::PhantomData;
23
24    use elliptic_curve::group::GroupEncoding;
25    use serde::de::Visitor;
26
27    pub fn serialize<S, G: GroupEncoding>(point: &G, serializer: S) -> Result<S::Ok, S::Error>
28    where
29        S: serde::Serializer,
30    {
31        use serde::ser::SerializeTuple;
32        let mut tup = serializer.serialize_tuple(G::Repr::default().as_ref().len())?;
33        for byte in point.to_bytes().as_ref().iter() {
34            tup.serialize_element(byte)?;
35        }
36        tup.end()
37    }
38
39    pub fn deserialize<'de, D, G: GroupEncoding>(deserializer: D) -> Result<G, D::Error>
40    where
41        D: serde::Deserializer<'de>,
42    {
43        struct PointVisitor<G: GroupEncoding>(PhantomData<G>);
44
45        impl<'de, G: GroupEncoding> Visitor<'de> for PointVisitor<G> {
46            type Value = G;
47
48            fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
49                formatter.write_str("a valid point in Edwards y + sign format")
50            }
51
52            fn visit_seq<A>(self, mut seq: A) -> Result<G, A::Error>
53            where
54                A: serde::de::SeqAccess<'de>,
55            {
56                let mut encoding = G::Repr::default();
57                for (idx, byte) in encoding.as_mut().iter_mut().enumerate() {
58                    *byte = seq.next_element()?.ok_or_else(|| {
59                        serde::de::Error::invalid_length(idx, &"wrong length of point")
60                    })?;
61                }
62
63                Option::from(G::from_bytes(&encoding))
64                    .ok_or(serde::de::Error::custom("point decompression failed"))
65            }
66        }
67
68        deserializer.deserialize_tuple(G::Repr::default().as_ref().len(), PointVisitor(PhantomData))
69    }
70}
71
72#[cfg(feature = "serde")]
73pub mod serde_vec_point {
74    use elliptic_curve::group::GroupEncoding;
75
76    pub fn serialize<S, G: GroupEncoding>(points: &[G], serializer: S) -> Result<S::Ok, S::Error>
77    where
78        S: serde::Serializer,
79    {
80        let mut bytes = Vec::with_capacity(points.len() * G::Repr::default().as_ref().len());
81        points.iter().for_each(|point| {
82            bytes.extend_from_slice(point.to_bytes().as_ref());
83        });
84        serializer.serialize_bytes(&bytes)
85    }
86
87    pub fn deserialize<'de, D, G: GroupEncoding>(deserializer: D) -> Result<Vec<G>, D::Error>
88    where
89        D: serde::Deserializer<'de>,
90    {
91        let bytes: Vec<u8> = serde::Deserialize::deserialize(deserializer)?;
92        let point_size = G::Repr::default().as_ref().len();
93        if bytes.len() % point_size != 0 {
94            return Err(serde::de::Error::custom("Invalid number of bytes"));
95        }
96        let mut points = Vec::with_capacity(bytes.len() / point_size);
97        for i in 0..bytes.len() / point_size {
98            let mut encoding = G::Repr::default();
99            encoding
100                .as_mut()
101                .copy_from_slice(&bytes[i * point_size..(i + 1) * point_size]);
102            points.push(
103                Option::from(G::from_bytes(&encoding))
104                    .ok_or_else(|| serde::de::Error::custom("Invalid point"))?,
105            );
106        }
107        Ok(points)
108    }
109}
110
111#[cfg(feature = "serde")]
112pub mod serde_arc {
113    use std::sync::Arc;
114
115    use serde::{Deserialize, Serialize};
116
117    pub fn serialize<S, T: Serialize>(value: &Arc<T>, serializer: S) -> Result<S::Ok, S::Error>
118    where
119        S: serde::Serializer,
120    {
121        value.serialize(serializer)
122    }
123
124    pub fn deserialize<'de, D, T: Deserialize<'de>>(deserializer: D) -> Result<Arc<T>, D::Error>
125    where
126        D: serde::Deserializer<'de>,
127    {
128        Ok(Arc::new(T::deserialize(deserializer)?))
129    }
130}
131
132#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
133#[derive(Clone, Copy, Debug, bytemuck::AnyBitPattern, bytemuck::NoUninit)]
134#[repr(C)]
135/// Encrypted data
136/// Specific to the keygen protocol
137/// It is an encryption of 64 bytes of data.
138/// The data:  [polynomial evaluation c_i_j (32 bytes) || chain_code_session_id (32 bytes)] for the party_{i}
139pub struct EncryptedData {
140    pub sender_pid: u8,
141    pub receiver_pid: u8,
142    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
143    pub ciphertext: [u8; SCALAR_CIPHERTEXT_SIZE],
144    // Size of the nonce is 24
145    pub nonce: [u8; <SalsaBox as AeadCore>::NonceSize::USIZE],
146}
147
148// Not using the SessionId type from sl_mpc_mate because it is not serializable.
149pub type SessionId = [u8; 32];
150pub type HashBytes = [u8; 32];
151
152impl EncryptedData {
153    pub fn new(
154        ciphertext: [u8; SCALAR_CIPHERTEXT_SIZE],
155        nonce: [u8; 24],
156        sender_pid: u8,
157        receiver_pid: u8,
158    ) -> Self {
159        Self {
160            ciphertext,
161            nonce,
162            sender_pid,
163            receiver_pid,
164        }
165    }
166}
167
168/// Common trait for all MPC messages.
169pub(crate) trait BaseMessage {
170    // Return the party id of the message sender.
171    fn party_id(&self) -> u8;
172}
173
174/// Calculates the final session id from the list of session ids.
175pub fn calculate_final_session_id(
176    party_ids: impl Iterator<Item = u8>,
177    sid_i_list: &[SessionId],
178    extra: &[&[u8]],
179) -> SessionId {
180    let mut hasher = Sha256::new();
181
182    for e in extra {
183        hasher.update(e);
184    }
185
186    party_ids.for_each(|pid| hasher.update((pid as u32).to_be_bytes()));
187    sid_i_list.iter().for_each(|sid| hasher.update(sid));
188
189    hasher.finalize().into()
190}
191
192pub fn encrypt_message<R: CryptoRngCore>(
193    sender_secret_info: (&SecretKey, u8),
194    receiver_public_info: (&PublicKey, u8),
195    message: &[u8; 64],
196    rng: &mut R,
197) -> Option<EncryptedData> {
198    let sender_box = SalsaBox::new(receiver_public_info.0, sender_secret_info.0);
199    let nonce = SalsaBox::generate_nonce(rng);
200    sender_box
201        .encrypt(&nonce, message.as_slice())
202        .ok()
203        .and_then(|data| {
204            Some(EncryptedData::new(
205                data.try_into().ok()?,
206                nonce.into(),
207                sender_secret_info.1,
208                receiver_public_info.1,
209            ))
210        })
211}
212
213pub fn decrypt_message(
214    receiver_private_key: &SecretKey,
215    sender_public_key: &PublicKey,
216    enc_data: &EncryptedData,
217) -> Option<[u8; 64]> {
218    let receiver_box = SalsaBox::new(sender_public_key, receiver_private_key);
219    receiver_box
220        .decrypt(
221            &GenericArray::from(enc_data.nonce),
222            enc_data.ciphertext.as_slice(),
223        )
224        .ok()
225        .and_then(|data| data.try_into().ok())
226}
227
228#[cfg(any(test, feature = "test-support"))]
229pub mod support {
230
231    use crate::keygen::{utils::setup_keygen, Keyshare};
232
233    use crate::common::{
234        ser::Serializable,
235        traits::{GroupElem, Round, ScalarReduce},
236    };
237
238    /// Execute one round of DKG protocol locally, execute parties in parallel
239    /// Used for testing purposes.
240    #[allow(clippy::map_identity)]
241    pub fn run_round<I, R, O, E>(actors: impl IntoIterator<Item = R>, msgs: I) -> Vec<O>
242    where
243        R: Round<Input = I, Output = O, Error = E> + Serializable + Send,
244        I: Clone + Sync,
245        E: std::fmt::Debug,
246        O: Send,
247    {
248        actors
249            .into_iter()
250            .map(|actor| {
251                #[cfg(all(feature = "serde", test))]
252                let actor: R = {
253                    let mut v = vec![];
254                    ciborium::into_writer(&actor, &mut v).unwrap();
255                    ciborium::from_reader(v.as_ref() as &[u8]).unwrap()
256                };
257
258                actor
259            })
260            .map(|actor| actor.process(msgs.clone()).unwrap())
261            .collect()
262    }
263
264    /// Utility function to run the keygen protocol.
265    pub fn run_keygen<const T: usize, const N: usize, G: GroupElem>() -> [Keyshare<G>; N]
266    where
267        G::Scalar: ScalarReduce<[u8; 32]>,
268        G::Scalar: Serializable,
269    {
270        let actors = setup_keygen(T as u8, N as u8);
271
272        let (actors, msgs): (Vec<_>, Vec<_>) = run_round(actors, ()).into_iter().unzip();
273        let (actors, msgs): (Vec<_>, Vec<_>) = run_round(actors, msgs).into_iter().unzip();
274
275        run_round(actors, msgs)
276            .try_into()
277            .map_err(|_| panic!("Failed to convert keyshares"))
278            .unwrap()
279    }
280}