Skip to main content

aven_protocol/artifact/
catalog.rs

1use super::codec::*;
2use super::{EncryptedArtifact, EncryptedChunk};
3use super::{Error, Result};
4use crate::{context::LocalSharedStatePackageContext, record as crypto};
5
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct Artifact {
8    pub total: u64,
9    pub aggregate: [u8; 32],
10    pub chunks: Vec<Chunk>,
11}
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct Chunk {
15    pub length: u64,
16    pub hash: [u8; 32],
17    pub nonce: [u8; 24],
18}
19
20impl Artifact {
21    pub fn from_encrypted(artifact: &EncryptedArtifact<'_>) -> Result<Self> {
22        let chunks = artifact
23            .chunks
24            .iter()
25            .map(|chunk| {
26                let (header, _) =
27                    crypto::split_record(&chunk.record).map_err(|_| Error::Invalid)?;
28                Ok(Chunk {
29                    length: number(chunk.record.len())?,
30                    hash: chunk.record_commitment,
31                    nonce: header[174..198].try_into().map_err(|_| Error::Invalid)?,
32                })
33            })
34            .collect::<Result<Vec<_>>>()?;
35        Ok(Self {
36            total: artifact.total_plaintext_bytes,
37            aggregate: artifact.aggregate_commitment,
38            chunks,
39        })
40    }
41    pub fn shape(&self, maximum: u64, image: bool) -> Result<()> {
42        bound(self.total, maximum)?;
43        valid(!image || self.total > 0)?;
44        valid(number(self.chunks.len())? == count(self.total))?;
45        for (i, chunk) in self.chunks.iter().enumerate() {
46            let offset = number(i)?.checked_mul(CHUNK).ok_or(Error::Invalid)?;
47            let expected = self
48                .total
49                .checked_sub(offset)
50                .ok_or(Error::Invalid)?
51                .min(CHUNK);
52            valid(chunk.length == add(expected, 222)?)?;
53        }
54        Ok(())
55    }
56    pub fn write(&self, out: &mut Vec<u8>) -> Result<()> {
57        u64_bytes(out, self.total);
58        out.extend_from_slice(&self.aggregate);
59        u64_bytes(out, number(self.chunks.len())?);
60        for (i, chunk) in self.chunks.iter().enumerate() {
61            u64_bytes(out, number(i)?);
62            u64_bytes(out, chunk.length);
63            out.extend_from_slice(&chunk.hash);
64            out.extend_from_slice(&chunk.nonce);
65        }
66        Ok(())
67    }
68    pub fn read(r: &mut Reader<'_>, maximum: u64, image: bool) -> Result<Self> {
69        let total = r.u64()?;
70        bound(total, maximum)?;
71        let aggregate = r.array()?;
72        let n = r.u64()?;
73        valid(n == count(total))?;
74        let mut chunks = Vec::new();
75        for index in 0..n {
76            valid(r.u64()? == index)?;
77            chunks.push(Chunk {
78                length: r.u64()?,
79                hash: r.array()?,
80                nonce: r.array()?,
81            });
82        }
83        let value = Self {
84            total,
85            aggregate,
86            chunks,
87        };
88        value.shape(maximum, image)?;
89        Ok(value)
90    }
91    #[allow(clippy::too_many_arguments)]
92    pub fn verify(
93        &self,
94        records: &[Vec<u8>],
95        context: LocalSharedStatePackageContext,
96        stream: [u8; 32],
97        id: [u8; 32],
98        family: u8,
99        class: u8,
100    ) -> Result<()> {
101        valid(records.len() == self.chunks.len())?;
102        let mut digest = sha2::Sha256::new();
103        use sha2::Digest;
104        for (index, record) in records.iter().enumerate() {
105            self.verify_chunk(record, index, context, stream, id, family, class)?;
106            digest.update(record);
107        }
108        valid(<[u8; 32]>::from(digest.finalize()) == self.aggregate)
109    }
110    #[allow(clippy::too_many_arguments)]
111    pub fn verify_chunk(
112        &self,
113        record: &[u8],
114        index: usize,
115        context: LocalSharedStatePackageContext,
116        stream: [u8; 32],
117        id: [u8; 32],
118        family: u8,
119        class: u8,
120    ) -> Result<()> {
121        let chunk = self.chunks.get(index).ok_or(Error::Invalid)?;
122        valid(number(record.len())? == chunk.length && crate::codec::hash(record) == chunk.hash)?;
123        let (header, _) = crypto::split_record(record).map_err(|_| Error::Invalid)?;
124        let nonce = crypto::validate_chunk_header(
125            header,
126            context,
127            stream,
128            id,
129            family,
130            class,
131            u32::try_from(index).map_err(|_| Error::Invalid)?,
132            u32::try_from(self.chunks.len()).map_err(|_| Error::Invalid)?,
133            self.total,
134        )
135        .map_err(|_| Error::Invalid)?;
136        valid(nonce == chunk.nonce)
137    }
138    pub fn encrypted<'a>(&self, records: &'a [Vec<u8>]) -> EncryptedArtifact<'a> {
139        EncryptedArtifact {
140            total_plaintext_bytes: self.total,
141            aggregate_commitment: self.aggregate,
142            chunks: records
143                .iter()
144                .zip(&self.chunks)
145                .map(|(record, chunk)| EncryptedChunk {
146                    record_commitment: chunk.hash,
147                    record: record.as_slice().into(),
148                })
149                .collect(),
150        }
151    }
152}
153
154#[derive(Clone, Debug, PartialEq, Eq)]
155pub struct Declaration {
156    pub count: u64,
157    pub length: u64,
158    pub hash: [u8; 32],
159    // One SHA-256 per transfer slice; the slice count follows from the length.
160    pub slices: Vec<[u8; 32]>,
161}
162impl Declaration {
163    pub fn new(bytes: &[u8], class: u8) -> Result<Self> {
164        Ok(Self {
165            count: number(read_stream(bytes, class)?.len())?,
166            length: number(bytes.len())?,
167            hash: crate::codec::hash(bytes),
168            slices: bytes.chunks(size(CHUNK)?).map(crate::codec::hash).collect(),
169        })
170    }
171    pub fn write(&self, out: &mut Vec<u8>) {
172        u64_bytes(out, self.count);
173        u64_bytes(out, self.length);
174        out.extend_from_slice(&self.hash);
175        for slice in &self.slices {
176            out.extend_from_slice(slice);
177        }
178    }
179    pub fn read(r: &mut Reader<'_>) -> Result<Self> {
180        let n = r.u64()?;
181        let length = r.u64()?;
182        bound(n, RECORD_LIMIT)?;
183        bound(length, CATALOG_LIMIT)?;
184        valid(length >= 15)?;
185        let hash = r.array()?;
186        let slices = (0..count(length))
187            .map(|_| r.array())
188            .collect::<Result<_>>()?;
189        Ok(Self {
190            count: n,
191            length,
192            hash,
193            slices,
194        })
195    }
196    pub fn slice_lengths(&self) -> Vec<u64> {
197        (0..count(self.length))
198            .map(|i| (self.length - i * CHUNK).min(CHUNK))
199            .collect()
200    }
201    /// Checks one slice against its own slot. It says nothing about the catalog.
202    pub fn verify_slice(&self, index: usize, bytes: &[u8]) -> Result<()> {
203        valid(
204            self.slice_lengths().get(index) == Some(&number(bytes.len())?)
205                && self.slices.get(index) == Some(&crate::codec::hash(bytes)),
206        )
207    }
208    /// Checks the complete catalog: slices, aggregate, length, framing and count.
209    pub fn verify(&self, bytes: &[u8], class: u8) -> Result<()> {
210        valid(number(bytes.len())? == self.length && crate::codec::hash(bytes) == self.hash)?;
211        valid(Self::new(bytes, class)? == *self)
212    }
213}
214
215#[derive(Clone, Debug, PartialEq, Eq)]
216pub struct Parent {
217    pub workspace: String,
218    pub task: String,
219    pub deleted: bool,
220    pub version: Option<String>,
221    pub protected: bool,
222}
223
224#[derive(Clone, Debug, PartialEq, Eq)]
225pub struct Reference {
226    pub workspace: String,
227    pub task: String,
228    pub reference: String,
229    pub deleted: bool,
230    pub object: Option<[u8; 32]>,
231}
232
233#[derive(Clone, Debug, PartialEq, Eq)]
234pub struct Image {
235    pub id: [u8; 32],
236    // 1 current selected, 2 extra selected. Both require complete bytes.
237    pub selection: u8,
238    pub artifact: Artifact,
239}
240
241#[derive(Clone, Debug, PartialEq, Eq)]
242pub struct Images {
243    pub objects: Vec<Image>,
244    pub parents: Vec<Parent>,
245    pub references: Vec<Reference>,
246}
247
248impl Images {
249    pub fn encode(&self) -> Result<Vec<u8>> {
250        let mut rows = Vec::new();
251        for object in &self.objects {
252            let mut row = vec![1];
253            row.extend_from_slice(&object.id);
254            row.push(object.selection);
255            object.artifact.write(&mut row)?;
256            rows.push(row);
257        }
258        for parent in &self.parents {
259            let mut row = vec![2];
260            text(&mut row, &parent.workspace)?;
261            text(&mut row, &parent.task)?;
262            row.push(u8::from(parent.deleted));
263            row.push(u8::from(parent.protected));
264            row.push(u8::from(parent.version.is_some()));
265            if let Some(version) = &parent.version {
266                text(&mut row, version)?;
267            }
268            rows.push(row);
269        }
270        for reference in &self.references {
271            let mut row = vec![3];
272            text(&mut row, &reference.workspace)?;
273            text(&mut row, &reference.task)?;
274            text(&mut row, &reference.reference)?;
275            row.push(u8::from(reference.deleted));
276            row.push(u8::from(reference.object.is_some()));
277            if let Some(object) = reference.object {
278                row.extend_from_slice(&object);
279            }
280            rows.push(row);
281        }
282        stream(3, &rows)
283    }
284    pub fn decode(bytes: &[u8]) -> Result<Self> {
285        let mut result = Self {
286            objects: Vec::new(),
287            parents: Vec::new(),
288            references: Vec::new(),
289        };
290        let mut previous_kind = 0;
291        let mut total = 0;
292        for row in read_stream(bytes, 3)? {
293            let mut r = Reader(row);
294            let kind = r.byte()?;
295            valid(kind >= previous_kind)?;
296            previous_kind = kind;
297            match kind {
298                1 => {
299                    bound(number(result.objects.len())? + 1, IMAGE_COUNT)?;
300                    let id = r.array()?;
301                    valid(
302                        result
303                            .objects
304                            .last()
305                            .is_none_or(|last: &Image| last.id < id),
306                    )?;
307                    let selection = r.byte()?;
308                    valid(matches!(selection, 1 | 2))?;
309                    let artifact = Artifact::read(&mut r, IMAGE_LIMIT, true)?;
310                    total = add(total, artifact.total)?;
311                    bound(total, STATE_LIMIT)?;
312                    result.objects.push(Image {
313                        id,
314                        selection,
315                        artifact,
316                    });
317                }
318                2 => {
319                    let workspace = r.text()?;
320                    let task = r.text()?;
321                    valid(result.parents.last().is_none_or(|last: &Parent| {
322                        (&last.workspace, &last.task) < (&workspace, &task)
323                    }))?;
324                    let deleted = r.flag()?;
325                    let protected = r.flag()?;
326                    let version = if r.flag()? { Some(r.text()?) } else { None };
327                    valid(version.is_some() || protected)?;
328                    result.parents.push(Parent {
329                        workspace,
330                        task,
331                        deleted,
332                        protected,
333                        version,
334                    });
335                }
336                3 => {
337                    let workspace = r.text()?;
338                    let task = r.text()?;
339                    let reference = r.text()?;
340                    valid(result.references.last().is_none_or(|last: &Reference| {
341                        (&last.workspace, &last.reference) < (&workspace, &reference)
342                    }))?;
343                    let deleted = r.flag()?;
344                    let object = if r.flag()? { Some(r.array()?) } else { None };
345                    result.references.push(Reference {
346                        workspace,
347                        task,
348                        reference,
349                        deleted,
350                        object,
351                    });
352                }
353                _ => return Err(Error::Invalid),
354            }
355            r.end()?;
356        }
357        for reference in &result.references {
358            valid(
359                result
360                    .parents
361                    .binary_search_by(|p| {
362                        (&p.workspace, &p.task).cmp(&(&reference.workspace, &reference.task))
363                    })
364                    .is_ok(),
365            )?;
366            if let Some(object) = reference.object {
367                valid(
368                    result
369                        .objects
370                        .binary_search_by_key(&object, |o| o.id)
371                        .is_ok(),
372                )?;
373            }
374        }
375        let mut current = std::collections::HashSet::new();
376        for reference in &result.references {
377            let parent = result
378                .parents
379                .binary_search_by(|p| {
380                    (&p.workspace, &p.task).cmp(&(&reference.workspace, &reference.task))
381                })
382                .map_err(|_| Error::Invalid)?;
383            if !reference.deleted
384                && !result.parents[parent].deleted
385                && let Some(object) = reference.object
386            {
387                current.insert(object);
388            }
389        }
390        for object in &result.objects {
391            valid((object.selection == 1) == current.contains(&object.id))?;
392        }
393        Ok(result)
394    }
395}
396
397pub fn prefix_encode(rows: &[(u64, String)]) -> Result<Vec<u8>> {
398    let mut records = Vec::new();
399    for (rank, id) in rows {
400        let mut record = Vec::new();
401        u64_bytes(&mut record, *rank);
402        text(&mut record, id)?;
403        records.push(record);
404    }
405    stream(2, &records)
406}
407
408pub fn prefix_decode(bytes: &[u8], expected: u64) -> Result<Vec<(u64, String)>> {
409    valid(expected < i64::MAX as u64)?;
410    bound(expected, RECORD_LIMIT)?;
411    let records = read_stream(bytes, 2)?;
412    valid(number(records.len())? == expected)?;
413    let mut ids = std::collections::HashSet::new();
414    let mut result = Vec::new();
415    for (index, record) in records.iter().enumerate() {
416        let mut r = Reader(record);
417        let rank = r.u64()?;
418        valid(rank == add(number(index)?, 1)?)?;
419        let id = r.text()?;
420        valid(ids.insert(id.clone()))?;
421        r.end()?;
422        result.push((rank, id));
423    }
424    Ok(result)
425}