diff --git a/README.md b/README.md
index ae07554f..1e4bc664 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,12 @@
+
+
+
+
diff --git a/Scarb.lock b/Scarb.lock
index 69689ee9..c302a5a8 100644
--- a/Scarb.lock
+++ b/Scarb.lock
@@ -17,6 +17,7 @@ version = "0.1.0"
dependencies = [
"shinigami_compiler",
"shinigami_engine",
+ "shinigami_tests",
"shinigami_utils",
]
@@ -34,7 +35,15 @@ version = "0.1.0"
dependencies = [
"ripemd160",
"sha1",
+ "shinigami_utils",
+]
+
+[[package]]
+name = "shinigami_tests"
+version = "0.1.0"
+dependencies = [
"shinigami_compiler",
+ "shinigami_engine",
"shinigami_utils",
]
diff --git a/packages/cmds/Scarb.toml b/packages/cmds/Scarb.toml
index 80089d4b..67b3b62a 100644
--- a/packages/cmds/Scarb.toml
+++ b/packages/cmds/Scarb.toml
@@ -6,6 +6,7 @@ edition = "2024_07"
[dependencies]
shinigami_compiler = { path = "../compiler" }
shinigami_engine = { path = "../engine" }
+shinigami_tests = { path = "../tests" }
shinigami_utils = { path = "../utils" }
[dev-dependencies]
diff --git a/packages/cmds/src/main.cairo b/packages/cmds/src/main.cairo
index c210c958..ba1f1c38 100644
--- a/packages/cmds/src/main.cairo
+++ b/packages/cmds/src/main.cairo
@@ -1,12 +1,13 @@
use shinigami_compiler::compiler::CompilerImpl;
-use shinigami_engine::engine::EngineInternalImpl;
-use shinigami_engine::transaction::{TransactionImpl, TransactionTrait};
-use shinigami_engine::utxo::UTXO;
-use shinigami_engine::validate;
-use shinigami_engine::scriptflags;
+use shinigami_engine::engine::{EngineImpl, EngineInternalImpl};
+use shinigami_engine::transaction::{EngineInternalTransactionImpl, EngineInternalTransactionTrait};
+use shinigami_engine::flags;
use shinigami_engine::witness;
+use shinigami_engine::hash_cache::HashCacheImpl;
use shinigami_utils::byte_array::felt252_to_byte_array;
use shinigami_utils::bytecode::hex_to_bytecode;
+use shinigami_tests::utxo::UTXO;
+use shinigami_tests::validate;
#[derive(Clone, Drop)]
struct InputData {
@@ -40,9 +41,10 @@ fn run_with_flags(input: InputDataWithFlags) -> Result<(), felt252> {
let script_pubkey = compiler.compile(input.ScriptPubKey)?;
let compiler = CompilerImpl::new();
let script_sig = compiler.compile(input.ScriptSig)?;
- let tx = TransactionImpl::new_signed(script_sig);
- let flags = scriptflags::parse_flags(input.Flags);
- let mut engine = EngineInternalImpl::new(@script_pubkey, tx, 0, flags, 0)?;
+ let tx = EngineInternalTransactionImpl::new_signed(script_sig, script_pubkey.clone());
+ let flags = flags::parse_flags(input.Flags);
+ let hash_cache = HashCacheImpl::new(@tx);
+ let mut engine = EngineImpl::new(@script_pubkey, @tx, 0, flags, 0, @hash_cache)?;
let _ = engine.execute()?;
Result::Ok(())
}
@@ -60,9 +62,13 @@ fn run_with_witness(input: InputDataWithWitness) -> Result<(), felt252> {
let compiler = CompilerImpl::new();
let script_sig = compiler.compile(input.ScriptSig)?;
let witness = witness::parse_witness_input(input.Witness);
- let tx = TransactionImpl::new_signed_witness(script_sig, witness);
- let flags = scriptflags::parse_flags(input.Flags);
- let mut engine = EngineInternalImpl::new(@script_pubkey, tx, 0, flags, 0)?;
+ let value = 1; // TODO
+ let tx = EngineInternalTransactionImpl::new_signed_witness(
+ script_sig, script_pubkey.clone(), witness, value
+ );
+ let flags = flags::parse_flags(input.Flags);
+ let hash_cache = HashCacheImpl::new(@tx);
+ let mut engine = EngineImpl::new(@script_pubkey, @tx, 0, flags, value, @hash_cache)?;
let _ = engine.execute()?;
Result::Ok(())
}
@@ -77,8 +83,9 @@ fn run(input: InputData) -> Result<(), felt252> {
let script_pubkey = compiler.compile(input.ScriptPubKey)?;
let compiler = CompilerImpl::new();
let script_sig = compiler.compile(input.ScriptSig)?;
- let tx = TransactionImpl::new_signed(script_sig);
- let mut engine = EngineInternalImpl::new(@script_pubkey, tx, 0, 0, 0)?;
+ let tx = EngineInternalTransactionImpl::new_signed(script_sig, script_pubkey.clone());
+ let hash_cache = HashCacheImpl::new(@tx);
+ let mut engine = EngineImpl::new(@script_pubkey, @tx, 0, 0, 0, @hash_cache)?;
let _ = engine.execute()?;
Result::Ok(())
}
@@ -93,8 +100,9 @@ fn run_with_json(input: InputData) -> Result<(), felt252> {
let script_pubkey = compiler.compile(input.ScriptPubKey)?;
let compiler = CompilerImpl::new();
let script_sig = compiler.compile(input.ScriptSig)?;
- let tx = TransactionImpl::new_signed(script_sig);
- let mut engine = EngineInternalImpl::new(@script_pubkey, tx, 0, 0, 0)?;
+ let tx = EngineInternalTransactionImpl::new_signed(script_sig, script_pubkey.clone());
+ let hash_cache = HashCacheImpl::new(@tx);
+ let mut engine = EngineImpl::new(@script_pubkey, @tx, 0, 0, 0, @hash_cache)?;
let _ = engine.execute()?;
engine.json();
Result::Ok(())
@@ -110,8 +118,9 @@ fn debug(input: InputData) -> Result {
let script_pubkey = compiler.compile(input.ScriptPubKey)?;
let compiler = CompilerImpl::new();
let script_sig = compiler.compile(input.ScriptSig)?;
- let tx = TransactionImpl::new_signed(script_sig);
- let mut engine = EngineInternalImpl::new(@script_pubkey, tx, 0, 0, 0)?;
+ let tx = EngineInternalTransactionImpl::new_signed(script_sig, script_pubkey.clone());
+ let hash_cache = HashCacheImpl::new(@tx);
+ let mut engine = EngineImpl::new(@script_pubkey, @tx, 0, 0, 0, @hash_cache)?;
let mut res = Result::Ok(true);
while true {
res = engine.step();
@@ -188,11 +197,26 @@ struct ValidateRawInput {
utxo_hints: Array
}
-fn run_raw_transaction(input: ValidateRawInput) -> u8 {
+fn run_raw_transaction(mut input: ValidateRawInput) -> u8 {
println!("Running Bitcoin Script with raw transaction: '{}'", input.raw_transaction);
let raw_transaction = hex_to_bytecode(@input.raw_transaction);
- let transaction = TransactionTrait::deserialize(raw_transaction);
- let res = validate::validate_transaction(transaction, 0, input.utxo_hints);
+ let transaction = EngineInternalTransactionTrait::deserialize(raw_transaction);
+ let mut utxo_hints = array![];
+ for hint in input
+ .utxo_hints
+ .span() {
+ println!("UTXO hint: 'amount: {}, script_pubkey: {}'", hint.amount, hint.pubkey_script);
+ let pubkey_script = hex_to_bytecode(hint.pubkey_script);
+ utxo_hints
+ .append(
+ UTXO {
+ amount: *hint.amount,
+ pubkey_script: pubkey_script,
+ block_height: *hint.block_height,
+ }
+ );
+ };
+ let res = validate::validate_transaction(@transaction, 0, utxo_hints);
match res {
Result::Ok(_) => {
println!("Execution successful");
diff --git a/packages/compiler/src/compiler.cairo b/packages/compiler/src/compiler.cairo
index 0e46d067..c089c320 100644
--- a/packages/compiler/src/compiler.cairo
+++ b/packages/compiler/src/compiler.cairo
@@ -26,7 +26,7 @@ pub impl CompilerImpl of CompilerTrait {
let mut compiler = Compiler { opcodes: Default::default() };
// Add the opcodes to the dict
compiler.add_opcode('OP_0', Opcode::OP_0);
- compiler.add_opcode('OP_FALSE', Opcode::OP_0);
+ compiler.add_opcode('OP_FALSE', Opcode::OP_FALSE);
compiler.add_opcode('OP_DATA_1', Opcode::OP_DATA_1);
compiler.add_opcode('OP_DATA_2', Opcode::OP_DATA_2);
compiler.add_opcode('OP_DATA_3', Opcode::OP_DATA_3);
@@ -286,6 +286,8 @@ pub impl CompilerImpl of CompilerTrait {
compiler.add_opcode('OP_LSHIFT', Opcode::OP_LSHIFT);
compiler.add_opcode('OP_RSHIFT', Opcode::OP_RSHIFT);
compiler.add_opcode('OP_NOP1', Opcode::OP_NOP1);
+ compiler.add_opcode('OP_NOP2', Opcode::OP_NOP2);
+ compiler.add_opcode('OP_NOP3', Opcode::OP_NOP3);
compiler.add_opcode('OP_NOP4', Opcode::OP_NOP4);
compiler.add_opcode('OP_NOP5', Opcode::OP_NOP5);
compiler.add_opcode('OP_NOP6', Opcode::OP_NOP6);
diff --git a/packages/engine/Scarb.toml b/packages/engine/Scarb.toml
index 0cd83f25..8e3c5923 100644
--- a/packages/engine/Scarb.toml
+++ b/packages/engine/Scarb.toml
@@ -6,7 +6,6 @@ edition = "2024_07"
[dependencies]
ripemd160.workspace = true
sha1.workspace = true
-shinigami_compiler = { path = "../compiler" }
shinigami_utils = { path = "../utils" }
[dev-dependencies]
diff --git a/packages/engine/src/engine.cairo b/packages/engine/src/engine.cairo
index c319563d..0b0e8573 100644
--- a/packages/engine/src/engine.cairo
+++ b/packages/engine/src/engine.cairo
@@ -1,48 +1,31 @@
use crate::cond_stack::{ConditionalStack, ConditionalStackImpl};
use crate::errors::Error;
-use crate::opcodes::{flow, opcodes::Opcode};
-use crate::scriptflags::ScriptFlags;
+use crate::parser;
+use crate::opcodes::opcodes::Opcode;
+use crate::flags::ScriptFlags;
use crate::stack::{ScriptStack, ScriptStackImpl};
use crate::transaction::{
- Transaction, EngineTransactionInputTrait, EngineTransactionOutputTrait, EngineTransactionTrait
+ EngineTransactionInputTrait, EngineTransactionOutputTrait, EngineTransactionTrait
};
-use shinigami_utils::byte_array::{byte_array_to_bool, byte_array_to_felt252_le};
+use crate::hash_cache::{HashCache, HashCacheTrait};
+use crate::witness;
+use shinigami_utils::byte_array::byte_array_to_bool;
use shinigami_utils::bytecode::hex_to_bytecode;
use shinigami_utils::hash::sha256_byte_array;
-use crate::witness;
+use crate::taproot;
+use crate::taproot::{TaprootContext, TaprootContextImpl, ControlBlockImpl};
-// SigCache implements an Schnorr+ECDSA signature verification cache. Only valid signatures will be
-// added to the cache.
-pub trait SigCacheTrait {
- // Returns true if sig cache contains sig_hash corresponding to signature and public key
- fn exists(sig_hash: u256, signature: ByteArray, pub_key: ByteArray) -> bool;
- // Adds a signature to the cache
- fn add(sig_hash: u256, signature: ByteArray, pub_key: ByteArray);
-}
+pub const MAX_STACK_SIZE: u32 = 1000;
+pub const MAX_SCRIPT_SIZE: u32 = 10000;
+pub const MAX_OPS_PER_SCRIPT: u32 = 201;
+pub const MAX_SCRIPT_ELEMENT_SIZE: u32 = 520;
-// HashCache caches the midstate of segwit v0 and v1 sighashes
-pub trait HashCacheTrait<
- H,
- I,
- O,
- T,
- +EngineTransactionInputTrait,
- +EngineTransactionOutputTrait,
- +EngineTransactionTrait
-> {
- fn new(transaction: @T) -> H;
-
- // v0 represents sighash midstate used in the base segwit signatures BIP-143
- fn get_hash_prevouts_v0(self: @H) -> u256;
- fn get_hash_sequence_v0(self: @H) -> u256;
- fn get_hash_outputs_v0(self: @H) -> u256;
-
- // v1 represents sighash midstate used to compute taproot signatures BIP-341
- fn get_hash_prevouts_v1(self: @H) -> u256;
- fn get_hash_sequence_v1(self: @H) -> u256;
- fn get_hash_outputs_v1(self: @H) -> u256;
- fn get_hash_input_scripts_v1(self: @H) -> u256;
-}
+const BASE_SEGWIT_WITNESS_VERSION: i64 = 0;
+const TAPROOT_WITNESS_VERSION: i64 = 1;
+
+const PAY_TO_WITNESS_PUBKEY_HASH_SIZE: u32 = 20;
+const PAY_TO_WITNESS_SCRIPT_HASH_SIZE: u32 = 32;
+const PAY_TO_TAPROOT_DATA_SIZE: u32 = 32;
// Represents the VM that executes Bitcoin scripts
#[derive(Destruct)]
@@ -52,7 +35,7 @@ pub struct Engine {
// Is Bip16 p2sh
bip16: bool,
// Transaction context being executed
- pub transaction: T,
+ pub transaction: @T,
// Input index within the tx containing signature script being executed
pub tx_idx: u32,
// Amount of the input being spent
@@ -67,12 +50,18 @@ pub struct Engine {
pub witness_program: ByteArray,
// The witness version
pub witness_version: i64,
+ // The taproot context for exection
+ pub taproot_context: TaprootContext,
+ // Whether to use taproot
+ pub use_taproot: bool,
// Primary data stack
pub dstack: ScriptStack,
// Alternate data stack
pub astack: ScriptStack,
// Tracks conditonal execution state supporting nested conditionals
pub cond_stack: ConditionalStack,
+ // Copy of the stack from 1st script in a P2SH exec
+ pub saved_first_stack: Span,
// Position within script of last OP_CODESEPARATOR
pub last_code_sep: u32,
// Count number of non-push opcodes
@@ -81,15 +70,13 @@ pub struct Engine {
// TODO: SigCache
pub trait EngineTrait<
- E,
I,
O,
T,
- H,
+EngineTransactionInputTrait,
+EngineTransactionOutputTrait,
+EngineTransactionTrait,
- +HashCacheTrait
+ +HashCacheTrait
> {
// Create a new Engine with the given script
fn new(
@@ -98,24 +85,27 @@ pub trait EngineTrait<
tx_idx: u32,
flags: u32,
amount: i64,
- hash_cache: @H
- ) -> Result;
+ hash_cache: @HashCache
+ ) -> Result, felt252>;
+ // Executes a single step of the script, returning true if more steps are needed
+ fn step(ref self: Engine) -> Result;
// Executes the entire script and returns top of stack or error if script fails
- fn execute(ref self: E) -> Result;
+ fn execute(ref self: Engine) -> Result;
}
pub impl EngineImpl<
I,
O,
T,
- H,
impl IEngineTransactionInput: EngineTransactionInputTrait,
impl IEngineTransactionOutput: EngineTransactionOutputTrait,
impl IEngineTransaction: EngineTransactionTrait<
T, I, O, IEngineTransactionInput, IEngineTransactionOutput
>,
- impl IHashCache: HashCacheTrait,
-> of EngineTrait, I, O, T, H> {
+ +Drop,
+ +Drop,
+ +Drop,
+> of EngineTrait {
// Create a new Engine with the given script
fn new(
script_pubkey: @ByteArray,
@@ -123,142 +113,20 @@ pub impl EngineImpl<
tx_idx: u32,
flags: u32,
amount: i64,
- hash_cache: @H
+ hash_cache: @HashCache
) -> Result, felt252> {
- let _ = transaction.get_transaction_inputs();
- return Result::Err('todo');
- }
-
- // Executes the entire script and returns top of stack or error if script fails
- fn execute(ref self: Engine) -> Result {
- // TODO
- Result::Ok("0")
- }
-}
-
-pub trait EngineExtrasTrait {
- // Pulls the next len bytes from the script and advances the program counter
- fn pull_data(ref self: Engine, len: usize) -> Result;
- // Return true if the script engine instance has the specified flag set.
- fn has_flag(ref self: Engine, flag: ScriptFlags) -> bool;
- // Pop bool enforcing minimal if
- fn pop_if_bool(ref self: Engine) -> Result;
- // Return true if the witness program was active
- fn is_witness_active(ref self: Engine, version: i64) -> bool;
- // Return the script since last OP_CODESEPARATOR
- fn sub_script(ref self: Engine) -> ByteArray;
-}
-
-pub impl EngineExtrasImpl> of EngineExtrasTrait {
- fn pull_data(ref self: Engine, len: usize) -> Result {
- let mut data = "";
- let mut i = self.opcode_idx + 1;
- let mut end = i + len;
- let script = *(self.scripts[self.script_idx]);
- if end > script.len() {
- return Result::Err(Error::SCRIPT_INVALID);
- }
- while i != end {
- data.append_byte(script[i]);
- i += 1;
- };
- self.opcode_idx = end - 1;
- return Result::Ok(data);
- }
-
- fn has_flag(ref self: Engine, flag: ScriptFlags) -> bool {
- self.flags & flag.into() == flag.into()
- }
-
- fn pop_if_bool(ref self: Engine) -> Result {
- if !self.is_witness_active(0) || !self.has_flag(ScriptFlags::ScriptVerifyMinimalIf) {
- return self.dstack.pop_bool();
- }
- let top = self.dstack.pop_byte_array()?;
- if top.len() > 1 {
- return Result::Err(Error::MINIMAL_IF);
- }
-
- if top.len() == 1 && top[0] != 0x01 {
- return Result::Err(Error::MINIMAL_IF);
- }
- return Result::Ok(byte_array_to_bool(@top));
- }
-
- fn is_witness_active(ref self: Engine, version: i64) -> bool {
- return self.witness_version == version && self.witness_program.len() != 0;
- }
-
- fn sub_script(ref self: Engine) -> ByteArray {
- let script = *(self.scripts[self.script_idx]);
- if self.last_code_sep == 0 {
- return script.clone();
- }
-
- let mut sub_script = "";
- let mut i = self.last_code_sep;
- while i != script.len() {
- sub_script.append_byte(script[i]);
- i += 1;
- };
- return sub_script;
- }
-}
-
-pub trait EngineInternalTrait {
- // Create a new Engine with the given script
- fn new(
- script_pubkey: @ByteArray, transaction: Transaction, tx_idx: u32, flags: u32, amount: i64
- ) -> Result, felt252>;
- // Returns true if the script is a script hash
- fn is_script_hash(ref self: Engine) -> bool;
- // Returns true if the script sig is push only
- fn is_push_only(ref self: Engine) -> bool;
- // Pulls the next len bytes from the script at the given index
- fn pull_data_at(
- ref self: Engine, idx: usize, len: usize
- ) -> Result;
- fn get_dstack(ref self: Engine) -> Span;
- fn get_astack(ref self: Engine) -> Span;
- // Returns the length of the next push data opcode
- fn push_data_len(ref self: Engine, opcode: u8, idx: u32) -> Result;
- // Skip the next opcode if it is a push opcode in unexecuted conditional branch
- fn skip_push_data(ref self: Engine, opcode: u8) -> Result<(), felt252>;
- // Executes the next instruction in the script
- fn step(ref self: Engine) -> Result;
- // Executes the entire script and returns top of stack or error if script fails
- fn execute(ref self: Engine) -> Result;
- // Validate witness program using witness input
- fn verify_witness(
- ref self: Engine, witness: Span
- ) -> Result<(), felt252>;
- // Ensure the stack size is within limits
- fn check_stack_size(ref self: Engine) -> Result<(), felt252>;
- // Check if the next opcode is a minimal push
- fn check_minimal_data_push(ref self: Engine, opcode: u8) -> Result<(), felt252>;
- // Print engine data as a JSON object
- fn json(ref self: Engine);
-}
-
-pub const MAX_STACK_SIZE: u32 = 1000;
-pub const MAX_SCRIPT_SIZE: u32 = 10000;
-pub const MAX_OPS_PER_SCRIPT: u32 = 201;
-pub const MAX_SCRIPT_ELEMENT_SIZE: u32 = 520;
-
-pub impl EngineInternalImpl of EngineInternalTrait {
- fn new(
- script_pubkey: @ByteArray, transaction: Transaction, tx_idx: u32, flags: u32, amount: i64
- ) -> Result, felt252> {
- if tx_idx >= transaction.transaction_inputs.len() {
+ let transaction_inputs = transaction.get_transaction_inputs();
+ if tx_idx >= transaction_inputs.len() {
return Result::Err('Engine::new: tx_idx invalid');
}
- let script_sig = transaction.transaction_inputs[tx_idx].signature_script;
+ let tx_input = transaction_inputs[tx_idx];
+ let script_sig = tx_input.get_signature_script();
if script_sig.len() == 0 && script_pubkey.len() == 0 {
return Result::Err(Error::SCRIPT_EMPTY_STACK);
}
- let witness_len = transaction.transaction_inputs[tx_idx].witness.len();
+ let witness_len = tx_input.get_witness().len();
let mut engine = Engine {
flags: flags,
bip16: false,
@@ -270,9 +138,12 @@ pub impl EngineInternalImpl of EngineInternalTrait {
opcode_idx: 0,
witness_program: "",
witness_version: 0,
+ taproot_context: TaprootContextImpl::empty(),
+ use_taproot: false,
dstack: ScriptStackImpl::new(),
astack: ScriptStackImpl::new(),
cond_stack: ConditionalStackImpl::new(),
+ saved_first_stack: array![].span(),
last_code_sep: 0,
num_ops: 0,
};
@@ -283,13 +154,15 @@ pub impl EngineInternalImpl of EngineInternalTrait {
return Result::Err('Engine::new: invalid flag combo');
}
- if engine.has_flag(ScriptFlags::ScriptVerifySigPushOnly) && !engine.is_push_only() {
+ if engine.has_flag(ScriptFlags::ScriptVerifySigPushOnly)
+ && !parser::is_push_only(script_sig) {
return Result::Err('Engine::new: not pushonly');
}
let mut bip16 = false;
- if engine.has_flag(ScriptFlags::ScriptBip16) && engine.is_script_hash() {
- if !engine.has_flag(ScriptFlags::ScriptVerifySigPushOnly) && !engine.is_push_only() {
+ if engine.has_flag(ScriptFlags::ScriptBip16) && parser::is_script_hash(script_pubkey) {
+ if !engine.has_flag(ScriptFlags::ScriptVerifySigPushOnly)
+ && !parser::is_push_only(script_sig) {
return Result::Err('Engine::new: p2sh not pushonly');
}
engine.bip16 = true;
@@ -298,7 +171,8 @@ pub impl EngineInternalImpl of EngineInternalTrait {
let mut i = 0;
let mut valid_sizes = true;
- while i != engine.scripts.len() {
+ let scripts_len = engine.scripts.len();
+ while i != scripts_len {
let script = *(engine.scripts[i]);
if script.len() > MAX_SCRIPT_SIZE {
valid_sizes = false;
@@ -328,17 +202,18 @@ pub impl EngineInternalImpl of EngineInternalTrait {
let mut witness_program: ByteArray = "";
if witness::is_witness_program(script_pubkey) {
if script_sig.len() != 0 {
- return Result::Err('Engine::new: witness w/ sig');
+ return Result::Err(Error::WITNESS_MALLEATED);
}
witness_program = script_pubkey.clone();
} else if witness_len != 0 && bip16 {
- let sig_clone = engine.scripts[0].clone();
+ let sig_clone = script_sig.clone();
if sig_clone.len() > 2 {
let first_elem = sig_clone[0];
let mut remaining = "";
let mut i = 1;
// TODO: Optimize
- while i != sig_clone.len() {
+ let sig_len = sig_clone.len();
+ while i != sig_len {
remaining.append_byte(sig_clone[i]);
i += 1;
};
@@ -346,10 +221,10 @@ pub impl EngineInternalImpl of EngineInternalTrait {
&& witness::is_witness_program(@remaining) {
witness_program = remaining;
} else {
- return Result::Err('Engine::new: sig malleability');
+ return Result::Err(Error::WITNESS_MALLEATED_P2SH);
}
} else {
- return Result::Err('Engine::new: sig malleability');
+ return Result::Err(Error::WITNESS_MALLEATED_P2SH);
}
}
@@ -360,101 +235,15 @@ pub impl EngineInternalImpl of EngineInternalTrait {
engine.witness_version = witness_version;
engine.witness_program = witness_program;
} else if engine.witness_program.len() == 0 && witness_len != 0 {
- return Result::Err('Engine::new: witness + no prog');
+ return Result::Err(Error::WITNESS_UNEXPECTED);
}
}
return Result::Ok(engine);
}
- fn is_script_hash(ref self: Engine) -> bool {
- let script_pubkey = *(self.scripts[1]);
- if script_pubkey.len() == 23
- && script_pubkey[0] == Opcode::OP_HASH160
- && script_pubkey[1] == Opcode::OP_DATA_20
- && script_pubkey[22] == Opcode::OP_EQUAL {
- return true;
- }
- return false;
- }
-
- fn is_push_only(ref self: Engine) -> bool {
- let script: @ByteArray = *(self.scripts[0]);
- let mut i = 0;
- let mut is_push_only = true;
- while i != script.len() {
- // TODO: Error handling if i outside bounds
- let opcode = script[i];
- if opcode > Opcode::OP_16 {
- is_push_only = false;
- break;
- }
-
- // TODO: Error handling
- let data_len = Opcode::data_len(i, script).unwrap();
- i += data_len + 1;
- };
- return is_push_only;
- }
-
- fn pull_data_at(
- ref self: Engine, idx: usize, len: usize
- ) -> Result {
- let mut data = "";
- let mut i = idx;
- let mut end = i + len;
- let script = *(self.scripts[self.script_idx]);
- if end > script.len() {
- return Result::Err(Error::SCRIPT_INVALID);
- }
- while i != end {
- data.append_byte(script[i]);
- i += 1;
- };
- return Result::Ok(data);
- }
-
- fn get_dstack(ref self: Engine) -> Span {
- return self.dstack.stack_to_span();
- }
-
- fn get_astack(ref self: Engine) -> Span {
- return self.astack.stack_to_span();
- }
-
- fn push_data_len(
- ref self: Engine, opcode: u8, idx: u32
- ) -> Result {
- if opcode == Opcode::OP_PUSHDATA1 {
- return Result::Ok(
- byte_array_to_felt252_le(@self.pull_data_at(idx + 1, 1)?).try_into().unwrap()
- );
- } else if opcode == Opcode::OP_PUSHDATA2 {
- return Result::Ok(
- byte_array_to_felt252_le(@self.pull_data_at(idx + 1, 2)?).try_into().unwrap()
- );
- } else if opcode == Opcode::OP_PUSHDATA4 {
- return Result::Ok(
- byte_array_to_felt252_le(@self.pull_data_at(idx + 1, 4)?).try_into().unwrap()
- );
- }
- return Result::Err('Engine::push_data_len: invalid');
- }
-
- fn skip_push_data(ref self: Engine, opcode: u8) -> Result<(), felt252> {
- if opcode == Opcode::OP_PUSHDATA1 {
- self.opcode_idx += self.push_data_len(opcode, self.opcode_idx)? + 2;
- } else if opcode == Opcode::OP_PUSHDATA2 {
- self.opcode_idx += self.push_data_len(opcode, self.opcode_idx)? + 3;
- } else if opcode == Opcode::OP_PUSHDATA4 {
- self.opcode_idx += self.push_data_len(opcode, self.opcode_idx)? + 5;
- } else {
- return Result::Err(Error::SCRIPT_INVALID);
- }
- Result::Ok(())
- }
-
- fn step(ref self: Engine) -> Result {
+ fn step(ref self: Engine) -> Result {
+ // TODO: Make it match engine.execute after recent changes
if self.script_idx >= self.scripts.len() {
return Result::Ok(false);
}
@@ -480,25 +269,9 @@ pub impl EngineInternalImpl of EngineInternalTrait {
return Result::Err(illegal_opcode.unwrap_err());
}
- if !self.cond_stack.branch_executing() && !flow::is_branching_opcode(opcode) {
- if Opcode::is_data_opcode(opcode) {
- let opcode_32: u32 = opcode.into();
- self.opcode_idx += opcode_32 + 1;
- return Result::Ok(true);
- } else if Opcode::is_push_opcode(opcode) {
- let res = self.skip_push_data(opcode);
- if res.is_err() {
- return Result::Err(res.unwrap_err());
- }
- return Result::Ok(true);
- } else {
- let res = Opcode::is_opcode_disabled(opcode, ref self);
- if res.is_err() {
- return Result::Err(res.unwrap_err());
- }
- self.opcode_idx += 1;
- return Result::Ok(true);
- }
+ if !self.cond_stack.branch_executing() && !Opcode::is_branching_opcode(opcode) {
+ self.skip()?;
+ return Result::Ok(true);
}
if self.dstack.verify_minimal_data
@@ -529,14 +302,22 @@ pub impl EngineInternalImpl of EngineInternalTrait {
return Result::Ok(true);
}
- fn execute(ref self: Engine) -> Result {
+ // Executes the entire script and returns top of stack or error if script fails
+ fn execute(ref self: Engine) -> Result {
let mut err = '';
// TODO: Optimize with != instead of < and check for bounds errors within the loop
while self.script_idx < self.scripts.len() {
let script: @ByteArray = *self.scripts[self.script_idx];
- while self.opcode_idx < script.len() {
- let opcode = script[self.opcode_idx];
+ let script_len = script.len();
+ if script_len == 0 {
+ self.script_idx += 1;
+ continue;
+ }
+ while self.opcode_idx < script_len {
+ let opcode_idx = self.opcode_idx;
+ let opcode = script[opcode_idx];
+ // TODO: Can this be defered to opcode execution like disabled
// Check if the opcode is always illegal (reserved).
let illegal_opcode = Opcode::is_opcode_always_illegal(opcode, ref self);
if illegal_opcode.is_err() {
@@ -544,14 +325,14 @@ pub impl EngineInternalImpl of EngineInternalTrait {
break;
}
- if opcode > Opcode::OP_16 {
+ if !self.use_taproot && opcode > Opcode::OP_16 {
self.num_ops += 1;
if self.num_ops > MAX_OPS_PER_SCRIPT {
err = Error::SCRIPT_TOO_MANY_OPERATIONS;
break;
}
} else if Opcode::is_push_opcode(opcode) {
- let res = self.push_data_len(opcode, self.opcode_idx);
+ let res = parser::push_data_len(script, opcode_idx);
if res.is_err() {
err = res.unwrap_err();
break;
@@ -562,27 +343,13 @@ pub impl EngineInternalImpl of EngineInternalTrait {
}
}
- if !self.cond_stack.branch_executing() && !flow::is_branching_opcode(opcode) {
- if Opcode::is_data_opcode(opcode) {
- let opcode_32: u32 = opcode.into();
- self.opcode_idx += opcode_32 + 1;
- continue;
- } else if Opcode::is_push_opcode(opcode) {
- let res = self.skip_push_data(opcode);
- if res.is_err() {
- err = res.unwrap_err();
- break;
- }
- continue;
- } else {
- let res = Opcode::is_opcode_disabled(opcode, ref self);
- if res.is_err() {
- err = res.unwrap_err();
- break;
- }
- self.opcode_idx += 1;
- continue;
+ if !self.cond_stack.branch_executing() && !Opcode::is_branching_opcode(opcode) {
+ let res = self.skip();
+ if res.is_err() {
+ err = res.unwrap_err();
+ break;
}
+ continue;
}
if self.dstack.verify_minimal_data
@@ -611,7 +378,7 @@ pub impl EngineInternalImpl of EngineInternalTrait {
if err != '' {
break;
}
- if self.cond_stack.len() > 0 {
+ if self.cond_stack.len() != 0 {
err = Error::SCRIPT_UNBALANCED_CONDITIONAL_STACK;
break;
}
@@ -621,11 +388,29 @@ pub impl EngineInternalImpl of EngineInternalTrait {
}
self.num_ops = 0;
self.opcode_idx = 0;
- if (self.script_idx == 1 && self.witness_program.len() != 0)
+ if self.script_idx == 0 && self.bip16 {
+ self.script_idx += 1;
+ // TODO: Use @ instead of clone span
+ self.saved_first_stack = self.dstack.stack_to_span();
+ } else if self.script_idx == 1 && self.bip16 {
+ self.script_idx += 1;
+
+ let res = self.check_error_condition(false);
+ if res.is_err() {
+ err = res.unwrap_err();
+ break;
+ }
+ let saved_stack_len = self.saved_first_stack.len();
+ let redeem_script = self.saved_first_stack[saved_stack_len - 1];
+ // TODO: check script parses?
+ self.scripts.append(redeem_script);
+ self.dstack.set_stack(self.saved_first_stack, 0, saved_stack_len - 1);
+ } else if (self.script_idx == 1 && self.witness_program.len() != 0)
|| (self.script_idx == 2 && self.witness_program.len() != 0 && self.bip16) {
self.script_idx += 1;
- let witness = self.transaction.transaction_inputs[self.tx_idx].witness;
- let res = self.verify_witness(witness.span());
+ let tx_input = self.transaction.get_transaction_inputs()[self.tx_idx];
+ let witness = tx_input.get_witness();
+ let res = self.verify_witness(witness);
if res.is_err() {
err = res.unwrap_err();
break;
@@ -640,46 +425,174 @@ pub impl EngineInternalImpl of EngineInternalTrait {
return Result::Err(err);
}
- // TODO: CheckErrorCondition
- if self.is_witness_active(0) && self.dstack.len() != 1 { // TODO: Hardcoded 0
- return Result::Err(Error::SCRIPT_NON_CLEAN_STACK);
+ return self.check_error_condition(true);
+ }
+}
+
+// TODO: Remove functions that can be locally used only
+pub trait EngineInternalTrait<
+ I,
+ O,
+ T,
+ +EngineTransactionInputTrait,
+ +EngineTransactionOutputTrait,
+ +EngineTransactionTrait,
+ +HashCacheTrait,
+> {
+ // Pulls the next len bytes from the script and advances the program counter
+ fn pull_data(ref self: Engine, len: usize) -> Result;
+ // Return true if the script engine instance has the specified flag set.
+ fn has_flag(ref self: Engine, flag: ScriptFlags) -> bool;
+ // Pop bool enforcing minimal if
+ fn pop_if_bool(ref self: Engine) -> Result;
+ // Return true if the witness program was active
+ fn is_witness_active(ref self: Engine, version: i64) -> bool;
+ // Return the script since last OP_CODESEPARATOR
+ fn sub_script(ref self: Engine) -> ByteArray;
+ // Returns the data stack
+ fn get_dstack(ref self: Engine) -> Span;
+ // Returns the alt stack
+ fn get_astack(ref self: Engine) -> Span;
+ // Skips the next opcode in execution based on execution rules
+ fn skip(ref self: Engine) -> Result<(), felt252>;
+ // Ensure the stack size is within limits
+ fn check_stack_size(ref self: Engine) -> Result<(), felt252>;
+ // Check if the next opcode is a minimal push
+ fn check_minimal_data_push(ref self: Engine, opcode: u8) -> Result<(), felt252>;
+ // Validate witness program using witness input
+ fn verify_witness(ref self: Engine, witness: Span) -> Result<(), felt252>;
+ // Check if the script has failed and return an error if it has
+ fn check_error_condition(ref self: Engine, final: bool) -> Result;
+ // Prints the engine state as json
+ fn json(ref self: Engine);
+}
+
+pub impl EngineInternalImpl<
+ I,
+ O,
+ T,
+ impl IEngineTransactionInput: EngineTransactionInputTrait,
+ impl IEngineTransactionOutput: EngineTransactionOutputTrait,
+ impl IEngineTransaction: EngineTransactionTrait<
+ T, I, O, IEngineTransactionInput, IEngineTransactionOutput
+ >,
+ +Drop,
+ +Drop,
+ +Drop,
+> of EngineInternalTrait {
+ fn pull_data(ref self: Engine, len: usize) -> Result {
+ let script = *(self.scripts[self.script_idx]);
+ let data = parser::data_at(script, self.opcode_idx + 1, len)?;
+ self.opcode_idx += len;
+ return Result::Ok(data);
+ }
+
+ fn has_flag(ref self: Engine, flag: ScriptFlags) -> bool {
+ self.flags & flag.into() == flag.into()
+ }
+
+ fn pop_if_bool(ref self: Engine) -> Result {
+ if !self.is_witness_active(0) || !self.has_flag(ScriptFlags::ScriptVerifyMinimalIf) {
+ return self.dstack.pop_bool();
}
- if self.has_flag(ScriptFlags::ScriptVerifyCleanStack) && self.dstack.len() != 1 {
- return Result::Err(Error::SCRIPT_NON_CLEAN_STACK);
+ let top = self.dstack.pop_byte_array()?;
+ if top.len() > 1 {
+ return Result::Err(Error::MINIMAL_IF);
}
- if self.dstack.len() < 1 {
- return Result::Err(Error::SCRIPT_EMPTY_STACK);
- } else {
- // TODO: pop bool?
- let top_stack = self.dstack.peek_byte_array(0)?;
- let ret_val = top_stack.clone();
- let mut is_ok = false;
- let mut i = 0;
- while i != top_stack.len() {
- if top_stack[i] != 0 {
- is_ok = true;
- break;
- }
- i += 1;
- };
- if is_ok {
- return Result::Ok(ret_val);
- } else {
- return Result::Err(Error::SCRIPT_FAILED);
+ if top.len() == 1 && top[0] != 0x01 {
+ return Result::Err(Error::MINIMAL_IF);
+ }
+ return Result::Ok(byte_array_to_bool(@top));
+ }
+
+ fn is_witness_active(ref self: Engine, version: i64) -> bool {
+ return self.witness_version == version && self.witness_program.len() != 0;
+ }
+
+ fn sub_script(ref self: Engine) -> ByteArray {
+ let script = *(self.scripts[self.script_idx]);
+ if self.last_code_sep == 0 {
+ return script.clone();
+ }
+
+ let mut sub_script = "";
+ let mut i = self.last_code_sep;
+ let script_len = script.len();
+ while i != script_len {
+ sub_script.append_byte(script[i]);
+ i += 1;
+ };
+ return sub_script;
+ }
+
+ fn get_dstack(ref self: Engine) -> Span {
+ return self.dstack.stack_to_span();
+ }
+
+ fn get_astack(ref self: Engine) -> Span {
+ return self.astack.stack_to_span();
+ }
+
+ fn skip(ref self: Engine) -> Result<(), felt252> {
+ let script = *(self.scripts[self.script_idx]);
+ let opcode = script.at(self.opcode_idx).unwrap();
+ Opcode::is_opcode_disabled(opcode, ref self)?;
+ let next = parser::next(script, self.opcode_idx)?;
+ self.opcode_idx = next;
+ return Result::Ok(());
+ }
+
+ fn check_minimal_data_push(ref self: Engine, opcode: u8) -> Result<(), felt252> {
+ if opcode == Opcode::OP_0 {
+ return Result::Ok(());
+ }
+ let script = *(self.scripts[self.script_idx]);
+ if opcode == Opcode::OP_DATA_1 {
+ let value: u8 = script.at(self.opcode_idx + 1).unwrap();
+ if value >= 1 && value <= 16 {
+ // Should be OP_1 to OP_16
+ return Result::Err(Error::MINIMAL_DATA);
}
+ if value == 0x81 {
+ // Should be OP_1NEGATE
+ return Result::Err(Error::MINIMAL_DATA);
+ }
+ }
+
+ // TODO: More checks?
+ if !Opcode::is_push_opcode(opcode) {
+ return Result::Ok(());
+ }
+
+ let len = parser::push_data_len(script, self.opcode_idx)?;
+ if len <= 75 {
+ // Should have used OP_DATA_X
+ return Result::Err(Error::MINIMAL_DATA);
+ } else if len <= 255 && opcode != Opcode::OP_PUSHDATA1 {
+ // Should have used OP_PUSHDATA1
+ return Result::Err(Error::MINIMAL_DATA);
+ } else if len <= 65535 && opcode != Opcode::OP_PUSHDATA2 {
+ // Should have used OP_PUSHDATA2
+ return Result::Err(Error::MINIMAL_DATA);
+ }
+ return Result::Ok(());
+ }
+
+ fn check_stack_size(ref self: Engine) -> Result<(), felt252> {
+ if self.dstack.len() + self.astack.len() > MAX_STACK_SIZE {
+ return Result::Err(Error::STACK_OVERFLOW);
}
+ return Result::Ok(());
}
- fn verify_witness(
- ref self: Engine, witness: Span
- ) -> Result<(), felt252> {
+ fn verify_witness(ref self: Engine, witness: Span) -> Result<(), felt252> {
if self.is_witness_active(0) {
// Verify a base witness (segwit) program, ie P2WSH || P2WPKH
- if self.witness_program.len() == 20 {
+ if self.witness_program.len() == PAY_TO_WITNESS_PUBKEY_HASH_SIZE {
// P2WPKH
if witness.len() != 2 {
- return Result::Err(Error::WITNESS_PROGRAM_INVALID);
+ return Result::Err(Error::WITNESS_PROGRAM_MISMATCH);
}
// OP_DUP OP_HASH160 OP_DATA_20 OP_EQUALVERIFY OP_CHECKSIG
let mut pk_script = hex_to_bytecode(@"0x76a914");
@@ -688,10 +601,10 @@ pub impl EngineInternalImpl of EngineInternalTrait {
self.scripts.append(@pk_script);
self.dstack.set_stack(witness, 0, witness.len());
- } else if self.witness_program.len() == 32 {
+ } else if self.witness_program.len() == PAY_TO_WITNESS_SCRIPT_HASH_SIZE {
// P2WSH
if witness.len() == 0 {
- return Result::Err(Error::WITNESS_PROGRAM_INVALID);
+ return Result::Err(Error::WITNESS_PROGRAM_EMPTY);
}
let witness_script = witness[witness.len() - 1];
if witness_script.len() > MAX_SCRIPT_SIZE {
@@ -699,14 +612,84 @@ pub impl EngineInternalImpl of EngineInternalTrait {
}
let witness_hash = sha256_byte_array(witness_script);
if witness_hash != self.witness_program {
- return Result::Err(Error::WITNESS_PROGRAM_INVALID);
+ return Result::Err(Error::WITNESS_PROGRAM_MISMATCH);
}
self.scripts.append(witness_script);
self.dstack.set_stack(witness, 0, witness.len() - 1);
} else {
+ return Result::Err(Error::WITNESS_PROGRAM_WRONG_LENGTH);
+ }
+ } else if self.is_witness_active(TAPROOT_WITNESS_VERSION)
+ && self.witness_program.len() == PAY_TO_TAPROOT_DATA_SIZE
+ && !self.bip16.clone() {
+ // Verify a taproot witness program
+ if !self.has_flag(ScriptFlags::ScriptVerifyTaproot) {
+ return Result::Ok(());
+ }
+
+ if witness.len() == 0 {
return Result::Err(Error::WITNESS_PROGRAM_INVALID);
}
+
+ self.use_taproot = true;
+ self
+ .taproot_context =
+ TaprootContextImpl::new(witness::serialized_witness_size(witness));
+ let mut witness_len = witness.len();
+ if taproot::is_annexed_witness(witness, witness_len) {
+ self.taproot_context.annex = witness[witness_len - 1];
+ witness_len -= 1; // Remove annex
+ }
+
+ if witness_len == 1 {
+ TaprootContextImpl::verify_taproot_spend(
+ @self.witness_program, witness[0], self.transaction, self.tx_idx
+ )?;
+ self.taproot_context.must_succeed = true;
+ return Result::Ok(());
+ } else {
+ let control_block = taproot::parse_control_block(witness[witness_len - 1])?;
+ let witness_script = witness[witness_len - 2];
+ control_block.verify_taproot_leaf(@self.witness_program, witness_script)?;
+
+ if Opcode::has_success_opcode(witness_script) {
+ if self.has_flag(ScriptFlags::ScriptVerifyDiscourageOpSuccess) {
+ return Result::Err(Error::DISCOURAGE_OP_SUCCESS);
+ }
+
+ self.taproot_context.must_succeed = true;
+ return Result::Ok(());
+ }
+
+ if control_block.leaf_version != taproot::BASE_LEAF_VERSION {
+ if self.has_flag(ScriptFlags::ScriptVerifyDiscourageUpgradeableTaprootVersion) {
+ return Result::Err(Error::DISCOURAGE_UPGRADABLE_TAPROOT_VERSION);
+ } else {
+ self.taproot_context.must_succeed = true;
+ return Result::Ok(());
+ }
+ }
+
+ self
+ .taproot_context
+ .tapleaf_hash = taproot::tap_hash(witness_script, taproot::BASE_LEAF_VERSION);
+ self.scripts.append(witness_script);
+ self.dstack.set_stack(witness, 0, witness_len - 2);
+ }
+ } else if self.has_flag(ScriptFlags::ScriptVerifyDiscourageUpgradeableWitnessProgram) {
+ return Result::Err(Error::DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM);
+ } else {
+ self.witness_program = "";
+ }
+
+ if self.is_witness_active(TAPROOT_WITNESS_VERSION) {
+ if self.dstack.len() > MAX_STACK_SIZE {
+ return Result::Err(Error::STACK_OVERFLOW);
+ }
+ }
+ if self.is_witness_active(BASE_SEGWIT_WITNESS_VERSION)
+ || self.is_witness_active(TAPROOT_WITNESS_VERSION) {
// Sanity checks
let mut err = '';
for w in self
@@ -720,62 +703,39 @@ pub impl EngineInternalImpl of EngineInternalTrait {
if err != '' {
return Result::Err(err);
}
- } else if self.is_witness_active(1) {
- // Verify a taproot witness program
- // TODO: Implement
- return Result::Err('Taproot not implemented');
- } else if self.has_flag(ScriptFlags::ScriptVerifyDiscourageUpgradeableWitnessProgram) {
- return Result::Err(Error::DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM);
- } else {
- self.witness_program = "";
}
-
return Result::Ok(());
}
- fn check_stack_size(ref self: Engine) -> Result<(), felt252> {
- if self.dstack.len() + self.astack.len() > MAX_STACK_SIZE {
- return Result::Err(Error::STACK_OVERFLOW);
+ fn check_error_condition(ref self: Engine, final: bool) -> Result {
+ // Check if execution is actually done
+ if self.script_idx < self.scripts.len() {
+ return Result::Err(Error::SCRIPT_UNFINISHED);
}
- return Result::Ok(());
- }
- fn check_minimal_data_push(ref self: Engine, opcode: u8) -> Result<(), felt252> {
- if opcode == Opcode::OP_0 {
- return Result::Ok(());
- }
- let script = *(self.scripts[self.script_idx]);
- if opcode == Opcode::OP_DATA_1 {
- let value: u8 = script.at(self.opcode_idx + 1).unwrap();
- if value <= 16 {
- // Should be OP_1 to OP_16
- return Result::Err(Error::MINIMAL_DATA);
- }
- if value == 0x81 {
- // Should be OP_1NEGATE
- return Result::Err(Error::MINIMAL_DATA);
- }
+ // Check if witness stack is clean
+ if final && self.is_witness_active(0) && self.dstack.len() != 1 { // TODO: Hardcoded 0
+ return Result::Err(Error::SCRIPT_NON_CLEAN_STACK);
}
- // TODO: More checks?
- if !Opcode::is_push_opcode(opcode) {
- return Result::Ok(());
+ if final && self.has_flag(ScriptFlags::ScriptVerifyCleanStack) && self.dstack.len() != 1 {
+ return Result::Err(Error::SCRIPT_NON_CLEAN_STACK);
}
- let len = self.push_data_len(opcode, self.opcode_idx)?;
- if len <= 75 {
- // Should have used OP_DATA_X
- return Result::Err(Error::MINIMAL_DATA);
- } else if len <= 255 && opcode != Opcode::OP_PUSHDATA1 {
- // Should have used OP_PUSHDATA1
- return Result::Err(Error::MINIMAL_DATA);
- } else if len <= 65535 && opcode != Opcode::OP_PUSHDATA2 {
- // Should have used OP_PUSHDATA2
- return Result::Err(Error::MINIMAL_DATA);
+ // Check if stack has at least one item
+ if self.dstack.len() == 0 {
+ return Result::Err(Error::SCRIPT_EMPTY_STACK);
+ } else {
+ // Check the final stack value
+ let is_ok = self.dstack.peek_bool(0)?;
+ if is_ok {
+ return Result::Ok(self.dstack.peek_byte_array(0)?);
+ } else {
+ return Result::Err(Error::SCRIPT_FAILED);
+ }
}
- return Result::Ok(());
}
- fn json(ref self: Engine) {
+ fn json(ref self: Engine) {
self.dstack.json();
}
}
diff --git a/packages/engine/src/errors.cairo b/packages/engine/src/errors.cairo
index 447347c6..fb75a47a 100644
--- a/packages/engine/src/errors.cairo
+++ b/packages/engine/src/errors.cairo
@@ -25,7 +25,26 @@ pub mod Error {
pub const MINIMAL_IF: felt252 = 'If conditional must be 0 or 1';
pub const DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM: felt252 = 'Upgradable witness program';
pub const WITNESS_PROGRAM_INVALID: felt252 = 'Invalid witness program';
+ pub const WITNESS_PROGRAM_MISMATCH: felt252 = 'Witness program mismatch';
+ pub const WITNESS_UNEXPECTED: felt252 = 'Unexpected witness data';
+ pub const WITNESS_MALLEATED: felt252 = 'Witness program with sig script';
+ pub const WITNESS_MALLEATED_P2SH: felt252 = 'Signature script for p2sh wit';
+ pub const WITNESS_PUBKEYTYPE: felt252 = 'Non-compressed key post-segwit';
+ pub const WITNESS_PROGRAM_WRONG_LENGTH: felt252 = 'Witness program wrong length';
+ pub const WITNESS_PROGRAM_EMPTY: felt252 = 'Empty witness program';
pub const SCRIPT_TOO_LARGE: felt252 = 'Script is too large';
+ pub const CODESEPARATOR_NON_SEGWIT: felt252 = 'CODESEPARATOR in non-segwit';
+ pub const TAPROOT_MULTISIG: felt252 = 'Multisig in taproot script';
+ pub const TAPROOT_EMPTY_PUBKEY: felt252 = 'Empty pubkey in taproot script';
+ pub const TAPROOT_INVALID_CONTROL_BLOCK: felt252 = 'Invalid control block';
+ pub const TAPROOT_INVALID_SIG: felt252 = 'Invalid signature in tap script';
+ pub const TAPROOT_PARITY_MISMATCH: felt252 = 'Parity mismatch in tap script';
+ pub const TAPROOT_INVALID_MERKLE_PROOF: felt252 = 'Invalid taproot merkle proof';
+ pub const DISCOURAGE_OP_SUCCESS: felt252 = 'OP_SUCCESS is discouraged';
+ pub const DISCOURAGE_UPGRADABLE_TAPROOT_VERSION: felt252 = 'Upgradable taproot version';
+ pub const TAPROOT_SIGOPS_EXCEEDED: felt252 = 'Taproot sigops exceeded';
+ pub const SCRIPT_UNFINISHED: felt252 = 'Script unfinished';
+ pub const SCRIPT_ERR_SIG_DER: felt252 = 'Signature DER error';
}
pub fn byte_array_err(err: felt252) -> ByteArray {
diff --git a/packages/engine/src/scriptflags.cairo b/packages/engine/src/flags.cairo
similarity index 100%
rename from packages/engine/src/scriptflags.cairo
rename to packages/engine/src/flags.cairo
diff --git a/packages/engine/src/hash_cache.cairo b/packages/engine/src/hash_cache.cairo
new file mode 100644
index 00000000..1eef18a7
--- /dev/null
+++ b/packages/engine/src/hash_cache.cairo
@@ -0,0 +1,145 @@
+use crate::transaction::{
+ EngineTransactionInputTrait, EngineTransactionOutputTrait, EngineTransactionTrait
+};
+use shinigami_utils::bytecode::int_size_in_bytes;
+use shinigami_utils::hash::double_sha256;
+
+#[derive(Clone, Copy, Drop)]
+pub struct SegwitSigHashMidstate {
+ pub hash_prevouts_v0: u256,
+ pub hash_sequence_v0: u256,
+ pub hash_outputs_v0: u256
+}
+
+pub trait SigHashMidstateTrait<
+ I,
+ O,
+ T,
+ +EngineTransactionInputTrait,
+ +EngineTransactionOutputTrait,
+ +EngineTransactionTrait
+> {
+ fn new(transaction: @T) -> SegwitSigHashMidstate;
+}
+
+pub impl SigHashMidstateImpl<
+ I,
+ O,
+ T,
+ impl IEngineTransactionInput: EngineTransactionInputTrait,
+ impl IEngineTransactionOutput: EngineTransactionOutputTrait,
+ impl IEngineTransaction: EngineTransactionTrait<
+ T, I, O, IEngineTransactionInput, IEngineTransactionOutput
+ >
+> of SigHashMidstateTrait {
+ fn new(transaction: @T) -> SegwitSigHashMidstate {
+ let mut prevouts_v0_bytes: ByteArray = "";
+ let inputs = transaction.get_transaction_inputs();
+ for input in inputs {
+ let txid = input.get_prevout_txid();
+ prevouts_v0_bytes.append_word(txid.high.into(), 16);
+ prevouts_v0_bytes.append_word(txid.low.into(), 16);
+ prevouts_v0_bytes.append_word_rev(input.get_prevout_vout().into(), 4);
+ };
+ let mut sequence_v0_bytes: ByteArray = "";
+ for input in inputs {
+ sequence_v0_bytes.append_word_rev(input.get_sequence().into(), 4);
+ };
+ let mut outputs_v0_bytes: ByteArray = "";
+ let outputs = transaction.get_transaction_outputs();
+ for output in outputs {
+ outputs_v0_bytes.append_word_rev(output.get_value().into(), 8);
+ outputs_v0_bytes
+ .append_word_rev(
+ output.get_publickey_script().len().into(),
+ int_size_in_bytes(output.get_publickey_script().len())
+ );
+ outputs_v0_bytes.append(output.get_publickey_script());
+ };
+ SegwitSigHashMidstate {
+ hash_prevouts_v0: double_sha256(@prevouts_v0_bytes),
+ hash_sequence_v0: double_sha256(@sequence_v0_bytes),
+ hash_outputs_v0: double_sha256(@outputs_v0_bytes)
+ }
+ }
+}
+
+// SigCache implements an Schnorr+ECDSA signature verification cache. Only valid signatures will be
+// added to the cache.
+pub trait SigCacheTrait {
+ // Returns true if sig cache contains sig_hash corresponding to signature and public key
+ fn exists(sig_hash: u256, signature: ByteArray, pub_key: ByteArray) -> bool;
+ // Adds a signature to the cache
+ fn add(sig_hash: u256, signature: ByteArray, pub_key: ByteArray);
+}
+
+// TODO
+#[derive(Drop)]
+pub struct HashCache {}
+
+// HashCache caches the midstate of segwit v0 and v1 sighashes
+pub trait HashCacheTrait<
+ I,
+ O,
+ T,
+ +EngineTransactionInputTrait,
+ +EngineTransactionOutputTrait,
+ +EngineTransactionTrait
+> {
+ fn new(transaction: @T) -> HashCache;
+
+ // v0 represents sighash midstate used in the base segwit signatures BIP-143
+ fn get_hash_prevouts_v0(self: @HashCache) -> u256;
+ fn get_hash_sequence_v0(self: @HashCache) -> u256;
+ fn get_hash_outputs_v0(self: @HashCache) -> u256;
+
+ // v1 represents sighash midstate used to compute taproot signatures BIP-341
+ fn get_hash_prevouts_v1(self: @HashCache) -> u256;
+ fn get_hash_sequence_v1(self: @HashCache) -> u256;
+ fn get_hash_outputs_v1(self: @HashCache) -> u256;
+ fn get_hash_input_scripts_v1(self: @HashCache) -> u256;
+}
+
+
+pub impl HashCacheImpl<
+ I,
+ O,
+ T,
+ impl IEngineTransactionInput: EngineTransactionInputTrait,
+ impl IEngineTransactionOutput: EngineTransactionOutputTrait,
+ impl IEngineTransaction: EngineTransactionTrait<
+ T, I, O, IEngineTransactionInput, IEngineTransactionOutput
+ >
+> of HashCacheTrait {
+ fn new(transaction: @T) -> HashCache {
+ HashCache {}
+ }
+
+ fn get_hash_prevouts_v0(self: @HashCache) -> u256 {
+ 0
+ }
+
+ fn get_hash_sequence_v0(self: @HashCache) -> u256 {
+ 0
+ }
+
+ fn get_hash_outputs_v0(self: @HashCache) -> u256 {
+ 0
+ }
+
+ fn get_hash_prevouts_v1(self: @HashCache) -> u256 {
+ 0
+ }
+
+ fn get_hash_sequence_v1(self: @HashCache) -> u256 {
+ 0
+ }
+
+ fn get_hash_outputs_v1(self: @HashCache) -> u256 {
+ 0
+ }
+
+ fn get_hash_input_scripts_v1(self: @HashCache) -> u256 {
+ 0
+ }
+}
diff --git a/packages/engine/src/lib.cairo b/packages/engine/src/lib.cairo
index d7a0fab3..8ab98940 100644
--- a/packages/engine/src/lib.cairo
+++ b/packages/engine/src/lib.cairo
@@ -1,9 +1,10 @@
pub mod engine;
+pub mod parser;
pub mod stack;
pub mod cond_stack;
-pub mod validate;
-pub mod utxo;
pub mod witness;
+pub mod taproot;
+pub mod hash_cache;
pub mod errors;
pub mod opcodes {
pub mod opcodes;
@@ -16,38 +17,22 @@ pub mod opcodes {
pub mod arithmetic;
pub mod crypto;
pub mod utils;
- #[cfg(test)]
- mod tests {
- mod test_constants;
- mod test_flow;
- mod test_locktime;
- mod test_stack;
- mod test_splice;
- mod test_bitwise;
- mod test_arithmetic;
- mod test_crypto;
- mod test_reserved;
- mod test_disabled;
- mod utils;
- }
pub use opcodes::Opcode;
}
pub mod scriptnum;
pub use scriptnum::ScriptNum;
-pub mod scriptflags;
+pub mod flags;
pub mod signature {
pub mod signature;
pub mod sighash;
pub mod constants;
pub mod utils;
- pub use signature::{BaseSigVerifier, BaseSigVerifierTrait};
+ pub use signature::{
+ BaseSigVerifier, BaseSigVerifierTrait, TaprootSigVerifier, TaprootSigVerifierTrait
+ };
}
pub mod transaction;
#[cfg(test)]
mod tests {
- mod test_coinbase;
- mod test_transactions;
mod test_scriptnum;
- mod test_p2pk;
- mod test_p2pkh;
}
diff --git a/packages/engine/src/opcodes/constants.cairo b/packages/engine/src/opcodes/constants.cairo
index adbb7b16..ca6ff4d3 100644
--- a/packages/engine/src/opcodes/constants.cairo
+++ b/packages/engine/src/opcodes/constants.cairo
@@ -1,4 +1,7 @@
-use crate::engine::{Engine, EngineExtrasTrait};
+use crate::engine::{Engine, EngineInternalTrait};
+use crate::transaction::{
+ EngineTransactionTrait, EngineTransactionInputTrait, EngineTransactionOutputTrait
+};
use crate::stack::ScriptStackTrait;
use shinigami_utils::byte_array::byte_array_to_felt252_le;
@@ -7,14 +10,42 @@ pub fn opcode_false>(ref engine: Engine) -> Result<(), felt252> {
return Result::Ok(());
}
-pub fn opcode_push_data>(n: usize, ref engine: Engine) -> Result<(), felt252> {
- let data = EngineExtrasTrait::::pull_data(ref engine, n)?;
+pub fn opcode_push_data<
+ T,
+ +Drop,
+ I,
+ +Drop,
+ impl IEngineTransactionInputTrait: EngineTransactionInputTrait,
+ O,
+ +Drop,
+ impl IEngineTransactionOutputTrait: EngineTransactionOutputTrait,
+ impl IEngineTransactionTrait: EngineTransactionTrait<
+ T, I, O, IEngineTransactionInputTrait, IEngineTransactionOutputTrait
+ >
+>(
+ n: usize, ref engine: Engine
+) -> Result<(), felt252> {
+ let data = EngineInternalTrait::::pull_data(ref engine, n)?;
engine.dstack.push_byte_array(data);
return Result::Ok(());
}
-pub fn opcode_push_data_x>(n: usize, ref engine: Engine) -> Result<(), felt252> {
- let data_len_bytes = EngineExtrasTrait::::pull_data(ref engine, n)?;
+pub fn opcode_push_data_x<
+ T,
+ +Drop,
+ I,
+ +Drop,
+ impl IEngineTransactionInputTrait: EngineTransactionInputTrait,
+ O,
+ +Drop,
+ impl IEngineTransactionOutputTrait: EngineTransactionOutputTrait,
+ impl IEngineTransactionTrait: EngineTransactionTrait<
+ T, I, O, IEngineTransactionInputTrait, IEngineTransactionOutputTrait
+ >
+>(
+ n: usize, ref engine: Engine
+) -> Result<(), felt252> {
+ let data_len_bytes = EngineInternalTrait::::pull_data(ref engine, n)?;
let data_len: usize = byte_array_to_felt252_le(@data_len_bytes).try_into().unwrap();
let data = engine.pull_data(data_len)?;
engine.dstack.push_byte_array(data);
diff --git a/packages/engine/src/opcodes/crypto.cairo b/packages/engine/src/opcodes/crypto.cairo
index f7738eb7..c9761aac 100644
--- a/packages/engine/src/opcodes/crypto.cairo
+++ b/packages/engine/src/opcodes/crypto.cairo
@@ -1,64 +1,44 @@
-use crate::engine::{Engine, EngineExtrasTrait};
+use crate::engine::{Engine, EngineInternalImpl};
use crate::transaction::{
EngineTransactionTrait, EngineTransactionInputTrait, EngineTransactionOutputTrait
};
use crate::stack::ScriptStackTrait;
-use crate::scriptflags::ScriptFlags;
+use crate::flags::ScriptFlags;
use crate::signature::signature;
use crate::signature::sighash;
-use crate::signature::signature::BaseSigVerifierTrait;
use starknet::secp256_trait::{is_valid_signature};
-use core::sha256::compute_sha256_byte_array;
+use core::num::traits::OverflowingAdd;
+use crate::signature::signature::{
+ BaseSigVerifierTrait, BaseSegwitSigVerifierTrait, TaprootSigVerifierTrait
+};
+use shinigami_utils::hash::{sha256_byte_array, double_sha256_bytearray};
use crate::opcodes::utils;
use crate::scriptnum::ScriptNum;
use crate::errors::Error;
+use crate::taproot::TaprootContextTrait;
const MAX_KEYS_PER_MULTISIG: i64 = 20;
+const BASE_SEGWIT_VERSION: i64 = 0;
pub fn opcode_sha256>(ref engine: Engine) -> Result<(), felt252> {
let arr = @engine.dstack.pop_byte_array()?;
- let res = compute_sha256_byte_array(arr).span();
- let mut res_bytes: ByteArray = "";
- let mut i: usize = 0;
- while i != res.len() {
- res_bytes.append_word((*res[i]).into(), 4);
- i += 1;
- };
- engine.dstack.push_byte_array(res_bytes);
+ let res = sha256_byte_array(arr);
+ engine.dstack.push_byte_array(res);
return Result::Ok(());
}
pub fn opcode_hash160>(ref engine: Engine) -> Result<(), felt252> {
let m = engine.dstack.pop_byte_array()?;
- let res = compute_sha256_byte_array(@m).span();
- let mut res_bytes: ByteArray = "";
- let mut i: usize = 0;
- while i != res.len() {
- res_bytes.append_word((*res[i]).into(), 4);
- i += 1;
- };
- let h: ByteArray = ripemd160::ripemd160_hash(@res_bytes).into();
+ let res = sha256_byte_array(@m);
+ let h: ByteArray = ripemd160::ripemd160_hash(@res).into();
engine.dstack.push_byte_array(h);
return Result::Ok(());
}
pub fn opcode_hash256>(ref engine: Engine) -> Result<(), felt252> {
let m = engine.dstack.pop_byte_array()?;
- let res = compute_sha256_byte_array(@m).span();
- let mut res_bytes: ByteArray = "";
- let mut i: usize = 0;
- while i != res.len() {
- res_bytes.append_word((*res[i]).into(), 4);
- i += 1;
- };
- let res2 = compute_sha256_byte_array(@res_bytes).span();
- let mut res2_bytes: ByteArray = "";
- let mut j: usize = 0;
- while j != res2.len() {
- res2_bytes.append_word((*res2[j]).into(), 4);
- j += 1;
- };
- engine.dstack.push_byte_array(res2_bytes);
+ let res = double_sha256_bytearray(@m);
+ engine.dstack.push_byte_array(res.into());
return Result::Ok(());
}
@@ -92,32 +72,66 @@ pub fn opcode_checksig<
return Result::Ok(());
}
- // TODO: add witness context inside engine to check if witness is active
- // if witness is active use BaseSigVerifier
let mut is_valid: bool = false;
- let res = BaseSigVerifierTrait::new(ref engine, @full_sig_bytes, @pk_bytes);
- if res.is_err() {
- // TODO: Some errors can return an error code instead of pushing false?
- engine.dstack.push_bool(false);
- return Result::Ok(());
- }
- let mut sig_verifier = res.unwrap();
+ if engine.witness_program.len() == 0 {
+ // Base Signature Verification
+ let res = BaseSigVerifierTrait::new(ref engine, @full_sig_bytes, @pk_bytes);
+ if res.is_err() {
+ let err = res.unwrap_err();
+ if err == Error::SCRIPT_ERR_SIG_DER || err == Error::WITNESS_PUBKEYTYPE {
+ return Result::Err(err);
+ };
+ engine.dstack.push_bool(false);
+ return Result::Ok(());
+ }
- if sig_verifier.verify(ref engine) {
- is_valid = true;
- } else {
- is_valid = false;
- }
- // else use BaseSigWitnessVerifier
- // let mut sig_verifier: BaseSigWitnessVerifier = BaseSigWitnessVerifierTrait::new(ref engine,
- // @full_sig_bytes, @pk_bytes)?;
+ let mut sig_verifier = res.unwrap();
+ if BaseSigVerifierTrait::verify(ref sig_verifier, ref engine) {
+ is_valid = true;
+ } else {
+ is_valid = false;
+ }
+ } else if engine.is_witness_active(0) {
+ // Witness Signature Verification
+ let res = BaseSigVerifierTrait::new(ref engine, @full_sig_bytes, @pk_bytes);
+ if res.is_err() {
+ let err = res.unwrap_err();
+ if err == Error::SCRIPT_ERR_SIG_DER || err == Error::WITNESS_PUBKEYTYPE {
+ return Result::Err(err);
+ };
+ engine.dstack.push_bool(false);
+ return Result::Ok(());
+ }
- // if sig_verifier.verify(ref engine) {
- // is_valid = true;
- // } else {
- // is_valid = false;
- // }
+ let mut sig_verifier = res.unwrap();
+ if BaseSegwitSigVerifierTrait::verify(ref sig_verifier, ref engine) {
+ is_valid = true;
+ } else {
+ is_valid = false;
+ }
+ } else if engine.use_taproot {
+ // Taproot Signature Verification
+ engine.taproot_context.use_ops_budget()?;
+ if pk_bytes.len() == 0 {
+ return Result::Err(Error::TAPROOT_EMPTY_PUBKEY);
+ }
+ let mut verifier = TaprootSigVerifierTrait::<
+ I, O, T
+ >::new(@full_sig_bytes, @pk_bytes, engine.taproot_context.annex)?;
+ if !(TaprootSigVerifierTrait::::verify(ref verifier)) {
+ return Result::Err(Error::TAPROOT_INVALID_SIG);
+ }
+
+ let mut verifier = TaprootSigVerifierTrait::<
+ I, O, T
+ >::new_base(@full_sig_bytes, @pk_bytes)?;
+ is_valid = TaprootSigVerifierTrait::::verify(ref verifier);
+ }
+
+ if !is_valid && @engine.use_taproot == @true {
+ return Result::Err(Error::SIG_NULLFAIL);
+ }
if !is_valid && engine.has_flag(ScriptFlags::ScriptVerifyNullFail) && full_sig_bytes.len() > 0 {
return Result::Err(Error::SIG_NULLFAIL);
}
@@ -141,8 +155,11 @@ pub fn opcode_checkmultisig<
>(
ref engine: Engine
) -> Result<(), felt252> {
- // TODO Error on taproot exec
+ if engine.use_taproot {
+ return Result::Err(Error::TAPROOT_MULTISIG);
+ }
+ let verify_der = engine.has_flag(ScriptFlags::ScriptVerifyDERSignatures);
// Get number of public keys and construct array
let num_keys = engine.dstack.pop_int()?;
let mut num_pub_keys: i64 = ScriptNum::to_int32(num_keys).into();
@@ -202,10 +219,10 @@ pub fn opcode_checkmultisig<
let mut script = engine.sub_script();
- // TODO: add witness context inside engine to check if witness is active
let mut s: u32 = 0;
- while s != sigs.len() {
- script = signature::remove_signature(script, sigs.at(s));
+ let end = sigs.len();
+ while s != end {
+ script = signature::remove_signature(@script, sigs.at(s)).clone();
s += 1;
};
@@ -227,36 +244,42 @@ pub fn opcode_checkmultisig<
if sig.len() == 0 {
continue;
}
-
let res = signature::parse_base_sig_and_pk(ref engine, pub_key, sig);
if res.is_err() {
success = false;
err = res.unwrap_err();
break;
}
+
let (parsed_pub_key, parsed_sig, hash_type) = res.unwrap();
let sig_hash: u256 = sighash::calc_signature_hash(
- @script, hash_type, ref engine.transaction, engine.tx_idx
+ @script, hash_type, engine.transaction, engine.tx_idx
);
+
if is_valid_signature(sig_hash, parsed_sig.r, parsed_sig.s, parsed_pub_key) {
sig_idx += 1;
num_sigs -= 1;
}
};
+
if err != 0 {
return Result::Err(err);
}
- if !success && engine.has_flag(ScriptFlags::ScriptVerifyNullFail) {
- let mut err = '';
- for s in sigs {
- if s.len() > 0 {
- err = Error::SIG_NULLFAIL;
- break;
+ if !success {
+ if engine.has_flag(ScriptFlags::ScriptVerifyNullFail) {
+ let mut err = '';
+ for s in sigs {
+ if s.len() > 0 {
+ err = Error::SIG_NULLFAIL;
+ break;
+ }
+ };
+ if err != '' {
+ return Result::Err(err);
}
- };
- if err != '' {
- return Result::Err(err);
+ } else if verify_der {
+ return Result::Err(Error::SCRIPT_ERR_SIG_DER);
}
}
@@ -264,15 +287,30 @@ pub fn opcode_checkmultisig<
Result::Ok(())
}
-pub fn opcode_codeseparator>(ref engine: Engine) -> Result<(), felt252> {
+pub fn opcode_codeseparator<
+ T,
+ +Drop,
+ I,
+ +Drop,
+ impl IEngineTransactionInputTrait: EngineTransactionInputTrait,
+ O,
+ +Drop,
+ impl IEngineTransactionOutputTrait: EngineTransactionOutputTrait,
+ impl IEngineTransactionTrait: EngineTransactionTrait<
+ T, I, O, IEngineTransactionInputTrait, IEngineTransactionOutputTrait
+ >
+>(
+ ref engine: Engine
+) -> Result<(), felt252> {
engine.last_code_sep = engine.opcode_idx;
- // TODO Disable OP_CODESEPARATOR for non-segwit scripts.
- // if engine.witness_program.len() == 0 &&
- // engine.has_flag(ScriptFlags::ScriptVerifyConstScriptCode) {
-
- // return Result::Err('opcode_codeseparator:non-segwit');
- // }
+ if !engine.use_taproot {
+ // TODO: Check if this is correct
+ engine.taproot_context.code_sep = engine.opcode_idx;
+ } else if engine.witness_program.len() == 0
+ && engine.has_flag(ScriptFlags::ScriptVerifyConstScriptCode) {
+ return Result::Err(Error::CODESEPARATOR_NON_SEGWIT);
+ }
Result::Ok(())
}
@@ -323,3 +361,79 @@ pub fn opcode_sha1>(ref engine: Engine) -> Result<(), felt252> {
engine.dstack.push_byte_array(h);
return Result::Ok(());
}
+
+// https://github.com/btcsuite/btcd/blob/67b8efd3ba53b60ff0eba5d79babe2c3d82f6c54/txscript/opcode.go#L2126
+// opcodeCheckSigAdd implements the OP_CHECKSIGADD operation defined in BIP
+// 342. This is a replacement for OP_CHECKMULTISIGVERIFY and OP_CHECKMULTISIG
+// that lends better to batch sig validation, as well as a possible future of
+// signature aggregation across inputs.
+//
+// The op code takes a public key, an integer (N) and a signature, and returns
+// N if the signature was the empty vector, and n+1 otherwise.
+//
+// Stack transformation: [... pubkey n signature] -> [... n | n+1 ] -> [...]
+pub fn opcode_checksigadd<
+ T,
+ +Drop,
+ I,
+ +Drop,
+ impl IEngineTransactionInputTrait: EngineTransactionInputTrait,
+ O,
+ +Drop,
+ impl IEngineTransactionOutputTrait: EngineTransactionOutputTrait,
+ impl IEngineTransactionTrait: EngineTransactionTrait<
+ T, I, O, IEngineTransactionInputTrait, IEngineTransactionOutputTrait
+ >
+>(
+ ref engine: Engine
+) -> Result<(), felt252> {
+ // This op code can only be used if tapscript execution is active.
+ // Before the soft fork, this opcode was marked as an invalid reserved
+ // op code.
+ if !engine.use_taproot {
+ return Result::Err(Error::OPCODE_RESERVED);
+ }
+
+ let pk_bytes: ByteArray = engine.dstack.pop_byte_array()?;
+ let n: i64 = engine.dstack.pop_int()?;
+ let sig_bytes: ByteArray = engine.dstack.pop_byte_array()?;
+
+ // Only non-empty signatures count towards the total tapscript sig op
+ // limit.
+ if sig_bytes.len() != 0 {
+ // Account for changes in the sig ops budget after this execution.
+ engine.taproot_context.use_ops_budget()?;
+ }
+
+ // Empty public keys immediately cause execution to fail.
+ if pk_bytes.len() == 0 {
+ return Result::Err(Error::TAPROOT_EMPTY_PUBKEY);
+ }
+
+ // If the signature is empty, then we'll just push the value N back
+ // onto the stack and continue from here.
+ if sig_bytes.len() == 0 {
+ engine.dstack.push_int(n);
+ return Result::Ok(());
+ }
+
+ // Otherwise, we'll attempt to validate the signature as normal.
+ //
+ // If the constructor fails immediately, then it's because the public
+ // key size is zero, so we'll fail all script execution.
+ let mut verifier = TaprootSigVerifierTrait::<
+ I, O, T
+ >::new(@sig_bytes, @pk_bytes, engine.taproot_context.annex)?;
+ if !(TaprootSigVerifierTrait::::verify(ref verifier)) {
+ return Result::Err(Error::TAPROOT_INVALID_SIG);
+ }
+
+ // Otherwise, we increment the accumulatorInt by one, and push that
+ // back onto the stack.
+ let (n_add_1, overflow) = n.overflowing_add(1);
+ if overflow {
+ return Result::Err(Error::STACK_OVERFLOW);
+ }
+ engine.dstack.push_int(n_add_1);
+ Result::Ok(())
+}
diff --git a/packages/engine/src/opcodes/flow.cairo b/packages/engine/src/opcodes/flow.cairo
index 56ec7e25..efe97979 100644
--- a/packages/engine/src/opcodes/flow.cairo
+++ b/packages/engine/src/opcodes/flow.cairo
@@ -1,23 +1,30 @@
-use crate::engine::{Engine, EngineExtrasTrait};
+use crate::engine::{Engine, EngineInternalTrait};
+use crate::transaction::{
+ EngineTransactionTrait, EngineTransactionInputTrait, EngineTransactionOutputTrait
+};
+use crate::flags::ScriptFlags;
use crate::cond_stack::ConditionalStackTrait;
use crate::opcodes::{utils, Opcode};
-use crate::scriptflags::ScriptFlags;
use crate::errors::Error;
-pub fn is_branching_opcode(opcode: u8) -> bool {
- if opcode == Opcode::OP_IF
- || opcode == Opcode::OP_NOTIF
- || opcode == Opcode::OP_ELSE
- || opcode == Opcode::OP_ENDIF {
- return true;
- }
- return false;
-}
-
-pub fn opcode_nop>(ref engine: Engine, opcode: u8) -> Result<(), felt252> {
+pub fn opcode_nop<
+ T,
+ +Drop,
+ I,
+ +Drop,
+ impl IEngineTransactionInputTrait: EngineTransactionInputTrait,
+ O,
+ +Drop,
+ impl IEngineTransactionOutputTrait: EngineTransactionOutputTrait,
+ impl IEngineTransactionTrait: EngineTransactionTrait<
+ T, I, O, IEngineTransactionInputTrait, IEngineTransactionOutputTrait
+ >
+>(
+ ref engine: Engine, opcode: u8
+) -> Result<(), felt252> {
if opcode != Opcode::OP_NOP
- && EngineExtrasTrait::<
- T
+ && EngineInternalTrait::<
+ I, O, T
>::has_flag(ref engine, ScriptFlags::ScriptDiscourageUpgradableNops) {
return Result::Err(Error::SCRIPT_DISCOURAGE_UPGRADABLE_NOPS);
}
@@ -28,7 +35,21 @@ pub fn opcode_nop>(ref engine: Engine, opcode: u8) -> Result<(),
const op_cond_false: u8 = 0;
const op_cond_true: u8 = 1;
const op_cond_skip: u8 = 2;
-pub fn opcode_if>(ref engine: Engine) -> Result<(), felt252> {
+pub fn opcode_if<
+ T,
+ +Drop,
+ I,
+ +Drop,
+ impl IEngineTransactionInputTrait: EngineTransactionInputTrait,
+ O,
+ +Drop,
+ impl IEngineTransactionOutputTrait: EngineTransactionOutputTrait,
+ impl IEngineTransactionTrait: EngineTransactionTrait<
+ T, I, O, IEngineTransactionInputTrait, IEngineTransactionOutputTrait
+ >
+>(
+ ref engine: Engine
+) -> Result<(), felt252> {
let mut cond = op_cond_false;
// TODO: Pop if bool
if engine.cond_stack.branch_executing() {
@@ -43,7 +64,21 @@ pub fn opcode_if>(ref engine: Engine) -> Result<(), felt252> {
return Result::Ok(());
}
-pub fn opcode_notif>(ref engine: Engine) -> Result<(), felt252> {
+pub fn opcode_notif<
+ T,
+ +Drop,
+ I,
+ +Drop,
+ impl IEngineTransactionInputTrait: EngineTransactionInputTrait,
+ O,
+ +Drop,
+ impl IEngineTransactionOutputTrait: EngineTransactionOutputTrait,
+ impl IEngineTransactionTrait: EngineTransactionTrait<
+ T, I, O, IEngineTransactionInputTrait, IEngineTransactionOutputTrait
+ >
+>(
+ ref engine: Engine
+) -> Result<(), felt252> {
let mut cond = op_cond_false;
if engine.cond_stack.branch_executing() {
let ok = engine.pop_if_bool()?;
diff --git a/packages/engine/src/opcodes/locktime.cairo b/packages/engine/src/opcodes/locktime.cairo
index 7dbd114f..4cd068c6 100644
--- a/packages/engine/src/opcodes/locktime.cairo
+++ b/packages/engine/src/opcodes/locktime.cairo
@@ -1,9 +1,9 @@
-use crate::engine::{Engine, EngineExtrasTrait};
+use crate::engine::{Engine, EngineInternalImpl};
use crate::transaction::{
EngineTransactionTrait, EngineTransactionInputTrait, EngineTransactionOutputTrait
};
use crate::errors::Error;
-use crate::scriptflags::ScriptFlags;
+use crate::flags::ScriptFlags;
use crate::scriptnum::ScriptNum;
use crate::stack::ScriptStackTrait;
@@ -53,7 +53,7 @@ pub fn opcode_checklocktimeverify<
let tx_locktime: i64 = EngineTransactionTrait::<
T, I, O, IEngineTransactionInputTrait, IEngineTransactionOutputTrait
- >::get_locktime(@engine.transaction)
+ >::get_locktime(engine.transaction)
.into();
// Get locktime as 5 byte integer because 'tx_locktime' is u32
let stack_locktime: i64 = ScriptNum::try_into_num_n_bytes(
@@ -68,7 +68,7 @@ pub fn opcode_checklocktimeverify<
// behavior of OP_CHECKLOCKTIMEVERIFY can be bypassed
let transaction_input = EngineTransactionTrait::<
T, I, O, IEngineTransactionInputTrait, IEngineTransactionOutputTrait
- >::get_transaction_inputs(@engine.transaction)
+ >::get_transaction_inputs(engine.transaction)
.at(engine.tx_idx);
let sequence = EngineTransactionInputTrait::::get_sequence(transaction_input);
if sequence == SEQUENCE_MAX {
@@ -121,14 +121,14 @@ pub fn opcode_checksequenceverify<
// Prevent trigger OP_CHECKSEQUENCEVERIFY before tx version 2
let version = EngineTransactionTrait::<
T, I, O, IEngineTransactionInputTrait, IEngineTransactionOutputTrait
- >::get_version(@engine.transaction);
+ >::get_version(engine.transaction);
if version < 2 {
return Result::Err(Error::INVALID_TX_VERSION);
}
let transaction_input = EngineTransactionTrait::<
T, I, O, IEngineTransactionInputTrait, IEngineTransactionOutputTrait
- >::get_transaction_inputs(@engine.transaction)
+ >::get_transaction_inputs(engine.transaction)
.at(engine.tx_idx);
let tx_sequence: u32 = EngineTransactionInputTrait::