Skip to main content

uts_core/codec/v1/timestamp/
builder.rs

1//! Timestamp Builder
2
3use crate::{
4    alloc::{Allocator, Global, vec, vec::Vec},
5    codec::v1::{
6        Attestation, Timestamp,
7        opcode::{DigestOpExt, OpCode},
8        timestamp::Step,
9    },
10    error::EncodeError,
11};
12use allocator_api2::SliceExt;
13use std::sync::OnceLock;
14use uts_bmt::{NodePosition, SiblingIter};
15
16#[derive(Debug, Clone)]
17pub struct TimestampBuilder<A: Allocator = Global> {
18    steps: Vec<LinearStep<A>, A>,
19}
20
21#[derive(Debug, Clone)]
22struct LinearStep<A: Allocator> {
23    op: OpCode,
24    data: Vec<u8, A>,
25}
26
27impl<A: Allocator + Clone> TimestampBuilder<A> {
28    /// Creates a new `TimestampBuilder`.
29    pub fn new_in(alloc: A) -> TimestampBuilder<A> {
30        TimestampBuilder {
31            steps: Vec::new_in(alloc),
32        }
33    }
34
35    /// Pushes a new execution step with immediate data to the timestamp.
36    ///
37    /// # Panics
38    ///
39    /// Panics if the opcode is not an opcode with immediate data.
40    pub(crate) fn push_immediate_step(&mut self, op: OpCode, data: Vec<u8, A>) -> &mut Self {
41        assert!(op.has_immediate());
42        self.steps.push(LinearStep { op, data });
43        self
44    }
45
46    /// Pushes a new execution step without immediate data to the timestamp.
47    ///
48    /// # Panics
49    ///
50    /// Panics if:
51    /// - the opcode is control opcode
52    /// - the opcode is an opcode with immediate data
53    pub fn push_step(&mut self, op: OpCode) -> &mut Self {
54        self.steps.push(LinearStep {
55            op,
56            data: Vec::new_in(self.allocator().clone()),
57        });
58        self
59    }
60
61    /// Pushes a new digest step to the timestamp.
62    pub fn digest<D: DigestOpExt>(&mut self) -> &mut Self {
63        self.push_step(D::OPCODE.to_opcode());
64        self
65    }
66
67    /// Pushes the steps corresponding to the given Merkle proof to the timestamp.
68    pub fn merkle_proof<D: DigestOpExt>(&mut self, proof: SiblingIter<'_, D>) -> &mut Self {
69        let alloc = self.allocator().clone();
70        for (side, sibling_hash) in proof {
71            let sibling_hash = SliceExt::to_vec_in(sibling_hash.as_slice(), alloc.clone());
72            match side {
73                NodePosition::Left => self
74                    .prepend(vec![in alloc.clone(); uts_bmt::INNER_NODE_PREFIX])
75                    .append(sibling_hash),
76                NodePosition::Right => self
77                    .prepend(sibling_hash)
78                    .prepend(vec![in alloc.clone(); uts_bmt::INNER_NODE_PREFIX]),
79            }
80            .digest::<D>();
81        }
82        self
83    }
84
85    /// Computes the commitment of the timestamp for the given input.
86    ///
87    /// In this context, the **commitment** is the deterministic result of
88    /// executing the timestamp's linear chain of operations over the input
89    /// bytes. It is computed by:
90    ///
91    /// 1. Taking the provided `input` bytes as the initial value.
92    /// 2. Iterating over all steps in the order they were added to the builder.
93    /// 3. For each step, applying its opcode to the current value together
94    ///    with the step's immediate data via [`OpCode::execute_in`], and using
95    ///    the result as the new current value.
96    ///
97    /// The final value after all steps have been applied is returned as the
98    /// commitment.
99    pub fn commitment(&self, input: impl AsRef<[u8]>) -> Vec<u8, A> {
100        let alloc = self.allocator().clone();
101        let mut commitment = SliceExt::to_vec_in(input.as_ref(), alloc.clone());
102        for step in &self.steps {
103            commitment = step.op.execute_in(&commitment, &step.data, alloc.clone());
104        }
105        commitment
106    }
107
108    /// Finalizes the timestamp with the given attestation.
109    ///
110    /// # Notes
111    ///
112    /// The built timestamp does not include any input data. The input data must be
113    /// provided later using the `finalize` method on the `Timestamp` object.
114    pub fn attest<'a, T: Attestation<'a>>(
115        self,
116        attestation: T,
117    ) -> Result<Timestamp<A>, EncodeError> {
118        let current = Timestamp::Attestation(attestation.to_raw_in(self.allocator().clone())?);
119        Ok(self.concat(current))
120    }
121
122    /// Append the given timestamp after the steps in the builder.
123    pub fn concat(self, timestamp: Timestamp<A>) -> Timestamp<A> {
124        let alloc = self.allocator().clone();
125
126        let mut current = timestamp;
127
128        for step in self.steps.into_iter().rev() {
129            let step_node = Step {
130                op: step.op,
131                data: step.data,
132                input: OnceLock::new(),
133                next: {
134                    let mut v = Vec::with_capacity_in(1, alloc.clone());
135                    v.push(current);
136                    v
137                },
138            };
139            current = Timestamp::Step(step_node);
140        }
141
142        current
143    }
144
145    #[inline]
146    fn allocator(&self) -> &A {
147        self.steps.allocator()
148    }
149}