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

tari-project / tari / 17121538955

21 Aug 2025 08:30AM UTC coverage: 55.905% (+1.6%) from 54.344%
17121538955

push

github

web-flow
chore: simplified tx builder (#7425)

Description
---
New simpliefied tx builder

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* New builder-based transaction flow with fee estimation, streamlined
inputs/outputs, automatic change handling, and clearer one-shot
finalization.
* Simplified one-sided/stealth payment paths unified across wallet and
gRPC.

* **Breaking Changes**
  * CLI: "SendMinotari" command removed.
* FFI: wallet_send_transaction signature no longer accepts a one_sided
flag — update callers.
* gRPC: standard Mimblewimble sends rejected; use one-sided or
stealth-address flows.
* Public services/handles now require key-manager–specific types;
integrations must be updated.
* **Chores**
* Removed legacy protocol restart utilities, protocol streams, and
related task APIs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

1135 of 2030 new or added lines in 25 files covered. (55.91%)

119 existing lines in 23 files now uncovered.

75634 of 135291 relevant lines covered (55.9%)

201597.51 hits per line

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

97.14
/infrastructure/tari_script/src/script.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
// pending updates to Dalek/Digest
19
use std::{cmp::Ordering, collections::HashSet, fmt, io, ops::Deref};
20

21
use blake2::Blake2b;
22
use borsh::{BorshDeserialize, BorshSerialize};
23
use digest::{consts::U32, Digest};
24
use integer_encoding::{VarIntReader, VarIntWriter};
25
use sha2::Sha256;
26
use sha3::Sha3_256;
27
use tari_crypto::{
28
    compressed_commitment::CompressedCommitment,
29
    compressed_key::CompressedKey,
30
    ristretto::{RistrettoPublicKey, RistrettoSecretKey},
31
};
32
use tari_max_size::MaxSizeVec;
33
use tari_utilities::{
34
    hex::{from_hex, to_hex, Hex, HexError},
35
    ByteArray,
36
};
37

38
use crate::{
39
    op_codes::Message,
40
    slice_to_hash,
41
    CompressedCheckSigSchnorrSignature,
42
    ExecutionStack,
43
    HashValue,
44
    Opcode,
45
    ScriptContext,
46
    ScriptError,
47
    StackItem,
48
};
49

50
#[macro_export]
51
macro_rules! script {
52
    ($($opcode:ident$(($($var:expr),+))?) +) => {{
53
        use $crate::TariScript;
54
        use $crate::Opcode;
55
        let script = vec![$(Opcode::$opcode $(($($var),+))?),+];
56
        TariScript::new(script)
57
    }}
58
}
59

60
const MAX_MULTISIG_LIMIT: u8 = 32;
61
const MAX_SCRIPT_BYTES: usize = 4096;
62

63
/// The sized vector of opcodes that make up a script
64
pub type ScriptOpcodes = MaxSizeVec<Opcode, 128>;
65

66
#[derive(Clone, Debug, PartialEq, Eq)]
67
pub struct TariScript {
68
    script: ScriptOpcodes,
69
}
70

71
impl BorshSerialize for TariScript {
72
    fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
32,411✔
73
        let bytes = self.to_bytes();
32,411✔
74
        writer.write_varint(bytes.len())?;
32,411✔
75
        for b in &bytes {
3,282,323✔
76
            b.serialize(writer)?;
3,249,912✔
77
        }
78
        Ok(())
32,411✔
79
    }
32,411✔
80
}
81

82
impl BorshDeserialize for TariScript {
83
    fn deserialize_reader<R>(reader: &mut R) -> Result<Self, io::Error>
4✔
84
    where R: io::Read {
4✔
85
        let len = reader.read_varint()?;
4✔
86
        if len > MAX_SCRIPT_BYTES {
4✔
87
            return Err(io::Error::new(
1✔
88
                io::ErrorKind::InvalidInput,
1✔
89
                "Larger than max script bytes".to_string(),
1✔
90
            ));
1✔
91
        }
3✔
92
        let mut data = Vec::with_capacity(len);
3✔
93
        for _ in 0..len {
3✔
94
            data.push(u8::deserialize_reader(reader)?);
177✔
95
        }
96
        let script = TariScript::from_bytes(data.as_slice())
3✔
97
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
3✔
98
        Ok(script)
3✔
99
    }
4✔
100
}
101

102
impl TariScript {
103
    pub fn new(script: Vec<Opcode>) -> Result<Self, ScriptError> {
1,310✔
104
        let script = ScriptOpcodes::try_from(script)?;
1,310✔
105
        Ok(TariScript { script })
1,310✔
106
    }
1,310✔
107

108
    /// This pattern matches two scripts ensure they have the same instructions in the opcodes, but not the same values
109
    /// inside example:
110
    /// Script A = {PushPubKey(AA)}, Script B = {PushPubKey(BB)} will pattern match, but doing Script A == Script B will
111
    /// not match Script A = {PushPubKey(AA)}, Script B = {PushPubKey(AA)} will pattern match, doing Script A ==
112
    /// Script B will also match Script A = {PushPubKey(AA)}, Script B = {PushHash(BB)} will not pattern match, and
113
    /// doing Script A == Script B will not match
114
    pub fn pattern_match(&self, script: &TariScript) -> bool {
4✔
115
        for (i, opcode) in self.script.iter().enumerate() {
5✔
116
            if let Some(code) = script.opcode(i) {
5✔
117
                if std::mem::discriminant(opcode) != std::mem::discriminant(code) {
5✔
118
                    return false;
×
119
                }
5✔
120
            } else {
121
                return false;
×
122
            }
123
        }
124
        // We need to ensure they are the same length
125
        script.opcode(self.script.len()).is_none()
4✔
126
    }
4✔
127

128
    /// Retrieve the opcode at the index, returns None if the index does not exist
129
    pub fn opcode(&self, i: usize) -> Option<&Opcode> {
9✔
130
        let opcode = self.script.get(i)?;
9✔
131
        Some(opcode)
6✔
132
    }
9✔
133

134
    /// Executes the script using a default context. If successful, returns the final stack item.
135
    pub fn execute(&self, inputs: &ExecutionStack) -> Result<StackItem, ScriptError> {
120✔
136
        self.execute_with_context(inputs, &ScriptContext::default())
120✔
137
    }
120✔
138

139
    /// Execute the script with the given inputs and the provided context. If successful, returns the final stack item.
140
    pub fn execute_with_context(
196✔
141
        &self,
196✔
142
        inputs: &ExecutionStack,
196✔
143
        context: &ScriptContext,
196✔
144
    ) -> Result<StackItem, ScriptError> {
196✔
145
        // Copy all inputs onto the stack
196✔
146
        let mut stack = inputs.clone();
196✔
147

196✔
148
        // Local execution state
196✔
149
        let mut state = ExecutionState::default();
196✔
150

151
        for opcode in self.script.iter() {
329✔
152
            if self.should_execute(opcode, &state)? {
329✔
153
                self.execute_opcode(opcode, &mut stack, context, &mut state)?
302✔
154
            } else {
155
                continue;
27✔
156
            }
157
        }
158

159
        // the script has finished but there was an open IfThen or Else!
160
        if !state.if_stack.is_empty() {
143✔
161
            return Err(ScriptError::MissingOpcode);
1✔
162
        }
142✔
163

142✔
164
        // After the script completes, it is successful if and only if it has not aborted, and there is exactly a single
142✔
165
        // element on the stack. The script fails if the stack is empty, or contains more than one element, or aborts
142✔
166
        // early.
142✔
167
        if stack.size() == 1 {
142✔
168
            stack.pop().ok_or(ScriptError::NonUnitLengthStack)
141✔
169
        } else {
170
            Err(ScriptError::NonUnitLengthStack)
1✔
171
        }
172
    }
196✔
173

174
    /// Returns the number of script op codes
UNCOV
175
    pub fn size(&self) -> usize {
×
UNCOV
176
        self.script.len()
×
UNCOV
177
    }
×
178

179
    fn should_execute(&self, opcode: &Opcode, state: &ExecutionState) -> Result<bool, ScriptError> {
329✔
180
        use Opcode::{Else, EndIf, IfThen};
181
        match opcode {
329✔
182
            // always execute these, they will update execution state
183
            IfThen | Else | EndIf => Ok(true),
64✔
184
            // otherwise keep calm and carry on
185
            _ => Ok(state.executing),
265✔
186
        }
187
    }
329✔
188

189
    pub fn to_bytes(&self) -> Vec<u8> {
39,435✔
190
        self.script.iter().fold(Vec::new(), |mut bytes, op| {
170,437✔
191
            op.to_bytes(&mut bytes);
170,437✔
192
            bytes
170,437✔
193
        })
170,437✔
194
    }
39,435✔
195

196
    pub fn as_slice(&self) -> &[Opcode] {
551✔
197
        self.script.as_ref()
551✔
198
    }
551✔
199

200
    /// Calculate the hash of the script.
201
    /// `as_hash` returns [ScriptError::InvalidDigest] if the digest function does not produce at least 32 bytes of
202
    /// output.
203
    pub fn as_hash<D: Digest>(&self) -> Result<HashValue, ScriptError> {
×
204
        if <D as Digest>::output_size() < 32 {
×
205
            return Err(ScriptError::InvalidDigest);
×
206
        }
×
207
        let h = D::digest(self.to_bytes());
×
208
        Ok(slice_to_hash(h.as_slice().get(..32).ok_or(ScriptError::InvalidDigest)?))
×
209
    }
×
210

211
    /// Try to deserialise a byte slice into a valid Tari script
212
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, ScriptError> {
9,234✔
213
        let script = ScriptOpcodes::try_from(Opcode::parse(bytes)?)?;
9,234✔
214

215
        Ok(TariScript { script })
9,234✔
216
    }
9,234✔
217

218
    /// Convert the script into an array of opcode strings.
219
    ///
220
    /// # Example
221
    /// ```edition2018
222
    /// use tari_script::TariScript;
223
    /// use tari_utilities::hex::Hex;
224
    ///
225
    /// let hex_script = "71b07aae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e58170ac276657a418820f34036b20ea615302b373c70ac8feab8d30681a3e0f0960e708";
226
    /// let script = TariScript::from_hex(hex_script).unwrap();
227
    /// let ops = vec![
228
    ///     "Dup",
229
    ///     "HashBlake256",
230
    ///     "PushHash(ae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e5)",
231
    ///     "EqualVerify",
232
    ///     "Drop",
233
    ///     "CheckSig(276657a418820f34036b20ea615302b373c70ac8feab8d30681a3e0f0960e708)",
234
    /// ]
235
    /// .into_iter()
236
    /// .map(String::from)
237
    /// .collect::<Vec<String>>();
238
    /// assert_eq!(script.to_opcodes(), ops);
239
    /// ```
240
    pub fn to_opcodes(&self) -> Vec<String> {
4✔
241
        self.script.iter().map(|op| op.to_string()).collect()
14✔
242
    }
4✔
243

244
    // pending updates to Dalek/Digest
245
    fn execute_opcode(
302✔
246
        &self,
302✔
247
        opcode: &Opcode,
302✔
248
        stack: &mut ExecutionStack,
302✔
249
        ctx: &ScriptContext,
302✔
250
        state: &mut ExecutionState,
302✔
251
    ) -> Result<(), ScriptError> {
302✔
252
        #[allow(clippy::enum_glob_use)]
253
        use Opcode::*;
254
        use StackItem::{Hash, Number, PublicKey};
255
        match opcode {
302✔
256
            CheckHeightVerify(height) => TariScript::handle_check_height_verify(*height, ctx.block_height()),
10✔
257
            CheckHeight(height) => TariScript::handle_check_height(stack, *height, ctx.block_height()),
20✔
258
            CompareHeightVerify => TariScript::handle_compare_height_verify(stack, ctx.block_height()),
10✔
259
            CompareHeight => TariScript::handle_compare_height(stack, ctx.block_height()),
15✔
260
            Nop => Ok(()),
19✔
261
            PushZero => stack.push(Number(0)),
2✔
262
            PushOne => stack.push(Number(1)),
6✔
263
            PushHash(h) => stack.push(Hash(*h.clone())),
1✔
264
            PushInt(n) => stack.push(Number(*n)),
8✔
265
            PushPubKey(p) => stack.push(PublicKey(*p.clone())),
15✔
266
            Drop => TariScript::handle_drop(stack),
5✔
267
            Dup => TariScript::handle_dup(stack),
8✔
268
            RevRot => stack.push_down(2),
×
269
            GeZero => TariScript::handle_cmp_to_zero(stack, &[Ordering::Greater, Ordering::Equal]),
6✔
270
            GtZero => TariScript::handle_cmp_to_zero(stack, &[Ordering::Greater]),
2✔
271
            LeZero => TariScript::handle_cmp_to_zero(stack, &[Ordering::Less, Ordering::Equal]),
2✔
272
            LtZero => TariScript::handle_cmp_to_zero(stack, &[Ordering::Less]),
2✔
273
            Add => TariScript::handle_op_add(stack),
7✔
274
            Sub => TariScript::handle_op_sub(stack),
2✔
275
            Equal => {
276
                if TariScript::handle_equal(stack)? {
×
277
                    stack.push(Number(1))
×
278
                } else {
279
                    stack.push(Number(0))
×
280
                }
281
            },
282
            EqualVerify => {
283
                if TariScript::handle_equal(stack)? {
3✔
284
                    Ok(())
2✔
285
                } else {
286
                    Err(ScriptError::VerifyFailed)
1✔
287
                }
288
            },
289
            Or(n) => TariScript::handle_or(stack, *n),
7✔
290
            OrVerify(n) => TariScript::handle_or_verify(stack, *n),
6✔
291
            HashBlake256 => TariScript::handle_hash::<Blake2b<U32>>(stack),
1✔
292
            HashSha256 => TariScript::handle_hash::<Sha256>(stack),
2✔
293
            HashSha3 => TariScript::handle_hash::<Sha3_256>(stack),
2✔
294
            CheckSig(msg) => {
9✔
295
                if self.check_sig(stack, *msg.deref())? {
9✔
296
                    stack.push(Number(1))
5✔
297
                } else {
298
                    stack.push(Number(0))
4✔
299
                }
300
            },
301
            CheckSigVerify(msg) => {
2✔
302
                if self.check_sig(stack, *msg.deref())? {
2✔
303
                    Ok(())
1✔
304
                } else {
305
                    Err(ScriptError::VerifyFailed)
1✔
306
                }
307
            },
308
            CheckMultiSig(m, n, public_keys, msg) => {
30✔
309
                if self.check_multisig(stack, *m, *n, public_keys, *msg.deref())?.is_some() {
30✔
310
                    stack.push(Number(1))
14✔
311
                } else {
312
                    stack.push(Number(0))
11✔
313
                }
314
            },
315
            CheckMultiSigVerify(m, n, public_keys, msg) => {
24✔
316
                if self.check_multisig(stack, *m, *n, public_keys, *msg.deref())?.is_some() {
24✔
317
                    Ok(())
12✔
318
                } else {
319
                    Err(ScriptError::VerifyFailed)
8✔
320
                }
321
            },
322
            CheckMultiSigVerifyAggregatePubKey(m, n, public_keys, msg) => {
6✔
323
                if let Some(agg_pub_key) = self.check_multisig(stack, *m, *n, public_keys, *msg.deref())? {
6✔
324
                    stack.push(PublicKey(agg_pub_key))
4✔
325
                } else {
326
                    Err(ScriptError::VerifyFailed)
1✔
327
                }
328
            },
329
            ToRistrettoPoint => self.handle_to_ristretto_point(stack),
4✔
330
            Return => Err(ScriptError::Return),
2✔
331
            IfThen => TariScript::handle_if_then(stack, state),
23✔
332
            Else => TariScript::handle_else(state),
22✔
333
            EndIf => TariScript::handle_end_if(state),
19✔
334
        }
335
    }
302✔
336

337
    fn handle_check_height_verify(height: u64, block_height: u64) -> Result<(), ScriptError> {
10✔
338
        if block_height >= height {
10✔
339
            Ok(())
6✔
340
        } else {
341
            Err(ScriptError::VerifyFailed)
4✔
342
        }
343
    }
10✔
344

345
    fn handle_check_height(stack: &mut ExecutionStack, height: u64, block_height: u64) -> Result<(), ScriptError> {
20✔
346
        let height = i64::try_from(height)?;
20✔
347
        let block_height = i64::try_from(block_height)?;
18✔
348

349
        // Due to the conversion of u64 into i64 which would fail above if they overflowed, these
350
        // numbers should never enter a state where a `sub` could fail. As they'd both be within range and 0 or above.
351
        // This differs from compare_height due to a stack number being used, which can be lower than 0
352
        let item = match block_height.checked_sub(height) {
17✔
353
            Some(num) => StackItem::Number(num),
17✔
354
            None => {
355
                return Err(ScriptError::CompareFailed(
×
356
                    "Subtraction of given height from current block height failed".to_string(),
×
357
                ))
×
358
            },
359
        };
360

361
        stack.push(item)
17✔
362
    }
20✔
363

364
    fn handle_compare_height_verify(stack: &mut ExecutionStack, block_height: u64) -> Result<(), ScriptError> {
10✔
365
        let target_height = stack.pop_into_number::<u64>()?;
10✔
366

367
        if block_height >= target_height {
10✔
368
            Ok(())
6✔
369
        } else {
370
            Err(ScriptError::VerifyFailed)
4✔
371
        }
372
    }
10✔
373

374
    fn handle_compare_height(stack: &mut ExecutionStack, block_height: u64) -> Result<(), ScriptError> {
15✔
375
        let target_height = stack.pop_into_number::<i64>()?;
15✔
376
        let block_height = i64::try_from(block_height)?;
14✔
377

378
        // Here it is possible to underflow because the stack can take lower numbers where check
379
        // height does not use a stack number and it's minimum can't be lower than 0.
380
        let item = match block_height.checked_sub(target_height) {
13✔
381
            Some(num) => StackItem::Number(num),
12✔
382
            None => {
383
                return Err(ScriptError::CompareFailed(
1✔
384
                    "Couldn't subtract the target height from the current block height".to_string(),
1✔
385
                ))
1✔
386
            },
387
        };
388

389
        stack.push(item)
12✔
390
    }
15✔
391

392
    fn handle_cmp_to_zero(stack: &mut ExecutionStack, valid_orderings: &[Ordering]) -> Result<(), ScriptError> {
12✔
393
        let stack_number = stack.pop_into_number::<i64>()?;
12✔
394
        let ordering = &stack_number.cmp(&0);
12✔
395

12✔
396
        if valid_orderings.contains(ordering) {
12✔
397
            stack.push(StackItem::Number(1))
8✔
398
        } else {
399
            stack.push(StackItem::Number(0))
4✔
400
        }
401
    }
12✔
402

403
    fn handle_or(stack: &mut ExecutionStack, n: u8) -> Result<(), ScriptError> {
7✔
404
        if stack.pop_n_plus_one_contains(n)? {
7✔
405
            stack.push(StackItem::Number(1))
2✔
406
        } else {
407
            stack.push(StackItem::Number(0))
2✔
408
        }
409
    }
7✔
410

411
    fn handle_or_verify(stack: &mut ExecutionStack, n: u8) -> Result<(), ScriptError> {
6✔
412
        if stack.pop_n_plus_one_contains(n)? {
6✔
413
            Ok(())
4✔
414
        } else {
415
            Err(ScriptError::VerifyFailed)
2✔
416
        }
417
    }
6✔
418

419
    fn handle_if_then(stack: &mut ExecutionStack, state: &mut ExecutionState) -> Result<(), ScriptError> {
23✔
420
        if state.executing {
23✔
421
            let pred = stack.pop().ok_or(ScriptError::StackUnderflow)?;
21✔
422
            match pred {
21✔
423
                StackItem::Number(1) => {
424
                    // continue execution until Else opcode
425
                    state.executing = true;
11✔
426
                    let if_state = IfState {
11✔
427
                        branch: Branch::ExecuteIf,
11✔
428
                        else_expected: true,
11✔
429
                    };
11✔
430
                    state.if_stack.push(if_state);
11✔
431
                    Ok(())
11✔
432
                },
433
                StackItem::Number(0) => {
434
                    // skip execution until Else opcode
435
                    state.executing = false;
10✔
436
                    let if_state = IfState {
10✔
437
                        branch: Branch::ExecuteElse,
10✔
438
                        else_expected: true,
10✔
439
                    };
10✔
440
                    state.if_stack.push(if_state);
10✔
441
                    Ok(())
10✔
442
                },
443
                _ => Err(ScriptError::InvalidInput),
×
444
            }
445
        } else {
446
            let if_state = IfState {
2✔
447
                branch: Branch::NotExecuted,
2✔
448
                else_expected: true,
2✔
449
            };
2✔
450
            state.if_stack.push(if_state);
2✔
451
            Ok(())
2✔
452
        }
453
    }
23✔
454

455
    fn handle_else(state: &mut ExecutionState) -> Result<(), ScriptError> {
22✔
456
        let if_state = state.if_stack.last_mut().ok_or(ScriptError::InvalidOpcode)?;
22✔
457

458
        // check to make sure Else is expected
459
        if !if_state.else_expected {
21✔
460
            return Err(ScriptError::InvalidOpcode);
1✔
461
        }
20✔
462

20✔
463
        match if_state.branch {
20✔
464
            Branch::NotExecuted => {
2✔
465
                state.executing = false;
2✔
466
            },
2✔
467
            Branch::ExecuteIf => {
8✔
468
                state.executing = false;
8✔
469
            },
8✔
470
            Branch::ExecuteElse => {
10✔
471
                state.executing = true;
10✔
472
            },
10✔
473
        }
474
        if_state.else_expected = false;
20✔
475
        Ok(())
20✔
476
    }
22✔
477

478
    fn handle_end_if(state: &mut ExecutionState) -> Result<(), ScriptError> {
19✔
479
        // check to make sure EndIf is expected
480
        let if_state = state.if_stack.pop().ok_or(ScriptError::InvalidOpcode)?;
19✔
481

482
        // check if we still expect an Else first
483
        if if_state.else_expected {
17✔
484
            return Err(ScriptError::MissingOpcode);
1✔
485
        }
16✔
486

16✔
487
        match if_state.branch {
16✔
488
            Branch::NotExecuted => {
2✔
489
                state.executing = false;
2✔
490
            },
2✔
491
            Branch::ExecuteIf => {
8✔
492
                state.executing = true;
8✔
493
            },
8✔
494
            Branch::ExecuteElse => {
6✔
495
                state.executing = true;
6✔
496
            },
6✔
497
        }
498
        Ok(())
16✔
499
    }
19✔
500

501
    /// Handle opcodes that push a hash to the stack. I'm not doing any length checks right now, so this should be
502
    /// added once other digest functions are provided that don't produce 32 byte hashes
503
    fn handle_hash<D: Digest>(stack: &mut ExecutionStack) -> Result<(), ScriptError> {
5✔
504
        use StackItem::{Commitment, Hash, PublicKey};
505
        let top = stack.pop().ok_or(ScriptError::StackUnderflow)?;
5✔
506
        // use a closure to grab &b while it still exists in the match expression
507
        let to_arr = |b: &[u8]| {
5✔
508
            let mut hash = [0u8; 32];
5✔
509
            hash.copy_from_slice(D::digest(b).as_slice());
5✔
510
            hash
5✔
511
        };
5✔
512
        let hash_value = match top {
5✔
513
            Commitment(c) => to_arr(c.as_bytes()),
2✔
514
            PublicKey(k) => to_arr(k.as_bytes()),
3✔
515
            Hash(h) => to_arr(&h),
×
516
            _ => return Err(ScriptError::IncompatibleTypes),
×
517
        };
518

519
        stack.push(Hash(hash_value))
5✔
520
    }
5✔
521

522
    fn handle_dup(stack: &mut ExecutionStack) -> Result<(), ScriptError> {
8✔
523
        let last = if let Some(last) = stack.peek() {
8✔
524
            last.clone()
8✔
525
        } else {
526
            return Err(ScriptError::StackUnderflow);
×
527
        };
528
        stack.push(last)
8✔
529
    }
8✔
530

531
    fn handle_drop(stack: &mut ExecutionStack) -> Result<(), ScriptError> {
5✔
532
        match stack.pop() {
5✔
533
            Some(_) => Ok(()),
5✔
534
            None => Err(ScriptError::StackUnderflow),
×
535
        }
536
    }
5✔
537

538
    fn handle_op_add(stack: &mut ExecutionStack) -> Result<(), ScriptError> {
7✔
539
        use StackItem::{Commitment, Number, PublicKey};
540
        let top = stack.pop().ok_or(ScriptError::StackUnderflow)?;
7✔
541
        let two = stack.pop().ok_or(ScriptError::StackUnderflow)?;
7✔
542
        match (top, two) {
6✔
543
            (Number(v1), Number(v2)) => stack.push(Number(v1.checked_add(v2).ok_or(ScriptError::ValueExceedsBounds)?)),
5✔
544
            (Commitment(c1), Commitment(c2)) => {
1✔
545
                let com_1 = c1.to_commitment()?;
1✔
546
                let com_2 = c2.to_commitment()?;
1✔
547
                stack.push(Commitment(CompressedCommitment::from_commitment(&com_1 + &com_2)))
1✔
548
            },
549
            (PublicKey(p1), PublicKey(p2)) => {
×
550
                let key1 = p1.to_public_key().map_err(|_| ScriptError::InvalidInput)?;
×
551
                let key2 = p2.to_public_key().map_err(|_| ScriptError::InvalidInput)?;
×
552
                let compressed_key = CompressedKey::new_from_pk(&key1 + &key2);
×
553
                stack.push(PublicKey(compressed_key))
×
554
            },
555
            (_, _) => Err(ScriptError::IncompatibleTypes),
×
556
        }
557
    }
7✔
558

559
    fn handle_op_sub(stack: &mut ExecutionStack) -> Result<(), ScriptError> {
2✔
560
        use StackItem::{Commitment, Number};
561
        let top = stack.pop().ok_or(ScriptError::StackUnderflow)?;
2✔
562
        let two = stack.pop().ok_or(ScriptError::StackUnderflow)?;
2✔
563
        match (top, two) {
2✔
564
            (Number(v1), Number(v2)) => stack.push(Number(v2.checked_sub(v1).ok_or(ScriptError::ValueExceedsBounds)?)),
2✔
565
            (Commitment(c1), Commitment(c2)) => {
×
566
                let com_1 = c1.to_commitment()?;
×
567
                let com_2 = c2.to_commitment()?;
×
568
                stack.push(Commitment(CompressedCommitment::from_commitment(&com_2 - &com_1)))
×
569
            },
570
            (..) => Err(ScriptError::IncompatibleTypes),
×
571
        }
572
    }
2✔
573

574
    fn handle_equal(stack: &mut ExecutionStack) -> Result<bool, ScriptError> {
3✔
575
        use StackItem::{Commitment, Hash, Number, PublicKey, Signature};
576
        let top = stack.pop().ok_or(ScriptError::StackUnderflow)?;
3✔
577
        let two = stack.pop().ok_or(ScriptError::StackUnderflow)?;
3✔
578
        match (top, two) {
3✔
579
            (Number(v1), Number(v2)) => Ok(v1 == v2),
×
580
            (Commitment(c1), Commitment(c2)) => Ok(c1 == c2),
×
581
            (Signature(s1), Signature(s2)) => Ok(s1 == s2),
×
582
            (PublicKey(p1), PublicKey(p2)) => Ok(p1 == p2),
2✔
583
            (Hash(h1), Hash(h2)) => Ok(h1 == h2),
1✔
584
            (..) => Err(ScriptError::IncompatibleTypes),
×
585
        }
586
    }
3✔
587

588
    fn check_sig(&self, stack: &mut ExecutionStack, message: Message) -> Result<bool, ScriptError> {
11✔
589
        use StackItem::{PublicKey, Signature};
590
        let pk = stack.pop().ok_or(ScriptError::StackUnderflow)?;
11✔
591
        let sig = stack.pop().ok_or(ScriptError::StackUnderflow)?;
11✔
592
        match (pk, sig) {
11✔
593
            (PublicKey(p), Signature(s)) => {
11✔
594
                let key = p.to_public_key().map_err(|_| ScriptError::InvalidInput)?;
11✔
595
                let sig = s.to_schnorr_signature()?;
11✔
596
                Ok(sig.verify(&key, message))
11✔
597
            },
598
            (..) => Err(ScriptError::IncompatibleTypes),
×
599
        }
600
    }
11✔
601

602
    /// Validates an m-of-n multisig script
603
    ///
604
    /// This validation broadly proceeds to check if **exactly** _m_ signatures are valid signatures out of a
605
    /// possible _n_ public keys.
606
    ///
607
    /// A successful validation returns `Ok(P)` where _P_ is the sum of the public keys that matched the _m_
608
    /// signatures. If the validation was NOT successful, `check_multisig` returns `Ok(None)`. This is a private
609
    /// function, and callers will interpret these results according to their use cases.
610
    ///
611
    /// Other problems, such as stack underflows, invalid parameters etc return an `Err` as usual.
612
    ///
613
    /// Notes:
614
    /// * The _m_ signatures are expected to be the top _m_ items on the stack.
615
    /// * The ordering of signatures on the stack MUST match the relative ordering of the corresponding public keys.
616
    /// * The list may contain duplicate keys, but each occurrence of a public key may be used AT MOST once.
617
    /// * Every signature MUST be a valid signature using one of the public keys
618
    /// * _m_ and _n_ must be positive AND m <= n AND n <= MAX_MULTISIG_LIMIT (32).
619
    fn check_multisig(
60✔
620
        &self,
60✔
621
        stack: &mut ExecutionStack,
60✔
622

60✔
623
        m: u8,
60✔
624
        n: u8,
60✔
625
        public_keys: &[CompressedKey<RistrettoPublicKey>],
60✔
626
        message: Message,
60✔
627
    ) -> Result<Option<CompressedKey<RistrettoPublicKey>>, ScriptError> {
60✔
628
        if m == 0 || n == 0 || m > n || n > MAX_MULTISIG_LIMIT || public_keys.len() != n as usize {
60✔
629
            return Err(ScriptError::ValueExceedsBounds);
7✔
630
        }
53✔
631
        // pop m sigs
53✔
632
        let m = m as usize;
53✔
633
        let signatures = stack
53✔
634
            .pop_num_items(m)?
53✔
635
            .into_iter()
51✔
636
            .map(|item| match item {
101✔
637
                StackItem::Signature(s) => Ok(s),
100✔
638
                _ => Err(ScriptError::IncompatibleTypes),
1✔
639
            })
101✔
640
            .collect::<Result<Vec<CompressedCheckSigSchnorrSignature>, ScriptError>>()?;
51✔
641

642
        // keep a hashset of unique signatures used to prevent someone putting the same signature in more than once.
643
        #[allow(clippy::mutable_key_type)]
644
        let mut sig_set = HashSet::new();
50✔
645

50✔
646
        let mut agg_pub_key = RistrettoPublicKey::default();
50✔
647

50✔
648
        // Create an iterator that allows each pubkey to only be checked a single time as they are
50✔
649
        // removed from the collection when referenced
50✔
650
        let mut pub_keys = public_keys.iter();
50✔
651

652
        // Signatures and public keys must be ordered
653
        for s in &signatures {
124✔
654
            if pub_keys.len() == 0 {
92✔
655
                return Ok(None);
8✔
656
            }
84✔
657

84✔
658
            if sig_set.contains(s) {
84✔
659
                continue;
2✔
660
            }
82✔
661

662
            for pk in pub_keys.by_ref() {
127✔
663
                let ristretto_key = pk.to_public_key().map_err(|_| ScriptError::InvalidInput)?;
127✔
664
                let sig = s.to_schnorr_signature()?;
127✔
665
                if sig.verify(&ristretto_key, message) {
127✔
666
                    sig_set.insert(s);
72✔
667
                    agg_pub_key = agg_pub_key + ristretto_key;
72✔
668
                    break;
72✔
669
                }
55✔
670
            }
671
            // Make sure the signature matched a public key
672
            if !sig_set.contains(s) {
82✔
673
                return Ok(None);
10✔
674
            }
72✔
675
        }
676
        if sig_set.len() == m {
32✔
677
            let key = CompressedKey::new_from_pk(agg_pub_key);
30✔
678
            Ok(Some(key))
30✔
679
        } else {
680
            Ok(None)
2✔
681
        }
682
    }
60✔
683

684
    fn handle_to_ristretto_point(&self, stack: &mut ExecutionStack) -> Result<(), ScriptError> {
4✔
685
        let item = stack.pop().ok_or(ScriptError::StackUnderflow)?;
4✔
686
        let scalar = match &item {
4✔
687
            StackItem::Hash(hash) => hash.as_slice(),
1✔
688
            StackItem::Scalar(scalar) => scalar.as_slice(),
2✔
689
            _ => return Err(ScriptError::IncompatibleTypes),
1✔
690
        };
691
        let ristretto_sk = RistrettoSecretKey::from_canonical_bytes(scalar).map_err(|_| ScriptError::InvalidInput)?;
3✔
692
        let ristretto_pk = CompressedKey::from_secret_key(&ristretto_sk);
2✔
693
        stack.push(StackItem::PublicKey(ristretto_pk))?;
2✔
694
        Ok(())
2✔
695
    }
4✔
696
}
697

698
impl Hex for TariScript {
699
    fn from_hex(hex: &str) -> Result<Self, HexError>
5✔
700
    where Self: Sized {
5✔
701
        let bytes = from_hex(hex)?;
5✔
702
        TariScript::from_bytes(&bytes).map_err(|_| HexError::HexConversionError {})
5✔
703
    }
5✔
704

705
    fn to_hex(&self) -> String {
3✔
706
        to_hex(&self.to_bytes())
3✔
707
    }
3✔
708
}
709

710
/// The default Tari script is to push a sender pubkey onto the stack
711
impl Default for TariScript {
712
    fn default() -> Self {
28✔
713
        script!(PushPubKey(Box::default())).expect("default will not fail")
28✔
714
    }
28✔
715
}
716

717
impl fmt::Display for TariScript {
718
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1✔
719
        let s = self.to_opcodes().join(" ");
1✔
720
        f.write_str(&s)
1✔
721
    }
1✔
722
}
723

724
#[derive(Clone, Debug, PartialEq, Eq)]
725
enum Branch {
726
    NotExecuted,
727
    ExecuteIf,
728
    ExecuteElse,
729
}
730

731
#[derive(Clone, Debug, PartialEq, Eq)]
732
struct IfState {
733
    branch: Branch,
734
    else_expected: bool,
735
}
736

737
#[derive(Clone, Debug, PartialEq, Eq)]
738
struct ExecutionState {
739
    executing: bool,
740
    if_stack: Vec<IfState>,
741
}
742

743
impl Default for ExecutionState {
744
    fn default() -> Self {
196✔
745
        Self {
196✔
746
            executing: true,
196✔
747
            if_stack: Vec::new(),
196✔
748
        }
196✔
749
    }
196✔
750
}
751

752
#[cfg(test)]
753
mod test {
754
    #![allow(clippy::indexing_slicing)]
755
    use blake2::Blake2b;
756
    use borsh::{BorshDeserialize, BorshSerialize};
757
    use digest::{consts::U32, Digest};
758
    use sha2::Sha256;
759
    use sha3::Sha3_256 as Sha3;
760
    use tari_crypto::{
761
        compressed_commitment::CompressedCommitment,
762
        compressed_key::CompressedKey,
763
        keys::SecretKey,
764
        ristretto::{pedersen::CompressedPedersenCommitment, RistrettoPublicKey, RistrettoSecretKey},
765
    };
766
    use tari_utilities::{hex::Hex, ByteArray};
767

768
    use crate::{
769
        error::ScriptError,
770
        inputs,
771
        op_codes::{slice_to_boxed_hash, slice_to_boxed_message, HashValue, Message},
772
        CheckSigSchnorrSignature,
773
        CompressedCheckSigSchnorrSignature,
774
        ExecutionStack,
775
        Opcode::CheckMultiSigVerifyAggregatePubKey,
776
        ScriptContext,
777
        StackItem,
778
        StackItem::{Commitment, Hash, Number},
779
        TariScript,
780
    };
781

782
    fn context_with_height(height: u64) -> ScriptContext {
55✔
783
        ScriptContext::new(height, &HashValue::default(), &CompressedPedersenCommitment::default())
55✔
784
    }
55✔
785

786
    #[test]
787
    fn pattern_match() {
1✔
788
        let script_a = script!(Or(1)).unwrap();
1✔
789
        let script_b = script!(Or(1)).unwrap();
1✔
790
        assert_eq!(script_a, script_b);
1✔
791
        assert!(script_a.pattern_match(&script_b));
1✔
792

793
        let script_b = script!(Or(2)).unwrap();
1✔
794
        assert_ne!(script_a, script_b);
1✔
795
        assert!(script_a.pattern_match(&script_b));
1✔
796

797
        let script_b = script!(Or(2) Or(2)).unwrap();
1✔
798
        assert_ne!(script_a, script_b);
1✔
799
        assert!(!script_a.pattern_match(&script_b));
1✔
800

801
        let script_a = script!(Or(2) Or(1)).unwrap();
1✔
802
        let script_b = script!(Or(3) Or(5)).unwrap();
1✔
803
        assert_ne!(script_a, script_b);
1✔
804
        assert!(script_a.pattern_match(&script_b));
1✔
805
    }
1✔
806

807
    #[test]
808
    fn op_or() {
1✔
809
        let script = script!(Or(1)).unwrap();
1✔
810

1✔
811
        let inputs = inputs!(4, 4);
1✔
812
        let result = script.execute(&inputs).unwrap();
1✔
813
        assert_eq!(result, Number(1));
1✔
814

815
        let inputs = inputs!(3, 4);
1✔
816
        let result = script.execute(&inputs).unwrap();
1✔
817
        assert_eq!(result, Number(0));
1✔
818

819
        let script = script!(Or(3)).unwrap();
1✔
820

1✔
821
        let inputs = inputs!(1, 2, 1, 3);
1✔
822
        let result = script.execute(&inputs).unwrap();
1✔
823
        assert_eq!(result, Number(1));
1✔
824

825
        let inputs = inputs!(1, 2, 4, 3);
1✔
826
        let result = script.execute(&inputs).unwrap();
1✔
827
        assert_eq!(result, Number(0));
1✔
828

829
        let mut rng = rand::thread_rng();
1✔
830
        let (_, p) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
831
        let inputs = inputs!(1, p.clone(), 1, 3);
1✔
832
        let err = script.execute(&inputs).unwrap_err();
1✔
833
        assert!(matches!(err, ScriptError::InvalidInput));
1✔
834

835
        let inputs = inputs!(p, 2, 1, 3);
1✔
836
        let err = script.execute(&inputs).unwrap_err();
1✔
837
        assert!(matches!(err, ScriptError::InvalidInput));
1✔
838

839
        let inputs = inputs!(2, 4, 3);
1✔
840
        let err = script.execute(&inputs).unwrap_err();
1✔
841
        assert!(matches!(err, ScriptError::StackUnderflow));
1✔
842

843
        let script = script!(OrVerify(1)).unwrap();
1✔
844

1✔
845
        let inputs = inputs!(1, 4, 4);
1✔
846
        let result = script.execute(&inputs).unwrap();
1✔
847
        assert_eq!(result, Number(1));
1✔
848

849
        let inputs = inputs!(1, 3, 4);
1✔
850
        let err = script.execute(&inputs).unwrap_err();
1✔
851
        assert!(matches!(err, ScriptError::VerifyFailed));
1✔
852

853
        let script = script!(OrVerify(2)).unwrap();
1✔
854

1✔
855
        let inputs = inputs!(1, 2, 2, 3);
1✔
856
        let result = script.execute(&inputs).unwrap();
1✔
857
        assert_eq!(result, Number(1));
1✔
858

859
        let inputs = inputs!(1, 2, 3, 4);
1✔
860
        let err = script.execute(&inputs).unwrap_err();
1✔
861
        assert!(matches!(err, ScriptError::VerifyFailed));
1✔
862
    }
1✔
863

864
    #[test]
865
    fn op_if_then_else() {
1✔
866
        // basic
1✔
867
        let script = script!(IfThen PushInt(420) Else PushInt(66) EndIf).unwrap();
1✔
868
        let inputs = inputs!(1);
1✔
869
        let result = script.execute(&inputs);
1✔
870
        assert_eq!(result.unwrap(), Number(420));
1✔
871

872
        let inputs = inputs!(0);
1✔
873
        let result = script.execute(&inputs);
1✔
874
        assert_eq!(result.unwrap(), Number(66));
1✔
875

876
        // nested
877
        let script =
1✔
878
            script!(IfThen PushOne IfThen PushInt(420) Else PushInt(555) EndIf Else PushInt(66) EndIf).unwrap();
1✔
879
        let inputs = inputs!(1);
1✔
880
        let result = script.execute(&inputs);
1✔
881
        assert_eq!(result.unwrap(), Number(420));
1✔
882

883
        let script =
1✔
884
            script!(IfThen PushInt(420) Else PushZero IfThen PushInt(111) Else PushInt(66) EndIf Nop EndIf).unwrap();
1✔
885
        let inputs = inputs!(0);
1✔
886
        let result = script.execute(&inputs);
1✔
887
        assert_eq!(result.unwrap(), Number(66));
1✔
888

889
        // duplicate else
890
        let script = script!(IfThen PushInt(420) Else PushInt(66) Else PushInt(777) EndIf).unwrap();
1✔
891
        let inputs = inputs!(0);
1✔
892
        let result = script.execute(&inputs);
1✔
893
        assert_eq!(result.unwrap_err(), ScriptError::InvalidOpcode);
1✔
894

895
        // unexpected else
896
        let script = script!(Else).unwrap();
1✔
897
        let inputs = inputs!(0);
1✔
898
        let result = script.execute(&inputs);
1✔
899
        assert_eq!(result.unwrap_err(), ScriptError::InvalidOpcode);
1✔
900

901
        // unexpected endif
902
        let script = script!(EndIf).unwrap();
1✔
903
        let inputs = inputs!(0);
1✔
904
        let result = script.execute(&inputs);
1✔
905
        assert_eq!(result.unwrap_err(), ScriptError::InvalidOpcode);
1✔
906

907
        // duplicate endif
908
        let script = script!(IfThen PushInt(420) Else PushInt(66) EndIf EndIf).unwrap();
1✔
909
        let inputs = inputs!(0);
1✔
910
        let result = script.execute(&inputs);
1✔
911
        assert_eq!(result.unwrap_err(), ScriptError::InvalidOpcode);
1✔
912

913
        // no else or endif
914
        let script = script!(IfThen PushOne IfThen PushOne).unwrap();
1✔
915
        let inputs = inputs!(1);
1✔
916
        let result = script.execute(&inputs);
1✔
917
        assert_eq!(result.unwrap_err(), ScriptError::MissingOpcode);
1✔
918

919
        // no else
920
        let script = script!(IfThen PushOne EndIf).unwrap();
1✔
921
        let inputs = inputs!(1);
1✔
922
        let result = script.execute(&inputs);
1✔
923
        assert_eq!(result.unwrap_err(), ScriptError::MissingOpcode);
1✔
924

925
        // nested bug
926
        let script =
1✔
927
            script!(IfThen PushInt(111) Else PushZero IfThen PushInt(222) Else PushInt(333) EndIf EndIf).unwrap();
1✔
928
        let inputs = inputs!(1);
1✔
929
        let result = script.execute(&inputs);
1✔
930
        assert_eq!(result.unwrap(), Number(111));
1✔
931
    }
1✔
932

933
    #[test]
934
    fn op_check_height() {
1✔
935
        let inputs = ExecutionStack::default();
1✔
936
        let script = script!(CheckHeight(5)).unwrap();
1✔
937

938
        for block_height in 1..=10 {
11✔
939
            let ctx = context_with_height(u64::try_from(block_height).unwrap());
10✔
940
            assert_eq!(
10✔
941
                script.execute_with_context(&inputs, &ctx).unwrap(),
10✔
942
                Number(block_height - 5)
10✔
943
            );
10✔
944
        }
945

946
        let script = script!(CheckHeight(u64::MAX)).unwrap();
1✔
947
        let ctx = context_with_height(i64::MAX as u64);
1✔
948
        let err = script.execute_with_context(&inputs, &ctx).unwrap_err();
1✔
949
        assert!(matches!(err, ScriptError::ValueExceedsBounds));
1✔
950

951
        let script = script!(CheckHeightVerify(5)).unwrap();
1✔
952
        let inputs = inputs!(1);
1✔
953

954
        for block_height in 1..5 {
5✔
955
            let ctx = context_with_height(block_height);
4✔
956
            let err = script.execute_with_context(&inputs, &ctx).unwrap_err();
4✔
957
            assert!(matches!(err, ScriptError::VerifyFailed));
4✔
958
        }
959

960
        for block_height in 5..=10 {
7✔
961
            let ctx = context_with_height(block_height);
6✔
962
            let result = script.execute_with_context(&inputs, &ctx).unwrap();
6✔
963
            assert_eq!(result, Number(1));
6✔
964
        }
965
    }
1✔
966

967
    #[test]
968
    fn op_compare_height() {
1✔
969
        let script = script!(CompareHeight).unwrap();
1✔
970
        let inputs = inputs!(5);
1✔
971

972
        for block_height in 1..=10 {
11✔
973
            let ctx = context_with_height(u64::try_from(block_height).unwrap());
10✔
974
            assert_eq!(
10✔
975
                script.execute_with_context(&inputs, &ctx).unwrap(),
10✔
976
                Number(block_height - 5)
10✔
977
            );
10✔
978
        }
979

980
        let script = script!(CompareHeightVerify).unwrap();
1✔
981
        let inputs = inputs!(1, 5);
1✔
982

983
        for block_height in 1..5 {
5✔
984
            let ctx = context_with_height(block_height);
4✔
985
            let err = script.execute_with_context(&inputs, &ctx).unwrap_err();
4✔
986
            assert!(matches!(err, ScriptError::VerifyFailed));
4✔
987
        }
988

989
        for block_height in 5..=10 {
7✔
990
            let ctx = context_with_height(block_height);
6✔
991
            let result = script.execute_with_context(&inputs, &ctx).unwrap();
6✔
992
            assert_eq!(result, Number(1));
6✔
993
        }
994
    }
1✔
995

996
    #[test]
997
    fn op_drop_push() {
1✔
998
        let inputs = inputs!(420);
1✔
999
        let script = script!(Drop PushOne).unwrap();
1✔
1000
        assert_eq!(script.execute(&inputs).unwrap(), Number(1));
1✔
1001

1002
        let script = script!(Drop PushZero).unwrap();
1✔
1003
        assert_eq!(script.execute(&inputs).unwrap(), Number(0));
1✔
1004

1005
        let script = script!(Drop PushInt(5)).unwrap();
1✔
1006
        assert_eq!(script.execute(&inputs).unwrap(), Number(5));
1✔
1007
    }
1✔
1008

1009
    #[test]
1010
    fn op_comparison_to_zero() {
1✔
1011
        let script = script!(GeZero).unwrap();
1✔
1012
        let inputs = inputs!(1);
1✔
1013
        assert_eq!(script.execute(&inputs).unwrap(), Number(1));
1✔
1014
        let inputs = inputs!(0);
1✔
1015
        assert_eq!(script.execute(&inputs).unwrap(), Number(1));
1✔
1016

1017
        let script = script!(GtZero).unwrap();
1✔
1018
        let inputs = inputs!(1);
1✔
1019
        assert_eq!(script.execute(&inputs).unwrap(), Number(1));
1✔
1020
        let inputs = inputs!(0);
1✔
1021
        assert_eq!(script.execute(&inputs).unwrap(), Number(0));
1✔
1022

1023
        let script = script!(LeZero).unwrap();
1✔
1024
        let inputs = inputs!(-1);
1✔
1025
        assert_eq!(script.execute(&inputs).unwrap(), Number(1));
1✔
1026
        let inputs = inputs!(0);
1✔
1027
        assert_eq!(script.execute(&inputs).unwrap(), Number(1));
1✔
1028

1029
        let script = script!(LtZero).unwrap();
1✔
1030
        let inputs = inputs!(-1);
1✔
1031
        assert_eq!(script.execute(&inputs).unwrap(), Number(1));
1✔
1032
        let inputs = inputs!(0);
1✔
1033
        assert_eq!(script.execute(&inputs).unwrap(), Number(0));
1✔
1034
    }
1✔
1035

1036
    #[test]
1037
    fn op_hash() {
1✔
1038
        let mut rng = rand::thread_rng();
1✔
1039
        let (_, p) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1040
        let c = CompressedCommitment::<RistrettoPublicKey>::from_compressed_key(p.clone());
1✔
1041
        let script = script!(HashSha256).unwrap();
1✔
1042

1✔
1043
        let hash = Sha256::digest(p.as_bytes());
1✔
1044
        let inputs = inputs!(p.clone());
1✔
1045
        assert_eq!(script.execute(&inputs).unwrap(), Hash(hash.into()));
1✔
1046

1047
        let hash = Sha256::digest(c.as_bytes());
1✔
1048
        let inputs = inputs!(c.clone());
1✔
1049
        assert_eq!(script.execute(&inputs).unwrap(), Hash(hash.into()));
1✔
1050

1051
        let script = script!(HashSha3).unwrap();
1✔
1052

1✔
1053
        let hash = Sha3::digest(p.as_bytes());
1✔
1054
        let inputs = inputs!(p);
1✔
1055
        assert_eq!(script.execute(&inputs).unwrap(), Hash(hash.into()));
1✔
1056

1057
        let hash = Sha3::digest(c.as_bytes());
1✔
1058
        let inputs = inputs!(c);
1✔
1059
        assert_eq!(script.execute(&inputs).unwrap(), Hash(hash.into()));
1✔
1060
    }
1✔
1061

1062
    #[test]
1063
    fn op_return() {
1✔
1064
        let script = script!(Return).unwrap();
1✔
1065
        let inputs = ExecutionStack::default();
1✔
1066
        assert_eq!(script.execute(&inputs), Err(ScriptError::Return));
1✔
1067
    }
1✔
1068

1069
    #[test]
1070
    fn op_add() {
1✔
1071
        let script = script!(Add).unwrap();
1✔
1072
        let inputs = inputs!(3, 2);
1✔
1073
        assert_eq!(script.execute(&inputs).unwrap(), Number(5));
1✔
1074
        let inputs = inputs!(3, -3);
1✔
1075
        assert_eq!(script.execute(&inputs).unwrap(), Number(0));
1✔
1076
        let inputs = inputs!(i64::MAX, 1);
1✔
1077
        assert_eq!(script.execute(&inputs), Err(ScriptError::ValueExceedsBounds));
1✔
1078
        let inputs = inputs!(1);
1✔
1079
        assert_eq!(script.execute(&inputs), Err(ScriptError::StackUnderflow));
1✔
1080
    }
1✔
1081

1082
    #[test]
1083
    fn op_add_commitments() {
1✔
1084
        let script = script!(Add).unwrap();
1✔
1085
        let mut rng = rand::thread_rng();
1✔
1086
        let (_, c1) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1087
        let (_, c2) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1088
        let c3 = &c1.to_public_key().unwrap() + &c2.to_public_key().unwrap();
1✔
1089
        let c3 = CompressedCommitment::<RistrettoPublicKey>::from_public_key(c3);
1✔
1090
        let inputs = inputs!(
1✔
1091
            CompressedCommitment::<RistrettoPublicKey>::from_compressed_key(c1),
1✔
1092
            CompressedCommitment::<RistrettoPublicKey>::from_compressed_key(c2)
1✔
1093
        );
1✔
1094
        assert_eq!(script.execute(&inputs).unwrap(), Commitment(c3));
1✔
1095
    }
1✔
1096

1097
    #[test]
1098
    fn op_sub() {
1✔
1099
        use crate::StackItem::Number;
1100
        let script = script!(Add Sub).unwrap();
1✔
1101
        let inputs = inputs!(5, 3, 2);
1✔
1102
        assert_eq!(script.execute(&inputs).unwrap(), Number(0));
1✔
1103
        let inputs = inputs!(i64::MAX, 1);
1✔
1104
        assert_eq!(script.execute(&inputs), Err(ScriptError::ValueExceedsBounds));
1✔
1105
        let script = script!(Sub).unwrap();
1✔
1106
        let inputs = inputs!(5, 3);
1✔
1107
        assert_eq!(script.execute(&inputs).unwrap(), Number(2));
1✔
1108
    }
1✔
1109

1110
    #[test]
1111
    fn serialisation() {
1✔
1112
        let script = script!(Add Sub Add).unwrap();
1✔
1113
        assert_eq!(&script.to_bytes(), &[0x93, 0x94, 0x93]);
1✔
1114
        assert_eq!(TariScript::from_bytes(&[0x93, 0x94, 0x93]).unwrap(), script);
1✔
1115
        assert_eq!(script.to_hex(), "939493");
1✔
1116
        assert_eq!(TariScript::from_hex("939493").unwrap(), script);
1✔
1117
    }
1✔
1118

1119
    #[test]
1120
    fn check_sig() {
1✔
1121
        use crate::StackItem::Number;
1122
        let mut rng = rand::thread_rng();
1✔
1123
        let (pvt_key, pub_key) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1124
        let m_key = RistrettoSecretKey::random(&mut rng);
1✔
1125
        let sig = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1126
            CheckSigSchnorrSignature::sign(&pvt_key, m_key.as_bytes(), &mut rng).unwrap(),
1✔
1127
        );
1✔
1128
        let msg = slice_to_boxed_message(m_key.as_bytes());
1✔
1129
        let script = script!(CheckSig(msg)).unwrap();
1✔
1130
        let inputs = inputs!(sig.clone(), pub_key.clone());
1✔
1131
        let result = script.execute(&inputs).unwrap();
1✔
1132
        assert_eq!(result, Number(1));
1✔
1133

1134
        let n_key = RistrettoSecretKey::random(&mut rng);
1✔
1135
        let msg = slice_to_boxed_message(n_key.as_bytes());
1✔
1136
        let script = script!(CheckSig(msg)).unwrap();
1✔
1137
        let inputs = inputs!(sig, pub_key);
1✔
1138
        let result = script.execute(&inputs).unwrap();
1✔
1139
        assert_eq!(result, Number(0));
1✔
1140
    }
1✔
1141

1142
    #[test]
1143
    fn check_sig_verify() {
1✔
1144
        use crate::StackItem::Number;
1145
        let mut rng = rand::thread_rng();
1✔
1146
        let (pvt_key, pub_key) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1147
        let m_key = RistrettoSecretKey::random(&mut rng);
1✔
1148
        let sig = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1149
            CheckSigSchnorrSignature::sign(&pvt_key, m_key.as_bytes(), &mut rng).unwrap(),
1✔
1150
        );
1✔
1151
        let msg = slice_to_boxed_message(m_key.as_bytes());
1✔
1152
        let script = script!(CheckSigVerify(msg) PushOne).unwrap();
1✔
1153
        let inputs = inputs!(sig.clone(), pub_key.clone());
1✔
1154
        let result = script.execute(&inputs).unwrap();
1✔
1155
        assert_eq!(result, Number(1));
1✔
1156

1157
        let n_key = RistrettoSecretKey::random(&mut rng);
1✔
1158
        let msg = slice_to_boxed_message(n_key.as_bytes());
1✔
1159
        let script = script!(CheckSigVerify(msg)).unwrap();
1✔
1160
        let inputs = inputs!(sig, pub_key);
1✔
1161
        let err = script.execute(&inputs).unwrap_err();
1✔
1162
        assert!(matches!(err, ScriptError::VerifyFailed));
1✔
1163
    }
1✔
1164

1165
    #[allow(clippy::type_complexity)]
1166
    fn multisig_data(
5✔
1167
        n: usize,
5✔
1168
    ) -> (
5✔
1169
        Box<Message>,
5✔
1170
        Vec<(
5✔
1171
            RistrettoSecretKey,
5✔
1172
            CompressedKey<RistrettoPublicKey>,
5✔
1173
            CompressedCheckSigSchnorrSignature,
5✔
1174
        )>,
5✔
1175
    ) {
5✔
1176
        let mut rng = rand::thread_rng();
5✔
1177
        let mut data = Vec::with_capacity(n);
5✔
1178
        let m = RistrettoSecretKey::random(&mut rng);
5✔
1179
        let msg = slice_to_boxed_message(m.as_bytes());
5✔
1180

5✔
1181
        for _ in 0..n {
55✔
1182
            let (k, p) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
55✔
1183
            let s = CompressedCheckSigSchnorrSignature::new_from_schnorr(
55✔
1184
                CheckSigSchnorrSignature::sign(&k, m.as_bytes(), &mut rng).unwrap(),
55✔
1185
            );
55✔
1186
            data.push((k, p, s));
55✔
1187
        }
55✔
1188

1189
        (msg, data)
5✔
1190
    }
5✔
1191

1192
    #[allow(clippy::too_many_lines)]
1193
    #[test]
1194
    fn check_multisig() {
1✔
1195
        use crate::{op_codes::Opcode::CheckMultiSig, StackItem::Number};
1196
        let mut rng = rand::thread_rng();
1✔
1197
        let (k_alice, p_alice) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1198
        let (k_bob, p_bob) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1199
        let (k_eve, _) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1200
        let (k_carol, p_carol) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1201
        let m = RistrettoSecretKey::random(&mut rng);
1✔
1202
        let s_alice = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1203
            CheckSigSchnorrSignature::sign(&k_alice, m.as_bytes(), &mut rng).unwrap(),
1✔
1204
        );
1✔
1205
        let s_bob = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1206
            CheckSigSchnorrSignature::sign(&k_bob, m.as_bytes(), &mut rng).unwrap(),
1✔
1207
        );
1✔
1208
        let s_eve = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1209
            CheckSigSchnorrSignature::sign(&k_eve, m.as_bytes(), &mut rng).unwrap(),
1✔
1210
        );
1✔
1211
        let s_carol = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1212
            CheckSigSchnorrSignature::sign(&k_carol, m.as_bytes(), &mut rng).unwrap(),
1✔
1213
        );
1✔
1214
        let s_alice2 = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1215
            CheckSigSchnorrSignature::sign(&k_alice, m.as_bytes(), &mut rng).unwrap(),
1✔
1216
        );
1✔
1217
        let msg = slice_to_boxed_message(m.as_bytes());
1✔
1218

1✔
1219
        // 1 of 2
1✔
1220
        let keys = vec![p_alice.clone(), p_bob.clone()];
1✔
1221
        let ops = vec![CheckMultiSig(1, 2, keys, msg.clone())];
1✔
1222
        let script = TariScript::new(ops).unwrap();
1✔
1223

1✔
1224
        let inputs = inputs!(s_alice.clone());
1✔
1225
        let result = script.execute(&inputs).unwrap();
1✔
1226
        assert_eq!(result, Number(1));
1✔
1227
        let inputs = inputs!(s_bob.clone());
1✔
1228
        let result = script.execute(&inputs).unwrap();
1✔
1229
        assert_eq!(result, Number(1));
1✔
1230
        let inputs = inputs!(s_eve.clone());
1✔
1231
        let result = script.execute(&inputs).unwrap();
1✔
1232
        assert_eq!(result, Number(0));
1✔
1233

1234
        // 2 of 2
1235
        let keys = vec![p_alice.clone(), p_bob.clone()];
1✔
1236
        let ops = vec![CheckMultiSig(2, 2, keys, msg.clone())];
1✔
1237
        let script = TariScript::new(ops).unwrap();
1✔
1238

1✔
1239
        let inputs = inputs!(s_alice.clone(), s_bob.clone());
1✔
1240
        let result = script.execute(&inputs).unwrap();
1✔
1241
        assert_eq!(result, Number(1));
1✔
1242
        let inputs = inputs!(s_bob.clone(), s_alice.clone());
1✔
1243
        let result = script.execute(&inputs).unwrap();
1✔
1244
        assert_eq!(result, Number(0));
1✔
1245
        let inputs = inputs!(s_eve.clone(), s_bob.clone());
1✔
1246
        let result = script.execute(&inputs).unwrap();
1✔
1247
        assert_eq!(result, Number(0));
1✔
1248

1249
        // 2 of 2 - don't allow same sig to sign twice
1250
        let inputs = inputs!(s_alice.clone(), s_alice.clone());
1✔
1251
        let result = script.execute(&inputs).unwrap();
1✔
1252
        assert_eq!(result, Number(0));
1✔
1253

1254
        // 1 of 3
1255
        let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
1✔
1256
        let ops = vec![CheckMultiSig(1, 3, keys, msg.clone())];
1✔
1257
        let script = TariScript::new(ops).unwrap();
1✔
1258

1✔
1259
        let inputs = inputs!(s_alice.clone());
1✔
1260
        let result = script.execute(&inputs).unwrap();
1✔
1261
        assert_eq!(result, Number(1));
1✔
1262
        let inputs = inputs!(s_bob.clone());
1✔
1263
        let result = script.execute(&inputs).unwrap();
1✔
1264
        assert_eq!(result, Number(1));
1✔
1265
        let inputs = inputs!(s_carol.clone());
1✔
1266
        let result = script.execute(&inputs).unwrap();
1✔
1267
        assert_eq!(result, Number(1));
1✔
1268
        let inputs = inputs!(s_eve.clone());
1✔
1269
        let result = script.execute(&inputs).unwrap();
1✔
1270
        assert_eq!(result, Number(0));
1✔
1271

1272
        // 2 of 3
1273
        let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
1✔
1274
        let ops = vec![CheckMultiSig(2, 3, keys, msg.clone())];
1✔
1275
        let script = TariScript::new(ops).unwrap();
1✔
1276

1✔
1277
        let inputs = inputs!(s_alice.clone(), s_bob.clone());
1✔
1278
        let result = script.execute(&inputs).unwrap();
1✔
1279
        assert_eq!(result, Number(1));
1✔
1280
        let inputs = inputs!(s_alice.clone(), s_carol.clone());
1✔
1281
        let result = script.execute(&inputs).unwrap();
1✔
1282
        assert_eq!(result, Number(1));
1✔
1283
        let inputs = inputs!(s_bob.clone(), s_carol.clone());
1✔
1284
        let result = script.execute(&inputs).unwrap();
1✔
1285
        assert_eq!(result, Number(1));
1✔
1286
        let inputs = inputs!(s_carol.clone(), s_bob.clone());
1✔
1287
        let result = script.execute(&inputs).unwrap();
1✔
1288
        assert_eq!(result, Number(0));
1✔
1289
        let inputs = inputs!(s_carol.clone(), s_eve.clone());
1✔
1290
        let result = script.execute(&inputs).unwrap();
1✔
1291
        assert_eq!(result, Number(0));
1✔
1292

1293
        // check that sigs are only counted once
1294
        let keys = vec![p_alice.clone(), p_bob.clone(), p_alice.clone()];
1✔
1295
        let ops = vec![CheckMultiSig(2, 3, keys, msg.clone())];
1✔
1296
        let script = TariScript::new(ops).unwrap();
1✔
1297

1✔
1298
        let inputs = inputs!(s_alice.clone(), s_carol.clone());
1✔
1299
        let result = script.execute(&inputs).unwrap();
1✔
1300
        assert_eq!(result, Number(0));
1✔
1301
        let inputs = inputs!(s_alice.clone(), s_alice.clone());
1✔
1302
        let result = script.execute(&inputs).unwrap();
1✔
1303
        assert_eq!(result, Number(0));
1✔
1304
        let inputs = inputs!(s_alice.clone(), s_alice2.clone());
1✔
1305
        let result = script.execute(&inputs).unwrap();
1✔
1306
        assert_eq!(result, Number(1));
1✔
1307
        // Interesting case where either sig could match either pubkey
1308
        let inputs = inputs!(s_alice2, s_alice.clone());
1✔
1309
        let result = script.execute(&inputs).unwrap();
1✔
1310
        assert_eq!(result, Number(1));
1✔
1311

1312
        // 3 of 3
1313
        let keys = vec![p_alice.clone(), p_bob.clone(), p_carol];
1✔
1314
        let ops = vec![CheckMultiSig(3, 3, keys, msg.clone())];
1✔
1315
        let script = TariScript::new(ops).unwrap();
1✔
1316

1✔
1317
        let inputs = inputs!(s_alice.clone(), s_bob.clone(), s_carol.clone());
1✔
1318
        let result = script.execute(&inputs).unwrap();
1✔
1319
        assert_eq!(result, Number(1));
1✔
1320
        let inputs = inputs!(s_carol.clone(), s_alice.clone(), s_bob.clone());
1✔
1321
        let result = script.execute(&inputs).unwrap();
1✔
1322
        assert_eq!(result, Number(0));
1✔
1323
        let inputs = inputs!(s_eve.clone(), s_bob.clone(), s_carol);
1✔
1324
        let result = script.execute(&inputs).unwrap();
1✔
1325
        assert_eq!(result, Number(0));
1✔
1326
        let inputs = inputs!(s_eve, s_bob);
1✔
1327
        let err = script.execute(&inputs).unwrap_err();
1✔
1328
        assert_eq!(err, ScriptError::StackUnderflow);
1✔
1329

1330
        // errors
1331
        let keys = vec![p_alice.clone(), p_bob.clone()];
1✔
1332
        let ops = vec![CheckMultiSig(0, 2, keys, msg.clone())];
1✔
1333
        let script = TariScript::new(ops).unwrap();
1✔
1334
        let inputs = inputs!(s_alice.clone());
1✔
1335
        let err = script.execute(&inputs).unwrap_err();
1✔
1336
        assert_eq!(err, ScriptError::ValueExceedsBounds);
1✔
1337

1338
        let keys = vec![p_alice.clone(), p_bob.clone()];
1✔
1339
        let ops = vec![CheckMultiSig(1, 0, keys, msg.clone())];
1✔
1340
        let script = TariScript::new(ops).unwrap();
1✔
1341
        let inputs = inputs!(s_alice.clone());
1✔
1342
        let err = script.execute(&inputs).unwrap_err();
1✔
1343
        assert_eq!(err, ScriptError::ValueExceedsBounds);
1✔
1344

1345
        let keys = vec![p_alice, p_bob];
1✔
1346
        let ops = vec![CheckMultiSig(2, 1, keys, msg)];
1✔
1347
        let script = TariScript::new(ops).unwrap();
1✔
1348
        let inputs = inputs!(s_alice);
1✔
1349
        let err = script.execute(&inputs).unwrap_err();
1✔
1350
        assert_eq!(err, ScriptError::ValueExceedsBounds);
1✔
1351

1352
        // max n is 32
1353
        let (msg, data) = multisig_data(33);
1✔
1354
        let keys = data.iter().map(|(_, p, _)| p.clone()).collect();
33✔
1355
        let sigs = data.iter().take(17).map(|(_, _, s)| s.clone());
17✔
1356
        let script = script!(CheckMultiSig(17, 33, keys, msg)).unwrap();
1✔
1357
        let items = sigs.map(StackItem::Signature).collect();
1✔
1358
        let inputs = ExecutionStack::new(items);
1✔
1359
        let err = script.execute(&inputs).unwrap_err();
1✔
1360
        assert_eq!(err, ScriptError::ValueExceedsBounds);
1✔
1361

1362
        // 3 of 4
1363
        let (msg, data) = multisig_data(4);
1✔
1364
        let keys = vec![
1✔
1365
            data[0].1.clone(),
1✔
1366
            data[1].1.clone(),
1✔
1367
            data[2].1.clone(),
1✔
1368
            data[3].1.clone(),
1✔
1369
        ];
1✔
1370
        let ops = vec![CheckMultiSig(3, 4, keys, msg)];
1✔
1371
        let script = TariScript::new(ops).unwrap();
1✔
1372
        let inputs = inputs!(data[0].2.clone(), data[1].2.clone(), data[2].2.clone());
1✔
1373
        let result = script.execute(&inputs).unwrap();
1✔
1374
        assert_eq!(result, Number(1));
1✔
1375

1376
        // 5 of 7
1377
        let (msg, data) = multisig_data(7);
1✔
1378
        let keys = vec![
1✔
1379
            data[0].1.clone(),
1✔
1380
            data[1].1.clone(),
1✔
1381
            data[2].1.clone(),
1✔
1382
            data[3].1.clone(),
1✔
1383
            data[4].1.clone(),
1✔
1384
            data[5].1.clone(),
1✔
1385
            data[6].1.clone(),
1✔
1386
        ];
1✔
1387
        let ops = vec![CheckMultiSig(5, 7, keys, msg)];
1✔
1388
        let script = TariScript::new(ops).unwrap();
1✔
1389
        let inputs = inputs!(
1✔
1390
            data[0].2.clone(),
1✔
1391
            data[1].2.clone(),
1✔
1392
            data[2].2.clone(),
1✔
1393
            data[3].2.clone(),
1✔
1394
            data[4].2.clone()
1✔
1395
        );
1✔
1396
        let result = script.execute(&inputs).unwrap();
1✔
1397
        assert_eq!(result, Number(1));
1✔
1398
    }
1✔
1399

1400
    #[allow(clippy::too_many_lines)]
1401
    #[test]
1402
    fn check_multisig_verify() {
1✔
1403
        use crate::{op_codes::Opcode::CheckMultiSigVerify, StackItem::Number};
1404
        let mut rng = rand::thread_rng();
1✔
1405
        let (k_alice, p_alice) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1406
        let (k_bob, p_bob) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1407
        let (k_eve, _) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1408
        let (k_carol, p_carol) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1409
        let m = RistrettoSecretKey::random(&mut rng);
1✔
1410
        let s_alice = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1411
            CheckSigSchnorrSignature::sign(&k_alice, m.as_bytes(), &mut rng).unwrap(),
1✔
1412
        );
1✔
1413
        let s_bob = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1414
            CheckSigSchnorrSignature::sign(&k_bob, m.as_bytes(), &mut rng).unwrap(),
1✔
1415
        );
1✔
1416
        let s_eve = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1417
            CheckSigSchnorrSignature::sign(&k_eve, m.as_bytes(), &mut rng).unwrap(),
1✔
1418
        );
1✔
1419
        let s_carol = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1420
            CheckSigSchnorrSignature::sign(&k_carol, m.as_bytes(), &mut rng).unwrap(),
1✔
1421
        );
1✔
1422
        let msg = slice_to_boxed_message(m.as_bytes());
1✔
1423

1✔
1424
        // 1 of 2
1✔
1425
        let keys = vec![p_alice.clone(), p_bob.clone()];
1✔
1426
        let ops = vec![CheckMultiSigVerify(1, 2, keys, msg.clone())];
1✔
1427
        let script = TariScript::new(ops).unwrap();
1✔
1428

1✔
1429
        let inputs = inputs!(Number(1), s_alice.clone());
1✔
1430
        let result = script.execute(&inputs).unwrap();
1✔
1431
        assert_eq!(result, Number(1));
1✔
1432
        let inputs = inputs!(Number(1), s_bob.clone());
1✔
1433
        let result = script.execute(&inputs).unwrap();
1✔
1434
        assert_eq!(result, Number(1));
1✔
1435
        let inputs = inputs!(Number(1), s_eve.clone());
1✔
1436
        let err = script.execute(&inputs).unwrap_err();
1✔
1437
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1438

1439
        // 2 of 2
1440
        let keys = vec![p_alice.clone(), p_bob.clone()];
1✔
1441
        let ops = vec![CheckMultiSigVerify(2, 2, keys, msg.clone())];
1✔
1442
        let script = TariScript::new(ops).unwrap();
1✔
1443

1✔
1444
        let inputs = inputs!(Number(1), s_alice.clone(), s_bob.clone());
1✔
1445
        let result = script.execute(&inputs).unwrap();
1✔
1446
        assert_eq!(result, Number(1));
1✔
1447
        let inputs = inputs!(Number(1), s_bob.clone(), s_alice.clone());
1✔
1448
        let err = script.execute(&inputs).unwrap_err();
1✔
1449
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1450
        let inputs = inputs!(Number(1), s_eve.clone(), s_bob.clone());
1✔
1451
        let err = script.execute(&inputs).unwrap_err();
1✔
1452
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1453

1454
        // 1 of 3
1455
        let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
1✔
1456
        let ops = vec![CheckMultiSigVerify(1, 3, keys, msg.clone())];
1✔
1457
        let script = TariScript::new(ops).unwrap();
1✔
1458

1✔
1459
        let inputs = inputs!(Number(1), s_alice.clone());
1✔
1460
        let result = script.execute(&inputs).unwrap();
1✔
1461
        assert_eq!(result, Number(1));
1✔
1462
        let inputs = inputs!(Number(1), s_bob.clone());
1✔
1463
        let result = script.execute(&inputs).unwrap();
1✔
1464
        assert_eq!(result, Number(1));
1✔
1465
        let inputs = inputs!(Number(1), s_carol.clone());
1✔
1466
        let result = script.execute(&inputs).unwrap();
1✔
1467
        assert_eq!(result, Number(1));
1✔
1468
        let inputs = inputs!(Number(1), s_eve.clone());
1✔
1469
        let err = script.execute(&inputs).unwrap_err();
1✔
1470
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1471

1472
        // 2 of 3
1473
        let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
1✔
1474
        let ops = vec![CheckMultiSigVerify(2, 3, keys, msg.clone())];
1✔
1475
        let script = TariScript::new(ops).unwrap();
1✔
1476

1✔
1477
        let inputs = inputs!(Number(1), s_alice.clone(), s_bob.clone());
1✔
1478
        let result = script.execute(&inputs).unwrap();
1✔
1479
        assert_eq!(result, Number(1));
1✔
1480
        let inputs = inputs!(Number(1), s_alice.clone(), s_carol.clone());
1✔
1481
        let result = script.execute(&inputs).unwrap();
1✔
1482
        assert_eq!(result, Number(1));
1✔
1483
        let inputs = inputs!(Number(1), s_bob.clone(), s_carol.clone());
1✔
1484
        let result = script.execute(&inputs).unwrap();
1✔
1485
        assert_eq!(result, Number(1));
1✔
1486
        let inputs = inputs!(Number(1), s_carol.clone(), s_bob.clone());
1✔
1487
        let err = script.execute(&inputs).unwrap_err();
1✔
1488
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1489
        let inputs = inputs!(Number(1), s_carol.clone(), s_eve.clone());
1✔
1490
        let err = script.execute(&inputs).unwrap_err();
1✔
1491
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1492

1493
        // 2 of 3 (returning the aggregate public key of the signatories)
1494
        let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
1✔
1495
        let ops = vec![CheckMultiSigVerifyAggregatePubKey(2, 3, keys, msg.clone())];
1✔
1496
        let script = TariScript::new(ops).unwrap();
1✔
1497

1✔
1498
        let inputs = inputs!(s_alice.clone(), s_bob.clone());
1✔
1499
        let agg_pub_key = script.execute(&inputs).unwrap();
1✔
1500
        assert_eq!(
1✔
1501
            agg_pub_key,
1✔
1502
            StackItem::PublicKey(CompressedKey::<RistrettoPublicKey>::new_from_pk(
1✔
1503
                &p_alice.clone().to_public_key().unwrap() + &p_bob.clone().to_public_key().unwrap()
1✔
1504
            ))
1✔
1505
        );
1✔
1506

1507
        let inputs = inputs!(s_alice.clone(), s_carol.clone());
1✔
1508
        let agg_pub_key = script.execute(&inputs).unwrap();
1✔
1509
        assert_eq!(
1✔
1510
            agg_pub_key,
1✔
1511
            StackItem::PublicKey(CompressedKey::<RistrettoPublicKey>::new_from_pk(
1✔
1512
                &p_alice.clone().to_public_key().unwrap() + &p_carol.clone().to_public_key().unwrap()
1✔
1513
            ))
1✔
1514
        );
1✔
1515

1516
        let inputs = inputs!(s_bob.clone(), s_carol.clone());
1✔
1517
        let agg_pub_key = script.execute(&inputs).unwrap();
1✔
1518
        assert_eq!(
1✔
1519
            agg_pub_key,
1✔
1520
            StackItem::PublicKey(CompressedKey::<RistrettoPublicKey>::new_from_pk(
1✔
1521
                &p_bob.clone().to_public_key().unwrap() + &p_carol.clone().to_public_key().unwrap()
1✔
1522
            ))
1✔
1523
        );
1✔
1524

1525
        let inputs = inputs!(s_carol.clone(), s_bob.clone());
1✔
1526
        let err = script.execute(&inputs).unwrap_err();
1✔
1527
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1528

1529
        let inputs = inputs!(s_alice.clone(), s_bob.clone(), s_carol.clone());
1✔
1530
        let err = script.execute(&inputs).unwrap_err();
1✔
1531
        assert_eq!(err, ScriptError::NonUnitLengthStack);
1✔
1532

1533
        let inputs = inputs!(p_bob.clone());
1✔
1534
        let err = script.execute(&inputs).unwrap_err();
1✔
1535
        assert_eq!(err, ScriptError::StackUnderflow);
1✔
1536

1537
        // 3 of 3
1538
        let keys = vec![p_alice.clone(), p_bob.clone(), p_carol];
1✔
1539
        let ops = vec![CheckMultiSigVerify(3, 3, keys, msg.clone())];
1✔
1540
        let script = TariScript::new(ops).unwrap();
1✔
1541

1✔
1542
        let inputs = inputs!(Number(1), s_alice.clone(), s_bob.clone(), s_carol.clone());
1✔
1543
        let result = script.execute(&inputs).unwrap();
1✔
1544
        assert_eq!(result, Number(1));
1✔
1545
        let inputs = inputs!(Number(1), s_bob.clone(), s_alice.clone(), s_carol.clone());
1✔
1546
        let err = script.execute(&inputs).unwrap_err();
1✔
1547
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1548
        let inputs = inputs!(Number(1), s_eve.clone(), s_bob.clone(), s_carol);
1✔
1549
        let err = script.execute(&inputs).unwrap_err();
1✔
1550
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1551
        let inputs = inputs!(Number(1), s_eve, s_bob);
1✔
1552
        let err = script.execute(&inputs).unwrap_err();
1✔
1553
        assert_eq!(err, ScriptError::IncompatibleTypes);
1✔
1554

1555
        // errors
1556
        let keys = vec![p_alice.clone(), p_bob.clone()];
1✔
1557
        let ops = vec![CheckMultiSigVerify(0, 2, keys, msg.clone())];
1✔
1558
        let script = TariScript::new(ops).unwrap();
1✔
1559
        let inputs = inputs!(s_alice.clone());
1✔
1560
        let err = script.execute(&inputs).unwrap_err();
1✔
1561
        assert_eq!(err, ScriptError::ValueExceedsBounds);
1✔
1562

1563
        let keys = vec![p_alice.clone(), p_bob.clone()];
1✔
1564
        let ops = vec![CheckMultiSigVerify(1, 0, keys, msg.clone())];
1✔
1565
        let script = TariScript::new(ops).unwrap();
1✔
1566
        let inputs = inputs!(s_alice.clone());
1✔
1567
        let err = script.execute(&inputs).unwrap_err();
1✔
1568
        assert_eq!(err, ScriptError::ValueExceedsBounds);
1✔
1569

1570
        let keys = vec![p_alice, p_bob];
1✔
1571
        let ops = vec![CheckMultiSigVerify(2, 1, keys, msg)];
1✔
1572
        let script = TariScript::new(ops).unwrap();
1✔
1573
        let inputs = inputs!(s_alice);
1✔
1574
        let err = script.execute(&inputs).unwrap_err();
1✔
1575
        assert_eq!(err, ScriptError::ValueExceedsBounds);
1✔
1576

1577
        // 3 of 4
1578
        let (msg, data) = multisig_data(4);
1✔
1579
        let keys = vec![
1✔
1580
            data[0].1.clone(),
1✔
1581
            data[1].1.clone(),
1✔
1582
            data[2].1.clone(),
1✔
1583
            data[3].1.clone(),
1✔
1584
        ];
1✔
1585
        let ops = vec![CheckMultiSigVerify(3, 4, keys, msg)];
1✔
1586
        let script = TariScript::new(ops).unwrap();
1✔
1587
        let inputs = inputs!(Number(1), data[0].2.clone(), data[1].2.clone(), data[2].2.clone());
1✔
1588
        let result = script.execute(&inputs).unwrap();
1✔
1589
        assert_eq!(result, Number(1));
1✔
1590

1591
        // 5 of 7
1592
        let (msg, data) = multisig_data(7);
1✔
1593
        let keys = vec![
1✔
1594
            data[0].1.clone(),
1✔
1595
            data[1].1.clone(),
1✔
1596
            data[2].1.clone(),
1✔
1597
            data[3].1.clone(),
1✔
1598
            data[4].1.clone(),
1✔
1599
            data[5].1.clone(),
1✔
1600
            data[6].1.clone(),
1✔
1601
        ];
1✔
1602
        let ops = vec![CheckMultiSigVerify(5, 7, keys, msg)];
1✔
1603
        let script = TariScript::new(ops).unwrap();
1✔
1604
        let inputs = inputs!(
1✔
1605
            Number(1),
1✔
1606
            data[0].2.clone(),
1✔
1607
            data[1].2.clone(),
1✔
1608
            data[2].2.clone(),
1✔
1609
            data[3].2.clone(),
1✔
1610
            data[4].2.clone()
1✔
1611
        );
1✔
1612
        let result = script.execute(&inputs).unwrap();
1✔
1613
        assert_eq!(result, Number(1));
1✔
1614
    }
1✔
1615

1616
    #[test]
1617
    fn pay_to_public_key_hash() {
1✔
1618
        use crate::StackItem::PublicKey;
1619
        let k =
1✔
1620
            RistrettoSecretKey::from_hex("7212ac93ee205cdbbb57c4f0f815fbf8db25b4d04d3532e2262e31907d82c700").unwrap();
1✔
1621
        let p = CompressedKey::<RistrettoPublicKey>::from_secret_key(&k); // 56c0fa32558d6edc0916baa26b48e745de834571534ca253ea82435f08ebbc7c
1✔
1622
        let hash = Blake2b::<U32>::digest(p.as_bytes());
1✔
1623
        let pkh = slice_to_boxed_hash(hash.as_slice()); // ae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e5
1✔
1624

1✔
1625
        // Unlike in Bitcoin where P2PKH includes a CheckSig at the end of the script, that part of the process is built
1✔
1626
        // into definition of how TariScript is evaluated by a base node or wallet
1✔
1627
        let script = script!(Dup HashBlake256 PushHash(pkh) EqualVerify).unwrap();
1✔
1628
        let hex_script = "71b07aae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e581";
1✔
1629
        // Test serialisation
1✔
1630
        assert_eq!(script.to_hex(), hex_script);
1✔
1631
        // Test de-serialisation
1632
        assert_eq!(TariScript::from_hex(hex_script).unwrap(), script);
1✔
1633

1634
        let inputs = inputs!(p.clone());
1✔
1635

1✔
1636
        let result = script.execute(&inputs).unwrap();
1✔
1637

1✔
1638
        assert_eq!(result, PublicKey(p));
1✔
1639
    }
1✔
1640

1641
    #[test]
1642
    fn hex_roundtrip() {
1✔
1643
        // Generate a signature
1✔
1644
        let mut rng = rand::thread_rng();
1✔
1645
        let (secret_key, public_key) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1646
        let message = [1u8; 32];
1✔
1647
        let sig = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1648
            CheckSigSchnorrSignature::sign(&secret_key, message, &mut rng).unwrap(),
1✔
1649
        );
1✔
1650

1✔
1651
        // Produce a script using the signature
1✔
1652
        let script = script!(CheckSig(slice_to_boxed_message(message.as_bytes()))).unwrap();
1✔
1653

1✔
1654
        // Produce input satisfying the script
1✔
1655
        let input = inputs!(sig, public_key);
1✔
1656

1✔
1657
        // Check that script execution succeeds
1✔
1658
        assert_eq!(script.execute(&input).unwrap(), StackItem::Number(1));
1✔
1659

1660
        // Convert the script to hex and back
1661
        let parsed_script = TariScript::from_hex(script.to_hex().as_str()).unwrap();
1✔
1662
        assert_eq!(script.to_opcodes(), parsed_script.to_opcodes());
1✔
1663

1664
        // Convert the input to hex and back
1665
        let parsed_input = ExecutionStack::from_hex(input.to_hex().as_str()).unwrap();
1✔
1666
        assert_eq!(input, parsed_input);
1✔
1667

1668
        // Check that script execution still succeeds
1669
        assert_eq!(parsed_script.execute(&parsed_input).unwrap(), StackItem::Number(1));
1✔
1670
    }
1✔
1671

1672
    #[test]
1673
    fn disassemble() {
1✔
1674
        let hex_script = "71b07aae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e58170ac276657a418820f34036b20ea615302b373c70ac8feab8d30681a3e0f0960e708";
1✔
1675
        let script = TariScript::from_hex(hex_script).unwrap();
1✔
1676
        let ops = vec![
1✔
1677
            "Dup",
1✔
1678
            "HashBlake256",
1✔
1679
            "PushHash(ae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e5)",
1✔
1680
            "EqualVerify",
1✔
1681
            "Drop",
1✔
1682
            "CheckSig(276657a418820f34036b20ea615302b373c70ac8feab8d30681a3e0f0960e708)",
1✔
1683
        ]
1✔
1684
        .into_iter()
1✔
1685
        .map(String::from)
1✔
1686
        .collect::<Vec<String>>();
1✔
1687
        assert_eq!(script.to_opcodes(), ops);
1✔
1688
        assert_eq!(
1✔
1689
            script.to_string(),
1✔
1690
            "Dup HashBlake256 PushHash(ae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e5) EqualVerify \
1✔
1691
             Drop CheckSig(276657a418820f34036b20ea615302b373c70ac8feab8d30681a3e0f0960e708)"
1✔
1692
        );
1✔
1693
    }
1✔
1694

1695
    #[test]
1696
    fn time_locked_contract_example() {
1✔
1697
        let k_alice =
1✔
1698
            RistrettoSecretKey::from_hex("f305e64c0e73cbdb665165ac97b69e5df37b2cd81f9f8f569c3bd854daff290e").unwrap();
1✔
1699
        let p_alice = CompressedKey::<RistrettoPublicKey>::from_secret_key(&k_alice); // 9c35e9f0f11cf25ce3ca1182d37682ab5824aa033f2024651e007364d06ec355
1✔
1700

1✔
1701
        let k_bob =
1✔
1702
            RistrettoSecretKey::from_hex("e0689386a018e88993a7bb14cbff5bad8a8858ea101d6e0da047df3ddf499c0e").unwrap();
1✔
1703
        let p_bob = CompressedKey::<RistrettoPublicKey>::from_secret_key(&k_bob); // 3a58f371e94da76a8902e81b4b55ddabb7dc006cd8ebde3011c46d0e02e9172f
1✔
1704

1✔
1705
        let lock_height = 4000u64;
1✔
1706

1✔
1707
        let script = script!(Dup PushPubKey(Box::new(p_bob.clone())) CheckHeight(lock_height) GeZero IfThen PushPubKey(Box::new(p_alice.clone())) OrVerify(2) Else EqualVerify EndIf ).unwrap();
1✔
1708

1✔
1709
        // Alice tries to spend the output before the height is reached
1✔
1710
        let inputs_alice_spends_early = inputs!(p_alice.clone());
1✔
1711
        let ctx = context_with_height(3990u64);
1✔
1712
        assert_eq!(
1✔
1713
            script.execute_with_context(&inputs_alice_spends_early, &ctx),
1✔
1714
            Err(ScriptError::VerifyFailed)
1✔
1715
        );
1✔
1716

1717
        // Alice tries to spend the output after the height is reached
1718
        let inputs_alice_spends_early = inputs!(p_alice.clone());
1✔
1719
        let ctx = context_with_height(4000u64);
1✔
1720
        assert_eq!(
1✔
1721
            script.execute_with_context(&inputs_alice_spends_early, &ctx).unwrap(),
1✔
1722
            StackItem::PublicKey(p_alice)
1✔
1723
        );
1✔
1724

1725
        // Bob spends before time lock is reached
1726
        let inputs_bob_spends_early = inputs!(p_bob.clone());
1✔
1727
        let ctx = context_with_height(3990u64);
1✔
1728
        assert_eq!(
1✔
1729
            script.execute_with_context(&inputs_bob_spends_early, &ctx).unwrap(),
1✔
1730
            StackItem::PublicKey(p_bob.clone())
1✔
1731
        );
1✔
1732

1733
        // Bob spends after time lock is reached
1734
        let inputs_bob_spends_early = inputs!(p_bob.clone());
1✔
1735
        let ctx = context_with_height(4001u64);
1✔
1736
        assert_eq!(
1✔
1737
            script.execute_with_context(&inputs_bob_spends_early, &ctx).unwrap(),
1✔
1738
            StackItem::PublicKey(p_bob)
1✔
1739
        );
1✔
1740
    }
1✔
1741

1742
    #[test]
1743
    fn m_of_n_signatures() {
1✔
1744
        use crate::StackItem::PublicKey;
1745
        let mut rng = rand::thread_rng();
1✔
1746
        let (k_alice, p_alice) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1747
        let (k_bob, p_bob) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1748
        let (k_eve, _) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1749

1✔
1750
        let m = RistrettoSecretKey::random(&mut rng);
1✔
1751
        let msg = slice_to_boxed_message(m.as_bytes());
1✔
1752

1✔
1753
        let s_alice = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1754
            CheckSigSchnorrSignature::sign(&k_alice, m.as_bytes(), &mut rng).unwrap(),
1✔
1755
        );
1✔
1756
        let s_bob = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1757
            CheckSigSchnorrSignature::sign(&k_bob, m.as_bytes(), &mut rng).unwrap(),
1✔
1758
        );
1✔
1759
        let s_eve = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1760
            CheckSigSchnorrSignature::sign(&k_eve, m.as_bytes(), &mut rng).unwrap(),
1✔
1761
        );
1✔
1762

1763
        // 1 of 2
1764
        use crate::Opcode::{CheckSig, Drop, Dup, Else, EndIf, IfThen, PushPubKey, Return};
1765
        let ops = vec![
1✔
1766
            Dup,
1✔
1767
            PushPubKey(Box::new(p_alice.clone())),
1✔
1768
            CheckSig(msg.clone()),
1✔
1769
            IfThen,
1✔
1770
            Drop,
1✔
1771
            PushPubKey(Box::new(p_alice.clone())),
1✔
1772
            Else,
1✔
1773
            PushPubKey(Box::new(p_bob.clone())),
1✔
1774
            CheckSig(msg),
1✔
1775
            IfThen,
1✔
1776
            PushPubKey(Box::new(p_bob.clone())),
1✔
1777
            Else,
1✔
1778
            Return,
1✔
1779
            EndIf,
1✔
1780
            EndIf,
1✔
1781
        ];
1✔
1782
        let script = TariScript::new(ops).unwrap();
1✔
1783

1✔
1784
        // alice
1✔
1785
        let inputs = inputs!(s_alice);
1✔
1786
        let result = script.execute(&inputs).unwrap();
1✔
1787
        assert_eq!(result, PublicKey(p_alice));
1✔
1788

1789
        // bob
1790
        let inputs = inputs!(s_bob);
1✔
1791
        let result = script.execute(&inputs).unwrap();
1✔
1792
        assert_eq!(result, PublicKey(p_bob));
1✔
1793

1794
        // eve
1795
        let inputs = inputs!(s_eve);
1✔
1796
        let result = script.execute(&inputs).unwrap_err();
1✔
1797
        assert_eq!(result, ScriptError::Return);
1✔
1798
    }
1✔
1799

1800
    #[test]
1801
    fn to_ristretto_point() {
1✔
1802
        use crate::{Opcode::ToRistrettoPoint, StackItem::PublicKey};
1803

1804
        // Generate a key pair
1805
        let mut rng = rand::thread_rng();
1✔
1806
        let (k_1, p_1) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1807

1✔
1808
        // Generate a test script
1✔
1809
        let ops = vec![ToRistrettoPoint];
1✔
1810
        let script = TariScript::new(ops).unwrap();
1✔
1811

1✔
1812
        // Invalid stack type
1✔
1813
        let inputs = inputs!(CompressedKey::<RistrettoPublicKey>::default());
1✔
1814
        let err = script.execute(&inputs).unwrap_err();
1✔
1815
        assert!(matches!(err, ScriptError::IncompatibleTypes));
1✔
1816

1817
        // Valid scalar
1818
        let mut scalar = [0u8; 32];
1✔
1819
        scalar.copy_from_slice(k_1.as_bytes());
1✔
1820
        let inputs = inputs!(scalar);
1✔
1821
        let result = script.execute(&inputs).unwrap();
1✔
1822
        assert_eq!(result, PublicKey(p_1.clone()));
1✔
1823

1824
        // Valid hash
1825
        let inputs = ExecutionStack::new(vec![Hash(scalar)]);
1✔
1826
        let result = script.execute(&inputs).unwrap();
1✔
1827
        assert_eq!(result, PublicKey(p_1));
1✔
1828

1829
        // Invalid bytes
1830
        let invalid = [u8::MAX; 32]; // not a canonical scalar encoding!
1✔
1831
        let inputs = inputs!(invalid);
1✔
1832
        assert!(matches!(script.execute(&inputs), Err(ScriptError::InvalidInput)));
1✔
1833
    }
1✔
1834

1835
    #[test]
1836
    fn test_borsh_de_serialization() {
1✔
1837
        let hex_script = "71b07aae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e58170ac276657a418820f34036b20ea615302b373c70ac8feab8d30681a3e0f0960e708";
1✔
1838
        let script = TariScript::from_hex(hex_script).unwrap();
1✔
1839
        let mut buf = Vec::new();
1✔
1840
        script.serialize(&mut buf).unwrap();
1✔
1841
        buf.extend_from_slice(&[1, 2, 3]);
1✔
1842
        let buf = &mut buf.as_slice();
1✔
1843
        assert_eq!(script, TariScript::deserialize(buf).unwrap());
1✔
1844
        assert_eq!(buf, &[1, 2, 3]);
1✔
1845
    }
1✔
1846

1847
    #[test]
1848
    fn test_borsh_de_serialization_too_large() {
1✔
1849
        // We dont care about the actual script here, just that its not too large on the varint size
1✔
1850
        // We lie about the size to try and get a mem panic, and say this script is u64::max large.
1✔
1851
        let buf = vec![255, 255, 255, 255, 255, 255, 255, 255, 255, 1, 49, 8, 2, 5, 6];
1✔
1852
        let buf = &mut buf.as_slice();
1✔
1853
        assert!(TariScript::deserialize(buf).is_err());
1✔
1854
    }
1✔
1855

1856
    #[test]
1857
    fn test_compare_height_block_height_exceeds_bounds() {
1✔
1858
        let script = script!(CompareHeight).unwrap();
1✔
1859

1✔
1860
        let inputs = inputs!(0);
1✔
1861
        let ctx = context_with_height(u64::MAX);
1✔
1862
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1863
        assert!(matches!(stack_item, Err(ScriptError::ValueExceedsBounds)));
1✔
1864
    }
1✔
1865

1866
    #[test]
1867
    fn test_compare_height_underflows() {
1✔
1868
        let script = script!(CompareHeight).unwrap();
1✔
1869

1✔
1870
        let inputs = ExecutionStack::new(vec![Number(i64::MIN)]);
1✔
1871
        let ctx = context_with_height(i64::MAX as u64);
1✔
1872
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1873
        assert!(matches!(stack_item, Err(ScriptError::CompareFailed(_))));
1✔
1874
    }
1✔
1875

1876
    #[test]
1877
    fn test_compare_height_underflows_on_empty_stack() {
1✔
1878
        let script = script!(CompareHeight).unwrap();
1✔
1879

1✔
1880
        let inputs = ExecutionStack::new(vec![]);
1✔
1881
        let ctx = context_with_height(i64::MAX as u64);
1✔
1882
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1883
        assert!(matches!(stack_item, Err(ScriptError::StackUnderflow)));
1✔
1884
    }
1✔
1885

1886
    #[test]
1887
    fn test_compare_height_valid_with_uint_result() {
1✔
1888
        let script = script!(CompareHeight).unwrap();
1✔
1889

1✔
1890
        let inputs = inputs!(100);
1✔
1891
        let ctx = context_with_height(24_u64);
1✔
1892
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1893
        assert!(stack_item.is_ok());
1✔
1894
        assert_eq!(stack_item.unwrap(), Number(-76))
1✔
1895
    }
1✔
1896

1897
    #[test]
1898
    fn test_compare_height_valid_with_int_result() {
1✔
1899
        let script = script!(CompareHeight).unwrap();
1✔
1900

1✔
1901
        let inputs = inputs!(100);
1✔
1902
        let ctx = context_with_height(110_u64);
1✔
1903
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1904
        assert!(stack_item.is_ok());
1✔
1905
        assert_eq!(stack_item.unwrap(), Number(10))
1✔
1906
    }
1✔
1907

1908
    #[test]
1909
    fn test_check_height_block_height_exceeds_bounds() {
1✔
1910
        let script = script!(CheckHeight(0)).unwrap();
1✔
1911

1✔
1912
        let inputs = ExecutionStack::new(vec![]);
1✔
1913
        let ctx = context_with_height(u64::MAX);
1✔
1914
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1915
        assert!(matches!(stack_item, Err(ScriptError::ValueExceedsBounds)));
1✔
1916
    }
1✔
1917

1918
    #[test]
1919
    fn test_check_height_exceeds_bounds() {
1✔
1920
        let script = script!(CheckHeight(u64::MAX)).unwrap();
1✔
1921

1✔
1922
        let inputs = ExecutionStack::new(vec![]);
1✔
1923
        let ctx = context_with_height(10_u64);
1✔
1924
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1925
        assert!(matches!(stack_item, Err(ScriptError::ValueExceedsBounds)));
1✔
1926
    }
1✔
1927

1928
    #[test]
1929
    fn test_check_height_overflows_on_max_stack() {
1✔
1930
        let script = script!(CheckHeight(0)).unwrap();
1✔
1931

1✔
1932
        let mut inputs = ExecutionStack::new(vec![]);
1✔
1933

1934
        for i in 0..255 {
256✔
1935
            inputs.push(Number(i)).unwrap();
255✔
1936
        }
255✔
1937

1938
        let ctx = context_with_height(i64::MAX as u64);
1✔
1939
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1940
        assert!(matches!(stack_item, Err(ScriptError::StackOverflow)));
1✔
1941
    }
1✔
1942

1943
    #[test]
1944
    fn test_check_height_valid_with_uint_result() {
1✔
1945
        let script = script!(CheckHeight(24)).unwrap();
1✔
1946

1✔
1947
        let inputs = ExecutionStack::new(vec![]);
1✔
1948
        let ctx = context_with_height(100_u64);
1✔
1949
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1950
        assert!(stack_item.is_ok());
1✔
1951
        assert_eq!(stack_item.unwrap(), Number(76))
1✔
1952
    }
1✔
1953

1954
    #[test]
1955
    fn test_check_height_valid_with_int_result() {
1✔
1956
        let script = script!(CheckHeight(100)).unwrap();
1✔
1957

1✔
1958
        let inputs = ExecutionStack::new(vec![]);
1✔
1959
        let ctx = context_with_height(24_u64);
1✔
1960
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1961
        assert!(stack_item.is_ok());
1✔
1962
        assert_eq!(stack_item.unwrap(), Number(-76))
1✔
1963
    }
1✔
1964
}
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