Skip to main content

p2panda_core/operation/
any.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use crate::Operation;
4use crate::hash::{HASH_LEN, Hash};
5use crate::identity::{SIGNATURE_LEN, Signature, VERIFYING_KEY_LEN, VerifyingKey};
6use crate::logs::SeqNum;
7use crate::operation::HeaderError;
8use crate::operation::header::encode_header;
9use crate::operation::{Body, Header, PayloadSize, RawOperation, Version};
10use crate::traits::{Chain, Digest, Offchain, Provenance};
11
12/// Combined [`AnyHeader`], [`Body`] and operation [`struct@Hash`] (Operation Id).
13///
14/// ## Extensions
15///
16/// `AnyOperation` does not know the concrete extensions type. On this level it is only concerned
17/// with the validity and integrity of the append-only log type itself which is enough for most
18/// low-level protocols, such as the sync protocol.
19///
20/// Applications usually want to attach custom extensions to the operation, if you need to know the
21/// type you can easily convert from `AnyOperation` to [`Operation`](crate::Operation) with an
22/// explicit `E` extensions type.
23#[derive(Clone, Debug, PartialEq)]
24pub struct AnyOperation {
25    pub hash: Hash,
26    pub header: AnyHeader,
27    pub body: Option<Body>,
28}
29
30impl Digest<Hash> for AnyOperation {
31    fn hash(&self) -> Hash {
32        self.hash
33    }
34}
35
36impl Provenance<VerifyingKey> for AnyOperation {
37    fn author(&self) -> VerifyingKey {
38        self.header.verifying_key
39    }
40
41    fn verify(&self) -> bool {
42        self.header.verify()
43    }
44}
45
46impl Chain<Hash> for AnyOperation {
47    fn backlink(&self) -> Option<Hash> {
48        self.header.backlink
49    }
50
51    fn seq_num(&self) -> SeqNum {
52        self.header.seq_num
53    }
54}
55
56impl Offchain<Hash> for AnyOperation {
57    fn payload(&self) -> Option<&Body> {
58        self.body.as_ref()
59    }
60
61    fn payload_hash(&self) -> Option<Hash> {
62        self.header.payload_hash
63    }
64
65    fn payload_size(&self) -> PayloadSize {
66        self.header.payload_size
67    }
68}
69
70impl TryFrom<RawOperation> for AnyOperation {
71    type Error = HeaderError;
72
73    fn try_from(bytes: RawOperation) -> Result<Self, Self::Error> {
74        let (header_bytes, body_bytes) = bytes;
75        let header: AnyHeader = AnyHeader::decode(&header_bytes)?;
76
77        Ok(AnyOperation {
78            hash: header.hash(),
79            header,
80            body: body_bytes.map(Body::from),
81        })
82    }
83}
84
85impl<E> From<Operation<E>> for AnyOperation {
86    fn from(value: Operation<E>) -> Self {
87        AnyOperation {
88            hash: value.hash,
89            header: value.header.into(),
90            body: value.body,
91        }
92    }
93}
94
95/// Header of a p2panda operation.
96///
97/// The header holds all metadata required to cryptographically secure and authenticate a message
98/// [`Body`] and it's custom extensions.
99///
100/// ## Extensions
101///
102/// `AnyHeader` does not know the concrete extensions type. On this level it is only concerned with
103/// the validity and integrity of the append-only log type itself which is enough for most low-level
104/// protocols, such as the sync protocol.
105///
106/// Applications usually want to attach custom extensions to the header, if you need to know the
107/// type you can easily convert from `AnyHeader` to [`Header`] with an explicit `E` extensions
108/// type.
109///
110/// ```rust
111/// # fn example() -> Result<(), p2panda_core::HeaderError> {
112/// use p2panda_core::{AnyHeader, Hash, Header, SigningKey};
113/// use serde::{Deserialize, Serialize};
114///
115/// #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
116/// struct MyExtensions {
117///     dependencies: Vec<Hash>,
118/// }
119///
120/// let signing_key = SigningKey::generate();
121///
122/// // Create a Header with concrete extension type `MyExtensions`.
123/// let header = Header::builder()
124///     .build(&signing_key, MyExtensions {
125///         dependencies: vec![Hash::from([0; 32])],
126///     });
127///
128/// // Encode it to CBOR bytes, this is how we transmit operations over the network.
129/// let bytes = header.encode();
130///
131/// // Convert it to `AnyHeader` which doesn't know the extensions type.
132/// let any_header = AnyHeader::decode(&bytes)?;
133///
134/// // Bring it back to a concrete Header type with `MyExtensions`.
135/// let header_again = Header::try_from(any_header)?;
136/// assert_eq!(header, header_again);
137/// # Ok(())
138/// # }
139/// ```
140///
141/// Please note that at this stage we can only verify the integrity and authenticity of the attached
142/// extensions, we _don't know_ if the extensions themselves are valid. We can only find out if this
143/// is correct if we know the concrete E type.
144#[derive(Clone, Debug, PartialEq, Eq)]
145pub struct AnyHeader {
146    /// Operation format version, allowing backwards compatibility when specification changes.
147    pub version: Version,
148
149    /// Author of this operation.
150    pub verifying_key: VerifyingKey,
151
152    /// Signature by author over all fields in header, providing authenticity.
153    pub signature: Signature,
154
155    /// Number of bytes of the body of this operation, must be zero if no body is given.
156    pub payload_size: PayloadSize,
157
158    /// Hash of the body of this operation, must be included if payload_size is non-zero and
159    /// omitted otherwise.
160    ///
161    /// Keeping the hash here allows us to delete the payload (off-chain data) while retaining the
162    /// ability to check the signature of the header.
163    pub payload_hash: Option<Hash>,
164
165    /// Number of operations this author has published to this log, begins with 0 and is always
166    /// incremented by 1 with each new operation by the same author.
167    pub seq_num: SeqNum,
168
169    /// Hash of the previous operation of the same author and log. Can be omitted if first
170    /// operation in log.
171    pub backlink: Option<Hash>,
172
173    /// Size of header in encoded CBOR bytes.
174    pub(crate) size: u32,
175
176    /// BLAKE3 hash digest of header.
177    pub(crate) digest: Hash,
178
179    /// Custom additional data.
180    ///
181    /// We don't know the exact Rust type of the extensions here, only the AST representation of
182    /// CBOR. To decode the value to an extensions Rust type `E` use
183    /// `Header::<E>::try_from`(crate::Header::try_from).
184    pub(crate) extensions: Option<cbor_core::Value<'static>>,
185}
186
187impl AnyHeader {
188    /// Attempts decoding header from bytes.
189    ///
190    /// This fails if integrity checks failed or header formatting is invalid.
191    pub fn decode(bytes: &[u8]) -> Result<Self, HeaderError> {
192        // Attempt decoding bytes as CBOR.
193        //
194        // The bytes are decoded in a zero-copy manner, only reading from the given byte slice.
195        let cbor = {
196            let codec = cbor_core::DecodeOptions::new()
197                // Enforce a strict, canonical CBOR encoding, otherwise integrity checks would fail
198                // when decoding & encoding the headers again on our end. See `encode_header` for
199                // details.
200                .strictness(cbor_core::Strictness::STRICT)
201                // Make sure some attacks are mitigated and set rather low / pessimistic thresholds.
202                .recursion_limit(64)
203                .length_limit(512) // 0.5kb
204                .oom_mitigation(64);
205
206            codec.decode(bytes).map_err(HeaderError::DecodingHeader)?
207        };
208
209        // Validate each field in header based on p2panda specification and extract Rust types.
210        //
211        // Every header is a tuple (CBOR array). We iterate over each field and check if the
212        // expected CBOR and Rust type is given.
213        //
214        // The types are converted into owned objects (leaving the zero-copy nature of this process)
215        // and kept to allow further validation (log integrity) or conversion into the more
216        // specialised Header<E> type (where the Extensions are known).
217        //
218        // We don't keep the CBOR representation or bytes around anymore in the end (except of the
219        // decoded extensions) to not waste memory with duplicate representations of the same data.
220        let mut seq = cbor
221            .into_array()
222            .map_err(HeaderError::UnexpectedHeaderType)?;
223        let mut iter = seq.iter();
224
225        let version = {
226            let next = iter.next().ok_or(HeaderError::MissingField("version"))?;
227
228            Version::try_from(next)
229                .map_err(|err| HeaderError::UnexpectedFieldType(err, "version"))?
230        };
231
232        if version != 1 {
233            return Err(HeaderError::UnsupportedVersion(version, 1));
234        }
235
236        let verifying_key = {
237            let next = iter
238                .next()
239                .ok_or(HeaderError::MissingField("verifying_key"))?;
240
241            let bytes = next
242                .as_bytes()
243                .map_err(|err| HeaderError::UnexpectedFieldType(err, "verifying_key"))?;
244
245            let bytes: [u8; VERIFYING_KEY_LEN] = bytes.try_into().map_err(|_| {
246                HeaderError::InvalidBytesLen("verifying_key", VERIFYING_KEY_LEN, bytes.len())
247            })?;
248
249            VerifyingKey::from_bytes(&bytes).map_err(HeaderError::InvalidVerifyingKey)?
250        };
251
252        let signature = {
253            let next = iter.next().ok_or(HeaderError::MissingField("signature"))?;
254
255            let bytes = next
256                .as_bytes()
257                .map_err(|err| HeaderError::UnexpectedFieldType(err, "signature"))?;
258
259            let bytes: [u8; SIGNATURE_LEN] = bytes.try_into().map_err(|_| {
260                HeaderError::InvalidBytesLen("signature", SIGNATURE_LEN, bytes.len())
261            })?;
262
263            Signature::from(&bytes)
264        };
265
266        let payload_size = {
267            let next = iter
268                .next()
269                .ok_or(HeaderError::MissingField("payload_size"))?;
270
271            PayloadSize::try_from(next)
272                .map_err(|err| HeaderError::UnexpectedFieldType(err, "payload_size"))?
273        };
274
275        let payload_hash = if payload_size > 0 {
276            let next = iter
277                .next()
278                .ok_or(HeaderError::MissingField("payload_hash"))?;
279
280            let bytes = next
281                .as_bytes()
282                .map_err(|err| HeaderError::UnexpectedFieldType(err, "payload_hash"))?;
283
284            let bytes: [u8; HASH_LEN] = bytes
285                .try_into()
286                .map_err(|_| HeaderError::InvalidBytesLen("payload_hash", HASH_LEN, bytes.len()))?;
287
288            Some(Hash::from(bytes))
289        } else {
290            None
291        };
292
293        let seq_num = {
294            let next = iter.next().ok_or(HeaderError::MissingField("seq_num"))?;
295
296            SeqNum::try_from(next)
297                .map_err(|err| HeaderError::UnexpectedFieldType(err, "seq_num"))?
298        };
299
300        let backlink = if seq_num > 0 {
301            let next = iter.next().ok_or(HeaderError::MissingField("backlink"))?;
302
303            let bytes = next
304                .as_bytes()
305                .map_err(|err| HeaderError::UnexpectedFieldType(err, "backlink"))?;
306
307            let bytes: [u8; HASH_LEN] = bytes
308                .try_into()
309                .map_err(|_| HeaderError::InvalidBytesLen("backlink", HASH_LEN, bytes.len()))?;
310
311            Some(Hash::from(bytes))
312        } else {
313            None
314        };
315
316        // Extract extensions and keep them for later, in case we need to deserialize them into
317        // Header<E> in the future.
318        //
319        // AnyHeader doesn't know the Rust type for E, only it's "raw" CBOR representation. To use
320        // extensions properly with Rust types we eventually want to convert into the concrete E
321        // type.
322        //
323        // Please note that at this stage we _don't know_ if this header is valid with the
324        // extensions set. We can only find out if this is correct if we know the concrete E type
325        // (if it's a ZST then there should not be an extensions field).
326        let extensions = iter.next().map(|value| value.to_owned());
327
328        // If anything came after all expected fields, something is wrong.
329        if iter.next().is_some() {
330            return Err(HeaderError::ExcessiveFields);
331        }
332
333        // Verify signature.
334        //
335        // Extract signature from field position 2. It'll be removed from the CBOR value, so we can
336        // encode the bytes without it.
337        //
338        //  [0]      [1]            [2]
339        // (version, verifying_key, signature, ..)
340        //                          =========
341        seq.remove(2);
342
343        let verify_bytes = cbor_core::Value::from(seq).encode();
344        if !verifying_key.verify(&verify_bytes, &signature) {
345            return Err(HeaderError::InvalidSignature);
346        }
347
348        // Calculate header size and generate hash digest.
349        //
350        // We keep these values around so if users of this object require the size or hash, it will
351        // not be re-computed again.
352        //
353        // Since we also have the bytes in our hands already we don't need to encode either.
354        let size = bytes.len() as u32;
355        let digest = Hash::digest(bytes);
356
357        Ok(Self {
358            version,
359            verifying_key,
360            signature,
361            payload_size,
362            payload_hash,
363            seq_num,
364            backlink,
365            size,
366            digest,
367            extensions,
368        })
369    }
370
371    /// Encodes header to byte-representation (CBOR).
372    pub fn encode(&self) -> Vec<u8> {
373        encode_header(
374            self.version,
375            self.verifying_key,
376            Some(&self.signature),
377            self.payload_size,
378            self.payload_hash,
379            self.seq_num,
380            self.backlink,
381            self.extensions.as_ref(),
382        )
383    }
384
385    /// BLAKE3 hash of the header bytes.
386    ///
387    /// This hash is used as the unique identifier of an operation, aka the Operation Id.
388    pub fn hash(&self) -> Hash {
389        self.digest
390    }
391
392    /// Size of header when encoded as CBOR bytes.
393    pub fn size(&self) -> u32 {
394        self.size
395    }
396}
397
398impl Digest<Hash> for AnyHeader {
399    fn hash(&self) -> Hash {
400        self.hash()
401    }
402}
403
404impl Provenance<VerifyingKey> for AnyHeader {
405    fn author(&self) -> VerifyingKey {
406        self.verifying_key
407    }
408
409    fn verify(&self) -> bool {
410        // Was checked during decoding.
411        true
412    }
413}
414
415impl Chain<Hash> for AnyHeader {
416    fn backlink(&self) -> Option<Hash> {
417        self.backlink
418    }
419
420    fn seq_num(&self) -> SeqNum {
421        self.seq_num
422    }
423}
424
425impl Offchain<Hash> for AnyHeader {
426    fn payload(&self) -> Option<&Body> {
427        None
428    }
429
430    fn payload_hash(&self) -> Option<Hash> {
431        self.payload_hash
432    }
433
434    fn payload_size(&self) -> PayloadSize {
435        self.payload_size
436    }
437}
438
439impl TryFrom<&[u8]> for AnyHeader {
440    type Error = HeaderError;
441
442    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
443        Self::decode(value)
444    }
445}
446
447impl TryFrom<Vec<u8>> for AnyHeader {
448    type Error = HeaderError;
449
450    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
451        Self::decode(&value)
452    }
453}
454
455impl<E> From<Header<E>> for AnyHeader {
456    fn from(value: Header<E>) -> Self {
457        AnyHeader {
458            version: value.version,
459            verifying_key: value.verifying_key,
460            signature: value.signature,
461            payload_size: value.payload_size,
462            payload_hash: value.payload_hash,
463            seq_num: value.seq_num,
464            backlink: value.backlink,
465            size: value.size,
466            digest: value.digest,
467            extensions: value.extensions_cbor,
468        }
469    }
470}