• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tari-project / tari / 19227006544

10 Nov 2025 09:31AM UTC coverage: 51.608% (-7.9%) from 59.471%
19227006544

push

github

web-flow
feat: add deterministic transaction id (#7541)

Description
---
Added deterministic transaction IDs, which are an 8-byte (u64) hash
based on the transaction output hash in question and the wallet view
key.
- Any scanned or recovered wallet output will have the same transaction
ID across view or spend wallets.
- Sender wallets will be able to calculate the transaction ID for
receiver wallets if they need to, for that specific output.
- Sender wallets will use their change output as the determining output
hash for the transaction; this will result in the same transaction ID
being allocated upon wallet recovery. In the case of no change output,
the hash of the first ordered output will be used for the transaction
ID.
- For coin split transactions, the hash of the first ordered output will
be used for the transaction ID.

Fixed the issue with the Windows test build target link:
```
: error LNK2019: unresolved external symbol __imp_InitializeSecurityDescriptor referenced in function mdb_env_setup_locks
: error LNK2019: unresolved external symbol __imp_SetSecurityDescriptorDacl referenced in function mdb_env_setup_lock
```

Fixes #7485.

Motivation and Context
---
See #7485.

How Has This Been Tested?
---
Added unit tests.
Performed system-level testing.

What process can a PR reviewer use to test or verify this change?
---
Code review.
System-level testing.

<!-- Checklist -->
<!-- 1. Is the title of your PR in the form that would make nice release
notes? The title, excluding the conventional commit
tag, will be included exactly as is in the CHANGELOG, so please think
about it carefully. -->


Breaking Changes
---

- [x] None
- [ ] Requires data directory on base node to be deleted
- [ ] Requires hard fork
- [ ] Other - Please specify

<!-- Does this include a breaking change? If so, include this line as a
footer -->
<!-- BREAKING CHANGE: Description what the user should do, e.g. delete a
database, resync the chain -->


<!-- This is an auto-generated comm... (continued)

52 of 1260 new or added lines in 14 files covered. (4.13%)

9213 existing lines in 93 files now uncovered.

59188 of 114687 relevant lines covered (51.61%)

8172.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.25
/infrastructure/tari_script/src/stack.rs
1
// Copyright 2020. The Tari Project
2
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
3
// following conditions are met:
4
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
5
// disclaimer.
6
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
7
// following disclaimer in the documentation and/or other materials provided with the distribution.
8
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
9
// products derived from this software without specific prior written permission.
10
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
11
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
12
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
14
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
15
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
16
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17

18
use std::io;
19

20
use borsh::{BorshDeserialize, BorshSerialize};
21
use integer_encoding::{VarIntReader, VarIntWriter};
22
use tari_crypto::{
23
    compressed_commitment::CompressedCommitment,
24
    compressed_key::CompressedKey,
25
    ristretto::{RistrettoPublicKey, RistrettoSecretKey},
26
};
27
use tari_utilities::{
28
    hex::{from_hex, to_hex, Hex, HexError},
29
    ByteArray,
30
};
31

32
use crate::{
33
    error::ScriptError,
34
    op_codes::{HashValue, ScalarValue},
35
    CheckSigSchnorrSignature,
36
    CompressedCheckSigSchnorrSignature,
37
};
38

39
pub const MAX_STACK_SIZE: usize = 255;
40

41
#[macro_export]
42
macro_rules! inputs {
43
    ($($input:expr),+) => {{
44
        use $crate::{ExecutionStack, StackItem};
45

46
        let items = vec![$(StackItem::from($input)),+];
47
        ExecutionStack::new(items)
48
    }}
49
}
50

51
macro_rules! stack_item_from {
52
    ($from_type:ty => $variant:ident) => {
53
        impl From<$from_type> for StackItem {
54
            fn from(item: $from_type) -> Self {
1,229✔
55
                StackItem::$variant(item)
1,229✔
56
            }
1,229✔
57
        }
58
    };
59
}
60

61
pub const TYPE_NUMBER: u8 = 1;
62
pub const TYPE_HASH: u8 = 2;
63
pub const TYPE_COMMITMENT: u8 = 3;
64
pub const TYPE_PUBKEY: u8 = 4;
65
pub const TYPE_SIG: u8 = 5;
66
pub const TYPE_SCALAR: u8 = 6;
67

68
#[derive(Debug, Clone, PartialEq, Eq)]
69
pub enum StackItem {
70
    Number(i64),
71
    Hash(HashValue),
72
    Scalar(ScalarValue),
73
    Commitment(CompressedCommitment<RistrettoPublicKey>),
74
    PublicKey(CompressedKey<RistrettoPublicKey>),
75
    Signature(CompressedCheckSigSchnorrSignature),
76
}
77

78
impl StackItem {
79
    /// Convert an input item into its binary representation and append it to the array. The function returns the byte
80
    /// slice that matches the item as a convenience
81
    pub fn to_bytes<'a>(&self, array: &'a mut Vec<u8>) -> &'a [u8] {
13,339✔
82
        let n = array.len();
13,339✔
83
        match self {
13,339✔
84
            StackItem::Number(v) => {
2✔
85
                array.push(TYPE_NUMBER);
2✔
86
                array.extend_from_slice(&v.to_le_bytes());
2✔
87
            },
2✔
UNCOV
88
            StackItem::Hash(h) => {
×
UNCOV
89
                array.push(TYPE_HASH);
×
UNCOV
90
                array.extend_from_slice(&h[..]);
×
UNCOV
91
            },
×
92
            StackItem::Commitment(c) => {
×
93
                array.push(TYPE_COMMITMENT);
×
94
                array.extend_from_slice(c.as_bytes());
×
95
            },
×
96
            StackItem::PublicKey(p) => {
279✔
97
                array.push(TYPE_PUBKEY);
279✔
98
                array.extend_from_slice(p.as_bytes());
279✔
99
            },
279✔
100
            StackItem::Signature(s) => {
13,056✔
101
                array.push(TYPE_SIG);
13,056✔
102
                array.extend_from_slice(s.get_compressed_public_nonce().as_bytes());
13,056✔
103
                array.extend_from_slice(s.get_signature().as_bytes());
13,056✔
104
            },
13,056✔
105
            StackItem::Scalar(scalar) => {
2✔
106
                array.push(TYPE_SCALAR);
2✔
107
                array.extend_from_slice(scalar);
2✔
108
            },
2✔
109
        };
110
        array.get(n..).expect("Length is always valid")
13,339✔
111
    }
13,339✔
112

113
    /// Take a byte slice and read the next stack item from it, including any associated data. `read_next` returns a
114
    /// tuple of the deserialised item, and an updated slice that has the Opcode and data removed.
115
    pub fn read_next(bytes: &[u8]) -> Option<(Self, &[u8])> {
5,319✔
116
        let code = bytes.first()?;
5,319✔
117
        let remaining = bytes.get(1..)?;
5,319✔
118
        match *code {
5,319✔
119
            TYPE_NUMBER => StackItem::b_to_number(remaining),
3✔
120
            TYPE_HASH => StackItem::b_to_hash(remaining),
×
121
            TYPE_COMMITMENT => StackItem::b_to_commitment(remaining),
1✔
122
            TYPE_PUBKEY => StackItem::b_to_pubkey(remaining),
30✔
123
            TYPE_SIG => StackItem::b_to_sig(remaining),
5,280✔
124
            TYPE_SCALAR => StackItem::b_to_scalar(remaining),
5✔
125
            _ => None,
×
126
        }
127
    }
5,319✔
128

129
    fn b_to_number(b: &[u8]) -> Option<(Self, &[u8])> {
3✔
130
        let mut arr = [0u8; 8];
3✔
131
        arr.copy_from_slice(b.get(..8)?);
3✔
132
        Some((StackItem::Number(i64::from_le_bytes(arr)), b.get(8..)?))
3✔
133
    }
3✔
134

135
    fn b_to_hash(b: &[u8]) -> Option<(Self, &[u8])> {
×
136
        let mut arr = [0u8; 32];
×
137
        arr.copy_from_slice(b.get(..32)?);
×
138
        Some((StackItem::Hash(arr), b.get(32..)?))
×
139
    }
×
140

141
    fn b_to_scalar(b: &[u8]) -> Option<(Self, &[u8])> {
5✔
142
        let mut arr = [0u8; 32];
5✔
143
        arr.copy_from_slice(b.get(..32)?);
5✔
144
        Some((StackItem::Scalar(arr), b.get(32..)?))
5✔
145
    }
5✔
146

147
    fn b_to_commitment(b: &[u8]) -> Option<(Self, &[u8])> {
1✔
148
        let c = CompressedCommitment::from_canonical_bytes(b.get(..32)?).ok()?;
1✔
149
        Some((StackItem::Commitment(c), b.get(32..)?))
1✔
150
    }
1✔
151

152
    fn b_to_pubkey(b: &[u8]) -> Option<(Self, &[u8])> {
30✔
153
        let p = CompressedKey::from_canonical_bytes(b.get(..32)?).ok()?;
30✔
154
        Some((StackItem::PublicKey(p), b.get(32..)?))
30✔
155
    }
30✔
156

157
    fn b_to_sig(b: &[u8]) -> Option<(Self, &[u8])> {
5,280✔
158
        let r = RistrettoPublicKey::from_canonical_bytes(b.get(..32)?).ok()?;
5,280✔
159
        let s = RistrettoSecretKey::from_canonical_bytes(b.get(32..64)?).ok()?;
5,280✔
160
        let sig = CompressedCheckSigSchnorrSignature::new_from_schnorr(CheckSigSchnorrSignature::new(r, s));
5,280✔
161
        Some((StackItem::Signature(sig), b.get(64..)?))
5,280✔
162
    }
5,280✔
163
}
164

165
stack_item_from!(i64 => Number);
166
stack_item_from!(CompressedCommitment<RistrettoPublicKey> => Commitment);
167
stack_item_from!(CompressedKey<RistrettoPublicKey> => PublicKey);
168
stack_item_from!(CompressedCheckSigSchnorrSignature => Signature);
169
stack_item_from!(ScalarValue => Scalar);
170

171
#[derive(Debug, Default, Clone, PartialEq, Eq)]
172
pub struct ExecutionStack {
173
    items: Vec<StackItem>,
174
}
175

176
impl BorshSerialize for ExecutionStack {
177
    fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
2,683✔
178
        let bytes = self.to_bytes();
2,683✔
179
        writer.write_varint(bytes.len())?;
2,683✔
180
        for b in &bytes {
353,610✔
181
            b.serialize(writer)?;
350,927✔
182
        }
183
        Ok(())
2,683✔
184
    }
2,683✔
185
}
186

187
impl BorshDeserialize for ExecutionStack {
188
    fn deserialize_reader<R>(reader: &mut R) -> Result<Self, io::Error>
2✔
189
    where R: io::Read {
2✔
190
        let len = reader.read_varint()?;
2✔
191
        if len > MAX_STACK_SIZE {
2✔
192
            return Err(io::Error::new(
1✔
193
                io::ErrorKind::InvalidInput,
1✔
194
                "Larger than max execution stack bytes".to_string(),
1✔
195
            ));
1✔
196
        }
1✔
197
        let mut data = Vec::with_capacity(len);
1✔
198
        for _ in 0..len {
1✔
199
            data.push(u8::deserialize_reader(reader)?);
131✔
200
        }
201
        let stack = Self::from_bytes(data.as_slice())
1✔
202
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?;
1✔
203
        Ok(stack)
1✔
204
    }
2✔
205
}
206

207
impl ExecutionStack {
208
    /// Return a new `ExecutionStack` using the vector of [StackItem] in `items`
209
    pub fn new(items: Vec<StackItem>) -> Self {
1,139✔
210
        ExecutionStack { items }
1,139✔
211
    }
1,139✔
212

213
    /// Returns the number of entries in the execution stack
214
    pub fn size(&self) -> usize {
5,900✔
215
        self.items.len()
5,900✔
216
    }
5,900✔
217

218
    /// Returns a reference to the top entry in the stack without affecting the stack
219
    pub fn peek(&self) -> Option<&StackItem> {
8✔
220
        self.items.last()
8✔
221
    }
8✔
222

223
    /// Returns true if the stack is empty
224
    pub fn is_empty(&self) -> bool {
1✔
225
        self.items.is_empty()
1✔
226
    }
1✔
227

228
    /// Pops the top item in the stack. If the stack is not empty, `pop` returns the item, otherwise return `None` if
229
    /// it is empty.
230
    pub fn pop(&mut self) -> Option<StackItem> {
224✔
231
        self.items.pop()
224✔
232
    }
224✔
233

234
    /// Pops the top item in the stack and applies TryFrom for the given generic type. If the stack is not empty, and is
235
    /// a StackItem::Number, `pop_into_number` returns the parsed number. Returns an error if the stack is empty or if
236
    /// the top item is a different variant.
237
    pub fn pop_into_number<T: TryFrom<i64>>(&mut self) -> Result<T, ScriptError> {
37✔
238
        let item = self.items.pop().ok_or(ScriptError::StackUnderflow)?;
37✔
239

240
        let number = match item {
36✔
241
            StackItem::Number(n) => T::try_from(n).map_err(|_| ScriptError::ValueExceedsBounds)?,
36✔
242
            _ => return Err(ScriptError::InvalidInput),
×
243
        };
244

245
        Ok(number)
36✔
246
    }
37✔
247

248
    /// Pops n + 1 items from the stack. Checks if the last popped item matches any of the first n items. Returns an
249
    /// error if all n + 1 items aren't of the same variant, or if there are not n + 1 items on the stack.
250
    pub fn pop_n_plus_one_contains(&mut self, n: u8) -> Result<bool, ScriptError> {
13✔
251
        let items = self.pop_num_items(n as usize)?;
13✔
252
        let item = self.pop().ok_or(ScriptError::StackUnderflow)?;
13✔
253

254
        // check that all popped items are of the same variant
255
        // first count each variant
256
        let counts = items.iter().fold([0; 6], counter);
12✔
257
        // also check the n + 1 item
258
        let counts = counter(counts, &item);
12✔
259

260
        // then filter those with more than 0
261
        let num_distinct_variants = counts.iter().filter(|&c| *c > 0).count();
72✔
262

263
        if num_distinct_variants > 1 {
12✔
264
            return Err(ScriptError::InvalidInput);
2✔
265
        }
10✔
266

267
        Ok(items.contains(&item))
10✔
268
    }
13✔
269

270
    /// Pops the top n items in the stack. If the stack has at least n items, `pop_num_items` returns the items in stack
271
    /// order (ie. bottom first), otherwise returns an error.
272
    pub fn pop_num_items(&mut self, num_items: usize) -> Result<Vec<StackItem>, ScriptError> {
66✔
273
        let stack_size = self.size();
66✔
274

275
        if stack_size < num_items {
66✔
276
            Err(ScriptError::StackUnderflow)
2✔
277
        } else {
278
            let at = stack_size - num_items;
64✔
279
            let items = self.items.split_off(at);
64✔
280

281
            Ok(items)
64✔
282
        }
283
    }
66✔
284

285
    /// Return a binary array representation of the input stack
286
    pub fn to_bytes(&self) -> Vec<u8> {
6,377✔
287
        self.items.iter().fold(Vec::new(), |mut bytes, item| {
13,339✔
288
            item.to_bytes(&mut bytes);
13,339✔
289
            bytes
13,339✔
290
        })
13,339✔
291
    }
6,377✔
292

293
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, ScriptError> {
2,432✔
294
        let mut stack = ExecutionStack { items: Vec::new() };
2,432✔
295
        let mut byte_str = bytes;
2,432✔
296
        while !byte_str.is_empty() {
7,751✔
297
            match StackItem::read_next(byte_str) {
5,319✔
298
                Some((item, b)) => {
5,319✔
299
                    stack.push(item)?;
5,319✔
300
                    byte_str = b;
5,319✔
301
                },
302
                None => return Err(ScriptError::InvalidInput),
×
303
            }
304
        }
305
        Ok(stack)
2,432✔
306
    }
2,432✔
307

308
    /// Pushes the item onto the top of the stack. This function will only error if the new stack size exceeds the
309
    /// maximum allowed stack size, given by [MAX_STACK_SIZE]
310
    pub fn push(&mut self, item: StackItem) -> Result<(), ScriptError> {
5,710✔
311
        if self.size() >= MAX_STACK_SIZE {
5,710✔
312
            return Err(ScriptError::StackOverflow);
1✔
313
        }
5,709✔
314
        self.items.push(item);
5,709✔
315
        Ok(())
5,709✔
316
    }
5,710✔
317

318
    /// Pushes the top stack element down `depth` positions
319
    pub(crate) fn push_down(&mut self, depth: usize) -> Result<(), ScriptError> {
×
320
        let n = self.size();
×
321
        if n < depth + 1 {
×
322
            return Err(ScriptError::StackUnderflow);
×
323
        }
×
324
        if depth == 0 {
×
325
            return Ok(());
×
326
        }
×
327
        let top = self.pop().unwrap();
×
328
        self.items.insert(n - depth - 1, top);
×
329
        Ok(())
×
330
    }
×
331
}
332

333
impl Hex for ExecutionStack {
334
    fn from_hex(hex: &str) -> Result<Self, HexError>
3✔
335
    where Self: Sized {
3✔
336
        let b = from_hex(hex)?;
3✔
337
        ExecutionStack::from_bytes(&b).map_err(|_| HexError::HexConversionError {})
3✔
338
    }
3✔
339

340
    fn to_hex(&self) -> String {
2✔
341
        to_hex(&self.to_bytes())
2✔
342
    }
2✔
343
}
344

345
/// Utility function that given a count of `StackItem` variants, adds 1 for the given item.
346
#[allow(clippy::many_single_char_names)]
347
fn counter(values: [u8; 6], item: &StackItem) -> [u8; 6] {
36✔
348
    let [n, h, c, p, s, z] = values;
36✔
349
    #[allow(clippy::enum_glob_use)]
350
    use StackItem::*;
351
    match item {
36✔
352
        Number(_) => {
353
            let n = n + 1;
28✔
354
            [n, h, c, p, s, z]
28✔
355
        },
356
        Hash(_) => {
357
            let h = h + 1;
×
358
            [n, h, c, p, s, z]
×
359
        },
360
        Commitment(_) => {
361
            let c = c + 1;
×
362
            [n, h, c, p, s, z]
×
363
        },
364
        PublicKey(_) => {
365
            let p = p + 1;
8✔
366
            [n, h, c, p, s, z]
8✔
367
        },
368
        Signature(_) => {
369
            let s = s + 1;
×
370
            [n, h, c, p, s, z]
×
371
        },
372
        Scalar(_) => {
373
            let z = z + 1;
×
374
            [n, h, c, p, s, z]
×
375
        },
376
    }
377
}
36✔
378

379
#[cfg(test)]
380
mod test {
381
    use blake2::Blake2b;
382
    use borsh::{BorshDeserialize, BorshSerialize};
383
    use digest::{consts::U32, Digest};
384
    use rand::rngs::OsRng;
385
    use tari_crypto::{
386
        compressed_commitment::CompressedCommitment,
387
        compressed_key::CompressedKey,
388
        keys::SecretKey,
389
        ristretto::{RistrettoPublicKey, RistrettoSecretKey},
390
    };
391
    use tari_utilities::{
392
        hex::{from_hex, Hex},
393
        message_format::MessageFormat,
394
    };
395

396
    use crate::{
397
        op_codes::ScalarValue,
398
        CheckSigSchnorrSignature,
399
        CompressedCheckSigSchnorrSignature,
400
        ExecutionStack,
401
        HashValue,
402
        StackItem,
403
    };
404

405
    #[test]
406
    fn as_bytes_roundtrip() {
1✔
407
        use crate::StackItem::{Number, PublicKey, Signature};
408
        let k = RistrettoSecretKey::random(&mut rand::thread_rng());
1✔
409
        let p = CompressedKey::<RistrettoPublicKey>::from_secret_key(&k);
1✔
410
        let s = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
411
            CheckSigSchnorrSignature::sign(&k, b"hi", &mut OsRng).unwrap(),
1✔
412
        );
413
        let items = vec![Number(5432), Number(21), Signature(s), PublicKey(p)];
1✔
414
        let stack = ExecutionStack::new(items);
1✔
415
        let bytes = stack.to_bytes();
1✔
416
        let stack2 = ExecutionStack::from_bytes(&bytes).unwrap();
1✔
417
        assert_eq!(stack, stack2);
1✔
418
    }
1✔
419

420
    #[test]
421
    fn deserialisation() {
1✔
422
        let k =
1✔
423
            RistrettoSecretKey::from_hex("7212ac93ee205cdbbb57c4f0f815fbf8db25b4d04d3532e2262e31907d82c700").unwrap();
1✔
424
        let r =
1✔
425
            RistrettoSecretKey::from_hex("193ee873f3de511eda8ae387db6498f3d194d31a130a94cdf13dc5890ec1ad0f").unwrap();
1✔
426
        let p = CompressedKey::<RistrettoPublicKey>::from_secret_key(&k);
1✔
427
        let m = [1u8; 32];
1✔
428
        let sig = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
429
            CheckSigSchnorrSignature::sign_with_nonce_and_message(&k, r, m).unwrap(),
1✔
430
        );
431
        let inputs = inputs!(sig, p, m as HashValue);
1✔
432
        assert_eq!(inputs.to_hex(),
1✔
433
        "0500f7c695528c858cde76dab3076908e01228b6dbdd5f671bed1b03b89e170c31c6134be1c65544fa3f26c59903165f664db0dc364cbbaa4b35a9b33342cc01000456c0fa32558d6edc0916baa26b48e745de834571534ca253ea82435f08ebbc7c060101010101010101010101010101010101010101010101010101010101010101");
434
    }
1✔
435

436
    #[test]
437
    fn serialisation() {
1✔
438
        // let p =
439
        //     RistrettoPublicKey::from_hex("56c0fa32558d6edc0916baa26b48e745de834571534ca253ea82435f08ebbc7c").
440
        // unwrap(); let r =
441
        //     RistrettoPublicKey::from_hex("00f7c695528c858cde76dab3076908e01228b6dbdd5f671bed1b03b89e170c31").
442
        // unwrap(); let s =
443
        //     RistrettoSecretKey::from_hex("6db1023d5c46d78a97da8eb6c5a37e00d5f2fee182dcb38c1b6c65e90a43c109").
444
        // unwrap(); let sig = RistrettoSchnorr::new(r, s);
445
        // let m: HashValue = Blake2b::<U32>::digest(b"Hello Tari Script").into();
446
        // let inputs = inputs!(m, sig, p);
447
        // eprintln!("to_hex(&m) = {:?}", tari_utilities::hex::to_hex(&m));
448
        // eprintln!("inputs.to_hex() = {:?}", inputs.to_hex());
449

450
        let s = "06fdf9fc345d2cdd8aff624a55f824c7c9ce3cc972e011b4e750e417a90ecc5da50500f7c695528c858cde76dab3076908e0122\
1✔
451
        8b6dbdd5f671bed1b03b89e170c316db1023d5c46d78a97da8eb6c5a37e00d5f2fee182dcb38c1b6c65e90a43c1090456c0fa32558d6edc0916baa2\
1✔
452
        6b48e745de834571534ca253ea82435f08ebbc7c";
1✔
453
        let mut stack = ExecutionStack::from_hex(s).unwrap();
1✔
454
        assert_eq!(stack.size(), 3);
1✔
455
        if let Some(StackItem::PublicKey(p)) = stack.pop() {
1✔
456
            assert_eq!(
1✔
457
                p.to_hex(),
1✔
458
                "56c0fa32558d6edc0916baa26b48e745de834571534ca253ea82435f08ebbc7c"
459
            );
460
        } else {
461
            panic!("Expected pubkey")
×
462
        }
463
        if let Some(StackItem::Signature(s)) = stack.pop() {
1✔
464
            assert_eq!(
1✔
465
                s.get_compressed_public_nonce().to_hex(),
1✔
466
                "00f7c695528c858cde76dab3076908e01228b6dbdd5f671bed1b03b89e170c31"
467
            );
468
            assert_eq!(
1✔
469
                s.get_signature().to_hex(),
1✔
470
                "6db1023d5c46d78a97da8eb6c5a37e00d5f2fee182dcb38c1b6c65e90a43c109"
471
            );
472
        } else {
473
            panic!("Expected signature")
×
474
        }
475
        if let Some(StackItem::Scalar(s)) = stack.pop() {
1✔
476
            assert_eq!(
1✔
477
                s.as_slice(),
1✔
478
                from_hex("fdf9fc345d2cdd8aff624a55f824c7c9ce3cc972e011b4e750e417a90ecc5da5").unwrap()
1✔
479
            );
480
        } else {
481
            panic!("Expected scalar")
×
482
        }
483
    }
1✔
484

485
    #[test]
486
    fn serde_serialization_non_breaking() {
1✔
487
        const SERDE_ENCODED_BYTES: &str = "ce0000000000000006fdf9fc345d2cdd8aff624a55f824c7c9ce3cc9\
488
        72e011b4e750e417a90ecc5da50456c0fa32558d6edc0916baa26b48e745de834571534ca253ea82435f08ebbc\
489
        7c0556c0fa32558d6edc0916baa26b48e745de834571534ca253ea82435f08ebbc7c6db1023d5c46d78a97da8eb\
490
        6c5a37e00d5f2fee182dcb38c1b6c65e90a43c10906fdf9fc345d2cdd8aff624a55f824c7c9ce3cc972e011b4e7\
491
        50e417a90ecc5da501d2040000000000000356c0fa32558d6edc0916baa26b48e745de834571534ca253ea82435\
492
        f08ebbc7c";
493
        let p = CompressedKey::<RistrettoPublicKey>::from_hex(
1✔
494
            "56c0fa32558d6edc0916baa26b48e745de834571534ca253ea82435f08ebbc7c",
1✔
495
        )
496
        .unwrap();
1✔
497
        let s =
1✔
498
            RistrettoSecretKey::from_hex("6db1023d5c46d78a97da8eb6c5a37e00d5f2fee182dcb38c1b6c65e90a43c109").unwrap();
1✔
499
        let sig = CompressedCheckSigSchnorrSignature::new(p.clone(), s);
1✔
500
        let m: HashValue = Blake2b::<U32>::digest(b"Hello Tari Script").into();
1✔
501
        let s: ScalarValue = m;
1✔
502
        let commitment = CompressedCommitment::<RistrettoPublicKey>::from_compressed_key(p.clone());
1✔
503

504
        // Includes all variants for StackItem
505
        let mut expected_inputs = inputs!(s, p, sig, m, 1234, commitment);
1✔
506
        let stack = ExecutionStack::from_binary(&from_hex(SERDE_ENCODED_BYTES).unwrap()).unwrap();
1✔
507

508
        for (i, item) in stack.items.into_iter().enumerate().rev() {
6✔
509
            assert_eq!(
6✔
510
                item,
511
                expected_inputs.pop().unwrap(),
6✔
512
                "Stack items did not match at index {i}"
×
513
            );
514
        }
515

516
        assert!(expected_inputs.is_empty());
1✔
517
    }
1✔
518

519
    #[test]
520
    fn test_borsh_de_serialization() {
1✔
521
        let s = "06fdf9fc345d2cdd8aff624a55f824c7c9ce3cc972e011b4e750e417a90ecc5da50500f7c695528c858cde76dab3076908e0122\
1✔
522
        8b6dbdd5f671bed1b03b89e170c316db1023d5c46d78a97da8eb6c5a37e00d5f2fee182dcb38c1b6c65e90a43c1090456c0fa32558d6edc0916baa2\
1✔
523
        6b48e745de834571534ca253ea82435f08ebbc7c";
1✔
524
        let stack = ExecutionStack::from_hex(s).unwrap();
1✔
525
        let mut buf = Vec::new();
1✔
526
        stack.serialize(&mut buf).unwrap();
1✔
527
        buf.extend_from_slice(&[1, 2, 3]);
1✔
528
        let buf = &mut buf.as_slice();
1✔
529
        assert_eq!(stack, ExecutionStack::deserialize(buf).unwrap());
1✔
530
        assert_eq!(buf, &[1, 2, 3]);
1✔
531
    }
1✔
532

533
    #[test]
534
    fn test_borsh_de_serialization_too_large() {
1✔
535
        // We dont care about the actual stack here, just that its not too large on the varint size
536
        // We lie about the size to try and get a mem panic, and say this stack is u64::max large.
537
        let buf = vec![255, 255, 255, 255, 255, 255, 255, 255, 255, 1, 49, 8, 2, 5, 6];
1✔
538
        let buf = &mut buf.as_slice();
1✔
539
        assert!(ExecutionStack::deserialize(buf).is_err());
1✔
540
    }
1✔
541
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc