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

tari-project / tari / 17549972632

08 Sep 2025 11:57AM UTC coverage: 60.917% (-0.2%) from 61.111%
17549972632

push

github

web-flow
feat: wallet multisignature feature (#7481)

Description
---
This PR introduces support for multisignature (multisig) functionality
across the CLI and gRPC interfaces.

Cli methods provided
- SignMessage - Needed in Tari_Universe to sign messages 
- SignScriptMessage- Needed in tari_universe to sign script message
- PrepareDepositMultisigTransaction - Test purpose only. Create
transaction for converting normal utxo into multisign one. Without
signing it (needed in tari_universe for offline signing)
- PrepareWithdrawMultisigTransaction - Test purpose only. Create
transaction for converting multisig utxo into normal one. Without
signing it (needed in tari_universe for offline signing)
- SignOneSidedDepositMultisigTransaction - Sign the Prepared deposit
transaction (for offline signing in tari_universe).
- SignOneSidedWithdrawMultisigTransaction - Sign the Prepared withdraw
transaction (for offline signing in tari_universe).
- CreateMultisigUtxo - It is the same as
PrepareDepositMultisigTransaction /
SignOneSidedDepositMultisigTransaction but it can be called only from
cli.(won't work with offline signing)
- GetMultisigUtxoData - Return Data from utxo that are needed for
multisig (challenge / public_offset_key).
- SendMultisigUtxo - It is the same as
PrepareWithdrawMultisigTransaction /
SignOneSidedWithdrawMultisigTransaction but it can be called only from
cli.(won't work with offline signing)


GRPC methods
- PrepareDepositMultisigTransaction 
- PrepareWithdrawMultisigTransaction - transaction for converting
multisig utxo into normal one. Without signing it (needed in
tari_universe for offline signing)

Motivation and Context
---

How Has This Been Tested?
---
Written unit test and manually both from CLI and from tari universe
offline signing.

What process can a PR reviewer use to test or verify this change?
---
The change can be tested from the wallet using Docker.

**1. Create a multisignature UTXO (replace with your own data):**

```
docker compose ... (continued)

847 of 1880 new or added lines in 17 files covered. (45.05%)

8 existing lines in 5 files now uncovered.

73757 of 121077 relevant lines covered (60.92%)

296113.71 hits per line

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

96.93
/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<()> {
31,283✔
73
        let bytes = self.to_bytes();
31,283✔
74
        writer.write_varint(bytes.len())?;
31,283✔
75
        for b in &bytes {
3,112,222✔
76
            b.serialize(writer)?;
3,080,939✔
77
        }
78
        Ok(())
31,283✔
79
    }
31,283✔
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,331✔
104
        let script = ScriptOpcodes::try_from(script)?;
1,331✔
105
        Ok(TariScript { script })
1,331✔
106
    }
1,331✔
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(
212✔
141
        &self,
212✔
142
        inputs: &ExecutionStack,
212✔
143
        context: &ScriptContext,
212✔
144
    ) -> Result<StackItem, ScriptError> {
212✔
145
        // Copy all inputs onto the stack
212✔
146
        let mut stack = inputs.clone();
212✔
147
        // Local execution state
212✔
148
        let mut state = ExecutionState::default();
212✔
149

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

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

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

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

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

188
    pub fn to_bytes(&self) -> Vec<u8> {
38,317✔
189
        self.script.iter().fold(Vec::new(), |mut bytes, op| {
161,840✔
190
            op.to_bytes(&mut bytes);
161,840✔
191
            bytes
161,840✔
192
        })
161,840✔
193
    }
38,317✔
194

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

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

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

214
        Ok(TariScript { script })
7,067✔
215
    }
7,067✔
216

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

51✔
645
        let mut agg_pub_key = RistrettoPublicKey::default();
51✔
646

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

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

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

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

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

697
impl Iterator for TariScript {
698
    // Define the type of item returned by the iterator
699
    type Item = Opcode;
700

701
    // Implement the next method
NEW
702
    fn next(&mut self) -> Option<Self::Item> {
×
NEW
703
        self.script.iter().next().cloned()
×
NEW
704
    }
×
705
}
706

707
impl Hex for TariScript {
708
    fn from_hex(hex: &str) -> Result<Self, HexError>
5✔
709
    where Self: Sized {
5✔
710
        let bytes = from_hex(hex)?;
5✔
711
        TariScript::from_bytes(&bytes).map_err(|_| HexError::HexConversionError {})
5✔
712
    }
5✔
713

714
    fn to_hex(&self) -> String {
3✔
715
        to_hex(&self.to_bytes())
3✔
716
    }
3✔
717
}
718

719
/// The default Tari script is to push a sender pubkey onto the stack
720
impl Default for TariScript {
721
    fn default() -> Self {
28✔
722
        script!(PushPubKey(Box::default())).expect("default will not fail")
28✔
723
    }
28✔
724
}
725

726
impl fmt::Display for TariScript {
727
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1✔
728
        let s = self.to_opcodes().join(" ");
1✔
729
        f.write_str(&s)
1✔
730
    }
1✔
731
}
732

733
#[derive(Clone, Debug, PartialEq, Eq)]
734
enum Branch {
735
    NotExecuted,
736
    ExecuteIf,
737
    ExecuteElse,
738
}
739

740
#[derive(Clone, Debug, PartialEq, Eq)]
741
struct IfState {
742
    branch: Branch,
743
    else_expected: bool,
744
}
745

746
#[derive(Clone, Debug, PartialEq, Eq)]
747
struct ExecutionState {
748
    executing: bool,
749
    if_stack: Vec<IfState>,
750
}
751

752
impl Default for ExecutionState {
753
    fn default() -> Self {
212✔
754
        Self {
212✔
755
            executing: true,
212✔
756
            if_stack: Vec::new(),
212✔
757
        }
212✔
758
    }
212✔
759
}
760

761
#[cfg(test)]
762
mod test {
763
    #![allow(clippy::indexing_slicing)]
764
    use blake2::Blake2b;
765
    use borsh::{BorshDeserialize, BorshSerialize};
766
    use digest::{consts::U32, Digest};
767
    use sha2::Sha256;
768
    use sha3::Sha3_256 as Sha3;
769
    use tari_crypto::{
770
        compressed_commitment::CompressedCommitment,
771
        compressed_key::CompressedKey,
772
        keys::SecretKey,
773
        ristretto::{pedersen::CompressedPedersenCommitment, RistrettoPublicKey, RistrettoSecretKey},
774
    };
775
    use tari_utilities::{hex::Hex, ByteArray};
776

777
    use crate::{
778
        error::ScriptError,
779
        inputs,
780
        op_codes::{slice_to_boxed_hash, slice_to_boxed_message, HashValue, Message},
781
        CheckSigSchnorrSignature,
782
        CompressedCheckSigSchnorrSignature,
783
        ExecutionStack,
784
        Opcode::CheckMultiSigVerifyAggregatePubKey,
785
        ScriptContext,
786
        StackItem,
787
        StackItem::{Commitment, Hash, Number},
788
        TariScript,
789
    };
790

791
    fn context_with_height(height: u64) -> ScriptContext {
55✔
792
        ScriptContext::new(height, &HashValue::default(), &CompressedPedersenCommitment::default())
55✔
793
    }
55✔
794

795
    #[test]
796
    fn pattern_match() {
1✔
797
        let script_a = script!(Or(1)).unwrap();
1✔
798
        let script_b = script!(Or(1)).unwrap();
1✔
799
        assert_eq!(script_a, script_b);
1✔
800
        assert!(script_a.pattern_match(&script_b));
1✔
801

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

806
        let script_b = script!(Or(2) Or(2)).unwrap();
1✔
807
        assert_ne!(script_a, script_b);
1✔
808
        assert!(!script_a.pattern_match(&script_b));
1✔
809

810
        let script_a = script!(Or(2) Or(1)).unwrap();
1✔
811
        let script_b = script!(Or(3) Or(5)).unwrap();
1✔
812
        assert_ne!(script_a, script_b);
1✔
813
        assert!(script_a.pattern_match(&script_b));
1✔
814
    }
1✔
815

816
    #[test]
817
    fn op_or() {
1✔
818
        let script = script!(Or(1)).unwrap();
1✔
819

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

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

828
        let script = script!(Or(3)).unwrap();
1✔
829

1✔
830
        let inputs = inputs!(1, 2, 1, 3);
1✔
831
        let result = script.execute(&inputs).unwrap();
1✔
832
        assert_eq!(result, Number(1));
1✔
833

834
        let inputs = inputs!(1, 2, 4, 3);
1✔
835
        let result = script.execute(&inputs).unwrap();
1✔
836
        assert_eq!(result, Number(0));
1✔
837

838
        let mut rng = rand::thread_rng();
1✔
839
        let (_, p) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
840
        let inputs = inputs!(1, p.clone(), 1, 3);
1✔
841
        let err = script.execute(&inputs).unwrap_err();
1✔
842
        assert!(matches!(err, ScriptError::InvalidInput));
1✔
843

844
        let inputs = inputs!(p, 2, 1, 3);
1✔
845
        let err = script.execute(&inputs).unwrap_err();
1✔
846
        assert!(matches!(err, ScriptError::InvalidInput));
1✔
847

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

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

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

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

862
        let script = script!(OrVerify(2)).unwrap();
1✔
863

1✔
864
        let inputs = inputs!(1, 2, 2, 3);
1✔
865
        let result = script.execute(&inputs).unwrap();
1✔
866
        assert_eq!(result, Number(1));
1✔
867

868
        let inputs = inputs!(1, 2, 3, 4);
1✔
869
        let err = script.execute(&inputs).unwrap_err();
1✔
870
        assert!(matches!(err, ScriptError::VerifyFailed));
1✔
871
    }
1✔
872

873
    #[test]
874
    fn op_if_then_else() {
1✔
875
        // basic
1✔
876
        let script = script!(IfThen PushInt(420) Else PushInt(66) EndIf).unwrap();
1✔
877
        let inputs = inputs!(1);
1✔
878
        let result = script.execute(&inputs);
1✔
879
        assert_eq!(result.unwrap(), Number(420));
1✔
880

881
        let inputs = inputs!(0);
1✔
882
        let result = script.execute(&inputs);
1✔
883
        assert_eq!(result.unwrap(), Number(66));
1✔
884

885
        // nested
886
        let script =
1✔
887
            script!(IfThen PushOne IfThen PushInt(420) Else PushInt(555) EndIf Else PushInt(66) EndIf).unwrap();
1✔
888
        let inputs = inputs!(1);
1✔
889
        let result = script.execute(&inputs);
1✔
890
        assert_eq!(result.unwrap(), Number(420));
1✔
891

892
        let script =
1✔
893
            script!(IfThen PushInt(420) Else PushZero IfThen PushInt(111) Else PushInt(66) EndIf Nop EndIf).unwrap();
1✔
894
        let inputs = inputs!(0);
1✔
895
        let result = script.execute(&inputs);
1✔
896
        assert_eq!(result.unwrap(), Number(66));
1✔
897

898
        // duplicate else
899
        let script = script!(IfThen PushInt(420) Else PushInt(66) Else PushInt(777) EndIf).unwrap();
1✔
900
        let inputs = inputs!(0);
1✔
901
        let result = script.execute(&inputs);
1✔
902
        assert_eq!(result.unwrap_err(), ScriptError::InvalidOpcode);
1✔
903

904
        // unexpected else
905
        let script = script!(Else).unwrap();
1✔
906
        let inputs = inputs!(0);
1✔
907
        let result = script.execute(&inputs);
1✔
908
        assert_eq!(result.unwrap_err(), ScriptError::InvalidOpcode);
1✔
909

910
        // unexpected endif
911
        let script = script!(EndIf).unwrap();
1✔
912
        let inputs = inputs!(0);
1✔
913
        let result = script.execute(&inputs);
1✔
914
        assert_eq!(result.unwrap_err(), ScriptError::InvalidOpcode);
1✔
915

916
        // duplicate endif
917
        let script = script!(IfThen PushInt(420) Else PushInt(66) EndIf EndIf).unwrap();
1✔
918
        let inputs = inputs!(0);
1✔
919
        let result = script.execute(&inputs);
1✔
920
        assert_eq!(result.unwrap_err(), ScriptError::InvalidOpcode);
1✔
921

922
        // no else or endif
923
        let script = script!(IfThen PushOne IfThen PushOne).unwrap();
1✔
924
        let inputs = inputs!(1);
1✔
925
        let result = script.execute(&inputs);
1✔
926
        assert_eq!(result.unwrap_err(), ScriptError::MissingOpcode);
1✔
927

928
        // no else
929
        let script = script!(IfThen PushOne EndIf).unwrap();
1✔
930
        let inputs = inputs!(1);
1✔
931
        let result = script.execute(&inputs);
1✔
932
        assert_eq!(result.unwrap_err(), ScriptError::MissingOpcode);
1✔
933

934
        // nested bug
935
        let script =
1✔
936
            script!(IfThen PushInt(111) Else PushZero IfThen PushInt(222) Else PushInt(333) EndIf EndIf).unwrap();
1✔
937
        let inputs = inputs!(1);
1✔
938
        let result = script.execute(&inputs);
1✔
939
        assert_eq!(result.unwrap(), Number(111));
1✔
940
    }
1✔
941

942
    #[test]
943
    fn op_check_height() {
1✔
944
        let inputs = ExecutionStack::default();
1✔
945
        let script = script!(CheckHeight(5)).unwrap();
1✔
946

947
        for block_height in 1..=10 {
11✔
948
            let ctx = context_with_height(u64::try_from(block_height).unwrap());
10✔
949
            assert_eq!(
10✔
950
                script.execute_with_context(&inputs, &ctx).unwrap(),
10✔
951
                Number(block_height - 5)
10✔
952
            );
10✔
953
        }
954

955
        let script = script!(CheckHeight(u64::MAX)).unwrap();
1✔
956
        let ctx = context_with_height(i64::MAX as u64);
1✔
957
        let err = script.execute_with_context(&inputs, &ctx).unwrap_err();
1✔
958
        assert!(matches!(err, ScriptError::ValueExceedsBounds));
1✔
959

960
        let script = script!(CheckHeightVerify(5)).unwrap();
1✔
961
        let inputs = inputs!(1);
1✔
962

963
        for block_height in 1..5 {
5✔
964
            let ctx = context_with_height(block_height);
4✔
965
            let err = script.execute_with_context(&inputs, &ctx).unwrap_err();
4✔
966
            assert!(matches!(err, ScriptError::VerifyFailed));
4✔
967
        }
968

969
        for block_height in 5..=10 {
7✔
970
            let ctx = context_with_height(block_height);
6✔
971
            let result = script.execute_with_context(&inputs, &ctx).unwrap();
6✔
972
            assert_eq!(result, Number(1));
6✔
973
        }
974
    }
1✔
975

976
    #[test]
977
    fn op_compare_height() {
1✔
978
        let script = script!(CompareHeight).unwrap();
1✔
979
        let inputs = inputs!(5);
1✔
980

981
        for block_height in 1..=10 {
11✔
982
            let ctx = context_with_height(u64::try_from(block_height).unwrap());
10✔
983
            assert_eq!(
10✔
984
                script.execute_with_context(&inputs, &ctx).unwrap(),
10✔
985
                Number(block_height - 5)
10✔
986
            );
10✔
987
        }
988

989
        let script = script!(CompareHeightVerify).unwrap();
1✔
990
        let inputs = inputs!(1, 5);
1✔
991

992
        for block_height in 1..5 {
5✔
993
            let ctx = context_with_height(block_height);
4✔
994
            let err = script.execute_with_context(&inputs, &ctx).unwrap_err();
4✔
995
            assert!(matches!(err, ScriptError::VerifyFailed));
4✔
996
        }
997

998
        for block_height in 5..=10 {
7✔
999
            let ctx = context_with_height(block_height);
6✔
1000
            let result = script.execute_with_context(&inputs, &ctx).unwrap();
6✔
1001
            assert_eq!(result, Number(1));
6✔
1002
        }
1003
    }
1✔
1004

1005
    #[test]
1006
    fn op_drop_push() {
1✔
1007
        let inputs = inputs!(420);
1✔
1008
        let script = script!(Drop PushOne).unwrap();
1✔
1009
        assert_eq!(script.execute(&inputs).unwrap(), Number(1));
1✔
1010

1011
        let script = script!(Drop PushZero).unwrap();
1✔
1012
        assert_eq!(script.execute(&inputs).unwrap(), Number(0));
1✔
1013

1014
        let script = script!(Drop PushInt(5)).unwrap();
1✔
1015
        assert_eq!(script.execute(&inputs).unwrap(), Number(5));
1✔
1016
    }
1✔
1017

1018
    #[test]
1019
    fn op_comparison_to_zero() {
1✔
1020
        let script = script!(GeZero).unwrap();
1✔
1021
        let inputs = inputs!(1);
1✔
1022
        assert_eq!(script.execute(&inputs).unwrap(), Number(1));
1✔
1023
        let inputs = inputs!(0);
1✔
1024
        assert_eq!(script.execute(&inputs).unwrap(), Number(1));
1✔
1025

1026
        let script = script!(GtZero).unwrap();
1✔
1027
        let inputs = inputs!(1);
1✔
1028
        assert_eq!(script.execute(&inputs).unwrap(), Number(1));
1✔
1029
        let inputs = inputs!(0);
1✔
1030
        assert_eq!(script.execute(&inputs).unwrap(), Number(0));
1✔
1031

1032
        let script = script!(LeZero).unwrap();
1✔
1033
        let inputs = inputs!(-1);
1✔
1034
        assert_eq!(script.execute(&inputs).unwrap(), Number(1));
1✔
1035
        let inputs = inputs!(0);
1✔
1036
        assert_eq!(script.execute(&inputs).unwrap(), Number(1));
1✔
1037

1038
        let script = script!(LtZero).unwrap();
1✔
1039
        let inputs = inputs!(-1);
1✔
1040
        assert_eq!(script.execute(&inputs).unwrap(), Number(1));
1✔
1041
        let inputs = inputs!(0);
1✔
1042
        assert_eq!(script.execute(&inputs).unwrap(), Number(0));
1✔
1043
    }
1✔
1044

1045
    #[test]
1046
    fn op_hash() {
1✔
1047
        let mut rng = rand::thread_rng();
1✔
1048
        let (_, p) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1049
        let c = CompressedCommitment::<RistrettoPublicKey>::from_compressed_key(p.clone());
1✔
1050
        let script = script!(HashSha256).unwrap();
1✔
1051

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

1056
        let hash = Sha256::digest(c.as_bytes());
1✔
1057
        let inputs = inputs!(c.clone());
1✔
1058
        assert_eq!(script.execute(&inputs).unwrap(), Hash(hash.into()));
1✔
1059

1060
        let script = script!(HashSha3).unwrap();
1✔
1061

1✔
1062
        let hash = Sha3::digest(p.as_bytes());
1✔
1063
        let inputs = inputs!(p);
1✔
1064
        assert_eq!(script.execute(&inputs).unwrap(), Hash(hash.into()));
1✔
1065

1066
        let hash = Sha3::digest(c.as_bytes());
1✔
1067
        let inputs = inputs!(c);
1✔
1068
        assert_eq!(script.execute(&inputs).unwrap(), Hash(hash.into()));
1✔
1069
    }
1✔
1070

1071
    #[test]
1072
    fn op_return() {
1✔
1073
        let script = script!(Return).unwrap();
1✔
1074
        let inputs = ExecutionStack::default();
1✔
1075
        assert_eq!(script.execute(&inputs), Err(ScriptError::Return));
1✔
1076
    }
1✔
1077

1078
    #[test]
1079
    fn op_add() {
1✔
1080
        let script = script!(Add).unwrap();
1✔
1081
        let inputs = inputs!(3, 2);
1✔
1082
        assert_eq!(script.execute(&inputs).unwrap(), Number(5));
1✔
1083
        let inputs = inputs!(3, -3);
1✔
1084
        assert_eq!(script.execute(&inputs).unwrap(), Number(0));
1✔
1085
        let inputs = inputs!(i64::MAX, 1);
1✔
1086
        assert_eq!(script.execute(&inputs), Err(ScriptError::ValueExceedsBounds));
1✔
1087
        let inputs = inputs!(1);
1✔
1088
        assert_eq!(script.execute(&inputs), Err(ScriptError::StackUnderflow));
1✔
1089
    }
1✔
1090

1091
    #[test]
1092
    fn op_add_commitments() {
1✔
1093
        let script = script!(Add).unwrap();
1✔
1094
        let mut rng = rand::thread_rng();
1✔
1095
        let (_, c1) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1096
        let (_, c2) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1097
        let c3 = &c1.to_public_key().unwrap() + &c2.to_public_key().unwrap();
1✔
1098
        let c3 = CompressedCommitment::<RistrettoPublicKey>::from_public_key(c3);
1✔
1099
        let inputs = inputs!(
1✔
1100
            CompressedCommitment::<RistrettoPublicKey>::from_compressed_key(c1),
1✔
1101
            CompressedCommitment::<RistrettoPublicKey>::from_compressed_key(c2)
1✔
1102
        );
1✔
1103
        assert_eq!(script.execute(&inputs).unwrap(), Commitment(c3));
1✔
1104
    }
1✔
1105

1106
    #[test]
1107
    fn op_sub() {
1✔
1108
        use crate::StackItem::Number;
1109
        let script = script!(Add Sub).unwrap();
1✔
1110
        let inputs = inputs!(5, 3, 2);
1✔
1111
        assert_eq!(script.execute(&inputs).unwrap(), Number(0));
1✔
1112
        let inputs = inputs!(i64::MAX, 1);
1✔
1113
        assert_eq!(script.execute(&inputs), Err(ScriptError::ValueExceedsBounds));
1✔
1114
        let script = script!(Sub).unwrap();
1✔
1115
        let inputs = inputs!(5, 3);
1✔
1116
        assert_eq!(script.execute(&inputs).unwrap(), Number(2));
1✔
1117
    }
1✔
1118

1119
    #[test]
1120
    fn serialisation() {
1✔
1121
        let script = script!(Add Sub Add).unwrap();
1✔
1122
        assert_eq!(&script.to_bytes(), &[0x93, 0x94, 0x93]);
1✔
1123
        assert_eq!(TariScript::from_bytes(&[0x93, 0x94, 0x93]).unwrap(), script);
1✔
1124
        assert_eq!(script.to_hex(), "939493");
1✔
1125
        assert_eq!(TariScript::from_hex("939493").unwrap(), script);
1✔
1126
    }
1✔
1127

1128
    #[test]
1129
    fn check_sig() {
1✔
1130
        use crate::StackItem::Number;
1131
        let mut rng = rand::thread_rng();
1✔
1132
        let (pvt_key, pub_key) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1133
        let m_key = RistrettoSecretKey::random(&mut rng);
1✔
1134
        let sig = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1135
            CheckSigSchnorrSignature::sign(&pvt_key, m_key.as_bytes(), &mut rng).unwrap(),
1✔
1136
        );
1✔
1137
        let msg = slice_to_boxed_message(m_key.as_bytes());
1✔
1138
        let script = script!(CheckSig(msg)).unwrap();
1✔
1139
        let inputs = inputs!(sig.clone(), pub_key.clone());
1✔
1140
        let result = script.execute(&inputs).unwrap();
1✔
1141
        assert_eq!(result, Number(1));
1✔
1142

1143
        let n_key = RistrettoSecretKey::random(&mut rng);
1✔
1144
        let msg = slice_to_boxed_message(n_key.as_bytes());
1✔
1145
        let script = script!(CheckSig(msg)).unwrap();
1✔
1146
        let inputs = inputs!(sig, pub_key);
1✔
1147
        let result = script.execute(&inputs).unwrap();
1✔
1148
        assert_eq!(result, Number(0));
1✔
1149
    }
1✔
1150

1151
    #[test]
1152
    fn check_sig_verify() {
1✔
1153
        use crate::StackItem::Number;
1154
        let mut rng = rand::thread_rng();
1✔
1155
        let (pvt_key, pub_key) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1156
        let m_key = RistrettoSecretKey::random(&mut rng);
1✔
1157
        let sig = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1158
            CheckSigSchnorrSignature::sign(&pvt_key, m_key.as_bytes(), &mut rng).unwrap(),
1✔
1159
        );
1✔
1160
        let msg = slice_to_boxed_message(m_key.as_bytes());
1✔
1161
        let script = script!(CheckSigVerify(msg) PushOne).unwrap();
1✔
1162
        let inputs = inputs!(sig.clone(), pub_key.clone());
1✔
1163
        let result = script.execute(&inputs).unwrap();
1✔
1164
        assert_eq!(result, Number(1));
1✔
1165

1166
        let n_key = RistrettoSecretKey::random(&mut rng);
1✔
1167
        let msg = slice_to_boxed_message(n_key.as_bytes());
1✔
1168
        let script = script!(CheckSigVerify(msg)).unwrap();
1✔
1169
        let inputs = inputs!(sig, pub_key);
1✔
1170
        let err = script.execute(&inputs).unwrap_err();
1✔
1171
        assert!(matches!(err, ScriptError::VerifyFailed));
1✔
1172
    }
1✔
1173

1174
    #[allow(clippy::type_complexity)]
1175
    fn multisig_data(
5✔
1176
        n: usize,
5✔
1177
    ) -> (
5✔
1178
        Box<Message>,
5✔
1179
        Vec<(
5✔
1180
            RistrettoSecretKey,
5✔
1181
            CompressedKey<RistrettoPublicKey>,
5✔
1182
            CompressedCheckSigSchnorrSignature,
5✔
1183
        )>,
5✔
1184
    ) {
5✔
1185
        let mut rng = rand::thread_rng();
5✔
1186
        let mut data = Vec::with_capacity(n);
5✔
1187
        let m = RistrettoSecretKey::random(&mut rng);
5✔
1188
        let msg = slice_to_boxed_message(m.as_bytes());
5✔
1189

5✔
1190
        for _ in 0..n {
55✔
1191
            let (k, p) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
55✔
1192
            let s = CompressedCheckSigSchnorrSignature::new_from_schnorr(
55✔
1193
                CheckSigSchnorrSignature::sign(&k, m.as_bytes(), &mut rng).unwrap(),
55✔
1194
            );
55✔
1195
            data.push((k, p, s));
55✔
1196
        }
55✔
1197

1198
        (msg, data)
5✔
1199
    }
5✔
1200

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

1✔
1228
        // 1 of 2
1✔
1229
        let keys = vec![p_alice.clone(), p_bob.clone()];
1✔
1230
        let ops = vec![CheckMultiSig(1, 2, keys, msg.clone())];
1✔
1231
        let script = TariScript::new(ops).unwrap();
1✔
1232

1✔
1233
        let inputs = inputs!(s_alice.clone());
1✔
1234
        let result = script.execute(&inputs).unwrap();
1✔
1235
        assert_eq!(result, Number(1));
1✔
1236
        let inputs = inputs!(s_bob.clone());
1✔
1237
        let result = script.execute(&inputs).unwrap();
1✔
1238
        assert_eq!(result, Number(1));
1✔
1239
        let inputs = inputs!(s_eve.clone());
1✔
1240
        let result = script.execute(&inputs).unwrap();
1✔
1241
        assert_eq!(result, Number(0));
1✔
1242

1243
        // 2 of 2
1244
        let keys = vec![p_alice.clone(), p_bob.clone()];
1✔
1245
        let ops = vec![CheckMultiSig(2, 2, keys, msg.clone())];
1✔
1246
        let script = TariScript::new(ops).unwrap();
1✔
1247

1✔
1248
        let inputs = inputs!(s_alice.clone(), s_bob.clone());
1✔
1249
        let result = script.execute(&inputs).unwrap();
1✔
1250
        assert_eq!(result, Number(1));
1✔
1251
        let inputs = inputs!(s_bob.clone(), s_alice.clone());
1✔
1252
        let result = script.execute(&inputs).unwrap();
1✔
1253
        assert_eq!(result, Number(0));
1✔
1254
        let inputs = inputs!(s_eve.clone(), s_bob.clone());
1✔
1255
        let result = script.execute(&inputs).unwrap();
1✔
1256
        assert_eq!(result, Number(0));
1✔
1257

1258
        // 2 of 2 - don't allow same sig to sign twice
1259
        let inputs = inputs!(s_alice.clone(), s_alice.clone());
1✔
1260
        let result = script.execute(&inputs).unwrap();
1✔
1261
        assert_eq!(result, Number(0));
1✔
1262

1263
        // 1 of 3
1264
        let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
1✔
1265
        let ops = vec![CheckMultiSig(1, 3, keys, msg.clone())];
1✔
1266
        let script = TariScript::new(ops).unwrap();
1✔
1267

1✔
1268
        let inputs = inputs!(s_alice.clone());
1✔
1269
        let result = script.execute(&inputs).unwrap();
1✔
1270
        assert_eq!(result, Number(1));
1✔
1271
        let inputs = inputs!(s_bob.clone());
1✔
1272
        let result = script.execute(&inputs).unwrap();
1✔
1273
        assert_eq!(result, Number(1));
1✔
1274
        let inputs = inputs!(s_carol.clone());
1✔
1275
        let result = script.execute(&inputs).unwrap();
1✔
1276
        assert_eq!(result, Number(1));
1✔
1277
        let inputs = inputs!(s_eve.clone());
1✔
1278
        let result = script.execute(&inputs).unwrap();
1✔
1279
        assert_eq!(result, Number(0));
1✔
1280

1281
        // 2 of 3
1282
        let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
1✔
1283
        let ops = vec![CheckMultiSig(2, 3, keys, msg.clone())];
1✔
1284
        let script = TariScript::new(ops).unwrap();
1✔
1285

1✔
1286
        let inputs = inputs!(s_alice.clone(), s_bob.clone());
1✔
1287
        let result = script.execute(&inputs).unwrap();
1✔
1288
        assert_eq!(result, Number(1));
1✔
1289
        let inputs = inputs!(s_alice.clone(), s_carol.clone());
1✔
1290
        let result = script.execute(&inputs).unwrap();
1✔
1291
        assert_eq!(result, Number(1));
1✔
1292
        let inputs = inputs!(s_bob.clone(), s_carol.clone());
1✔
1293
        let result = script.execute(&inputs).unwrap();
1✔
1294
        assert_eq!(result, Number(1));
1✔
1295
        let inputs = inputs!(s_carol.clone(), s_bob.clone());
1✔
1296
        let result = script.execute(&inputs).unwrap();
1✔
1297
        assert_eq!(result, Number(0));
1✔
1298
        let inputs = inputs!(s_carol.clone(), s_eve.clone());
1✔
1299
        let result = script.execute(&inputs).unwrap();
1✔
1300
        assert_eq!(result, Number(0));
1✔
1301

1302
        // check that sigs are only counted once
1303
        let keys = vec![p_alice.clone(), p_bob.clone(), p_alice.clone()];
1✔
1304
        let ops = vec![CheckMultiSig(2, 3, keys, msg.clone())];
1✔
1305
        let script = TariScript::new(ops).unwrap();
1✔
1306

1✔
1307
        let inputs = inputs!(s_alice.clone(), s_carol.clone());
1✔
1308
        let result = script.execute(&inputs).unwrap();
1✔
1309
        assert_eq!(result, Number(0));
1✔
1310
        let inputs = inputs!(s_alice.clone(), s_alice.clone());
1✔
1311
        let result = script.execute(&inputs).unwrap();
1✔
1312
        assert_eq!(result, Number(0));
1✔
1313
        let inputs = inputs!(s_alice.clone(), s_alice2.clone());
1✔
1314
        let result = script.execute(&inputs).unwrap();
1✔
1315
        assert_eq!(result, Number(1));
1✔
1316
        // Interesting case where either sig could match either pubkey
1317
        let inputs = inputs!(s_alice2, s_alice.clone());
1✔
1318
        let result = script.execute(&inputs).unwrap();
1✔
1319
        assert_eq!(result, Number(1));
1✔
1320

1321
        // 3 of 3
1322
        let keys = vec![p_alice.clone(), p_bob.clone(), p_carol];
1✔
1323
        let ops = vec![CheckMultiSig(3, 3, keys, msg.clone())];
1✔
1324
        let script = TariScript::new(ops).unwrap();
1✔
1325

1✔
1326
        let inputs = inputs!(s_alice.clone(), s_bob.clone(), s_carol.clone());
1✔
1327
        let result = script.execute(&inputs).unwrap();
1✔
1328
        assert_eq!(result, Number(1));
1✔
1329
        let inputs = inputs!(s_carol.clone(), s_alice.clone(), s_bob.clone());
1✔
1330
        let result = script.execute(&inputs).unwrap();
1✔
1331
        assert_eq!(result, Number(0));
1✔
1332
        let inputs = inputs!(s_eve.clone(), s_bob.clone(), s_carol);
1✔
1333
        let result = script.execute(&inputs).unwrap();
1✔
1334
        assert_eq!(result, Number(0));
1✔
1335
        let inputs = inputs!(s_eve, s_bob);
1✔
1336
        let err = script.execute(&inputs).unwrap_err();
1✔
1337
        assert_eq!(err, ScriptError::StackUnderflow);
1✔
1338

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

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

1354
        let keys = vec![p_alice, p_bob];
1✔
1355
        let ops = vec![CheckMultiSig(2, 1, keys, msg)];
1✔
1356
        let script = TariScript::new(ops).unwrap();
1✔
1357
        let inputs = inputs!(s_alice);
1✔
1358
        let err = script.execute(&inputs).unwrap_err();
1✔
1359
        assert_eq!(err, ScriptError::ValueExceedsBounds);
1✔
1360

1361
        // max n is 32
1362
        let (msg, data) = multisig_data(33);
1✔
1363
        let keys = data.iter().map(|(_, p, _)| p.clone()).collect();
33✔
1364
        let sigs = data.iter().take(17).map(|(_, _, s)| s.clone());
17✔
1365
        let script = script!(CheckMultiSig(17, 33, keys, msg)).unwrap();
1✔
1366
        let items = sigs.map(StackItem::Signature).collect();
1✔
1367
        let inputs = ExecutionStack::new(items);
1✔
1368
        let err = script.execute(&inputs).unwrap_err();
1✔
1369
        assert_eq!(err, ScriptError::ValueExceedsBounds);
1✔
1370

1371
        // 3 of 4
1372
        let (msg, data) = multisig_data(4);
1✔
1373
        let keys = vec![
1✔
1374
            data[0].1.clone(),
1✔
1375
            data[1].1.clone(),
1✔
1376
            data[2].1.clone(),
1✔
1377
            data[3].1.clone(),
1✔
1378
        ];
1✔
1379
        let ops = vec![CheckMultiSig(3, 4, keys, msg)];
1✔
1380
        let script = TariScript::new(ops).unwrap();
1✔
1381
        let inputs = inputs!(data[0].2.clone(), data[1].2.clone(), data[2].2.clone());
1✔
1382
        let result = script.execute(&inputs).unwrap();
1✔
1383
        assert_eq!(result, Number(1));
1✔
1384

1385
        // 5 of 7
1386
        let (msg, data) = multisig_data(7);
1✔
1387
        let keys = vec![
1✔
1388
            data[0].1.clone(),
1✔
1389
            data[1].1.clone(),
1✔
1390
            data[2].1.clone(),
1✔
1391
            data[3].1.clone(),
1✔
1392
            data[4].1.clone(),
1✔
1393
            data[5].1.clone(),
1✔
1394
            data[6].1.clone(),
1✔
1395
        ];
1✔
1396
        let ops = vec![CheckMultiSig(5, 7, keys, msg)];
1✔
1397
        let script = TariScript::new(ops).unwrap();
1✔
1398
        let inputs = inputs!(
1✔
1399
            data[0].2.clone(),
1✔
1400
            data[1].2.clone(),
1✔
1401
            data[2].2.clone(),
1✔
1402
            data[3].2.clone(),
1✔
1403
            data[4].2.clone()
1✔
1404
        );
1✔
1405
        let result = script.execute(&inputs).unwrap();
1✔
1406
        assert_eq!(result, Number(1));
1✔
1407
    }
1✔
1408

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

1✔
1433
        // 1 of 2
1✔
1434
        let keys = vec![p_alice.clone(), p_bob.clone()];
1✔
1435
        let ops = vec![CheckMultiSigVerify(1, 2, keys, msg.clone())];
1✔
1436
        let script = TariScript::new(ops).unwrap();
1✔
1437

1✔
1438
        let inputs = inputs!(Number(1), s_alice.clone());
1✔
1439
        let result = script.execute(&inputs).unwrap();
1✔
1440
        assert_eq!(result, Number(1));
1✔
1441
        let inputs = inputs!(Number(1), s_bob.clone());
1✔
1442
        let result = script.execute(&inputs).unwrap();
1✔
1443
        assert_eq!(result, Number(1));
1✔
1444
        let inputs = inputs!(Number(1), s_eve.clone());
1✔
1445
        let err = script.execute(&inputs).unwrap_err();
1✔
1446
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1447

1448
        // 2 of 2
1449
        let keys = vec![p_alice.clone(), p_bob.clone()];
1✔
1450
        let ops = vec![CheckMultiSigVerify(2, 2, keys, msg.clone())];
1✔
1451
        let script = TariScript::new(ops).unwrap();
1✔
1452

1✔
1453
        let inputs = inputs!(Number(1), s_alice.clone(), s_bob.clone());
1✔
1454
        let result = script.execute(&inputs).unwrap();
1✔
1455
        assert_eq!(result, Number(1));
1✔
1456
        let inputs = inputs!(Number(1), s_bob.clone(), s_alice.clone());
1✔
1457
        let err = script.execute(&inputs).unwrap_err();
1✔
1458
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1459
        let inputs = inputs!(Number(1), s_eve.clone(), s_bob.clone());
1✔
1460
        let err = script.execute(&inputs).unwrap_err();
1✔
1461
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1462

1463
        // 1 of 3
1464
        let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
1✔
1465
        let ops = vec![CheckMultiSigVerify(1, 3, keys, msg.clone())];
1✔
1466
        let script = TariScript::new(ops).unwrap();
1✔
1467

1✔
1468
        let inputs = inputs!(Number(1), s_alice.clone());
1✔
1469
        let result = script.execute(&inputs).unwrap();
1✔
1470
        assert_eq!(result, Number(1));
1✔
1471
        let inputs = inputs!(Number(1), s_bob.clone());
1✔
1472
        let result = script.execute(&inputs).unwrap();
1✔
1473
        assert_eq!(result, Number(1));
1✔
1474
        let inputs = inputs!(Number(1), s_carol.clone());
1✔
1475
        let result = script.execute(&inputs).unwrap();
1✔
1476
        assert_eq!(result, Number(1));
1✔
1477
        let inputs = inputs!(Number(1), s_eve.clone());
1✔
1478
        let err = script.execute(&inputs).unwrap_err();
1✔
1479
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1480

1481
        // 2 of 3
1482
        let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
1✔
1483
        let ops = vec![CheckMultiSigVerify(2, 3, keys, msg.clone())];
1✔
1484
        let script = TariScript::new(ops).unwrap();
1✔
1485

1✔
1486
        let inputs = inputs!(Number(1), s_alice.clone(), s_bob.clone());
1✔
1487
        let result = script.execute(&inputs).unwrap();
1✔
1488
        assert_eq!(result, Number(1));
1✔
1489
        let inputs = inputs!(Number(1), s_alice.clone(), s_carol.clone());
1✔
1490
        let result = script.execute(&inputs).unwrap();
1✔
1491
        assert_eq!(result, Number(1));
1✔
1492
        let inputs = inputs!(Number(1), s_bob.clone(), s_carol.clone());
1✔
1493
        let result = script.execute(&inputs).unwrap();
1✔
1494
        assert_eq!(result, Number(1));
1✔
1495
        let inputs = inputs!(Number(1), s_carol.clone(), s_bob.clone());
1✔
1496
        let err = script.execute(&inputs).unwrap_err();
1✔
1497
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1498
        let inputs = inputs!(Number(1), s_carol.clone(), s_eve.clone());
1✔
1499
        let err = script.execute(&inputs).unwrap_err();
1✔
1500
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1501

1502
        // 2 of 3 (returning the aggregate public key of the signatories)
1503
        let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
1✔
1504
        let ops = vec![CheckMultiSigVerifyAggregatePubKey(2, 3, keys, msg.clone())];
1✔
1505
        let script = TariScript::new(ops).unwrap();
1✔
1506

1✔
1507
        let inputs = inputs!(s_alice.clone(), s_bob.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_bob.clone().to_public_key().unwrap()
1✔
1513
            ))
1✔
1514
        );
1✔
1515

1516
        let inputs = inputs!(s_alice.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_alice.clone().to_public_key().unwrap() + &p_carol.clone().to_public_key().unwrap()
1✔
1522
            ))
1✔
1523
        );
1✔
1524

1525
        let inputs = inputs!(s_bob.clone(), s_carol.clone());
1✔
1526
        let agg_pub_key = script.execute(&inputs).unwrap();
1✔
1527
        assert_eq!(
1✔
1528
            agg_pub_key,
1✔
1529
            StackItem::PublicKey(CompressedKey::<RistrettoPublicKey>::new_from_pk(
1✔
1530
                &p_bob.clone().to_public_key().unwrap() + &p_carol.clone().to_public_key().unwrap()
1✔
1531
            ))
1✔
1532
        );
1✔
1533

1534
        let inputs = inputs!(s_carol.clone(), s_bob.clone());
1✔
1535
        let err = script.execute(&inputs).unwrap_err();
1✔
1536
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1537

1538
        let inputs = inputs!(s_alice.clone(), s_bob.clone(), s_carol.clone());
1✔
1539
        let err = script.execute(&inputs).unwrap_err();
1✔
1540
        assert_eq!(err, ScriptError::NonUnitLengthStack);
1✔
1541

1542
        let inputs = inputs!(p_bob.clone());
1✔
1543
        let err = script.execute(&inputs).unwrap_err();
1✔
1544
        assert_eq!(err, ScriptError::StackUnderflow);
1✔
1545

1546
        // 3 of 3
1547
        let keys = vec![p_alice.clone(), p_bob.clone(), p_carol];
1✔
1548
        let ops = vec![CheckMultiSigVerify(3, 3, keys, msg.clone())];
1✔
1549
        let script = TariScript::new(ops).unwrap();
1✔
1550

1✔
1551
        let inputs = inputs!(Number(1), s_alice.clone(), s_bob.clone(), s_carol.clone());
1✔
1552
        let result = script.execute(&inputs).unwrap();
1✔
1553
        assert_eq!(result, Number(1));
1✔
1554
        let inputs = inputs!(Number(1), s_bob.clone(), s_alice.clone(), s_carol.clone());
1✔
1555
        let err = script.execute(&inputs).unwrap_err();
1✔
1556
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1557
        let inputs = inputs!(Number(1), s_eve.clone(), s_bob.clone(), s_carol);
1✔
1558
        let err = script.execute(&inputs).unwrap_err();
1✔
1559
        assert_eq!(err, ScriptError::VerifyFailed);
1✔
1560
        let inputs = inputs!(Number(1), s_eve, s_bob);
1✔
1561
        let err = script.execute(&inputs).unwrap_err();
1✔
1562
        assert_eq!(err, ScriptError::IncompatibleTypes);
1✔
1563

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

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

1579
        let keys = vec![p_alice, p_bob];
1✔
1580
        let ops = vec![CheckMultiSigVerify(2, 1, keys, msg)];
1✔
1581
        let script = TariScript::new(ops).unwrap();
1✔
1582
        let inputs = inputs!(s_alice);
1✔
1583
        let err = script.execute(&inputs).unwrap_err();
1✔
1584
        assert_eq!(err, ScriptError::ValueExceedsBounds);
1✔
1585

1586
        // 3 of 4
1587
        let (msg, data) = multisig_data(4);
1✔
1588
        let keys = vec![
1✔
1589
            data[0].1.clone(),
1✔
1590
            data[1].1.clone(),
1✔
1591
            data[2].1.clone(),
1✔
1592
            data[3].1.clone(),
1✔
1593
        ];
1✔
1594
        let ops = vec![CheckMultiSigVerify(3, 4, keys, msg)];
1✔
1595
        let script = TariScript::new(ops).unwrap();
1✔
1596
        let inputs = inputs!(Number(1), data[0].2.clone(), data[1].2.clone(), data[2].2.clone());
1✔
1597
        let result = script.execute(&inputs).unwrap();
1✔
1598
        assert_eq!(result, Number(1));
1✔
1599

1600
        // 5 of 7
1601
        let (msg, data) = multisig_data(7);
1✔
1602
        let keys = vec![
1✔
1603
            data[0].1.clone(),
1✔
1604
            data[1].1.clone(),
1✔
1605
            data[2].1.clone(),
1✔
1606
            data[3].1.clone(),
1✔
1607
            data[4].1.clone(),
1✔
1608
            data[5].1.clone(),
1✔
1609
            data[6].1.clone(),
1✔
1610
        ];
1✔
1611
        let ops = vec![CheckMultiSigVerify(5, 7, keys, msg)];
1✔
1612
        let script = TariScript::new(ops).unwrap();
1✔
1613
        let inputs = inputs!(
1✔
1614
            Number(1),
1✔
1615
            data[0].2.clone(),
1✔
1616
            data[1].2.clone(),
1✔
1617
            data[2].2.clone(),
1✔
1618
            data[3].2.clone(),
1✔
1619
            data[4].2.clone()
1✔
1620
        );
1✔
1621
        let result = script.execute(&inputs).unwrap();
1✔
1622
        assert_eq!(result, Number(1));
1✔
1623
    }
1✔
1624

1625
    #[test]
1626
    fn pay_to_public_key_hash() {
1✔
1627
        use crate::StackItem::PublicKey;
1628
        let k =
1✔
1629
            RistrettoSecretKey::from_hex("7212ac93ee205cdbbb57c4f0f815fbf8db25b4d04d3532e2262e31907d82c700").unwrap();
1✔
1630
        let p = CompressedKey::<RistrettoPublicKey>::from_secret_key(&k); // 56c0fa32558d6edc0916baa26b48e745de834571534ca253ea82435f08ebbc7c
1✔
1631
        let hash = Blake2b::<U32>::digest(p.as_bytes());
1✔
1632
        let pkh = slice_to_boxed_hash(hash.as_slice()); // ae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e5
1✔
1633

1✔
1634
        // Unlike in Bitcoin where P2PKH includes a CheckSig at the end of the script, that part of the process is built
1✔
1635
        // into definition of how TariScript is evaluated by a base node or wallet
1✔
1636
        let script = script!(Dup HashBlake256 PushHash(pkh) EqualVerify).unwrap();
1✔
1637
        let hex_script = "71b07aae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e581";
1✔
1638
        // Test serialisation
1✔
1639
        assert_eq!(script.to_hex(), hex_script);
1✔
1640
        // Test de-serialisation
1641
        assert_eq!(TariScript::from_hex(hex_script).unwrap(), script);
1✔
1642

1643
        let inputs = inputs!(p.clone());
1✔
1644

1✔
1645
        let result = script.execute(&inputs).unwrap();
1✔
1646

1✔
1647
        assert_eq!(result, PublicKey(p));
1✔
1648
    }
1✔
1649

1650
    #[test]
1651
    fn hex_roundtrip() {
1✔
1652
        // Generate a signature
1✔
1653
        let mut rng = rand::thread_rng();
1✔
1654
        let (secret_key, public_key) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1655
        let message = [1u8; 32];
1✔
1656
        let sig = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1657
            CheckSigSchnorrSignature::sign(&secret_key, message, &mut rng).unwrap(),
1✔
1658
        );
1✔
1659

1✔
1660
        // Produce a script using the signature
1✔
1661
        let script = script!(CheckSig(slice_to_boxed_message(message.as_bytes()))).unwrap();
1✔
1662

1✔
1663
        // Produce input satisfying the script
1✔
1664
        let input = inputs!(sig, public_key);
1✔
1665

1✔
1666
        // Check that script execution succeeds
1✔
1667
        assert_eq!(script.execute(&input).unwrap(), StackItem::Number(1));
1✔
1668

1669
        // Convert the script to hex and back
1670
        let parsed_script = TariScript::from_hex(script.to_hex().as_str()).unwrap();
1✔
1671
        assert_eq!(script.to_opcodes(), parsed_script.to_opcodes());
1✔
1672

1673
        // Convert the input to hex and back
1674
        let parsed_input = ExecutionStack::from_hex(input.to_hex().as_str()).unwrap();
1✔
1675
        assert_eq!(input, parsed_input);
1✔
1676

1677
        // Check that script execution still succeeds
1678
        assert_eq!(parsed_script.execute(&parsed_input).unwrap(), StackItem::Number(1));
1✔
1679
    }
1✔
1680

1681
    #[test]
1682
    fn disassemble() {
1✔
1683
        let hex_script = "71b07aae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e58170ac276657a418820f34036b20ea615302b373c70ac8feab8d30681a3e0f0960e708";
1✔
1684
        let script = TariScript::from_hex(hex_script).unwrap();
1✔
1685
        let ops = vec![
1✔
1686
            "Dup",
1✔
1687
            "HashBlake256",
1✔
1688
            "PushHash(ae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e5)",
1✔
1689
            "EqualVerify",
1✔
1690
            "Drop",
1✔
1691
            "CheckSig(276657a418820f34036b20ea615302b373c70ac8feab8d30681a3e0f0960e708)",
1✔
1692
        ]
1✔
1693
        .into_iter()
1✔
1694
        .map(String::from)
1✔
1695
        .collect::<Vec<String>>();
1✔
1696
        assert_eq!(script.to_opcodes(), ops);
1✔
1697
        assert_eq!(
1✔
1698
            script.to_string(),
1✔
1699
            "Dup HashBlake256 PushHash(ae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e5) EqualVerify \
1✔
1700
             Drop CheckSig(276657a418820f34036b20ea615302b373c70ac8feab8d30681a3e0f0960e708)"
1✔
1701
        );
1✔
1702
    }
1✔
1703

1704
    #[test]
1705
    fn time_locked_contract_example() {
1✔
1706
        let k_alice =
1✔
1707
            RistrettoSecretKey::from_hex("f305e64c0e73cbdb665165ac97b69e5df37b2cd81f9f8f569c3bd854daff290e").unwrap();
1✔
1708
        let p_alice = CompressedKey::<RistrettoPublicKey>::from_secret_key(&k_alice); // 9c35e9f0f11cf25ce3ca1182d37682ab5824aa033f2024651e007364d06ec355
1✔
1709

1✔
1710
        let k_bob =
1✔
1711
            RistrettoSecretKey::from_hex("e0689386a018e88993a7bb14cbff5bad8a8858ea101d6e0da047df3ddf499c0e").unwrap();
1✔
1712
        let p_bob = CompressedKey::<RistrettoPublicKey>::from_secret_key(&k_bob); // 3a58f371e94da76a8902e81b4b55ddabb7dc006cd8ebde3011c46d0e02e9172f
1✔
1713

1✔
1714
        let lock_height = 4000u64;
1✔
1715

1✔
1716
        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✔
1717

1✔
1718
        // Alice tries to spend the output before the height is reached
1✔
1719
        let inputs_alice_spends_early = inputs!(p_alice.clone());
1✔
1720
        let ctx = context_with_height(3990u64);
1✔
1721
        assert_eq!(
1✔
1722
            script.execute_with_context(&inputs_alice_spends_early, &ctx),
1✔
1723
            Err(ScriptError::VerifyFailed)
1✔
1724
        );
1✔
1725

1726
        // Alice tries to spend the output after the height is reached
1727
        let inputs_alice_spends_early = inputs!(p_alice.clone());
1✔
1728
        let ctx = context_with_height(4000u64);
1✔
1729
        assert_eq!(
1✔
1730
            script.execute_with_context(&inputs_alice_spends_early, &ctx).unwrap(),
1✔
1731
            StackItem::PublicKey(p_alice)
1✔
1732
        );
1✔
1733

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

1742
        // Bob spends after time lock is reached
1743
        let inputs_bob_spends_early = inputs!(p_bob.clone());
1✔
1744
        let ctx = context_with_height(4001u64);
1✔
1745
        assert_eq!(
1✔
1746
            script.execute_with_context(&inputs_bob_spends_early, &ctx).unwrap(),
1✔
1747
            StackItem::PublicKey(p_bob)
1✔
1748
        );
1✔
1749
    }
1✔
1750

1751
    #[test]
1752
    fn m_of_n_signatures() {
1✔
1753
        use crate::StackItem::PublicKey;
1754
        let mut rng = rand::thread_rng();
1✔
1755
        let (k_alice, p_alice) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1756
        let (k_bob, p_bob) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1757
        let (k_eve, _) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1758

1✔
1759
        let m = RistrettoSecretKey::random(&mut rng);
1✔
1760
        let msg = slice_to_boxed_message(m.as_bytes());
1✔
1761

1✔
1762
        let s_alice = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1763
            CheckSigSchnorrSignature::sign(&k_alice, m.as_bytes(), &mut rng).unwrap(),
1✔
1764
        );
1✔
1765
        let s_bob = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1766
            CheckSigSchnorrSignature::sign(&k_bob, m.as_bytes(), &mut rng).unwrap(),
1✔
1767
        );
1✔
1768
        let s_eve = CompressedCheckSigSchnorrSignature::new_from_schnorr(
1✔
1769
            CheckSigSchnorrSignature::sign(&k_eve, m.as_bytes(), &mut rng).unwrap(),
1✔
1770
        );
1✔
1771

1772
        // 1 of 2
1773
        use crate::Opcode::{CheckSig, Drop, Dup, Else, EndIf, IfThen, PushPubKey, Return};
1774
        let ops = vec![
1✔
1775
            Dup,
1✔
1776
            PushPubKey(Box::new(p_alice.clone())),
1✔
1777
            CheckSig(msg.clone()),
1✔
1778
            IfThen,
1✔
1779
            Drop,
1✔
1780
            PushPubKey(Box::new(p_alice.clone())),
1✔
1781
            Else,
1✔
1782
            PushPubKey(Box::new(p_bob.clone())),
1✔
1783
            CheckSig(msg),
1✔
1784
            IfThen,
1✔
1785
            PushPubKey(Box::new(p_bob.clone())),
1✔
1786
            Else,
1✔
1787
            Return,
1✔
1788
            EndIf,
1✔
1789
            EndIf,
1✔
1790
        ];
1✔
1791
        let script = TariScript::new(ops).unwrap();
1✔
1792

1✔
1793
        // alice
1✔
1794
        let inputs = inputs!(s_alice);
1✔
1795
        let result = script.execute(&inputs).unwrap();
1✔
1796
        assert_eq!(result, PublicKey(p_alice));
1✔
1797

1798
        // bob
1799
        let inputs = inputs!(s_bob);
1✔
1800
        let result = script.execute(&inputs).unwrap();
1✔
1801
        assert_eq!(result, PublicKey(p_bob));
1✔
1802

1803
        // eve
1804
        let inputs = inputs!(s_eve);
1✔
1805
        let result = script.execute(&inputs).unwrap_err();
1✔
1806
        assert_eq!(result, ScriptError::Return);
1✔
1807
    }
1✔
1808

1809
    #[test]
1810
    fn to_ristretto_point() {
1✔
1811
        use crate::{Opcode::ToRistrettoPoint, StackItem::PublicKey};
1812

1813
        // Generate a key pair
1814
        let mut rng = rand::thread_rng();
1✔
1815
        let (k_1, p_1) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
1✔
1816

1✔
1817
        // Generate a test script
1✔
1818
        let ops = vec![ToRistrettoPoint];
1✔
1819
        let script = TariScript::new(ops).unwrap();
1✔
1820

1✔
1821
        // Invalid stack type
1✔
1822
        let inputs = inputs!(CompressedKey::<RistrettoPublicKey>::default());
1✔
1823
        let err = script.execute(&inputs).unwrap_err();
1✔
1824
        assert!(matches!(err, ScriptError::IncompatibleTypes));
1✔
1825

1826
        // Valid scalar
1827
        let mut scalar = [0u8; 32];
1✔
1828
        scalar.copy_from_slice(k_1.as_bytes());
1✔
1829
        let inputs = inputs!(scalar);
1✔
1830
        let result = script.execute(&inputs).unwrap();
1✔
1831
        assert_eq!(result, PublicKey(p_1.clone()));
1✔
1832

1833
        // Valid hash
1834
        let inputs = ExecutionStack::new(vec![Hash(scalar)]);
1✔
1835
        let result = script.execute(&inputs).unwrap();
1✔
1836
        assert_eq!(result, PublicKey(p_1));
1✔
1837

1838
        // Invalid bytes
1839
        let invalid = [u8::MAX; 32]; // not a canonical scalar encoding!
1✔
1840
        let inputs = inputs!(invalid);
1✔
1841
        assert!(matches!(script.execute(&inputs), Err(ScriptError::InvalidInput)));
1✔
1842
    }
1✔
1843

1844
    #[test]
1845
    fn test_borsh_de_serialization() {
1✔
1846
        let hex_script = "71b07aae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e58170ac276657a418820f34036b20ea615302b373c70ac8feab8d30681a3e0f0960e708";
1✔
1847
        let script = TariScript::from_hex(hex_script).unwrap();
1✔
1848
        let mut buf = Vec::new();
1✔
1849
        script.serialize(&mut buf).unwrap();
1✔
1850
        buf.extend_from_slice(&[1, 2, 3]);
1✔
1851
        let buf = &mut buf.as_slice();
1✔
1852
        assert_eq!(script, TariScript::deserialize(buf).unwrap());
1✔
1853
        assert_eq!(buf, &[1, 2, 3]);
1✔
1854
    }
1✔
1855

1856
    #[test]
1857
    fn test_borsh_de_serialization_too_large() {
1✔
1858
        // We dont care about the actual script here, just that its not too large on the varint size
1✔
1859
        // We lie about the size to try and get a mem panic, and say this script is u64::max large.
1✔
1860
        let buf = vec![255, 255, 255, 255, 255, 255, 255, 255, 255, 1, 49, 8, 2, 5, 6];
1✔
1861
        let buf = &mut buf.as_slice();
1✔
1862
        assert!(TariScript::deserialize(buf).is_err());
1✔
1863
    }
1✔
1864

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

1✔
1869
        let inputs = inputs!(0);
1✔
1870
        let ctx = context_with_height(u64::MAX);
1✔
1871
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1872
        assert!(matches!(stack_item, Err(ScriptError::ValueExceedsBounds)));
1✔
1873
    }
1✔
1874

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

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

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

1✔
1889
        let inputs = ExecutionStack::new(vec![]);
1✔
1890
        let ctx = context_with_height(i64::MAX as u64);
1✔
1891
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1892
        assert!(matches!(stack_item, Err(ScriptError::StackUnderflow)));
1✔
1893
    }
1✔
1894

1895
    #[test]
1896
    fn test_compare_height_valid_with_uint_result() {
1✔
1897
        let script = script!(CompareHeight).unwrap();
1✔
1898

1✔
1899
        let inputs = inputs!(100);
1✔
1900
        let ctx = context_with_height(24_u64);
1✔
1901
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1902
        assert!(stack_item.is_ok());
1✔
1903
        assert_eq!(stack_item.unwrap(), Number(-76))
1✔
1904
    }
1✔
1905

1906
    #[test]
1907
    fn test_compare_height_valid_with_int_result() {
1✔
1908
        let script = script!(CompareHeight).unwrap();
1✔
1909

1✔
1910
        let inputs = inputs!(100);
1✔
1911
        let ctx = context_with_height(110_u64);
1✔
1912
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1913
        assert!(stack_item.is_ok());
1✔
1914
        assert_eq!(stack_item.unwrap(), Number(10))
1✔
1915
    }
1✔
1916

1917
    #[test]
1918
    fn test_check_height_block_height_exceeds_bounds() {
1✔
1919
        let script = script!(CheckHeight(0)).unwrap();
1✔
1920

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

1927
    #[test]
1928
    fn test_check_height_exceeds_bounds() {
1✔
1929
        let script = script!(CheckHeight(u64::MAX)).unwrap();
1✔
1930

1✔
1931
        let inputs = ExecutionStack::new(vec![]);
1✔
1932
        let ctx = context_with_height(10_u64);
1✔
1933
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1934
        assert!(matches!(stack_item, Err(ScriptError::ValueExceedsBounds)));
1✔
1935
    }
1✔
1936

1937
    #[test]
1938
    fn test_check_height_overflows_on_max_stack() {
1✔
1939
        let script = script!(CheckHeight(0)).unwrap();
1✔
1940

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

1943
        for i in 0..255 {
256✔
1944
            inputs.push(Number(i)).unwrap();
255✔
1945
        }
255✔
1946

1947
        let ctx = context_with_height(i64::MAX as u64);
1✔
1948
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1949
        assert!(matches!(stack_item, Err(ScriptError::StackOverflow)));
1✔
1950
    }
1✔
1951

1952
    #[test]
1953
    fn test_check_height_valid_with_uint_result() {
1✔
1954
        let script = script!(CheckHeight(24)).unwrap();
1✔
1955

1✔
1956
        let inputs = ExecutionStack::new(vec![]);
1✔
1957
        let ctx = context_with_height(100_u64);
1✔
1958
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1959
        assert!(stack_item.is_ok());
1✔
1960
        assert_eq!(stack_item.unwrap(), Number(76))
1✔
1961
    }
1✔
1962

1963
    #[test]
1964
    fn test_check_height_valid_with_int_result() {
1✔
1965
        let script = script!(CheckHeight(100)).unwrap();
1✔
1966

1✔
1967
        let inputs = ExecutionStack::new(vec![]);
1✔
1968
        let ctx = context_with_height(24_u64);
1✔
1969
        let stack_item = script.execute_with_context(&inputs, &ctx);
1✔
1970
        assert!(stack_item.is_ok());
1✔
1971
        assert_eq!(stack_item.unwrap(), Number(-76))
1✔
1972
    }
1✔
1973
}
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