use std::{cmp::Ordering, collections::HashSet, fmt, io, ops::Deref};
use blake2::Blake2b;
use borsh::{BorshDeserialize, BorshSerialize};
use digest::{Digest, consts::U32};
use integer_encoding::{VarIntReader, VarIntWriter};
use sha2::Sha256;
use sha3::Sha3_256;
use tari_crypto::{
compressed_commitment::CompressedCommitment,
compressed_key::CompressedKey,
ristretto::{RistrettoPublicKey, RistrettoSecretKey},
};
use tari_max_size::MaxSizeVec;
use tari_utilities::{
ByteArray,
hex::{Hex, HexError, from_hex, to_hex},
};
use crate::{
CompressedCheckSigSchnorrSignature,
ExecutionStack,
HashValue,
Opcode,
ScriptContext,
ScriptError,
StackItem,
op_codes::Message,
slice_to_hash,
};
#[macro_export]
macro_rules! script {
($($opcode:ident$(($($var:expr),+))?) +) => {{
use $crate::TariScript;
use $crate::Opcode;
let script = vec![$(Opcode::$opcode $(($($var),+))?),+];
TariScript::new(script)
}}
}
const MAX_MULTISIG_LIMIT: u8 = 32;
const MAX_SCRIPT_BYTES: usize = 4096;
pub type ScriptOpcodes = MaxSizeVec<Opcode, 128>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TariScript {
script: ScriptOpcodes,
}
impl BorshSerialize for TariScript {
fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
let bytes = self.to_bytes();
writer.write_varint(bytes.len())?;
for b in &bytes {
b.serialize(writer)?;
}
Ok(())
}
}
impl BorshDeserialize for TariScript {
fn deserialize_reader<R>(reader: &mut R) -> Result<Self, io::Error>
where R: io::Read {
let len = reader.read_varint()?;
if len > MAX_SCRIPT_BYTES {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Larger than max script bytes".to_string(),
));
}
let mut data = Vec::with_capacity(len);
for _ in 0..len {
data.push(u8::deserialize_reader(reader)?);
}
let script = TariScript::from_bytes(data.as_slice())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
Ok(script)
}
}
impl TariScript {
pub fn new(script: Vec<Opcode>) -> Result<Self, ScriptError> {
let script = ScriptOpcodes::try_from(script)?;
Ok(TariScript { script })
}
pub fn iter(&self) -> std::slice::Iter<'_, Opcode> {
self.script.iter()
}
pub fn pattern_match(&self, script: &TariScript) -> bool {
for (i, opcode) in self.script.iter().enumerate() {
if let Some(code) = script.opcode(i) {
if std::mem::discriminant(opcode) != std::mem::discriminant(code) {
return false;
}
} else {
return false;
}
}
script.opcode(self.script.len()).is_none()
}
pub fn opcode(&self, i: usize) -> Option<&Opcode> {
let opcode = self.script.get(i)?;
Some(opcode)
}
pub fn execute(&self, inputs: &ExecutionStack) -> Result<StackItem, ScriptError> {
self.execute_with_context(inputs, &ScriptContext::default())
}
pub fn execute_with_context(
&self,
inputs: &ExecutionStack,
context: &ScriptContext,
) -> Result<StackItem, ScriptError> {
let mut stack = inputs.clone();
let mut state = ExecutionState::default();
for opcode in self.script.iter() {
if self.should_execute(opcode, &state)? {
self.execute_opcode(opcode, &mut stack, context, &mut state)?
} else {
continue;
}
}
if !state.if_stack.is_empty() {
return Err(ScriptError::MissingOpcode);
}
if stack.size() == 1 {
stack.pop().ok_or(ScriptError::NonUnitLengthStack)
} else {
Err(ScriptError::NonUnitLengthStack)
}
}
pub fn size(&self) -> usize {
self.script.len()
}
fn should_execute(&self, opcode: &Opcode, state: &ExecutionState) -> Result<bool, ScriptError> {
use Opcode::{Else, EndIf, IfThen};
match opcode {
IfThen | Else | EndIf => Ok(true),
_ => Ok(state.executing),
}
}
pub fn to_bytes(&self) -> Vec<u8> {
self.script.iter().fold(Vec::new(), |mut bytes, op| {
op.to_bytes(&mut bytes);
bytes
})
}
pub fn as_slice(&self) -> &[Opcode] {
self.script.as_ref()
}
pub fn as_hash<D: Digest>(&self) -> Result<HashValue, ScriptError> {
if <D as Digest>::output_size() < 32 {
return Err(ScriptError::InvalidDigest);
}
let h = D::digest(self.to_bytes());
Ok(slice_to_hash(h.as_slice().get(..32).ok_or(ScriptError::InvalidDigest)?))
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ScriptError> {
let script = ScriptOpcodes::try_from(Opcode::parse(bytes)?)?;
Ok(TariScript { script })
}
pub fn to_opcodes(&self) -> Vec<String> {
self.script.iter().map(|op| op.to_string()).collect()
}
fn execute_opcode(
&self,
opcode: &Opcode,
stack: &mut ExecutionStack,
ctx: &ScriptContext,
state: &mut ExecutionState,
) -> Result<(), ScriptError> {
#[allow(clippy::enum_glob_use)]
use Opcode::*;
use StackItem::{Hash, Number, PublicKey};
match opcode {
CheckHeightVerify(height) => TariScript::handle_check_height_verify(*height, ctx.block_height()),
CheckHeight(height) => TariScript::handle_check_height(stack, *height, ctx.block_height()),
CompareHeightVerify => TariScript::handle_compare_height_verify(stack, ctx.block_height()),
CompareHeight => TariScript::handle_compare_height(stack, ctx.block_height()),
Nop => Ok(()),
PushZero => stack.push(Number(0)),
PushOne => stack.push(Number(1)),
PushHash(h) => stack.push(Hash(*h.clone())),
PushInt(n) => stack.push(Number(*n)),
PushPubKey(p) => stack.push(PublicKey(*p.clone())),
Drop => TariScript::handle_drop(stack),
Dup => TariScript::handle_dup(stack),
RevRot => stack.push_down(2),
GeZero => TariScript::handle_cmp_to_zero(stack, &[Ordering::Greater, Ordering::Equal]),
GtZero => TariScript::handle_cmp_to_zero(stack, &[Ordering::Greater]),
LeZero => TariScript::handle_cmp_to_zero(stack, &[Ordering::Less, Ordering::Equal]),
LtZero => TariScript::handle_cmp_to_zero(stack, &[Ordering::Less]),
Add => TariScript::handle_op_add(stack),
Sub => TariScript::handle_op_sub(stack),
Equal => {
if TariScript::handle_equal(stack)? {
stack.push(Number(1))
} else {
stack.push(Number(0))
}
},
EqualVerify => {
if TariScript::handle_equal(stack)? {
Ok(())
} else {
Err(ScriptError::VerifyFailed)
}
},
Or(n) => TariScript::handle_or(stack, *n),
OrVerify(n) => TariScript::handle_or_verify(stack, *n),
HashBlake256 => TariScript::handle_hash::<Blake2b<U32>>(stack),
HashSha256 => TariScript::handle_hash::<Sha256>(stack),
HashSha3 => TariScript::handle_hash::<Sha3_256>(stack),
CheckSig(msg) => {
if self.check_sig(stack, *msg.deref())? {
stack.push(Number(1))
} else {
stack.push(Number(0))
}
},
CheckSigVerify(msg) => {
if self.check_sig(stack, *msg.deref())? {
Ok(())
} else {
Err(ScriptError::VerifyFailed)
}
},
CheckMultiSig(m, n, public_keys, msg) => {
if self.check_multisig(stack, *m, *n, public_keys, *msg.deref())?.is_some() {
stack.push(Number(1))
} else {
stack.push(Number(0))
}
},
CheckMultiSigVerify(m, n, public_keys, msg) => {
if self.check_multisig(stack, *m, *n, public_keys, *msg.deref())?.is_some() {
Ok(())
} else {
Err(ScriptError::VerifyFailed)
}
},
CheckMultiSigVerifyAggregatePubKey(m, n, public_keys, msg) => {
if let Some(agg_pub_key) = self.check_multisig(stack, *m, *n, public_keys, *msg.deref())? {
stack.push(PublicKey(agg_pub_key))
} else {
Err(ScriptError::VerifyFailed)
}
},
ToRistrettoPoint => self.handle_to_ristretto_point(stack),
Return => Err(ScriptError::Return),
IfThen => TariScript::handle_if_then(stack, state),
Else => TariScript::handle_else(state),
EndIf => TariScript::handle_end_if(state),
}
}
fn handle_check_height_verify(height: u64, block_height: u64) -> Result<(), ScriptError> {
if block_height >= height {
Ok(())
} else {
Err(ScriptError::VerifyFailed)
}
}
fn handle_check_height(stack: &mut ExecutionStack, height: u64, block_height: u64) -> Result<(), ScriptError> {
let height = i64::try_from(height)?;
let block_height = i64::try_from(block_height)?;
let item = match block_height.checked_sub(height) {
Some(num) => StackItem::Number(num),
None => {
return Err(ScriptError::CompareFailed(
"Subtraction of given height from current block height failed".to_string(),
));
},
};
stack.push(item)
}
fn handle_compare_height_verify(stack: &mut ExecutionStack, block_height: u64) -> Result<(), ScriptError> {
let target_height = stack.pop_into_number::<u64>()?;
if block_height >= target_height {
Ok(())
} else {
Err(ScriptError::VerifyFailed)
}
}
fn handle_compare_height(stack: &mut ExecutionStack, block_height: u64) -> Result<(), ScriptError> {
let target_height = stack.pop_into_number::<i64>()?;
let block_height = i64::try_from(block_height)?;
let item = match block_height.checked_sub(target_height) {
Some(num) => StackItem::Number(num),
None => {
return Err(ScriptError::CompareFailed(
"Couldn't subtract the target height from the current block height".to_string(),
));
},
};
stack.push(item)
}
fn handle_cmp_to_zero(stack: &mut ExecutionStack, valid_orderings: &[Ordering]) -> Result<(), ScriptError> {
let stack_number = stack.pop_into_number::<i64>()?;
let ordering = &stack_number.cmp(&0);
if valid_orderings.contains(ordering) {
stack.push(StackItem::Number(1))
} else {
stack.push(StackItem::Number(0))
}
}
fn handle_or(stack: &mut ExecutionStack, n: u8) -> Result<(), ScriptError> {
if stack.pop_n_plus_one_contains(n)? {
stack.push(StackItem::Number(1))
} else {
stack.push(StackItem::Number(0))
}
}
fn handle_or_verify(stack: &mut ExecutionStack, n: u8) -> Result<(), ScriptError> {
if stack.pop_n_plus_one_contains(n)? {
Ok(())
} else {
Err(ScriptError::VerifyFailed)
}
}
fn handle_if_then(stack: &mut ExecutionStack, state: &mut ExecutionState) -> Result<(), ScriptError> {
if state.executing {
let pred = stack.pop().ok_or(ScriptError::StackUnderflow)?;
match pred {
StackItem::Number(1) => {
state.executing = true;
let if_state = IfState {
branch: Branch::ExecuteIf,
else_expected: true,
};
state.if_stack.push(if_state);
Ok(())
},
StackItem::Number(0) => {
state.executing = false;
let if_state = IfState {
branch: Branch::ExecuteElse,
else_expected: true,
};
state.if_stack.push(if_state);
Ok(())
},
_ => Err(ScriptError::InvalidInput),
}
} else {
let if_state = IfState {
branch: Branch::NotExecuted,
else_expected: true,
};
state.if_stack.push(if_state);
Ok(())
}
}
fn handle_else(state: &mut ExecutionState) -> Result<(), ScriptError> {
let if_state = state.if_stack.last_mut().ok_or(ScriptError::InvalidOpcode)?;
if !if_state.else_expected {
return Err(ScriptError::InvalidOpcode);
}
match if_state.branch {
Branch::NotExecuted => {
state.executing = false;
},
Branch::ExecuteIf => {
state.executing = false;
},
Branch::ExecuteElse => {
state.executing = true;
},
}
if_state.else_expected = false;
Ok(())
}
fn handle_end_if(state: &mut ExecutionState) -> Result<(), ScriptError> {
let if_state = state.if_stack.pop().ok_or(ScriptError::InvalidOpcode)?;
if if_state.else_expected {
return Err(ScriptError::MissingOpcode);
}
match if_state.branch {
Branch::NotExecuted => {
state.executing = false;
},
Branch::ExecuteIf => {
state.executing = true;
},
Branch::ExecuteElse => {
state.executing = true;
},
}
Ok(())
}
fn handle_hash<D: Digest>(stack: &mut ExecutionStack) -> Result<(), ScriptError> {
use StackItem::{Commitment, Hash, PublicKey};
let top = stack.pop().ok_or(ScriptError::StackUnderflow)?;
let to_arr = |b: &[u8]| {
let mut hash = [0u8; 32];
hash.copy_from_slice(D::digest(b).as_slice());
hash
};
let hash_value = match top {
Commitment(c) => to_arr(c.as_bytes()),
PublicKey(k) => to_arr(k.as_bytes()),
Hash(h) => to_arr(&h),
_ => return Err(ScriptError::IncompatibleTypes),
};
stack.push(Hash(hash_value))
}
fn handle_dup(stack: &mut ExecutionStack) -> Result<(), ScriptError> {
let last = if let Some(last) = stack.peek() {
last.clone()
} else {
return Err(ScriptError::StackUnderflow);
};
stack.push(last)
}
fn handle_drop(stack: &mut ExecutionStack) -> Result<(), ScriptError> {
match stack.pop() {
Some(_) => Ok(()),
None => Err(ScriptError::StackUnderflow),
}
}
fn handle_op_add(stack: &mut ExecutionStack) -> Result<(), ScriptError> {
use StackItem::{Commitment, Number, PublicKey};
let top = stack.pop().ok_or(ScriptError::StackUnderflow)?;
let two = stack.pop().ok_or(ScriptError::StackUnderflow)?;
match (top, two) {
(Number(v1), Number(v2)) => stack.push(Number(v1.checked_add(v2).ok_or(ScriptError::ValueExceedsBounds)?)),
(Commitment(c1), Commitment(c2)) => {
let com_1 = c1.to_commitment()?;
let com_2 = c2.to_commitment()?;
stack.push(Commitment(CompressedCommitment::from_commitment(&com_1 + &com_2)))
},
(PublicKey(p1), PublicKey(p2)) => {
let key1 = p1.to_public_key().map_err(|_| ScriptError::InvalidInput)?;
let key2 = p2.to_public_key().map_err(|_| ScriptError::InvalidInput)?;
let compressed_key = CompressedKey::new_from_pk(&key1 + &key2);
stack.push(PublicKey(compressed_key))
},
(_, _) => Err(ScriptError::IncompatibleTypes),
}
}
fn handle_op_sub(stack: &mut ExecutionStack) -> Result<(), ScriptError> {
use StackItem::{Commitment, Number};
let top = stack.pop().ok_or(ScriptError::StackUnderflow)?;
let two = stack.pop().ok_or(ScriptError::StackUnderflow)?;
match (top, two) {
(Number(v1), Number(v2)) => stack.push(Number(v2.checked_sub(v1).ok_or(ScriptError::ValueExceedsBounds)?)),
(Commitment(c1), Commitment(c2)) => {
let com_1 = c1.to_commitment()?;
let com_2 = c2.to_commitment()?;
stack.push(Commitment(CompressedCommitment::from_commitment(&com_2 - &com_1)))
},
(..) => Err(ScriptError::IncompatibleTypes),
}
}
fn handle_equal(stack: &mut ExecutionStack) -> Result<bool, ScriptError> {
use StackItem::{Commitment, Hash, Number, PublicKey, Signature};
let top = stack.pop().ok_or(ScriptError::StackUnderflow)?;
let two = stack.pop().ok_or(ScriptError::StackUnderflow)?;
match (top, two) {
(Number(v1), Number(v2)) => Ok(v1 == v2),
(Commitment(c1), Commitment(c2)) => Ok(c1 == c2),
(Signature(s1), Signature(s2)) => Ok(s1 == s2),
(PublicKey(p1), PublicKey(p2)) => Ok(p1 == p2),
(Hash(h1), Hash(h2)) => Ok(h1 == h2),
(..) => Err(ScriptError::IncompatibleTypes),
}
}
fn check_sig(&self, stack: &mut ExecutionStack, message: Message) -> Result<bool, ScriptError> {
use StackItem::{PublicKey, Signature};
let pk = stack.pop().ok_or(ScriptError::StackUnderflow)?;
let sig = stack.pop().ok_or(ScriptError::StackUnderflow)?;
match (pk, sig) {
(PublicKey(p), Signature(s)) => {
let key = p.to_public_key().map_err(|_| ScriptError::InvalidInput)?;
let sig = s.to_schnorr_signature()?;
Ok(sig.verify(&key, message))
},
(..) => Err(ScriptError::IncompatibleTypes),
}
}
fn check_multisig(
&self,
stack: &mut ExecutionStack,
m: u8,
n: u8,
public_keys: &[CompressedKey<RistrettoPublicKey>],
message: Message,
) -> Result<Option<CompressedKey<RistrettoPublicKey>>, ScriptError> {
if m == 0 || n == 0 || m > n || n > MAX_MULTISIG_LIMIT || public_keys.len() != n as usize {
return Err(ScriptError::ValueExceedsBounds);
}
let m = m as usize;
let signatures = stack
.pop_num_items(m)?
.into_iter()
.map(|item| match item {
StackItem::Signature(s) => Ok(s),
_ => Err(ScriptError::IncompatibleTypes),
})
.collect::<Result<Vec<CompressedCheckSigSchnorrSignature>, ScriptError>>()?;
#[allow(clippy::mutable_key_type)]
let mut sig_set = HashSet::new();
let mut agg_pub_key = RistrettoPublicKey::default();
let mut pub_keys = public_keys.iter();
for s in &signatures {
if pub_keys.len() == 0 {
return Ok(None);
}
if sig_set.contains(s) {
continue;
}
for pk in pub_keys.by_ref() {
let ristretto_key = pk.to_public_key().map_err(|_| ScriptError::InvalidInput)?;
let sig = s.to_schnorr_signature()?;
if sig.verify(&ristretto_key, message) {
sig_set.insert(s);
agg_pub_key = agg_pub_key + ristretto_key;
break;
}
}
if !sig_set.contains(s) {
return Ok(None);
}
}
if sig_set.len() == m {
let key = CompressedKey::new_from_pk(agg_pub_key);
Ok(Some(key))
} else {
Ok(None)
}
}
fn handle_to_ristretto_point(&self, stack: &mut ExecutionStack) -> Result<(), ScriptError> {
let item = stack.pop().ok_or(ScriptError::StackUnderflow)?;
let scalar = match &item {
StackItem::Hash(hash) => hash.as_slice(),
StackItem::Scalar(scalar) => scalar.as_slice(),
_ => return Err(ScriptError::IncompatibleTypes),
};
let ristretto_sk = RistrettoSecretKey::from_canonical_bytes(scalar).map_err(|_| ScriptError::InvalidInput)?;
let ristretto_pk = CompressedKey::from_secret_key(&ristretto_sk);
stack.push(StackItem::PublicKey(ristretto_pk))?;
Ok(())
}
}
impl Iterator for TariScript {
type Item = Opcode;
fn next(&mut self) -> Option<Self::Item> {
self.script.next()
}
}
impl<'a> IntoIterator for &'a TariScript {
type IntoIter = std::slice::Iter<'a, Opcode>;
type Item = &'a Opcode;
fn into_iter(self) -> Self::IntoIter {
self.script.iter()
}
}
impl Hex for TariScript {
fn from_hex(hex: &str) -> Result<Self, HexError>
where Self: Sized {
let bytes = from_hex(hex)?;
TariScript::from_bytes(&bytes).map_err(|_| HexError::HexConversionError {})
}
fn to_hex(&self) -> String {
to_hex(&self.to_bytes())
}
}
impl Default for TariScript {
fn default() -> Self {
script!(PushPubKey(Box::default())).expect("default will not fail")
}
}
impl fmt::Display for TariScript {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = self.to_opcodes().join(" ");
f.write_str(&s)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum Branch {
NotExecuted,
ExecuteIf,
ExecuteElse,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct IfState {
branch: Branch,
else_expected: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct ExecutionState {
executing: bool,
if_stack: Vec<IfState>,
}
impl Default for ExecutionState {
fn default() -> Self {
Self {
executing: true,
if_stack: Vec::new(),
}
}
}
#[cfg(test)]
mod test {
#![allow(clippy::indexing_slicing)]
use blake2::Blake2b;
use borsh::{BorshDeserialize, BorshSerialize};
use digest::{Digest, consts::U32};
use sha2::Sha256;
use sha3::Sha3_256 as Sha3;
use tari_crypto::{
compressed_commitment::CompressedCommitment,
compressed_key::CompressedKey,
keys::SecretKey,
ristretto::{RistrettoPublicKey, RistrettoSecretKey, pedersen::CompressedPedersenCommitment},
};
use tari_utilities::{ByteArray, hex::Hex};
use crate::{
CheckSigSchnorrSignature,
CompressedCheckSigSchnorrSignature,
ExecutionStack,
Opcode::CheckMultiSigVerifyAggregatePubKey,
ScriptContext,
StackItem,
StackItem::{Commitment, Hash, Number},
TariScript,
error::ScriptError,
inputs,
op_codes::{HashValue, Message, slice_to_boxed_hash, slice_to_boxed_message},
};
fn context_with_height(height: u64) -> ScriptContext {
ScriptContext::new(height, &HashValue::default(), &CompressedPedersenCommitment::default())
}
#[test]
fn pattern_match() {
let script_a = script!(Or(1)).unwrap();
let script_b = script!(Or(1)).unwrap();
assert_eq!(script_a, script_b);
assert!(script_a.pattern_match(&script_b));
let script_b = script!(Or(2)).unwrap();
assert_ne!(script_a, script_b);
assert!(script_a.pattern_match(&script_b));
let script_b = script!(Or(2) Or(2)).unwrap();
assert_ne!(script_a, script_b);
assert!(!script_a.pattern_match(&script_b));
let script_a = script!(Or(2) Or(1)).unwrap();
let script_b = script!(Or(3) Or(5)).unwrap();
assert_ne!(script_a, script_b);
assert!(script_a.pattern_match(&script_b));
}
#[test]
fn op_or() {
let script = script!(Or(1)).unwrap();
let inputs = inputs!(4, 4);
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(3, 4);
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(0));
let script = script!(Or(3)).unwrap();
let inputs = inputs!(1, 2, 1, 3);
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(1, 2, 4, 3);
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(0));
let mut rng = rand::thread_rng();
let (_, p) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let inputs = inputs!(1, p.clone(), 1, 3);
let err = script.execute(&inputs).unwrap_err();
assert!(matches!(err, ScriptError::InvalidInput));
let inputs = inputs!(p, 2, 1, 3);
let err = script.execute(&inputs).unwrap_err();
assert!(matches!(err, ScriptError::InvalidInput));
let inputs = inputs!(2, 4, 3);
let err = script.execute(&inputs).unwrap_err();
assert!(matches!(err, ScriptError::StackUnderflow));
let script = script!(OrVerify(1)).unwrap();
let inputs = inputs!(1, 4, 4);
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(1, 3, 4);
let err = script.execute(&inputs).unwrap_err();
assert!(matches!(err, ScriptError::VerifyFailed));
let script = script!(OrVerify(2)).unwrap();
let inputs = inputs!(1, 2, 2, 3);
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(1, 2, 3, 4);
let err = script.execute(&inputs).unwrap_err();
assert!(matches!(err, ScriptError::VerifyFailed));
}
#[test]
fn op_if_then_else() {
let script = script!(IfThen PushInt(420) Else PushInt(66) EndIf).unwrap();
let inputs = inputs!(1);
let result = script.execute(&inputs);
assert_eq!(result.unwrap(), Number(420));
let inputs = inputs!(0);
let result = script.execute(&inputs);
assert_eq!(result.unwrap(), Number(66));
let script =
script!(IfThen PushOne IfThen PushInt(420) Else PushInt(555) EndIf Else PushInt(66) EndIf).unwrap();
let inputs = inputs!(1);
let result = script.execute(&inputs);
assert_eq!(result.unwrap(), Number(420));
let script =
script!(IfThen PushInt(420) Else PushZero IfThen PushInt(111) Else PushInt(66) EndIf Nop EndIf).unwrap();
let inputs = inputs!(0);
let result = script.execute(&inputs);
assert_eq!(result.unwrap(), Number(66));
let script = script!(IfThen PushInt(420) Else PushInt(66) Else PushInt(777) EndIf).unwrap();
let inputs = inputs!(0);
let result = script.execute(&inputs);
assert_eq!(result.unwrap_err(), ScriptError::InvalidOpcode);
let script = script!(Else).unwrap();
let inputs = inputs!(0);
let result = script.execute(&inputs);
assert_eq!(result.unwrap_err(), ScriptError::InvalidOpcode);
let script = script!(EndIf).unwrap();
let inputs = inputs!(0);
let result = script.execute(&inputs);
assert_eq!(result.unwrap_err(), ScriptError::InvalidOpcode);
let script = script!(IfThen PushInt(420) Else PushInt(66) EndIf EndIf).unwrap();
let inputs = inputs!(0);
let result = script.execute(&inputs);
assert_eq!(result.unwrap_err(), ScriptError::InvalidOpcode);
let script = script!(IfThen PushOne IfThen PushOne).unwrap();
let inputs = inputs!(1);
let result = script.execute(&inputs);
assert_eq!(result.unwrap_err(), ScriptError::MissingOpcode);
let script = script!(IfThen PushOne EndIf).unwrap();
let inputs = inputs!(1);
let result = script.execute(&inputs);
assert_eq!(result.unwrap_err(), ScriptError::MissingOpcode);
let script =
script!(IfThen PushInt(111) Else PushZero IfThen PushInt(222) Else PushInt(333) EndIf EndIf).unwrap();
let inputs = inputs!(1);
let result = script.execute(&inputs);
assert_eq!(result.unwrap(), Number(111));
}
#[test]
fn op_check_height() {
let inputs = ExecutionStack::default();
let script = script!(CheckHeight(5)).unwrap();
for block_height in 1..=10 {
let ctx = context_with_height(u64::try_from(block_height).unwrap());
assert_eq!(
script.execute_with_context(&inputs, &ctx).unwrap(),
Number(block_height - 5)
);
}
let script = script!(CheckHeight(u64::MAX)).unwrap();
let ctx = context_with_height(i64::MAX as u64);
let err = script.execute_with_context(&inputs, &ctx).unwrap_err();
assert!(matches!(err, ScriptError::ValueExceedsBounds));
let script = script!(CheckHeightVerify(5)).unwrap();
let inputs = inputs!(1);
for block_height in 1..5 {
let ctx = context_with_height(block_height);
let err = script.execute_with_context(&inputs, &ctx).unwrap_err();
assert!(matches!(err, ScriptError::VerifyFailed));
}
for block_height in 5..=10 {
let ctx = context_with_height(block_height);
let result = script.execute_with_context(&inputs, &ctx).unwrap();
assert_eq!(result, Number(1));
}
}
#[test]
fn op_compare_height() {
let script = script!(CompareHeight).unwrap();
let inputs = inputs!(5);
for block_height in 1..=10 {
let ctx = context_with_height(u64::try_from(block_height).unwrap());
assert_eq!(
script.execute_with_context(&inputs, &ctx).unwrap(),
Number(block_height - 5)
);
}
let script = script!(CompareHeightVerify).unwrap();
let inputs = inputs!(1, 5);
for block_height in 1..5 {
let ctx = context_with_height(block_height);
let err = script.execute_with_context(&inputs, &ctx).unwrap_err();
assert!(matches!(err, ScriptError::VerifyFailed));
}
for block_height in 5..=10 {
let ctx = context_with_height(block_height);
let result = script.execute_with_context(&inputs, &ctx).unwrap();
assert_eq!(result, Number(1));
}
}
#[test]
fn op_drop_push() {
let inputs = inputs!(420);
let script = script!(Drop PushOne).unwrap();
assert_eq!(script.execute(&inputs).unwrap(), Number(1));
let script = script!(Drop PushZero).unwrap();
assert_eq!(script.execute(&inputs).unwrap(), Number(0));
let script = script!(Drop PushInt(5)).unwrap();
assert_eq!(script.execute(&inputs).unwrap(), Number(5));
}
#[test]
fn op_comparison_to_zero() {
let script = script!(GeZero).unwrap();
let inputs = inputs!(1);
assert_eq!(script.execute(&inputs).unwrap(), Number(1));
let inputs = inputs!(0);
assert_eq!(script.execute(&inputs).unwrap(), Number(1));
let script = script!(GtZero).unwrap();
let inputs = inputs!(1);
assert_eq!(script.execute(&inputs).unwrap(), Number(1));
let inputs = inputs!(0);
assert_eq!(script.execute(&inputs).unwrap(), Number(0));
let script = script!(LeZero).unwrap();
let inputs = inputs!(-1);
assert_eq!(script.execute(&inputs).unwrap(), Number(1));
let inputs = inputs!(0);
assert_eq!(script.execute(&inputs).unwrap(), Number(1));
let script = script!(LtZero).unwrap();
let inputs = inputs!(-1);
assert_eq!(script.execute(&inputs).unwrap(), Number(1));
let inputs = inputs!(0);
assert_eq!(script.execute(&inputs).unwrap(), Number(0));
}
#[test]
fn op_hash() {
let mut rng = rand::thread_rng();
let (_, p) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let c = CompressedCommitment::<RistrettoPublicKey>::from_compressed_key(p.clone());
let script = script!(HashSha256).unwrap();
let hash = Sha256::digest(p.as_bytes());
let inputs = inputs!(p.clone());
assert_eq!(script.execute(&inputs).unwrap(), Hash(hash.into()));
let hash = Sha256::digest(c.as_bytes());
let inputs = inputs!(c.clone());
assert_eq!(script.execute(&inputs).unwrap(), Hash(hash.into()));
let script = script!(HashSha3).unwrap();
let hash = Sha3::digest(p.as_bytes());
let inputs = inputs!(p);
assert_eq!(script.execute(&inputs).unwrap(), Hash(hash.into()));
let hash = Sha3::digest(c.as_bytes());
let inputs = inputs!(c);
assert_eq!(script.execute(&inputs).unwrap(), Hash(hash.into()));
}
#[test]
fn op_return() {
let script = script!(Return).unwrap();
let inputs = ExecutionStack::default();
assert_eq!(script.execute(&inputs), Err(ScriptError::Return));
}
#[test]
fn op_add() {
let script = script!(Add).unwrap();
let inputs = inputs!(3, 2);
assert_eq!(script.execute(&inputs).unwrap(), Number(5));
let inputs = inputs!(3, -3);
assert_eq!(script.execute(&inputs).unwrap(), Number(0));
let inputs = inputs!(i64::MAX, 1);
assert_eq!(script.execute(&inputs), Err(ScriptError::ValueExceedsBounds));
let inputs = inputs!(1);
assert_eq!(script.execute(&inputs), Err(ScriptError::StackUnderflow));
}
#[test]
fn op_add_commitments() {
let script = script!(Add).unwrap();
let mut rng = rand::thread_rng();
let (_, c1) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let (_, c2) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let c3 = &c1.to_public_key().unwrap() + &c2.to_public_key().unwrap();
let c3 = CompressedCommitment::<RistrettoPublicKey>::from_public_key(c3);
let inputs = inputs!(
CompressedCommitment::<RistrettoPublicKey>::from_compressed_key(c1),
CompressedCommitment::<RistrettoPublicKey>::from_compressed_key(c2)
);
assert_eq!(script.execute(&inputs).unwrap(), Commitment(c3));
}
#[test]
fn op_sub() {
use crate::StackItem::Number;
let script = script!(Add Sub).unwrap();
let inputs = inputs!(5, 3, 2);
assert_eq!(script.execute(&inputs).unwrap(), Number(0));
let inputs = inputs!(i64::MAX, 1);
assert_eq!(script.execute(&inputs), Err(ScriptError::ValueExceedsBounds));
let script = script!(Sub).unwrap();
let inputs = inputs!(5, 3);
assert_eq!(script.execute(&inputs).unwrap(), Number(2));
}
#[test]
fn serialisation() {
let script = script!(Add Sub Add).unwrap();
assert_eq!(&script.to_bytes(), &[0x93, 0x94, 0x93]);
assert_eq!(TariScript::from_bytes(&[0x93, 0x94, 0x93]).unwrap(), script);
assert_eq!(script.to_hex(), "939493");
assert_eq!(TariScript::from_hex("939493").unwrap(), script);
}
#[test]
fn check_sig() {
use crate::StackItem::Number;
let mut rng = rand::thread_rng();
let (pvt_key, pub_key) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let m_key = RistrettoSecretKey::random(&mut rng);
let sig = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&pvt_key, m_key.as_bytes(), &mut rng).unwrap(),
);
let msg = slice_to_boxed_message(m_key.as_bytes());
let script = script!(CheckSig(msg)).unwrap();
let inputs = inputs!(sig.clone(), pub_key.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let n_key = RistrettoSecretKey::random(&mut rng);
let msg = slice_to_boxed_message(n_key.as_bytes());
let script = script!(CheckSig(msg)).unwrap();
let inputs = inputs!(sig, pub_key);
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(0));
}
#[test]
fn check_sig_verify() {
use crate::StackItem::Number;
let mut rng = rand::thread_rng();
let (pvt_key, pub_key) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let m_key = RistrettoSecretKey::random(&mut rng);
let sig = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&pvt_key, m_key.as_bytes(), &mut rng).unwrap(),
);
let msg = slice_to_boxed_message(m_key.as_bytes());
let script = script!(CheckSigVerify(msg) PushOne).unwrap();
let inputs = inputs!(sig.clone(), pub_key.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let n_key = RistrettoSecretKey::random(&mut rng);
let msg = slice_to_boxed_message(n_key.as_bytes());
let script = script!(CheckSigVerify(msg)).unwrap();
let inputs = inputs!(sig, pub_key);
let err = script.execute(&inputs).unwrap_err();
assert!(matches!(err, ScriptError::VerifyFailed));
}
#[allow(clippy::type_complexity)]
fn multisig_data(
n: usize,
) -> (
Box<Message>,
Vec<(
RistrettoSecretKey,
CompressedKey<RistrettoPublicKey>,
CompressedCheckSigSchnorrSignature,
)>,
) {
let mut rng = rand::thread_rng();
let mut data = Vec::with_capacity(n);
let m = RistrettoSecretKey::random(&mut rng);
let msg = slice_to_boxed_message(m.as_bytes());
for _ in 0..n {
let (k, p) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let s = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&k, m.as_bytes(), &mut rng).unwrap(),
);
data.push((k, p, s));
}
(msg, data)
}
#[allow(clippy::too_many_lines)]
#[test]
fn check_multisig() {
use crate::{StackItem::Number, op_codes::Opcode::CheckMultiSig};
let mut rng = rand::thread_rng();
let (k_alice, p_alice) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let (k_bob, p_bob) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let (k_eve, _) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let (k_carol, p_carol) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let m = RistrettoSecretKey::random(&mut rng);
let s_alice = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&k_alice, m.as_bytes(), &mut rng).unwrap(),
);
let s_bob = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&k_bob, m.as_bytes(), &mut rng).unwrap(),
);
let s_eve = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&k_eve, m.as_bytes(), &mut rng).unwrap(),
);
let s_carol = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&k_carol, m.as_bytes(), &mut rng).unwrap(),
);
let s_alice2 = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&k_alice, m.as_bytes(), &mut rng).unwrap(),
);
let msg = slice_to_boxed_message(m.as_bytes());
let keys = vec![p_alice.clone(), p_bob.clone()];
let ops = vec![CheckMultiSig(1, 2, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(s_alice.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(s_bob.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(s_eve.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(0));
let keys = vec![p_alice.clone(), p_bob.clone()];
let ops = vec![CheckMultiSig(2, 2, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(s_alice.clone(), s_bob.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(s_bob.clone(), s_alice.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(0));
let inputs = inputs!(s_eve.clone(), s_bob.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(0));
let inputs = inputs!(s_alice.clone(), s_alice.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(0));
let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
let ops = vec![CheckMultiSig(1, 3, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(s_alice.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(s_bob.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(s_carol.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(s_eve.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(0));
let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
let ops = vec![CheckMultiSig(2, 3, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(s_alice.clone(), s_bob.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(s_alice.clone(), s_carol.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(s_bob.clone(), s_carol.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(s_carol.clone(), s_bob.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(0));
let inputs = inputs!(s_carol.clone(), s_eve.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(0));
let keys = vec![p_alice.clone(), p_bob.clone(), p_alice.clone()];
let ops = vec![CheckMultiSig(2, 3, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(s_alice.clone(), s_carol.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(0));
let inputs = inputs!(s_alice.clone(), s_alice.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(0));
let inputs = inputs!(s_alice.clone(), s_alice2.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(s_alice2, s_alice.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let keys = vec![p_alice.clone(), p_bob.clone(), p_carol];
let ops = vec![CheckMultiSig(3, 3, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(s_alice.clone(), s_bob.clone(), s_carol.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(s_carol.clone(), s_alice.clone(), s_bob.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(0));
let inputs = inputs!(s_eve.clone(), s_bob.clone(), s_carol);
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(0));
let inputs = inputs!(s_eve, s_bob);
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::StackUnderflow);
let keys = vec![p_alice.clone(), p_bob.clone()];
let ops = vec![CheckMultiSig(0, 2, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(s_alice.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::ValueExceedsBounds);
let keys = vec![p_alice.clone(), p_bob.clone()];
let ops = vec![CheckMultiSig(1, 0, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(s_alice.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::ValueExceedsBounds);
let keys = vec![p_alice, p_bob];
let ops = vec![CheckMultiSig(2, 1, keys, msg)];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(s_alice);
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::ValueExceedsBounds);
let (msg, data) = multisig_data(33);
let keys = data.iter().map(|(_, p, _)| p.clone()).collect();
let sigs = data.iter().take(17).map(|(_, _, s)| s.clone());
let script = script!(CheckMultiSig(17, 33, keys, msg)).unwrap();
let items = sigs.map(StackItem::Signature).collect();
let inputs = ExecutionStack::new(items);
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::ValueExceedsBounds);
let (msg, data) = multisig_data(4);
let keys = vec![
data[0].1.clone(),
data[1].1.clone(),
data[2].1.clone(),
data[3].1.clone(),
];
let ops = vec![CheckMultiSig(3, 4, keys, msg)];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(data[0].2.clone(), data[1].2.clone(), data[2].2.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let (msg, data) = multisig_data(7);
let keys = vec![
data[0].1.clone(),
data[1].1.clone(),
data[2].1.clone(),
data[3].1.clone(),
data[4].1.clone(),
data[5].1.clone(),
data[6].1.clone(),
];
let ops = vec![CheckMultiSig(5, 7, keys, msg)];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(
data[0].2.clone(),
data[1].2.clone(),
data[2].2.clone(),
data[3].2.clone(),
data[4].2.clone()
);
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
}
#[allow(clippy::too_many_lines)]
#[test]
fn check_multisig_verify() {
use crate::{StackItem::Number, op_codes::Opcode::CheckMultiSigVerify};
let mut rng = rand::thread_rng();
let (k_alice, p_alice) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let (k_bob, p_bob) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let (k_eve, _) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let (k_carol, p_carol) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let m = RistrettoSecretKey::random(&mut rng);
let s_alice = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&k_alice, m.as_bytes(), &mut rng).unwrap(),
);
let s_bob = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&k_bob, m.as_bytes(), &mut rng).unwrap(),
);
let s_eve = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&k_eve, m.as_bytes(), &mut rng).unwrap(),
);
let s_carol = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&k_carol, m.as_bytes(), &mut rng).unwrap(),
);
let msg = slice_to_boxed_message(m.as_bytes());
let keys = vec![p_alice.clone(), p_bob.clone()];
let ops = vec![CheckMultiSigVerify(1, 2, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(Number(1), s_alice.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(Number(1), s_bob.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(Number(1), s_eve.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::VerifyFailed);
let keys = vec![p_alice.clone(), p_bob.clone()];
let ops = vec![CheckMultiSigVerify(2, 2, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(Number(1), s_alice.clone(), s_bob.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(Number(1), s_bob.clone(), s_alice.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::VerifyFailed);
let inputs = inputs!(Number(1), s_eve.clone(), s_bob.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::VerifyFailed);
let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
let ops = vec![CheckMultiSigVerify(1, 3, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(Number(1), s_alice.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(Number(1), s_bob.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(Number(1), s_carol.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(Number(1), s_eve.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::VerifyFailed);
let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
let ops = vec![CheckMultiSigVerify(2, 3, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(Number(1), s_alice.clone(), s_bob.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(Number(1), s_alice.clone(), s_carol.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(Number(1), s_bob.clone(), s_carol.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(Number(1), s_carol.clone(), s_bob.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::VerifyFailed);
let inputs = inputs!(Number(1), s_carol.clone(), s_eve.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::VerifyFailed);
let keys = vec![p_alice.clone(), p_bob.clone(), p_carol.clone()];
let ops = vec![CheckMultiSigVerifyAggregatePubKey(2, 3, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(s_alice.clone(), s_bob.clone());
let agg_pub_key = script.execute(&inputs).unwrap();
assert_eq!(
agg_pub_key,
StackItem::PublicKey(CompressedKey::<RistrettoPublicKey>::new_from_pk(
&p_alice.clone().to_public_key().unwrap() + &p_bob.clone().to_public_key().unwrap()
))
);
let inputs = inputs!(s_alice.clone(), s_carol.clone());
let agg_pub_key = script.execute(&inputs).unwrap();
assert_eq!(
agg_pub_key,
StackItem::PublicKey(CompressedKey::<RistrettoPublicKey>::new_from_pk(
&p_alice.clone().to_public_key().unwrap() + &p_carol.clone().to_public_key().unwrap()
))
);
let inputs = inputs!(s_bob.clone(), s_carol.clone());
let agg_pub_key = script.execute(&inputs).unwrap();
assert_eq!(
agg_pub_key,
StackItem::PublicKey(CompressedKey::<RistrettoPublicKey>::new_from_pk(
&p_bob.clone().to_public_key().unwrap() + &p_carol.clone().to_public_key().unwrap()
))
);
let inputs = inputs!(s_carol.clone(), s_bob.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::VerifyFailed);
let inputs = inputs!(s_alice.clone(), s_bob.clone(), s_carol.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::NonUnitLengthStack);
let inputs = inputs!(p_bob.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::StackUnderflow);
let keys = vec![p_alice.clone(), p_bob.clone(), p_carol];
let ops = vec![CheckMultiSigVerify(3, 3, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(Number(1), s_alice.clone(), s_bob.clone(), s_carol.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let inputs = inputs!(Number(1), s_bob.clone(), s_alice.clone(), s_carol.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::VerifyFailed);
let inputs = inputs!(Number(1), s_eve.clone(), s_bob.clone(), s_carol);
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::VerifyFailed);
let inputs = inputs!(Number(1), s_eve, s_bob);
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::IncompatibleTypes);
let keys = vec![p_alice.clone(), p_bob.clone()];
let ops = vec![CheckMultiSigVerify(0, 2, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(s_alice.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::ValueExceedsBounds);
let keys = vec![p_alice.clone(), p_bob.clone()];
let ops = vec![CheckMultiSigVerify(1, 0, keys, msg.clone())];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(s_alice.clone());
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::ValueExceedsBounds);
let keys = vec![p_alice, p_bob];
let ops = vec![CheckMultiSigVerify(2, 1, keys, msg)];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(s_alice);
let err = script.execute(&inputs).unwrap_err();
assert_eq!(err, ScriptError::ValueExceedsBounds);
let (msg, data) = multisig_data(4);
let keys = vec![
data[0].1.clone(),
data[1].1.clone(),
data[2].1.clone(),
data[3].1.clone(),
];
let ops = vec![CheckMultiSigVerify(3, 4, keys, msg)];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(Number(1), data[0].2.clone(), data[1].2.clone(), data[2].2.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
let (msg, data) = multisig_data(7);
let keys = vec![
data[0].1.clone(),
data[1].1.clone(),
data[2].1.clone(),
data[3].1.clone(),
data[4].1.clone(),
data[5].1.clone(),
data[6].1.clone(),
];
let ops = vec![CheckMultiSigVerify(5, 7, keys, msg)];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(
Number(1),
data[0].2.clone(),
data[1].2.clone(),
data[2].2.clone(),
data[3].2.clone(),
data[4].2.clone()
);
let result = script.execute(&inputs).unwrap();
assert_eq!(result, Number(1));
}
#[test]
fn pay_to_public_key_hash() {
use crate::StackItem::PublicKey;
let k =
RistrettoSecretKey::from_hex("7212ac93ee205cdbbb57c4f0f815fbf8db25b4d04d3532e2262e31907d82c700").unwrap();
let p = CompressedKey::<RistrettoPublicKey>::from_secret_key(&k); let hash = Blake2b::<U32>::digest(p.as_bytes());
let pkh = slice_to_boxed_hash(hash.as_slice());
let script = script!(Dup HashBlake256 PushHash(pkh) EqualVerify).unwrap();
let hex_script = "71b07aae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e581";
assert_eq!(script.to_hex(), hex_script);
assert_eq!(TariScript::from_hex(hex_script).unwrap(), script);
let inputs = inputs!(p.clone());
let result = script.execute(&inputs).unwrap();
assert_eq!(result, PublicKey(p));
}
#[test]
fn hex_roundtrip() {
let mut rng = rand::thread_rng();
let (secret_key, public_key) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let message = [1u8; 32];
let sig = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&secret_key, message, &mut rng).unwrap(),
);
let script = script!(CheckSig(slice_to_boxed_message(message.as_bytes()))).unwrap();
let input = inputs!(sig, public_key);
assert_eq!(script.execute(&input).unwrap(), StackItem::Number(1));
let parsed_script = TariScript::from_hex(script.to_hex().as_str()).unwrap();
assert_eq!(script.to_opcodes(), parsed_script.to_opcodes());
let parsed_input = ExecutionStack::from_hex(input.to_hex().as_str()).unwrap();
assert_eq!(input, parsed_input);
assert_eq!(parsed_script.execute(&parsed_input).unwrap(), StackItem::Number(1));
}
#[test]
fn disassemble() {
let hex_script = "71b07aae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e58170ac276657a418820f34036b20ea615302b373c70ac8feab8d30681a3e0f0960e708";
let script = TariScript::from_hex(hex_script).unwrap();
let ops = vec![
"Dup",
"HashBlake256",
"PushHash(ae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e5)",
"EqualVerify",
"Drop",
"CheckSig(276657a418820f34036b20ea615302b373c70ac8feab8d30681a3e0f0960e708)",
]
.into_iter()
.map(String::from)
.collect::<Vec<String>>();
assert_eq!(script.to_opcodes(), ops);
assert_eq!(
script.to_string(),
"Dup HashBlake256 PushHash(ae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e5) EqualVerify \
Drop CheckSig(276657a418820f34036b20ea615302b373c70ac8feab8d30681a3e0f0960e708)"
);
}
#[test]
fn time_locked_contract_example() {
let k_alice =
RistrettoSecretKey::from_hex("f305e64c0e73cbdb665165ac97b69e5df37b2cd81f9f8f569c3bd854daff290e").unwrap();
let p_alice = CompressedKey::<RistrettoPublicKey>::from_secret_key(&k_alice);
let k_bob =
RistrettoSecretKey::from_hex("e0689386a018e88993a7bb14cbff5bad8a8858ea101d6e0da047df3ddf499c0e").unwrap();
let p_bob = CompressedKey::<RistrettoPublicKey>::from_secret_key(&k_bob);
let lock_height = 4000u64;
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();
let inputs_alice_spends_early = inputs!(p_alice.clone());
let ctx = context_with_height(3990u64);
assert_eq!(
script.execute_with_context(&inputs_alice_spends_early, &ctx),
Err(ScriptError::VerifyFailed)
);
let inputs_alice_spends_early = inputs!(p_alice.clone());
let ctx = context_with_height(4000u64);
assert_eq!(
script.execute_with_context(&inputs_alice_spends_early, &ctx).unwrap(),
StackItem::PublicKey(p_alice)
);
let inputs_bob_spends_early = inputs!(p_bob.clone());
let ctx = context_with_height(3990u64);
assert_eq!(
script.execute_with_context(&inputs_bob_spends_early, &ctx).unwrap(),
StackItem::PublicKey(p_bob.clone())
);
let inputs_bob_spends_early = inputs!(p_bob.clone());
let ctx = context_with_height(4001u64);
assert_eq!(
script.execute_with_context(&inputs_bob_spends_early, &ctx).unwrap(),
StackItem::PublicKey(p_bob)
);
}
#[test]
fn m_of_n_signatures() {
use crate::StackItem::PublicKey;
let mut rng = rand::thread_rng();
let (k_alice, p_alice) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let (k_bob, p_bob) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let (k_eve, _) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let m = RistrettoSecretKey::random(&mut rng);
let msg = slice_to_boxed_message(m.as_bytes());
let s_alice = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&k_alice, m.as_bytes(), &mut rng).unwrap(),
);
let s_bob = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&k_bob, m.as_bytes(), &mut rng).unwrap(),
);
let s_eve = CompressedCheckSigSchnorrSignature::new_from_schnorr(
CheckSigSchnorrSignature::sign(&k_eve, m.as_bytes(), &mut rng).unwrap(),
);
use crate::Opcode::{CheckSig, Drop, Dup, Else, EndIf, IfThen, PushPubKey, Return};
let ops = vec![
Dup,
PushPubKey(Box::new(p_alice.clone())),
CheckSig(msg.clone()),
IfThen,
Drop,
PushPubKey(Box::new(p_alice.clone())),
Else,
PushPubKey(Box::new(p_bob.clone())),
CheckSig(msg),
IfThen,
PushPubKey(Box::new(p_bob.clone())),
Else,
Return,
EndIf,
EndIf,
];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(s_alice);
let result = script.execute(&inputs).unwrap();
assert_eq!(result, PublicKey(p_alice));
let inputs = inputs!(s_bob);
let result = script.execute(&inputs).unwrap();
assert_eq!(result, PublicKey(p_bob));
let inputs = inputs!(s_eve);
let result = script.execute(&inputs).unwrap_err();
assert_eq!(result, ScriptError::Return);
}
#[test]
fn to_ristretto_point() {
use crate::{Opcode::ToRistrettoPoint, StackItem::PublicKey};
let mut rng = rand::thread_rng();
let (k_1, p_1) = CompressedKey::<RistrettoPublicKey>::random_keypair(&mut rng);
let ops = vec![ToRistrettoPoint];
let script = TariScript::new(ops).unwrap();
let inputs = inputs!(CompressedKey::<RistrettoPublicKey>::default());
let err = script.execute(&inputs).unwrap_err();
assert!(matches!(err, ScriptError::IncompatibleTypes));
let mut scalar = [0u8; 32];
scalar.copy_from_slice(k_1.as_bytes());
let inputs = inputs!(scalar);
let result = script.execute(&inputs).unwrap();
assert_eq!(result, PublicKey(p_1.clone()));
let inputs = ExecutionStack::new(vec![Hash(scalar)]);
let result = script.execute(&inputs).unwrap();
assert_eq!(result, PublicKey(p_1));
let invalid = [u8::MAX; 32]; let inputs = inputs!(invalid);
assert!(matches!(script.execute(&inputs), Err(ScriptError::InvalidInput)));
}
#[test]
fn test_borsh_de_serialization() {
let hex_script = "71b07aae2337ce44f9ebb6169c863ec168046cb35ab4ef7aa9ed4f5f1f669bb74b09e58170ac276657a418820f34036b20ea615302b373c70ac8feab8d30681a3e0f0960e708";
let script = TariScript::from_hex(hex_script).unwrap();
let mut buf = Vec::new();
script.serialize(&mut buf).unwrap();
buf.extend_from_slice(&[1, 2, 3]);
let buf = &mut buf.as_slice();
assert_eq!(script, TariScript::deserialize(buf).unwrap());
assert_eq!(buf, &[1, 2, 3]);
}
#[test]
fn test_borsh_de_serialization_too_large() {
let buf = vec![255, 255, 255, 255, 255, 255, 255, 255, 255, 1, 49, 8, 2, 5, 6];
let buf = &mut buf.as_slice();
assert!(TariScript::deserialize(buf).is_err());
}
#[test]
fn test_compare_height_block_height_exceeds_bounds() {
let script = script!(CompareHeight).unwrap();
let inputs = inputs!(0);
let ctx = context_with_height(u64::MAX);
let stack_item = script.execute_with_context(&inputs, &ctx);
assert!(matches!(stack_item, Err(ScriptError::ValueExceedsBounds)));
}
#[test]
fn test_compare_height_underflows() {
let script = script!(CompareHeight).unwrap();
let inputs = ExecutionStack::new(vec![Number(i64::MIN)]);
let ctx = context_with_height(i64::MAX as u64);
let stack_item = script.execute_with_context(&inputs, &ctx);
assert!(matches!(stack_item, Err(ScriptError::CompareFailed(_))));
}
#[test]
fn test_compare_height_underflows_on_empty_stack() {
let script = script!(CompareHeight).unwrap();
let inputs = ExecutionStack::new(vec![]);
let ctx = context_with_height(i64::MAX as u64);
let stack_item = script.execute_with_context(&inputs, &ctx);
assert!(matches!(stack_item, Err(ScriptError::StackUnderflow)));
}
#[test]
fn test_compare_height_valid_with_uint_result() {
let script = script!(CompareHeight).unwrap();
let inputs = inputs!(100);
let ctx = context_with_height(24_u64);
let stack_item = script.execute_with_context(&inputs, &ctx);
assert!(stack_item.is_ok());
assert_eq!(stack_item.unwrap(), Number(-76))
}
#[test]
fn test_compare_height_valid_with_int_result() {
let script = script!(CompareHeight).unwrap();
let inputs = inputs!(100);
let ctx = context_with_height(110_u64);
let stack_item = script.execute_with_context(&inputs, &ctx);
assert!(stack_item.is_ok());
assert_eq!(stack_item.unwrap(), Number(10))
}
#[test]
fn test_check_height_block_height_exceeds_bounds() {
let script = script!(CheckHeight(0)).unwrap();
let inputs = ExecutionStack::new(vec![]);
let ctx = context_with_height(u64::MAX);
let stack_item = script.execute_with_context(&inputs, &ctx);
assert!(matches!(stack_item, Err(ScriptError::ValueExceedsBounds)));
}
#[test]
fn test_check_height_exceeds_bounds() {
let script = script!(CheckHeight(u64::MAX)).unwrap();
let inputs = ExecutionStack::new(vec![]);
let ctx = context_with_height(10_u64);
let stack_item = script.execute_with_context(&inputs, &ctx);
assert!(matches!(stack_item, Err(ScriptError::ValueExceedsBounds)));
}
#[test]
fn test_check_height_overflows_on_max_stack() {
let script = script!(CheckHeight(0)).unwrap();
let mut inputs = ExecutionStack::new(vec![]);
for i in 0..255 {
inputs.push(Number(i)).unwrap();
}
let ctx = context_with_height(i64::MAX as u64);
let stack_item = script.execute_with_context(&inputs, &ctx);
assert!(matches!(stack_item, Err(ScriptError::StackOverflow)));
}
#[test]
fn test_check_height_valid_with_uint_result() {
let script = script!(CheckHeight(24)).unwrap();
let inputs = ExecutionStack::new(vec![]);
let ctx = context_with_height(100_u64);
let stack_item = script.execute_with_context(&inputs, &ctx);
assert!(stack_item.is_ok());
assert_eq!(stack_item.unwrap(), Number(76))
}
#[test]
fn test_check_height_valid_with_int_result() {
let script = script!(CheckHeight(100)).unwrap();
let inputs = ExecutionStack::new(vec![]);
let ctx = context_with_height(24_u64);
let stack_item = script.execute_with_context(&inputs, &ctx);
assert!(stack_item.is_ok());
assert_eq!(stack_item.unwrap(), Number(-76))
}
}