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) => {
111                concatenation_proof.verify(msg, avk, parameters)
112            }
113            #[cfg(feature = "future_snark")]
114            AggregateSignature::Future => Err(anyhow!(
115                AggregateSignatureError::UnsupportedProofSystem(self.into())
116            )),
117        }
118    }
119
120    /// Batch verify a set of aggregate signatures
121    pub fn batch_verify(
122        stm_signatures: &[Self],
123        msgs: &[Vec<u8>],
124        avks: &[AggregateVerificationKey<D>],
125        parameters: &[Parameters],
126    ) -> StmResult<()> {
127        let stm_signatures: HashMap<AggregateSignatureType, Vec<Self>> =
128            stm_signatures.iter().fold(HashMap::new(), |mut acc, sig| {
129                acc.entry(sig.into()).or_default().push(sig.clone());
130                acc
131            });
132        stm_signatures.into_iter().try_for_each(
133            |(aggregate_signature_type, aggregate_signatures)| match aggregate_signature_type {
134                AggregateSignatureType::Concatenation => {
135                    let aggregate_signatures_length = aggregate_signatures.len();
136                    let concatenation_proofs = aggregate_signatures
137                        .into_iter()
138                        .filter_map(|s| s.to_concatenation_proof().cloned())
139                        .collect::<Vec<_>>();
140                    if concatenation_proofs.len() != aggregate_signatures_length {
141                        return Err(anyhow!(AggregateSignatureError::BatchInvalid));
142                    }
143
144                    ConcatenationProof::batch_verify(&concatenation_proofs, msgs, avks, parameters)
145                }
146                #[cfg(feature = "future_snark")]
147                AggregateSignatureType::Future => Err(anyhow!(
148                    AggregateSignatureError::UnsupportedProofSystem(aggregate_signature_type)
149                )),
150            },
151        )
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_snark")]
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]) -> StmResult<Self> {
175        let proof_type_byte = bytes.first().ok_or(AggregateSignatureError::SerializationError)?;
176        let proof_bytes = &bytes[1..];
177        let proof_type = AggregateSignatureType::from_byte_encoding_prefix(*proof_type_byte)
178            .ok_or(AggregateSignatureError::SerializationError)?;
179        match proof_type {
180            AggregateSignatureType::Concatenation => Ok(AggregateSignature::Concatenation(
181                ConcatenationProof::from_bytes(proof_bytes)?,
182            )),
183            #[cfg(feature = "future_snark")]
184            AggregateSignatureType::Future => Ok(AggregateSignature::Future),
185        }
186    }
187
188    /// If the aggregate signature is a concatenation proof, return it.
189    pub fn to_concatenation_proof(&self) -> Option<&ConcatenationProof<D>> {
190        match self {
191            AggregateSignature::Concatenation(proof) => Some(proof),
192            #[cfg(feature = "future_snark")]
193            AggregateSignature::Future => None,
194        }
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    mod aggregate_signature_type_golden {
203        use super::*;
204
205        #[test]
206        fn golden_bytes_encoding_prefix() {
207            assert_eq!(
208                0u8,
209                AggregateSignatureType::Concatenation.get_byte_encoding_prefix()
210            );
211            assert_eq!(
212                AggregateSignatureType::from_byte_encoding_prefix(0u8),
213                Some(AggregateSignatureType::Concatenation)
214            );
215        }
216    }
217
218    mod aggregate_signature_golden_concatenation {
219        use rand_chacha::ChaCha20Rng;
220        use rand_core::SeedableRng;
221
222        use super::{AggregateSignature, AggregateSignatureType};
223        use crate::{
224            Clerk, ClosedKeyRegistration, KeyRegistration, MithrilMembershipDigest, Parameters,
225            Signer,
226            signature_scheme::{BlsSigningKey, BlsVerificationKeyProofOfPossession},
227        };
228
229        type D = MithrilMembershipDigest;
230
231        const GOLDEN_JSON: &str = r#"
232        {
233            "signatures": [
234                [
235                {
236                    "sigma": [
237                    149, 157, 201, 187, 140, 54, 0, 128, 209, 88, 16, 203, 61, 78, 77, 98,
238                    161, 133, 58, 152, 29, 74, 217, 113, 64, 100, 10, 161, 186, 167, 133,
239                    114, 211, 153, 218, 56, 223, 84, 105, 242, 41, 54, 224, 170, 208, 185,
240                    126, 83
241                    ],
242                    "indexes": [1, 4, 5, 8],
243                    "signer_index": 0
244                },
245                [
246                    [
247                    143, 161, 255, 48, 78, 57, 204, 220, 25, 221, 164, 252, 248, 14, 56,
248                    126, 186, 135, 228, 188, 145, 181, 52, 200, 97, 99, 213, 46, 0, 199,
249                    193, 89, 187, 88, 29, 135, 173, 244, 86, 36, 83, 54, 67, 164, 6, 137,
250                    94, 72, 6, 105, 128, 128, 93, 48, 176, 11, 4, 246, 138, 48, 180, 133,
251                    90, 142, 192, 24, 193, 111, 142, 31, 76, 111, 110, 234, 153, 90, 208,
252                    192, 31, 124, 95, 102, 49, 158, 99, 52, 220, 165, 94, 251, 68, 69,
253                    121, 16, 224, 194
254                    ],
255                    1
256                ]
257                ],
258                [
259                {
260                    "sigma": [
261                    149, 169, 22, 201, 216, 97, 163, 188, 115, 210, 217, 236, 233, 161,
262                    201, 13, 42, 132, 12, 63, 5, 31, 120, 22, 78, 177, 125, 134, 208, 205,
263                    73, 58, 247, 141, 59, 62, 187, 81, 213, 30, 153, 218, 41, 42, 110,
264                    156, 161, 205
265                    ],
266                    "indexes": [0, 3, 6],
267                    "signer_index": 1
268                },
269                [
270                    [
271                    145, 56, 175, 32, 122, 187, 214, 226, 251, 148, 88, 9, 1, 103, 159,
272                    146, 80, 166, 107, 243, 251, 236, 41, 28, 111, 128, 207, 164, 132,
273                    147, 228, 83, 246, 228, 170, 68, 89, 78, 60, 28, 123, 130, 88, 234,
274                    38, 97, 42, 65, 1, 100, 53, 18, 78, 131, 8, 61, 122, 131, 238, 84,
275                    233, 223, 154, 118, 118, 73, 28, 27, 101, 78, 80, 233, 123, 206, 220,
276                    174, 134, 205, 71, 110, 112, 180, 97, 98, 0, 113, 69, 145, 231, 168,
277                    43, 173, 172, 56, 104, 208
278                    ],
279                    1
280                ]
281                ]
282            ],
283            "batch_proof": { "values": [], "indices": [0, 1], "hasher": null }
284        }
285        "#;
286
287        fn golden_value() -> AggregateSignature<D> {
288            let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
289            let msg = [0u8; 16];
290            let params = Parameters {
291                m: 10,
292                k: 5,
293                phi_f: 0.8,
294            };
295            let sk_1 = BlsSigningKey::generate(&mut rng);
296            let sk_2 = BlsSigningKey::generate(&mut rng);
297            let pk_1 = BlsVerificationKeyProofOfPossession::from(&sk_1);
298            let pk_2 = BlsVerificationKeyProofOfPossession::from(&sk_2);
299            let mut key_reg = KeyRegistration::init();
300            key_reg.register(1, pk_1).unwrap();
301            key_reg.register(1, pk_2).unwrap();
302            let closed_key_reg: ClosedKeyRegistration<D> = key_reg.close();
303            let clerk = Clerk::new_clerk_from_closed_key_registration(&params, &closed_key_reg);
304            let signer_1 = Signer::set_signer(0, 1, params, sk_1, pk_1.vk, closed_key_reg.clone());
305            let signer_2 = Signer::set_signer(1, 1, params, sk_2, pk_2.vk, closed_key_reg);
306            let signature_1 = signer_1.sign(&msg).unwrap();
307            let signature_2 = signer_2.sign(&msg).unwrap();
308
309            clerk
310                .aggregate_signatures_with_type(
311                    &[signature_1, signature_2],
312                    &msg,
313                    AggregateSignatureType::Concatenation,
314                )
315                .unwrap()
316        }
317
318        #[test]
319        fn golden_conversions() {
320            let value: AggregateSignature<D> = serde_json::from_str(GOLDEN_JSON)
321                .expect("This JSON deserialization should not fail");
322
323            let serialized =
324                serde_json::to_string(&value).expect("This JSON serialization should not fail");
325            let golden_serialized = serde_json::to_string(&golden_value())
326                .expect("This JSON serialization should not fail");
327            assert_eq!(golden_serialized, serialized);
328        }
329    }
330}