Skip to main content

p2panda_core/operation/
builder.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use std::marker::PhantomData;
4
5use cbor_core::Value;
6
7use crate::hash::Hash;
8use crate::identity::SigningKey;
9use crate::logs::SeqNum;
10use crate::operation::header::encode_header;
11use crate::operation::{Header, PayloadSize};
12use crate::traits::Extensions;
13
14/// Build & sign operations.
15pub struct Builder<E> {
16    payload_size: PayloadSize,
17    payload_hash: Option<Hash>,
18    seq_num: SeqNum,
19    backlink: Option<Hash>,
20    _marker: PhantomData<E>,
21}
22
23impl<E> Default for Builder<E>
24where
25    E: Extensions,
26{
27    fn default() -> Self {
28        Self::new()
29    }
30}
31
32impl<E> Builder<E>
33where
34    E: Extensions,
35{
36    pub fn new() -> Self {
37        Self {
38            payload_size: 0,
39            payload_hash: None,
40            seq_num: 0,
41            backlink: None,
42            _marker: PhantomData,
43        }
44    }
45
46    /// Attach payload to operation.
47    pub fn body(mut self, bytes: impl AsRef<[u8]>) -> Self {
48        let bytes = bytes.as_ref();
49
50        self.payload_size = bytes.len() as PayloadSize;
51        self.payload_hash = if self.payload_size == 0 {
52            None
53        } else {
54            Some(Hash::digest(bytes))
55        };
56
57        self
58    }
59
60    /// Sets the "hash chain" values of this operation: sequence number and backlink.
61    pub fn chain(mut self, seq_num: SeqNum, backlink: Hash) -> Self {
62        self.seq_num = seq_num;
63
64        if self.seq_num > 0 {
65            self.backlink = Some(backlink);
66        } else {
67            // Ignore backlink if user tries to set one at seq_num = 0.
68            self.backlink = None;
69        }
70
71        self
72    }
73
74    /// Number of operations this author has published to this log, begins with 0 and is always
75    /// incremented by 1 with each new operation by the same author.
76    pub fn seq_num(mut self, seq_num: SeqNum) -> Self {
77        self.seq_num = seq_num;
78        self
79    }
80
81    /// Hash of the previous operation of the same author and log. Can be omitted if first
82    /// operation in log.
83    pub fn backlink(mut self, backlink: Option<Hash>) -> Self {
84        self.backlink = backlink;
85        self
86    }
87
88    /// Encodes, signs and returns final header of operation.
89    ///
90    /// A custom header extensions type can be set here as well when required. It will be embedded
91    /// in the header. Set this to `()` (unit-type) when extensions are not necessary.
92    pub fn build(self, signing_key: &SigningKey, extensions: E) -> Header<E> {
93        let version = 1;
94
95        let verifying_key = signing_key.verifying_key();
96
97        let extensions_cbor = if !Header::<E>::has_zero_sized_extensions() {
98            let extensions_cbor = Value::serialized(&extensions).expect("serializable extensions");
99            Some(extensions_cbor)
100        } else {
101            None
102        };
103
104        let signing_bytes = encode_header(
105            version,
106            verifying_key,
107            None,
108            self.payload_size,
109            self.payload_hash,
110            self.seq_num,
111            self.backlink,
112            extensions_cbor.as_ref(),
113        );
114
115        let signature = signing_key.sign(&signing_bytes);
116
117        let bytes = encode_header(
118            version,
119            verifying_key,
120            Some(&signature),
121            self.payload_size,
122            self.payload_hash,
123            self.seq_num,
124            self.backlink,
125            extensions_cbor.as_ref(),
126        );
127
128        let digest = Hash::digest(&bytes);
129        let size = bytes.len() as u32;
130
131        Header {
132            version,
133            verifying_key,
134            signature,
135            payload_size: self.payload_size,
136            payload_hash: self.payload_hash,
137            seq_num: self.seq_num,
138            backlink: self.backlink,
139            extensions,
140            extensions_cbor,
141            digest,
142            size,
143        }
144    }
145}