ultrasonic/
state.rs

1// UltraSONIC: transactional execution layer with capability-based memory access for zk-AluVM
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Designed in 2019-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
6// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland.
9// Copyright (C) 2024-2025 Laboratories for Ubiquitous Deterministic Computing (UBIDECO),
10//                         Institute for Distributed and Cognitive Systems (InDCS), Switzerland.
11// Copyright (C) 2019-2025 Dr Maxim Orlovsky.
12// All rights under the above copyrights are reserved.
13//
14// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
15// in compliance with the License. You may obtain a copy of the License at
16//
17//        http://www.apache.org/licenses/LICENSE-2.0
18//
19// Unless required by applicable law or agreed to in writing, software distributed under the License
20// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
21// or implied. See the License for the specific language governing permissions and limitations under
22// the License.
23
24use core::cmp::Ordering;
25use core::str::FromStr;
26use std::vec;
27
28use aluvm::alu::LibSite;
29use aluvm::fe256;
30use amplify::confinement::SmallBlob;
31use amplify::hex::FromHex;
32use amplify::num::u256;
33use amplify::{hex, Bytes};
34use commit_verify::{CommitEncode, CommitEngine, MerkleHash, StrictHash};
35
36use crate::LIB_NAME_ULTRASONIC;
37
38#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, From)]
39#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
40#[strict_type(lib = LIB_NAME_ULTRASONIC)]
41#[cfg_attr(
42    all(feature = "serde", not(feature = "baid64")),
43    derive(Serialize, Deserialize),
44    serde(transparent)
45)]
46pub struct AuthToken(#[from] fe256);
47
48// Types in ultrasonic must not be ordered, since zk-STARK proofs are really inefficient in applying
49// ordering to field elements. However, upstream we need to put `AuthToken` into `BTreeMap`, thus we
50// need `Ord` implementation for pure rust reasons. It must not be used anywhere in the consensus
51// layer.
52impl PartialOrd for AuthToken {
53    fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
54}
55impl Ord for AuthToken {
56    fn cmp(&self, other: &Self) -> Ordering { self.0.to_u256().cmp(&other.0.to_u256()) }
57}
58
59impl From<[u8; 30]> for AuthToken {
60    fn from(value: [u8; 30]) -> Self { Self::from_byte_array(value) }
61}
62impl From<Bytes<30>> for AuthToken {
63    fn from(value: Bytes<30>) -> Self { Self::from_byte_array(value.to_byte_array()) }
64}
65
66impl AuthToken {
67    pub const fn to_fe256(&self) -> fe256 { self.0 }
68
69    pub fn from_byte_array(bytes: [u8; 30]) -> Self {
70        let mut buf = [0u8; 32];
71        buf[..30].copy_from_slice(&bytes);
72        let val = fe256::from(buf);
73        Self(val)
74    }
75
76    pub fn to_byte_array(&self) -> [u8; 30] {
77        let bytes = self.0.to_u256().to_le_bytes();
78        debug_assert_eq!(&bytes[30..], &[0, 0]);
79
80        let mut buf = [0u8; 30];
81        buf.copy_from_slice(&bytes[..30]);
82        buf
83    }
84
85    pub fn to_bytes30(&self) -> Bytes<30> {
86        let bytes = self.to_byte_array();
87        Bytes::from(bytes)
88    }
89}
90
91#[cfg(feature = "baid64")]
92mod _baid64 {
93    use core::fmt::{self, Display, Formatter};
94    use core::str::FromStr;
95
96    use baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str};
97
98    use super::*;
99
100    impl DisplayBaid64<30> for AuthToken {
101        const HRI: &'static str = "at";
102        const CHUNKING: bool = true;
103        const CHUNK_FIRST: usize = 8;
104        const CHUNK_LEN: usize = 8;
105        const PREFIX: bool = true;
106        const EMBED_CHECKSUM: bool = true;
107        const MNEMONIC: bool = false;
108        fn to_baid64_payload(&self) -> [u8; 30] { self.to_byte_array() }
109    }
110    impl FromBaid64Str<30> for AuthToken {}
111    impl FromStr for AuthToken {
112        type Err = Baid64ParseError;
113        fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_baid64_str(s) }
114    }
115    impl Display for AuthToken {
116        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.fmt_baid64(f) }
117    }
118}
119
120#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
121#[derive(StrictType, StrictEncode, StrictDecode)]
122#[strict_type(lib = LIB_NAME_ULTRASONIC, tags = custom)]
123#[cfg_attr(
124    feature = "serde",
125    derive(Serialize, Deserialize),
126    serde(tag = "type", rename_all = "camelCase")
127)]
128pub enum StateValue {
129    #[default]
130    #[strict_type(tag = 0x00)]
131    None,
132    #[strict_type(tag = 0x01)]
133    Single { first: fe256 },
134    #[strict_type(tag = 0x02)]
135    Double { first: fe256, second: fe256 },
136    #[strict_type(tag = 0x03)]
137    Triple {
138        first: fe256,
139        second: fe256,
140        third: fe256,
141    },
142    #[strict_type(tag = 0x04)]
143    Quadripple {
144        first: fe256,
145        second: fe256,
146        third: fe256,
147        fourth: fe256,
148    },
149}
150
151impl FromIterator<u256> for StateValue {
152    fn from_iter<T: IntoIterator<Item = u256>>(iter: T) -> Self {
153        Self::from_iter(iter.into_iter().map(fe256::from))
154    }
155}
156
157impl FromIterator<fe256> for StateValue {
158    fn from_iter<T: IntoIterator<Item = fe256>>(iter: T) -> Self {
159        let mut iter = iter.into_iter();
160        let first = iter.next();
161        let second = iter.next();
162        let third = iter.next();
163        let fourth = iter.next();
164        assert!(
165            iter.next().is_none(),
166            "the provided iterator for StateValue construction must not contain more than 4 \
167             elements"
168        );
169        let len = if fourth.is_some() {
170            4
171        } else if third.is_some() {
172            3
173        } else if second.is_some() {
174            2
175        } else if first.is_some() {
176            1
177        } else {
178            0
179        };
180        match len {
181            0 => StateValue::None,
182            1 => StateValue::Single { first: first.unwrap() },
183            2 => StateValue::Double { first: first.unwrap(), second: second.unwrap() },
184            3 => StateValue::Triple {
185                first: first.unwrap(),
186                second: second.unwrap(),
187                third: third.unwrap(),
188            },
189            4 => StateValue::Quadripple {
190                first: first.unwrap(),
191                second: second.unwrap(),
192                third: third.unwrap(),
193                fourth: fourth.unwrap(),
194            },
195            _ => panic!("state value can't use more than 4 elements"),
196        }
197    }
198}
199
200impl StateValue {
201    pub fn new(ty: impl Into<fe256>, val: impl Into<fe256>) -> Self {
202        StateValue::Double { first: ty.into(), second: val.into() }
203    }
204
205    pub const fn get(&self, pos: u8) -> Option<fe256> {
206        match (*self, pos) {
207            (Self::Single { first }, 0)
208            | (Self::Double { first, .. }, 0)
209            | (Self::Triple { first, .. }, 0)
210            | (Self::Quadripple { first, .. }, 0) => Some(first),
211
212            (Self::Double { second, .. }, 1)
213            | (Self::Triple { second, .. }, 1)
214            | (Self::Quadripple { second, .. }, 1) => Some(second),
215
216            (Self::Triple { third, .. }, 2) | (Self::Quadripple { third, .. }, 2) => Some(third),
217
218            (Self::Quadripple { fourth, .. }, 3) => Some(fourth),
219
220            _ => None,
221        }
222    }
223}
224
225impl IntoIterator for StateValue {
226    type Item = fe256;
227    type IntoIter = vec::IntoIter<fe256>;
228
229    fn into_iter(self) -> Self::IntoIter {
230        let vec = match self {
231            Self::None => vec![],
232            Self::Single { first } => vec![first],
233            Self::Double { first, second } => vec![first, second],
234            Self::Triple { first, second, third } => vec![first, second, third],
235            Self::Quadripple { first, second, third, fourth } => vec![first, second, third, fourth],
236        };
237        vec.into_iter()
238    }
239}
240
241/// Read-once access-controlled memory cell.
242#[derive(Copy, Clone, PartialEq, Eq, Debug)]
243#[derive(CommitEncode)]
244#[commit_encode(strategy = strict, id = MerkleHash)]
245#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
246#[strict_type(lib = LIB_NAME_ULTRASONIC)]
247#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
248pub struct StateCell {
249    pub data: StateValue,
250    /// Token of authority
251    pub auth: AuthToken,
252    pub lock: Option<LibSite>,
253}
254
255#[derive(Wrapper, WrapperMut, Clone, PartialEq, Eq, Debug, Display, From)]
256#[wrapper(AsSlice, BorrowSlice, Hex, RangeOps)]
257#[wrapper_mut(BorrowSliceMut, RangeMut)]
258#[display("0x{0:X}")]
259#[derive(CommitEncode)]
260#[commit_encode(strategy = strict, id = StrictHash)]
261#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
262#[strict_type(lib = LIB_NAME_ULTRASONIC)]
263pub struct RawData(#[from] SmallBlob);
264
265impl FromStr for RawData {
266    type Err = hex::Error;
267    fn from_str(mut s: &str) -> Result<Self, Self::Err> {
268        s = s.strip_prefix("0x").unwrap_or(s);
269        Self::from_hex(s)
270    }
271}
272
273#[derive(Clone, PartialEq, Eq, Debug)]
274#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
275#[strict_type(lib = LIB_NAME_ULTRASONIC)]
276#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
277pub struct StateData {
278    pub value: StateValue,
279    pub raw: Option<RawData>,
280}
281
282impl CommitEncode for StateData {
283    type CommitmentId = MerkleHash;
284
285    fn commit_encode(&self, e: &mut CommitEngine) {
286        e.commit_to_serialized(&self.value);
287        match &self.raw {
288            None => e.commit_to_option(&Option::<RawData>::None),
289            Some(raw) => e.commit_to_hash(raw),
290        }
291    }
292}
293
294impl StateData {
295    pub fn new(ty: impl Into<fe256>, val: impl Into<fe256>) -> Self {
296        Self { value: StateValue::new(ty, val), raw: None }
297    }
298
299    pub fn with_raw(ty: impl Into<fe256>, val: impl Into<fe256>, raw: impl Into<RawData>) -> Self {
300        Self { value: StateValue::new(ty, val), raw: Some(raw.into()) }
301    }
302}
303
304#[cfg(all(feature = "serde", feature = "baid64"))]
305mod _serde {
306    use serde::de::Error;
307    use serde::{Deserialize, Deserializer, Serialize, Serializer};
308
309    use super::*;
310
311    impl Serialize for AuthToken {
312        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
313        where S: Serializer {
314            if serializer.is_human_readable() {
315                serializer.serialize_str(&self.to_string())
316            } else {
317                self.0.serialize(serializer)
318            }
319        }
320    }
321
322    impl<'de> Deserialize<'de> for AuthToken {
323        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
324        where D: Deserializer<'de> {
325            if deserializer.is_human_readable() {
326                let s = String::deserialize(deserializer)?;
327                s.parse().map_err(D::Error::custom)
328            } else {
329                fe256::deserialize(deserializer).map(Self)
330            }
331        }
332    }
333}
334
335#[cfg(feature = "serde")]
336mod _serde2 {
337    use serde::de::Error;
338    use serde::{Deserialize, Deserializer, Serialize, Serializer};
339
340    use super::*;
341    impl Serialize for RawData {
342        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
343        where S: Serializer {
344            if serializer.is_human_readable() {
345                serializer.serialize_str(&self.to_string())
346            } else {
347                self.0.serialize(serializer)
348            }
349        }
350    }
351
352    impl<'de> Deserialize<'de> for RawData {
353        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
354        where D: Deserializer<'de> {
355            if deserializer.is_human_readable() {
356                let s = String::deserialize(deserializer)?;
357                s.parse().map_err(D::Error::custom)
358            } else {
359                SmallBlob::deserialize(deserializer).map(Self)
360            }
361        }
362    }
363}
364
365#[cfg(test)]
366mod test {
367    #[cfg(feature = "baid64")]
368    use super::*;
369
370    #[test]
371    #[cfg(feature = "baid64")]
372    fn auth_baid64() {
373        use baid64::DisplayBaid64;
374        let auth = AuthToken::from_byte_array([0xAD; 30]);
375
376        let baid64 = "at:ra2tra2t-ra2tra2t-ra2tra2t-ra2tra2t-ra2tra2t-HURE_w";
377        assert_eq!(baid64, auth.to_string());
378        assert_eq!(auth.to_string(), auth.to_baid64_string());
379
380        let auth2: AuthToken = baid64.parse().unwrap();
381        assert_eq!(auth, auth2);
382
383        let reconstructed = AuthToken::from_str(&baid64.replace('-', "")).unwrap();
384        assert_eq!(reconstructed, auth);
385    }
386}