Skip to main content

p2panda_core/operation/
any.rs

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