mithril_stm/protocol/aggregate_signature/
signature.rs

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