Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge feat/taproot to main #270

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ pie showData
| OP_CHECKLOCKTIMEVERIFY | 0xb1 | ✅ | Marks transaction as invalid if the top stack item is greater than the transaction's nLockTime field, otherwise script evaluation continues as though an OP_NOP was executed. |
| OP_CHECKSEQUENCEVERIFY | 0xb2 | ✅ | Marks transaction as invalid if the relative lock time of the input is not equal to or longer than the value of the top stack item. |
| OP_NOP4-OP_NOP10 | 0xb3-0xb9 | ✅ | The word is ignored. Does not mark transaction as invalid. |
| OP_CHECKSIGADD | 0xba | | Increments n by one and returns to the stack if the signature is valid for the public key and transaction. Only available in tapscript. |
| OP_CHECKSIGADD | 0xba | | Increments n by one and returns to the stack if the signature is valid for the public key and transaction. Only available in tapscript. |

## References

Expand Down
102 changes: 90 additions & 12 deletions packages/engine/src/engine.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,21 @@ 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::taproot;
use crate::taproot::{TaprootContext, TaprootContextImpl, ControlBlockImpl};

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;

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)]
pub struct Engine<T> {
Expand All @@ -41,6 +50,10 @@ pub struct Engine<T> {
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
Expand Down Expand Up @@ -125,6 +138,8 @@ pub impl EngineImpl<
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(),
Expand Down Expand Up @@ -310,7 +325,7 @@ pub impl EngineImpl<
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;
Expand Down Expand Up @@ -462,6 +477,8 @@ pub impl EngineInternalImpl<
T, I, O, IEngineTransactionInput, IEngineTransactionOutput
>,
+Drop<T>,
+Drop<I>,
+Drop<O>,
> of EngineInternalTrait<I, O, T> {
fn pull_data(ref self: Engine<T>, len: usize) -> Result<ByteArray, felt252> {
let script = *(self.scripts[self.script_idx]);
Expand Down Expand Up @@ -572,7 +589,7 @@ pub impl EngineInternalImpl<
fn verify_witness(ref self: Engine<T>, witness: Span<ByteArray>) -> 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_MISMATCH);
Expand All @@ -584,7 +601,7 @@ pub impl EngineInternalImpl<

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_EMPTY);
Expand All @@ -603,6 +620,76 @@ pub impl EngineInternalImpl<
} 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
Expand All @@ -616,16 +703,7 @@ pub impl EngineInternalImpl<
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(());
}

Expand Down
10 changes: 10 additions & 0 deletions packages/engine/src/errors.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ pub mod Error {
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 INVALID_P2MS: felt252 = 'Invalid P2MS transaction';
pub const SCRIPT_UNFINISHED: felt252 = 'Script unfinished';
pub const SCRIPT_ERR_SIG_DER: felt252 = 'Signature DER error';
Expand Down
5 changes: 4 additions & 1 deletion packages/engine/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod parser;
pub mod stack;
pub mod cond_stack;
pub mod witness;
pub mod taproot;
pub mod hash_cache;
pub mod errors;
pub mod opcodes {
Expand All @@ -26,7 +27,9 @@ 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)]
Expand Down
104 changes: 94 additions & 10 deletions packages/engine/src/opcodes/crypto.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ use crate::stack::ScriptStackTrait;
use crate::flags::ScriptFlags;
use crate::signature::signature;
use crate::signature::sighash;
use crate::signature::signature::{BaseSigVerifierTrait, BaseSegwitSigVerifierTrait};
use starknet::secp256_trait::{is_valid_signature};
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<T, +Drop<T>>(ref engine: Engine<T>) -> Result<(), felt252> {
let arr = @engine.dstack.pop_byte_array()?;
Expand Down Expand Up @@ -104,8 +108,24 @@ pub fn opcode_checksig<
} else {
is_valid = false;
}
} // TODO: Add Taproot verification
} 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::<I, O, T>::verify(ref verifier)) {
return Result::Err(Error::TAPROOT_INVALID_SIG);
}
}

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);
}
Expand All @@ -129,7 +149,9 @@ pub fn opcode_checkmultisig<
>(
ref engine: Engine<T>
) -> 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
Expand Down Expand Up @@ -259,15 +281,30 @@ pub fn opcode_checkmultisig<
Result::Ok(())
}

pub fn opcode_codeseparator<T, +Drop<T>>(ref engine: Engine<T>) -> Result<(), felt252> {
pub fn opcode_codeseparator<
T,
+Drop<T>,
I,
+Drop<I>,
impl IEngineTransactionInputTrait: EngineTransactionInputTrait<I>,
O,
+Drop<O>,
impl IEngineTransactionOutputTrait: EngineTransactionOutputTrait<O>,
impl IEngineTransactionTrait: EngineTransactionTrait<
T, I, O, IEngineTransactionInputTrait, IEngineTransactionOutputTrait
>
>(
ref engine: Engine<T>
) -> 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(())
}
Expand Down Expand Up @@ -318,3 +355,50 @@ pub fn opcode_sha1<T, +Drop<T>>(ref engine: Engine<T>) -> Result<(), felt252> {
engine.dstack.push_byte_array(h);
return Result::Ok(());
}

pub fn opcode_checksigadd<
T,
+Drop<T>,
I,
+Drop<I>,
impl IEngineTransactionInputTrait: EngineTransactionInputTrait<I>,
O,
+Drop<O>,
impl IEngineTransactionOutputTrait: EngineTransactionOutputTrait<O>,
impl IEngineTransactionTrait: EngineTransactionTrait<
T, I, O, IEngineTransactionInputTrait, IEngineTransactionOutputTrait
>
>(
ref engine: Engine<T>
) -> Result<(), felt252> {
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()?;

if sig_bytes.len() != 0 {
engine.taproot_context.use_ops_budget()?;
}

if pk_bytes.len() == 0 {
return Result::Err(Error::TAPROOT_EMPTY_PUBKEY);
}

if sig_bytes.len() == 0 {
engine.dstack.push_int(n);
return Result::Ok(());
}

let mut verifier = TaprootSigVerifierTrait::<
I, O, T
>::new(@sig_bytes, @pk_bytes, engine.taproot_context.annex)?;
if !(TaprootSigVerifierTrait::<I, O, T>::verify(ref verifier)) {
return Result::Err(Error::TAPROOT_INVALID_SIG);
}

engine.dstack.push_int(n + 1);
Result::Ok(())
}
Loading