mithril_stm/protocol/aggregate_signature/
signature.rs

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