mithril_stm/aggregate_signature/
signature.rs

1use std::collections::HashMap;
2use std::fmt::Display;
3use std::hash::Hash;
4
5use blake2::digest::{Digest, FixedOutput};
6use serde::{Deserialize, Serialize};
7
8use crate::error::StmAggregateSignatureError;
9use crate::merkle_tree::MerkleBatchPath;
10use crate::{AggregateVerificationKey, Parameters};
11
12use super::ConcatenationProof;
13
14/// The type of STM aggregate signature.
15#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
16pub enum AggregateSignatureType {
17    /// Concatenation proof system.
18    #[default]
19    Concatenation,
20    /// Future proof system. Not suitable for production.
21    #[cfg(feature = "future_proof_system")]
22    Future,
23}
24
25impl AggregateSignatureType {
26    /// The prefix byte used in the byte representation of the aggregate signature type.
27    ///
28    /// IMPORTANT: This value is used in serialization/deserialization. Changing it will break compatibility.
29    pub fn get_byte_encoding_prefix(&self) -> u8 {
30        match self {
31            AggregateSignatureType::Concatenation => 0,
32            #[cfg(feature = "future_proof_system")]
33            AggregateSignatureType::Future => 255,
34        }
35    }
36
37    /// Create an aggregate signature type from a prefix byte.
38    ///
39    /// IMPORTANT: This value is used in serialization/deserialization. Changing it will break compatibility.
40    pub fn from_byte_encoding_prefix(byte: u8) -> Option<Self> {
41        match byte {
42            0 => Some(AggregateSignatureType::Concatenation),
43            #[cfg(feature = "future_proof_system")]
44            255 => Some(AggregateSignatureType::Future),
45            _ => None,
46        }
47    }
48}
49
50impl<D: Clone + Digest + FixedOutput + Send + Sync> From<&AggregateSignature<D>>
51    for AggregateSignatureType
52{
53    fn from(aggr_sig: &AggregateSignature<D>) -> Self {
54        match aggr_sig {
55            AggregateSignature::Concatenation(_) => AggregateSignatureType::Concatenation,
56            #[cfg(feature = "future_proof_system")]
57            AggregateSignature::Future => AggregateSignatureType::Future,
58        }
59    }
60}
61
62impl Display for AggregateSignatureType {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        match self {
65            AggregateSignatureType::Concatenation => write!(f, "Concatenation"),
66            #[cfg(feature = "future_proof_system")]
67            AggregateSignatureType::Future => write!(f, "Future"),
68        }
69    }
70}
71
72/// An STM aggregate signature.
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(bound(
75    serialize = "MerkleBatchPath<D>: Serialize",
76    deserialize = "MerkleBatchPath<D>: Deserialize<'de>"
77))]
78pub enum AggregateSignature<D: Clone + Digest + FixedOutput + Send + Sync> {
79    /// A future proof system.
80    #[cfg(feature = "future_proof_system")]
81    Future,
82
83    /// Concatenation proof system.
84    // The 'untagged' attribute is required for backward compatibility.
85    // It implies that this variant is placed at the end of the enum.
86    // It will be removed when the support for JSON hex encoding is dropped in the calling crates.
87    #[serde(untagged)]
88    Concatenation(ConcatenationProof<D>),
89}
90
91impl<D: Clone + Digest + FixedOutput + Send + Sync> AggregateSignature<D> {
92    /// Verify an aggregate signature
93    pub fn verify(
94        &self,
95        msg: &[u8],
96        avk: &AggregateVerificationKey<D>,
97        parameters: &Parameters,
98    ) -> Result<(), StmAggregateSignatureError<D>> {
99        match self {
100            AggregateSignature::Concatenation(concatenation_proof) => {
101                concatenation_proof.verify(msg, avk, parameters)
102            }
103            #[cfg(feature = "future_proof_system")]
104            AggregateSignature::Future => Err(StmAggregateSignatureError::UnsupportedProofSystem(
105                self.into(),
106            )),
107        }
108    }
109
110    /// Batch verify a set of aggregate signatures
111    pub fn batch_verify(
112        stm_signatures: &[Self],
113        msgs: &[Vec<u8>],
114        avks: &[AggregateVerificationKey<D>],
115        parameters: &[Parameters],
116    ) -> Result<(), StmAggregateSignatureError<D>> {
117        let stm_signatures: HashMap<AggregateSignatureType, Vec<Self>> =
118            stm_signatures.iter().fold(HashMap::new(), |mut acc, sig| {
119                acc.entry(sig.into()).or_default().push(sig.clone());
120                acc
121            });
122        stm_signatures
123            .into_iter()
124            .try_for_each(|(aggregate_signature_type, aggregate_signatures)| {
125                match aggregate_signature_type {
126                    AggregateSignatureType::Concatenation => {
127                        let aggregate_signatures_length = aggregate_signatures.len();
128                        let concatenation_proofs = aggregate_signatures
129                            .into_iter()
130                            .filter_map(|s| s.to_concatenation_proof().cloned())
131                            .collect::<Vec<_>>();
132                        if concatenation_proofs.len() != aggregate_signatures_length {
133                            return Err(StmAggregateSignatureError::BatchInvalid);
134                        }
135
136                        ConcatenationProof::batch_verify(
137                            &concatenation_proofs,
138                            msgs,
139                            avks,
140                            parameters,
141                        )
142                    }
143                    #[cfg(feature = "future_proof_system")]
144                    AggregateSignatureType::Future => {
145                        Err(StmAggregateSignatureError::UnsupportedProofSystem(
146                            aggregate_signature_type,
147                        ))
148                    }
149                }
150            })
151            .map_err(|_| StmAggregateSignatureError::BatchInvalid)
152    }
153
154    /// Convert an aggregate signature to bytes
155    pub fn to_bytes(&self) -> Vec<u8> {
156        let mut aggregate_signature_bytes = Vec::new();
157        let aggregate_signature_type: AggregateSignatureType = self.into();
158        aggregate_signature_bytes
159            .extend_from_slice(&[aggregate_signature_type.get_byte_encoding_prefix()]);
160
161        let mut proof_bytes = match self {
162            AggregateSignature::Concatenation(concatenation_proof) => {
163                concatenation_proof.to_bytes()
164            }
165            #[cfg(feature = "future_proof_system")]
166            AggregateSignature::Future => vec![],
167        };
168        aggregate_signature_bytes.append(&mut proof_bytes);
169
170        aggregate_signature_bytes
171    }
172
173    /// Extract an aggregate signature from a byte slice.
174    pub fn from_bytes(bytes: &[u8]) -> Result<Self, StmAggregateSignatureError<D>> {
175        let proof_type_byte =
176            bytes.first().ok_or(StmAggregateSignatureError::SerializationError)?;
177        let proof_bytes = &bytes[1..];
178        let proof_type = AggregateSignatureType::from_byte_encoding_prefix(*proof_type_byte)
179            .ok_or(StmAggregateSignatureError::SerializationError)?;
180        match proof_type {
181            AggregateSignatureType::Concatenation => Ok(AggregateSignature::Concatenation(
182                ConcatenationProof::from_bytes(proof_bytes)?,
183            )),
184            #[cfg(feature = "future_proof_system")]
185            AggregateSignatureType::Future => Ok(AggregateSignature::Future),
186        }
187    }
188
189    /// If the aggregate signature is a concatenation proof, return it.
190    pub fn to_concatenation_proof(&self) -> Option<&ConcatenationProof<D>> {
191        match self {
192            AggregateSignature::Concatenation(proof) => Some(proof),
193            #[cfg(feature = "future_proof_system")]
194            AggregateSignature::Future => None,
195        }
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    mod aggregate_signature_type_golden {
204        use super::*;
205
206        #[test]
207        fn golden_bytes_encoding_prefix() {
208            assert_eq!(
209                0u8,
210                AggregateSignatureType::Concatenation.get_byte_encoding_prefix()
211            );
212            assert_eq!(
213                AggregateSignatureType::from_byte_encoding_prefix(0u8),
214                Some(AggregateSignatureType::Concatenation)
215            );
216        }
217    }
218
219    mod aggregate_signature_golden_concatenation {
220        use blake2::{Blake2b, digest::consts::U32};
221        use rand_chacha::ChaCha20Rng;
222        use rand_core::SeedableRng;
223
224        use crate::bls_multi_signature::{BlsSigningKey, BlsVerificationKeyProofOfPossession};
225        use crate::{
226            AggregateSignature, AggregateSignatureType, Clerk, ClosedKeyRegistration,
227            KeyRegistration, Parameters, Signer,
228        };
229
230        type D = Blake2b<U32>;
231
232        const GOLDEN_JSON: &str = r#"
233        {
234            "signatures": [
235                [
236                {
237                    "sigma": [
238                    149, 157, 201, 187, 140, 54, 0, 128, 209, 88, 16, 203, 61, 78, 77, 98,
239                    161, 133, 58, 152, 29, 74, 217, 113, 64, 100, 10, 161, 186, 167, 133,
240                    114, 211, 153, 218, 56, 223, 84, 105, 242, 41, 54, 224, 170, 208, 185,
241                    126, 83
242                    ],
243                    "indexes": [1, 4, 5, 8],
244                    "signer_index": 0
245                },
246                [
247                    [
248                    143, 161, 255, 48, 78, 57, 204, 220, 25, 221, 164, 252, 248, 14, 56,
249                    126, 186, 135, 228, 188, 145, 181, 52, 200, 97, 99, 213, 46, 0, 199,
250                    193, 89, 187, 88, 29, 135, 173, 244, 86, 36, 83, 54, 67, 164, 6, 137,
251                    94, 72, 6, 105, 128, 128, 93, 48, 176, 11, 4, 246, 138, 48, 180, 133,
252                    90, 142, 192, 24, 193, 111, 142, 31, 76, 111, 110, 234, 153, 90, 208,
253                    192, 31, 124, 95, 102, 49, 158, 99, 52, 220, 165, 94, 251, 68, 69,
254                    121, 16, 224, 194
255                    ],
256                    1
257                ]
258                ],
259                [
260                {
261                    "sigma": [
262                    149, 169, 22, 201, 216, 97, 163, 188, 115, 210, 217, 236, 233, 161,
263                    201, 13, 42, 132, 12, 63, 5, 31, 120, 22, 78, 177, 125, 134, 208, 205,
264                    73, 58, 247, 141, 59, 62, 187, 81, 213, 30, 153, 218, 41, 42, 110,
265                    156, 161, 205
266                    ],
267                    "indexes": [0, 3, 6],
268                    "signer_index": 1
269                },
270                [
271                    [
272                    145, 56, 175, 32, 122, 187, 214, 226, 251, 148, 88, 9, 1, 103, 159,
273                    146, 80, 166, 107, 243, 251, 236, 41, 28, 111, 128, 207, 164, 132,
274                    147, 228, 83, 246, 228, 170, 68, 89, 78, 60, 28, 123, 130, 88, 234,
275                    38, 97, 42, 65, 1, 100, 53, 18, 78, 131, 8, 61, 122, 131, 238, 84,
276                    233, 223, 154, 118, 118, 73, 28, 27, 101, 78, 80, 233, 123, 206, 220,
277                    174, 134, 205, 71, 110, 112, 180, 97, 98, 0, 113, 69, 145, 231, 168,
278                    43, 173, 172, 56, 104, 208
279                    ],
280                    1
281                ]
282                ]
283            ],
284            "batch_proof": { "values": [], "indices": [0, 1], "hasher": null }
285        }
286        "#;
287
288        fn golden_value() -> AggregateSignature<D> {
289            let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
290            let msg = [0u8; 16];
291            let params = Parameters {
292                m: 10,
293                k: 5,
294                phi_f: 0.8,
295            };
296            let sk_1 = BlsSigningKey::generate(&mut rng);
297            let sk_2 = BlsSigningKey::generate(&mut rng);
298            let pk_1 = BlsVerificationKeyProofOfPossession::from(&sk_1);
299            let pk_2 = BlsVerificationKeyProofOfPossession::from(&sk_2);
300            let mut key_reg = KeyRegistration::init();
301            key_reg.register(1, pk_1).unwrap();
302            key_reg.register(1, pk_2).unwrap();
303            let closed_key_reg: ClosedKeyRegistration<D> = key_reg.close();
304            let clerk = Clerk::new_clerk_from_closed_key_registration(&params, &closed_key_reg);
305            let signer_1 = Signer::set_signer(0, 1, params, sk_1, pk_1.vk, closed_key_reg.clone());
306            let signer_2 = Signer::set_signer(1, 1, params, sk_2, pk_2.vk, closed_key_reg);
307            let signature_1 = signer_1.sign(&msg).unwrap();
308            let signature_2 = signer_2.sign(&msg).unwrap();
309
310            clerk
311                .aggregate_signatures_with_type(
312                    &[signature_1, signature_2],
313                    &msg,
314                    AggregateSignatureType::Concatenation,
315                )
316                .unwrap()
317        }
318
319        #[test]
320        fn golden_conversions() {
321            let value: AggregateSignature<D> = serde_json::from_str(GOLDEN_JSON)
322                .expect("This JSON deserialization should not fail");
323
324            let serialized =
325                serde_json::to_string(&value).expect("This JSON serialization should not fail");
326            let golden_serialized = serde_json::to_string(&golden_value())
327                .expect("This JSON serialization should not fail");
328            assert_eq!(golden_serialized, serialized);
329        }
330    }
331}