Skip to main content

storage_proofs_post/fallback/
vanilla.rs

1use std::collections::BTreeSet;
2use std::marker::PhantomData;
3
4use anyhow::ensure;
5use blstrs::Scalar as Fr;
6use byteorder::{ByteOrder, LittleEndian};
7use filecoin_hashers::{Domain, HashFunction, Hasher};
8use generic_array::typenum::Unsigned;
9use log::{error, trace};
10use rayon::prelude::{
11    IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, ParallelIterator,
12};
13use serde::{de::DeserializeOwned, Deserialize, Serialize};
14use sha2::{Digest, Sha256};
15use storage_proofs_core::{
16    api_version::ApiVersion,
17    error::{Error, Result},
18    merkle::{MerkleProof, MerkleProofTrait, MerkleTreeTrait, MerkleTreeWrapper},
19    parameter_cache::ParameterSetMetadata,
20    proof::ProofScheme,
21    sector::SectorId,
22    util::{default_rows_to_discard, NODE_SIZE},
23};
24
25use super::utils::get_challenge_index;
26
27#[derive(Debug, Clone)]
28pub struct SetupParams {
29    /// Size of the sector in bytes.
30    pub sector_size: u64,
31    /// Number of challenges per sector.
32    pub challenge_count: usize,
33    /// Number of challenged sectors.
34    pub sector_count: usize,
35    pub api_version: ApiVersion,
36}
37
38#[derive(Debug, Clone)]
39pub struct PublicParams {
40    /// Size of the sector in bytes.
41    pub sector_size: u64,
42    /// Number of challenges per sector.
43    pub challenge_count: usize,
44    /// Number of challenged sectors.
45    pub sector_count: usize,
46    pub api_version: ApiVersion,
47}
48
49#[derive(Debug, Default)]
50pub struct ChallengeRequirements {
51    /// The sum of challenges across all challenged sectors. (even across partitions)
52    pub minimum_challenge_count: usize,
53}
54
55impl ParameterSetMetadata for PublicParams {
56    fn identifier(&self) -> String {
57        format!(
58            "FallbackPoSt::PublicParams{{sector_size: {}, challenge_count: {}, sector_count: {}}}",
59            self.sector_size(),
60            self.challenge_count,
61            self.sector_count,
62        )
63    }
64
65    fn sector_size(&self) -> u64 {
66        self.sector_size
67    }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct PublicInputs<T: Domain> {
72    #[serde(bound = "")]
73    pub randomness: T,
74    #[serde(bound = "")]
75    pub prover_id: T,
76    #[serde(bound = "")]
77    pub sectors: Vec<PublicSector<T>>,
78    /// Partition index
79    pub k: Option<usize>,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct PublicSector<T: Domain> {
84    pub id: SectorId,
85    #[serde(bound = "")]
86    pub comm_r: T,
87}
88
89#[derive(Debug)]
90pub struct PrivateSector<'a, Tree: MerkleTreeTrait> {
91    pub tree: &'a MerkleTreeWrapper<
92        Tree::Hasher,
93        Tree::Store,
94        Tree::Arity,
95        Tree::SubTreeArity,
96        Tree::TopTreeArity,
97    >,
98    pub comm_c: <Tree::Hasher as Hasher>::Domain,
99    pub comm_r_last: <Tree::Hasher as Hasher>::Domain,
100}
101
102#[derive(Debug)]
103pub struct PrivateInputs<'a, Tree: MerkleTreeTrait> {
104    pub sectors: &'a [PrivateSector<'a, Tree>],
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct Proof<P: MerkleProofTrait> {
109    #[serde(bound(
110        serialize = "SectorProof<P>: Serialize",
111        deserialize = "SectorProof<P>: Deserialize<'de>"
112    ))]
113    pub sectors: Vec<SectorProof<P>>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct SectorProof<Proof: MerkleProofTrait> {
118    #[serde(bound(
119        serialize = "MerkleProof<Proof::Hasher, Proof::Arity, Proof::SubTreeArity, Proof::TopTreeArity>: Serialize",
120        deserialize = "MerkleProof<Proof::Hasher, Proof::Arity, Proof::SubTreeArity, Proof::TopTreeArity>: DeserializeOwned"
121    ))]
122    pub inclusion_proofs:
123        Vec<MerkleProof<Proof::Hasher, Proof::Arity, Proof::SubTreeArity, Proof::TopTreeArity>>,
124    pub comm_c: <Proof::Hasher as Hasher>::Domain,
125    pub comm_r_last: <Proof::Hasher as Hasher>::Domain,
126}
127
128impl<P: MerkleProofTrait> SectorProof<P> {
129    pub fn leafs(&self) -> Vec<<P::Hasher as Hasher>::Domain> {
130        self.inclusion_proofs
131            .iter()
132            .map(MerkleProofTrait::leaf)
133            .collect()
134    }
135
136    pub fn comm_r_last(&self) -> <P::Hasher as Hasher>::Domain {
137        self.inclusion_proofs[0].root()
138    }
139
140    pub fn commitments(&self) -> Vec<<P::Hasher as Hasher>::Domain> {
141        self.inclusion_proofs
142            .iter()
143            .map(MerkleProofTrait::root)
144            .collect()
145    }
146
147    #[allow(clippy::type_complexity)]
148    pub fn paths(&self) -> Vec<Vec<(Vec<<P::Hasher as Hasher>::Domain>, usize)>> {
149        self.inclusion_proofs
150            .iter()
151            .map(MerkleProofTrait::path)
152            .collect()
153    }
154
155    pub fn as_options(&self) -> Vec<Vec<(Vec<Option<Fr>>, Option<usize>)>> {
156        self.inclusion_proofs
157            .iter()
158            .map(MerkleProofTrait::as_options)
159            .collect()
160    }
161
162    // Returns a read-only reference.
163    pub fn inclusion_proofs(
164        &self,
165    ) -> &Vec<MerkleProof<P::Hasher, P::Arity, P::SubTreeArity, P::TopTreeArity>> {
166        &self.inclusion_proofs
167    }
168}
169
170#[derive(Debug, Clone)]
171pub struct FallbackPoSt<'a, Tree>
172where
173    Tree: MerkleTreeTrait,
174{
175    _t: PhantomData<&'a Tree>,
176}
177
178pub fn generate_sector_challenges<T: Domain>(
179    randomness: T,
180    challenge_count: usize,
181    sector_set_len: u64,
182    prover_id: T,
183) -> Result<Vec<u64>> {
184    (0..challenge_count)
185        .map(|n| generate_sector_challenge(randomness, n, sector_set_len, prover_id))
186        .collect()
187}
188
189/// Generate a single sector challenge.
190pub fn generate_sector_challenge<T: Domain>(
191    randomness: T,
192    n: usize,
193    sector_set_len: u64,
194    prover_id: T,
195) -> Result<u64> {
196    let mut hasher = Sha256::new();
197    hasher.update(AsRef::<[u8]>::as_ref(&prover_id));
198    hasher.update(AsRef::<[u8]>::as_ref(&randomness));
199    hasher.update(&n.to_le_bytes()[..]);
200
201    let hash = hasher.finalize();
202
203    let sector_challenge = LittleEndian::read_u64(&hash[..8]);
204    let sector_index = sector_challenge % sector_set_len;
205
206    Ok(sector_index)
207}
208
209/// Generate all challenged leaf ranges for a single sector, such that the range fits into the sector.
210pub fn generate_leaf_challenges<T: Domain>(
211    pub_params: &PublicParams,
212    randomness: T,
213    sector_id: u64,
214    challenge_count: usize,
215) -> Vec<u64> {
216    let mut challenges = Vec::with_capacity(challenge_count);
217
218    let mut hasher = Sha256::new();
219    hasher.update(AsRef::<[u8]>::as_ref(&randomness));
220    hasher.update(&sector_id.to_le_bytes()[..]);
221
222    for challenge_index in 0..challenge_count {
223        let challenge =
224            generate_leaf_challenge_inner::<T>(hasher.clone(), pub_params, challenge_index as u64);
225        challenges.push(challenge)
226    }
227
228    challenges
229}
230
231/// Generates challenge, such that the range fits into the sector.
232pub fn generate_leaf_challenge<T: Domain>(
233    pub_params: &PublicParams,
234    randomness: T,
235    sector_id: u64,
236    leaf_challenge_index: u64,
237) -> u64 {
238    let mut hasher = Sha256::new();
239    hasher.update(AsRef::<[u8]>::as_ref(&randomness));
240    hasher.update(&sector_id.to_le_bytes()[..]);
241
242    generate_leaf_challenge_inner::<T>(hasher, pub_params, leaf_challenge_index)
243}
244
245pub fn generate_leaf_challenge_inner<T: Domain>(
246    mut hasher: Sha256,
247    pub_params: &PublicParams,
248    leaf_challenge_index: u64,
249) -> u64 {
250    hasher.update(&leaf_challenge_index.to_le_bytes()[..]);
251    let hash = hasher.finalize();
252
253    let leaf_challenge = LittleEndian::read_u64(&hash[..8]);
254
255    leaf_challenge % (pub_params.sector_size / NODE_SIZE as u64)
256}
257
258// Generates a single vanilla proof, given the private inputs and sector challenges.
259pub fn vanilla_proof<Tree: MerkleTreeTrait>(
260    sector_id: SectorId,
261    priv_inputs: &PrivateInputs<'_, Tree>,
262    challenges: &[u64],
263) -> Result<Proof<Tree::Proof>> {
264    ensure!(
265        priv_inputs.sectors.len() == 1,
266        "vanilla_proof called with multiple sector proofs"
267    );
268
269    let priv_sector = &priv_inputs.sectors[0];
270    let comm_c = priv_sector.comm_c;
271    let comm_r_last = priv_sector.comm_r_last;
272    let tree = priv_sector.tree;
273
274    let tree_leafs = tree.leafs();
275    let rows_to_discard = default_rows_to_discard(tree_leafs, Tree::Arity::to_usize());
276
277    trace!(
278        "Generating proof for tree leafs {} and arity {} for sector {}",
279        tree_leafs,
280        Tree::Arity::to_usize(),
281        sector_id,
282    );
283
284    let inclusion_proofs = (0..challenges.len())
285        .into_par_iter()
286        .map(|challenged_leaf_index| {
287            let challenged_leaf = challenges[challenged_leaf_index];
288            let proof = tree.gen_cached_proof(challenged_leaf as usize, Some(rows_to_discard))?;
289
290            ensure!(
291                proof.validate(challenged_leaf as usize) && proof.root() == priv_sector.comm_r_last,
292                "Generated vanilla proof for sector {} is invalid",
293                sector_id
294            );
295
296            Ok(proof)
297        })
298        .collect::<Result<Vec<_>>>()?;
299
300    Ok(Proof {
301        sectors: vec![SectorProof {
302            inclusion_proofs,
303            comm_c,
304            comm_r_last,
305        }],
306    })
307}
308
309impl<'a, Tree: 'a + MerkleTreeTrait> ProofScheme<'a> for FallbackPoSt<'a, Tree> {
310    type PublicParams = PublicParams;
311    type SetupParams = SetupParams;
312    type PublicInputs = PublicInputs<<Tree::Hasher as Hasher>::Domain>;
313    type PrivateInputs = PrivateInputs<'a, Tree>;
314    type Proof = Proof<Tree::Proof>;
315    type Requirements = ChallengeRequirements;
316
317    fn setup(sp: &Self::SetupParams) -> Result<Self::PublicParams> {
318        Ok(PublicParams {
319            sector_size: sp.sector_size,
320            challenge_count: sp.challenge_count,
321            sector_count: sp.sector_count,
322            api_version: sp.api_version,
323        })
324    }
325
326    fn prove<'b>(
327        pub_params: &'b Self::PublicParams,
328        pub_inputs: &'b Self::PublicInputs,
329        priv_inputs: &'b Self::PrivateInputs,
330    ) -> Result<Self::Proof> {
331        let proofs = Self::prove_all_partitions(pub_params, pub_inputs, priv_inputs, 1)?;
332        let k = pub_inputs.k.unwrap_or(0);
333        // Because partition proofs require a common setup, the general ProofScheme implementation,
334        // which makes use of `ProofScheme::prove` cannot be used here. Instead, we need to prove all
335        // partitions in one pass, as implemented by `prove_all_partitions` below.
336        assert!(
337            k < 1,
338            "It is a programmer error to call StackedDrg::prove with more than one partition."
339        );
340
341        Ok(proofs[k].to_owned())
342    }
343
344    fn prove_all_partitions<'b>(
345        pub_params: &'b Self::PublicParams,
346        pub_inputs: &'b Self::PublicInputs,
347        priv_inputs: &'b Self::PrivateInputs,
348        partition_count: usize,
349    ) -> Result<Vec<Self::Proof>> {
350        ensure!(
351            priv_inputs.sectors.len() == pub_inputs.sectors.len(),
352            "inconsistent number of private and public sectors {} != {}",
353            priv_inputs.sectors.len(),
354            pub_inputs.sectors.len(),
355        );
356
357        let num_sectors_per_chunk = pub_params.sector_count;
358        let num_sectors = pub_inputs.sectors.len();
359
360        ensure!(
361            num_sectors <= partition_count * num_sectors_per_chunk,
362            "cannot prove the provided number of sectors: {} > {} * {}",
363            num_sectors,
364            partition_count,
365            num_sectors_per_chunk,
366        );
367
368        let mut partition_proofs = Vec::new();
369
370        // Use `BTreeSet` so failure result will be canonically ordered (sorted).
371        let mut faulty_sectors = BTreeSet::new();
372
373        for (j, (pub_sectors_chunk, priv_sectors_chunk)) in pub_inputs
374            .sectors
375            .chunks(num_sectors_per_chunk)
376            .zip(priv_inputs.sectors.chunks(num_sectors_per_chunk))
377            .enumerate()
378        {
379            let (mut proofs, mut faults) = pub_sectors_chunk
380                .par_iter()
381                .zip(priv_sectors_chunk.par_iter())
382                .enumerate()
383                .map(|(i, (pub_sector, priv_sector))| {
384                    let sector_id = pub_sector.id;
385                    let tree = priv_sector.tree;
386                    let tree_leafs = tree.leafs();
387                    let rows_to_discard =
388                        default_rows_to_discard(tree_leafs, Tree::Arity::to_usize());
389
390                    trace!(
391                        "Generating proof for tree leafs {} and arity {} for sector {}",
392                        tree_leafs,
393                        Tree::Arity::to_usize(),
394                        sector_id,
395                    );
396
397                    // avoid rehashing fixed inputs
398                    let mut challenge_hasher = Sha256::new();
399                    challenge_hasher.update(AsRef::<[u8]>::as_ref(&pub_inputs.randomness));
400                    challenge_hasher.update(&u64::from(sector_id).to_le_bytes()[..]);
401
402                    let (inclusion_proofs, faults) = (0..pub_params.challenge_count)
403                        .into_par_iter()
404                        .fold(
405                            || (Vec::new(), BTreeSet::new()),
406                            |(mut inclusion_proofs, mut faults), n| {
407                                let sector_index = j * num_sectors_per_chunk + i;
408                                let challenge_index = get_challenge_index(
409                                    pub_params.api_version,
410                                    sector_index,
411                                    pub_params.challenge_count,
412                                    n,
413                                );
414                                let challenged_leaf = generate_leaf_challenge_inner::<
415                                    <Tree::Hasher as Hasher>::Domain,
416                                >(
417                                    challenge_hasher.clone(),
418                                    pub_params,
419                                    challenge_index,
420                                );
421                                let proof = tree.gen_cached_proof(
422                                    challenged_leaf as usize,
423                                    Some(rows_to_discard),
424                                );
425
426                                match proof {
427                                    Ok(proof) => {
428                                        if proof.validate(challenged_leaf as usize)
429                                            && proof.root() == priv_sector.comm_r_last
430                                            && pub_sector.comm_r
431                                                == <Tree::Hasher as Hasher>::Function::hash2(
432                                                    &priv_sector.comm_c,
433                                                    &priv_sector.comm_r_last,
434                                                )
435                                        {
436                                            inclusion_proofs.push(proof);
437                                        } else {
438                                            error!("Found faulty sector: {:?}", sector_id);
439                                            faults.insert(sector_id);
440                                        }
441                                    }
442                                    Err(err) => {
443                                        error!("Found faulty sector: {:?}: {:?}", sector_id, err);
444                                        faults.insert(sector_id);
445                                    }
446                                }
447                                (inclusion_proofs, faults)
448                            },
449                        )
450                        .reduce(
451                            || (Vec::new(), BTreeSet::new()),
452                            |(mut inclusion_proofs, mut faults), (p, f)| {
453                                inclusion_proofs.extend(p);
454                                faults.extend(f);
455                                (inclusion_proofs, faults)
456                            },
457                        );
458
459                    (
460                        SectorProof {
461                            inclusion_proofs,
462                            comm_c: priv_sector.comm_c,
463                            comm_r_last: priv_sector.comm_r_last,
464                        },
465                        faults,
466                    )
467                })
468                .fold(
469                    || (Vec::new(), BTreeSet::new()),
470                    |(mut sector_proofs, mut sector_faults), (sector_proof, mut faults)| {
471                        sector_faults.append(&mut faults);
472                        sector_proofs.push(sector_proof);
473                        (sector_proofs, sector_faults)
474                    },
475                )
476                .reduce(
477                    || (Vec::new(), BTreeSet::new()),
478                    |(mut sector_proofs, mut sector_faults), (proofs, mut faults)| {
479                        sector_proofs.extend(proofs);
480                        sector_faults.append(&mut faults);
481                        (sector_proofs, sector_faults)
482                    },
483                );
484
485            // If there were less than the required number of sectors provided, we duplicate the last one
486            // to pad the proof out, such that it works in the circuit part.
487            while proofs.len() < num_sectors_per_chunk {
488                proofs.push(proofs[proofs.len() - 1].clone());
489            }
490
491            partition_proofs.push(Proof { sectors: proofs });
492            faulty_sectors.append(&mut faults);
493        }
494
495        if faulty_sectors.is_empty() {
496            Ok(partition_proofs)
497        } else {
498            trace!("Faulty sectors being reported {:?}", faulty_sectors);
499            Err(Error::FaultySectors(faulty_sectors.into_iter().collect()).into())
500        }
501    }
502
503    fn verify_all_partitions(
504        pub_params: &Self::PublicParams,
505        pub_inputs: &Self::PublicInputs,
506        partition_proofs: &[Self::Proof],
507    ) -> Result<bool> {
508        let num_sectors_per_chunk = pub_params.sector_count;
509        let num_sectors = pub_inputs.sectors.len();
510
511        ensure!(
512            num_sectors <= num_sectors_per_chunk * partition_proofs.len(),
513            "inconsistent number of sectors: {} > {} * {}",
514            num_sectors,
515            num_sectors_per_chunk,
516            partition_proofs.len(),
517        );
518
519        for (j, (proof, pub_sectors_chunk)) in partition_proofs
520            .iter()
521            .zip(pub_inputs.sectors.chunks(num_sectors_per_chunk))
522            .enumerate()
523        {
524            let is_valid = Self::verify(
525                pub_params,
526                &PublicInputs {
527                    randomness: pub_inputs.randomness,
528                    prover_id: pub_inputs.prover_id,
529                    sectors: pub_sectors_chunk.to_vec(),
530                    k: Some(j),
531                },
532                proof,
533            )?;
534
535            if !is_valid {
536                return Ok(false);
537            }
538        }
539        Ok(true)
540    }
541
542    fn with_partition(mut pub_in: Self::PublicInputs, k: Option<usize>) -> Self::PublicInputs {
543        pub_in.k = k;
544        pub_in
545    }
546
547    fn satisfies_requirements(
548        public_params: &Self::PublicParams,
549        requirements: &Self::Requirements,
550        partitions: usize,
551    ) -> bool {
552        let checked = partitions * public_params.sector_count;
553
554        assert_eq!(
555            partitions.checked_mul(public_params.sector_count),
556            Some(checked)
557        );
558        assert_eq!(
559            checked.checked_mul(public_params.challenge_count),
560            Some(checked * public_params.challenge_count)
561        );
562
563        checked * public_params.challenge_count >= requirements.minimum_challenge_count
564    }
565
566    fn verify(
567        pub_params: &Self::PublicParams,
568        pub_inputs: &Self::PublicInputs,
569        partition_proof: &Self::Proof,
570    ) -> Result<bool> {
571        ensure!(
572            pub_inputs.k.is_some(),
573            "must be called with a partition index"
574        );
575        let partition_index = pub_inputs.k.expect("prechecked");
576        let challenge_count = pub_params.challenge_count;
577        let num_sectors_per_chunk = pub_params.sector_count;
578
579        let j = partition_index;
580        let proof = partition_proof;
581        let pub_sectors_chunk = &pub_inputs.sectors;
582
583        ensure!(
584            pub_sectors_chunk.len() <= num_sectors_per_chunk,
585            "inconsistent number of public sectors: {} > {}",
586            pub_sectors_chunk.len(),
587            num_sectors_per_chunk,
588        );
589        ensure!(
590            proof.sectors.len() == num_sectors_per_chunk,
591            "invalid number of sectors in the partition proof {}: {} != {}",
592            j,
593            proof.sectors.len(),
594            num_sectors_per_chunk,
595        );
596
597        let is_valid = pub_sectors_chunk
598            .par_iter()
599            .zip(proof.sectors.par_iter())
600            .enumerate()
601            .map(|(i, (pub_sector, sector_proof))| {
602                let sector_id = pub_sector.id;
603                let comm_r = &pub_sector.comm_r;
604                let comm_c = sector_proof.comm_c;
605                let inclusion_proofs = &sector_proof.inclusion_proofs;
606
607                trace!("Verifying inclusion proofs for sector {}", sector_id);
608
609                // Verify that H(Comm_c || Comm_r_last) == Comm_R
610
611                // comm_r_last is the root of the proof
612                let comm_r_last = inclusion_proofs[0].root();
613
614                if AsRef::<[u8]>::as_ref(&<Tree::Hasher as Hasher>::Function::hash2(
615                    &comm_c,
616                    &comm_r_last,
617                )) != AsRef::<[u8]>::as_ref(comm_r)
618                {
619                    error!("hash(comm_c || comm_r_last) != comm_r: {:?}", sector_id);
620                    return Ok(false);
621                }
622
623                ensure!(
624                    challenge_count == inclusion_proofs.len(),
625                    "unexpected number of inclusion proofs: {} != {}",
626                    challenge_count,
627                    inclusion_proofs.len()
628                );
629
630                // avoid rehashing fixed inputs
631                let mut challenge_hasher = Sha256::new();
632                challenge_hasher.update(AsRef::<[u8]>::as_ref(&pub_inputs.randomness));
633                challenge_hasher.update(&u64::from(sector_id).to_le_bytes()[..]);
634
635                let is_valid_list = inclusion_proofs
636                    .par_iter()
637                    .enumerate()
638                    .map(|(n, inclusion_proof)| -> Result<bool> {
639                        let sector_index = j * num_sectors_per_chunk + i;
640                        let challenge_index = get_challenge_index(
641                            pub_params.api_version,
642                            sector_index,
643                            pub_params.challenge_count,
644                            n,
645                        );
646                        let challenged_leaf =
647                            generate_leaf_challenge_inner::<<Tree::Hasher as Hasher>::Domain>(
648                                challenge_hasher.clone(),
649                                pub_params,
650                                challenge_index,
651                            );
652
653                        // validate all comm_r_lasts match
654                        if inclusion_proof.root() != comm_r_last {
655                            error!("inclusion proof root != comm_r_last: {:?}", sector_id);
656                            return Ok(false);
657                        }
658
659                        // validate the path length
660                        let expected_path_length = inclusion_proof
661                            .expected_len(pub_params.sector_size as usize / NODE_SIZE);
662
663                        if expected_path_length != inclusion_proof.path().len() {
664                            error!("wrong path length: {:?}", sector_id);
665                            return Ok(false);
666                        }
667
668                        if !inclusion_proof.validate(challenged_leaf as usize) {
669                            error!("invalid inclusion proof: {:?}", sector_id);
670                            return Ok(false);
671                        }
672                        Ok(true)
673                    })
674                    .collect::<Result<Vec<bool>>>()?;
675
676                Ok(is_valid_list.into_iter().all(|v| v))
677            })
678            .reduce(
679                || Ok(true),
680                |all_valid, is_valid| Ok(all_valid? && is_valid?),
681            )?;
682        if !is_valid {
683            return Ok(false);
684        }
685        Ok(true)
686    }
687}