Skip to main content

uts_core/codec/v1/
opcode.rs

1//! # OpenTimestamps OpCodes
2//!
3//! It contains opcode information and utilities to work with opcodes.
4
5use crate::{
6    alloc::{Allocator, Global, vec::Vec},
7    codec::{Decode, DecodeIn, Decoder, Encode, Encoder},
8    error::{DecodeError, EncodeError},
9};
10use alloy_primitives::hex;
11use core::{fmt, hint::unreachable_unchecked};
12use digest::Digest;
13use ripemd::Ripemd160;
14use sha1::Sha1;
15use sha2::Sha256;
16use sha3::Keccak256;
17
18/// An OpenTimestamps opcode.
19///
20/// This is always a valid opcode.
21#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
22#[cfg_attr(
23    feature = "serde",
24    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
25)]
26#[repr(transparent)]
27pub struct OpCode(u8);
28
29impl fmt::Debug for OpCode {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        f.write_str(self.name())
32    }
33}
34
35impl fmt::Display for OpCode {
36    /// Formats the opcode as a string.
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        f.write_str(self.name())
39    }
40}
41
42impl Encode for OpCode {
43    #[inline]
44    fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> {
45        encoder.encode_byte(self.tag())
46    }
47}
48
49impl Decode for OpCode {
50    #[inline]
51    fn decode(decoder: &mut impl Decoder) -> Result<Self, DecodeError> {
52        let byte = decoder.decode_byte()?;
53        OpCode::new(byte).ok_or(DecodeError::BadOpCode(byte))
54    }
55}
56
57impl OpCode {
58    /// Returns the 8-bit tag identifying the opcode.
59    #[inline]
60    pub const fn tag(self) -> u8 {
61        self.0
62    }
63
64    /// Returns `true` when the opcode requires an immediate operand.
65    #[inline]
66    pub const fn has_immediate(&self) -> bool {
67        matches!(*self, Self::APPEND | Self::PREPEND)
68    }
69
70    /// Returns `true` for control opcodes.
71    #[inline]
72    pub const fn is_control(&self) -> bool {
73        matches!(*self, Self::ATTESTATION | Self::FORK)
74    }
75
76    /// Returns `true` for digest opcodes.
77    #[inline]
78    pub const fn is_digest(&self) -> bool {
79        self.as_digest().is_some()
80    }
81
82    /// Returns the digest opcode wrapper, if applicable.
83    #[inline]
84    pub const fn as_digest(&self) -> Option<DigestOp> {
85        match *self {
86            Self::SHA1 | Self::SHA256 | Self::RIPEMD160 | Self::KECCAK256 => Some(DigestOp(*self)),
87            _ => None,
88        }
89    }
90
91    /// Executes the opcode on the given input data, with an optional immediate value.
92    ///
93    /// # Panics
94    ///
95    /// Panics if the opcode is a control opcode.
96    #[inline]
97    pub fn execute(&self, input: impl AsRef<[u8]>, immediate: impl AsRef<[u8]>) -> Vec<u8> {
98        self.execute_in(input, immediate, Global)
99    }
100
101    /// Executes the opcode on the given input data, with an optional immediate value.
102    ///
103    /// # Panics
104    ///
105    /// Panics if the opcode is a control opcode.
106    #[inline]
107    pub fn execute_in<A: Allocator>(
108        &self,
109        input: impl AsRef<[u8]>,
110        immediate: impl AsRef<[u8]>,
111        alloc: A,
112    ) -> Vec<u8, A> {
113        if let Some(digest_op) = self.as_digest() {
114            return digest_op.execute_in(input, alloc);
115        }
116
117        let input = input.as_ref();
118        match *self {
119            Self::APPEND => {
120                let immediate = immediate.as_ref();
121                let mut out = Vec::with_capacity_in(input.len() + immediate.len(), alloc);
122                out.extend_from_slice(input);
123                out.extend_from_slice(immediate);
124                out
125            }
126            Self::PREPEND => {
127                let immediate = immediate.as_ref();
128                let mut out = Vec::with_capacity_in(input.len() + immediate.len(), alloc);
129                out.extend_from_slice(immediate);
130                out.extend_from_slice(input);
131                out
132            }
133            Self::REVERSE => {
134                let len = input.len();
135                let mut out = Vec::<u8, A>::with_capacity_in(len, alloc);
136
137                unsafe {
138                    // SAFETY: The vector capacity is set to len, so setting the length to len is valid.
139                    out.set_len(len);
140
141                    // LLVM will take care of vectorization here.
142                    let input_ptr = input.as_ptr();
143                    let out_ptr = out.as_mut_ptr();
144                    for i in 0..len {
145                        // SAFETY: both pointers are valid for `len` bytes.
146                        *out_ptr.add(i) = *input_ptr.add(len - 1 - i);
147                    }
148                }
149                out
150            }
151            Self::HEXLIFY => {
152                let hex_len = input.len() * 2;
153                let mut out = Vec::<u8, A>::with_capacity_in(hex_len, alloc);
154                // SAFETY: that the vector is actually the specified size.
155                unsafe {
156                    out.set_len(hex_len);
157                }
158                // SAFETY: the output buffer is large enough.
159                unsafe {
160                    hex::encode_to_slice(input, &mut out).unwrap_unchecked();
161                }
162                out
163            }
164            _ => panic!("Cannot execute control opcode"),
165        }
166    }
167}
168
169impl PartialEq<u8> for OpCode {
170    fn eq(&self, other: &u8) -> bool {
171        self.tag().eq(other)
172    }
173}
174
175/// An OpenTimestamps digest opcode.
176///
177/// This is always a valid opcode.
178#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
179#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
180#[repr(transparent)]
181pub struct DigestOp(OpCode);
182
183impl fmt::Debug for DigestOp {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        f.write_str(self.0.name())
186    }
187}
188
189impl fmt::Display for DigestOp {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        write!(f, "{}", self.0)
192    }
193}
194
195impl PartialEq<OpCode> for DigestOp {
196    fn eq(&self, other: &OpCode) -> bool {
197        self.0.eq(other)
198    }
199}
200
201impl PartialEq<u8> for DigestOp {
202    fn eq(&self, other: &u8) -> bool {
203        self.0.eq(other)
204    }
205}
206
207impl Encode for DigestOp {
208    #[inline]
209    fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> {
210        self.0.encode(encoder)
211    }
212}
213
214impl<A: Allocator> DecodeIn<A> for DigestOp {
215    #[inline]
216    fn decode_in(decoder: &mut impl Decoder, _alloc: A) -> Result<Self, DecodeError> {
217        let opcode = OpCode::decode(decoder)?;
218        opcode
219            .as_digest()
220            .ok_or(DecodeError::ExpectedDigestOp(opcode))
221    }
222}
223
224impl DigestOp {
225    /// Returns the wrapped opcode.
226    #[inline]
227    pub const fn to_opcode(self) -> OpCode {
228        self.0
229    }
230
231    /// Returns the 8-bit tag identifying the digest opcode.
232    #[inline]
233    pub const fn tag(&self) -> u8 {
234        self.0.tag()
235    }
236}
237
238/// Extension trait for `Digest` implementors to get the corresponding `DigestOp`.
239pub trait DigestOpExt: Digest {
240    const OPCODE: DigestOp;
241
242    fn opcode() -> DigestOp;
243}
244
245macro_rules! define_opcodes {
246    ($($val:literal => $variant:ident),* $(,)?) => {
247         $(
248            #[doc = concat!("The `", stringify!($val), "` (\"", stringify!($variant),"\") opcode.")]
249            pub const $variant: u8 = $val;
250        )*
251
252        $(
253            impl OpCode {
254                #[doc = concat!("The `", stringify!($val), "` (\"", stringify!($variant),"\") opcode.")]
255                pub const $variant: Self = Self($val);
256            }
257        )*
258
259        impl OpCode {
260            #[inline]
261            pub const fn new(v: u8) -> Option<Self> {
262                match v {
263                    $( $val => Some(Self::$variant), )*
264                    _ => None,
265                }
266            }
267
268            #[inline]
269            pub const fn name(&self) -> &'static str {
270                match *self {
271                    $( Self::$variant => stringify!($variant), )*
272                    // SAFETY: unreachable as all variants are covered.
273                    _ => unsafe { unreachable_unchecked() }
274                }
275            }
276        }
277
278        /// Error returned when parsing an invalid opcode from a string.
279        #[derive(Debug)]
280        pub struct OpCodeFromStrError;
281
282        impl core::fmt::Display for OpCodeFromStrError {
283            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
284                write!(f, "invalid opcode string")
285            }
286        }
287
288        impl std::error::Error for OpCodeFromStrError {}
289
290        impl core::str::FromStr for OpCode {
291            type Err = OpCodeFromStrError;
292
293            #[inline]
294            fn from_str(s: &str) -> Result<Self, Self::Err> {
295                match s {
296                    $( stringify!($variant) => Ok(Self::$variant), )*
297                    _ => Err(OpCodeFromStrError),
298                }
299            }
300         }
301    };
302}
303
304macro_rules! define_digest_opcodes {
305    ($($val:literal => $variant:ident),* $(,)?) => {
306        $(
307            impl DigestOp {
308                #[doc = concat!("The `", stringify!($val), "` (\"", stringify!($variant),"\") digest opcode.")]
309                pub const $variant: Self = Self(OpCode::$variant);
310            }
311        )*
312
313        impl DigestOp {
314            /// Returns the output length of the digest in bytes.
315            #[inline]
316            pub const fn output_size(&self) -> usize {
317                use digest::typenum::Unsigned;
318                paste::paste! {
319                    match *self {
320                        $( Self::$variant => <[<$variant:camel>] as ::digest::OutputSizeUser>::OutputSize::USIZE, )*
321                        // SAFETY: unreachable as all variants are covered.
322                        _ => unsafe { unreachable_unchecked() }
323                    }
324                }
325            }
326
327            /// Executes the digest operation on the input data.
328            pub fn execute(&self, input: impl AsRef<[u8]>) -> $crate::alloc::vec::Vec<u8> {
329                self.execute_in(input, $crate::alloc::Global)
330            }
331
332            /// Executes the digest operation on the input data.
333            pub fn execute_in<A: $crate::alloc::Allocator>(&self, input: impl AsRef<[u8]>, alloc: A) -> $crate::alloc::vec::Vec<u8, A> {
334                match *self {
335                    $( Self::$variant => {
336                        paste::paste! {
337                            let mut hasher = [<$variant:camel>]::new();
338                            hasher.update(input);
339                            $crate::alloc::SliceExt::to_vec_in(hasher.finalize().as_slice(), alloc)
340                        }
341                    }, )*
342                    // SAFETY: unreachable as all variants are covered.
343                    _ => unsafe { unreachable_unchecked() }
344                }
345            }
346        }
347        paste::paste! {
348            $(
349                impl DigestOpExt for [<$variant:camel>] {
350                    const OPCODE: DigestOp = DigestOp::$variant;
351
352                    #[inline]
353                    fn opcode() -> DigestOp {
354                        DigestOp::$variant
355                    }
356                }
357            )*
358        }
359    };
360}
361
362macro_rules! impl_simple_step {
363    ($variant:ident) => {paste::paste! {
364        impl<A: $crate::alloc::Allocator + Clone> $crate::codec::v1::timestamp::builder::TimestampBuilder<A> {
365            #[doc = concat!("Push the `", stringify!($variant), "` opcode.")]
366            pub fn [< $variant:lower >](&mut self) -> &mut Self {
367                self.push_step(OpCode::[<$variant>])
368            }
369        }
370    }};
371    ($($variant:ident),* $(,)?) => {
372        $(
373            impl_simple_step! { $variant }
374        )*
375    };
376}
377
378macro_rules! impl_step_with_data {
379    ($variant:ident) => {paste::paste! {
380        impl<A: $crate::alloc::Allocator + Clone> $crate::codec::v1::timestamp::builder::TimestampBuilder<A> {
381            #[doc = concat!("Push the `", stringify!($variant), "` opcode.")]
382            pub fn [< $variant:lower >](&mut self, data: $crate::alloc::vec::Vec<u8, A>) -> &mut Self {
383                self.push_immediate_step(OpCode::[<$variant>], data)
384            }
385        }
386    }};
387    ($($variant:ident),* $(,)?) => {
388        $(
389            impl_step_with_data! { $variant }
390        )*
391    };
392}
393
394define_opcodes! {
395    0x02 => SHA1,
396    0x03 => RIPEMD160,
397    0x08 => SHA256,
398    0x67 => KECCAK256,
399    0xf0 => APPEND,
400    0xf1 => PREPEND,
401    0xf2 => REVERSE,
402    0xf3 => HEXLIFY,
403    0x00 => ATTESTATION,
404    0xff => FORK,
405}
406
407define_digest_opcodes! {
408    0x02 => SHA1,
409    0x03 => RIPEMD160,
410    0x08 => SHA256,
411    0x67 => KECCAK256,
412}
413
414impl_simple_step! {
415    SHA1,
416    RIPEMD160,
417    SHA256,
418    KECCAK256,
419    REVERSE,
420    HEXLIFY,
421}
422
423impl_step_with_data! {
424    APPEND,
425    PREPEND,
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431
432    #[test]
433    fn digest_len() {
434        assert_eq!(DigestOp::SHA1.output_size(), 20);
435        assert_eq!(DigestOp::RIPEMD160.output_size(), 20);
436        assert_eq!(DigestOp::SHA256.output_size(), 32);
437        assert_eq!(DigestOp::KECCAK256.output_size(), 32);
438    }
439
440    #[cfg(feature = "serde")]
441    #[test]
442    fn serde_opcode() {
443        let opcode = OpCode::SHA256;
444        let serialized = serde_json::to_string(&opcode).unwrap();
445        assert_eq!(serialized, "\"SHA256\"");
446        let deserialized: OpCode = serde_json::from_str(&serialized).unwrap();
447        assert_eq!(deserialized, opcode);
448
449        let digest_op = DigestOp::SHA256;
450        let serialized = serde_json::to_string(&digest_op).unwrap();
451        assert_eq!(serialized, "\"SHA256\"");
452        let deserialized: DigestOp = serde_json::from_str(&serialized).unwrap();
453        assert_eq!(deserialized, digest_op);
454    }
455}