Skip to content

Commit

Permalink
Add taproot: first bit
Browse files Browse the repository at this point in the history
  • Loading branch information
b-j-roberts committed Sep 23, 2024
1 parent 13f6cf4 commit 4fbe693
Show file tree
Hide file tree
Showing 9 changed files with 473 additions and 47 deletions.
103 changes: 88 additions & 15 deletions packages/engine/src/engine.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use shinigami_utils::byte_array::{byte_array_to_bool, byte_array_to_felt252_le};
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.
Expand Down Expand Up @@ -44,6 +46,13 @@ pub trait HashCacheTrait<
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)]
pub struct Engine<T> {
Expand All @@ -67,6 +76,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 @@ -171,7 +184,7 @@ pub impl EngineExtrasImpl<T, +Drop<T>> of EngineExtrasTrait<T> {
}

fn pop_if_bool(ref self: Engine<T>) -> Result<bool, felt252> {
if !self.is_witness_active(0) || !self.has_flag(ScriptFlags::ScriptVerifyMinimalIf) {
if !self.is_witness_active(TAPROOT_WITNESS_VERSION) && (!self.is_witness_active(BASE_SEGWIT_WITNESS_VERSION) || !self.has_flag(ScriptFlags::ScriptVerifyMinimalIf)) {
return self.dstack.pop_bool();
}
let top = self.dstack.pop_byte_array()?;
Expand Down Expand Up @@ -270,6 +283,8 @@ 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(),
Expand Down Expand Up @@ -544,7 +559,7 @@ 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;
Expand Down Expand Up @@ -641,7 +656,7 @@ pub impl EngineInternalImpl of EngineInternalTrait {
}

// TODO: CheckErrorCondition
if self.is_witness_active(0) && self.dstack.len() != 1 { // TODO: Hardcoded 0
if self.is_witness_active(BASE_SEGWIT_WITNESS_VERSION) && self.dstack.len() != 1 { // TODO: Hardcoded 0
return Result::Err(Error::SCRIPT_NON_CLEAN_STACK);
}
if self.has_flag(ScriptFlags::ScriptVerifyCleanStack) && self.dstack.len() != 1 {
Expand Down Expand Up @@ -674,9 +689,10 @@ pub impl EngineInternalImpl of EngineInternalTrait {
fn verify_witness(
ref self: Engine<Transaction>, witness: Span<ByteArray>
) -> Result<(), felt252> {
if self.is_witness_active(0) {
let witness_prog_len = self.witness_program.len();
if self.is_witness_active(BASE_SEGWIT_WITNESS_VERSION) {
// 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);
Expand All @@ -688,7 +704,7 @@ 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 witness_prog_len == PAY_TO_WITNESS_SCRIPT_HASH_SIZE {
// P2WSH
if witness.len() == 0 {
return Result::Err(Error::WITNESS_PROGRAM_INVALID);
Expand All @@ -707,6 +723,72 @@ pub impl EngineInternalImpl of EngineInternalTrait {
} else {
return Result::Err(Error::WITNESS_PROGRAM_INVALID);
}
} else if self.is_witness_active(TAPROOT_WITNESS_VERSION) && witness_prog_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 @@ -720,16 +802,7 @@ 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(());
}

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 @@ -26,6 +26,16 @@ pub mod Error {
pub const DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM: felt252 = 'Upgradable witness program';
pub const WITNESS_PROGRAM_INVALID: felt252 = 'Invalid 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 fn byte_array_err(err: felt252) -> ByteArray {
Expand Down
1 change: 1 addition & 0 deletions packages/engine/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod cond_stack;
pub mod validate;
pub mod utxo;
pub mod witness;
pub mod taproot;
pub mod errors;
pub mod opcodes {
pub mod opcodes;
Expand Down
64 changes: 36 additions & 28 deletions packages/engine/src/opcodes/crypto.cairo
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
use crate::engine::{Engine, EngineExtrasTrait};
use crate::transaction::{
EngineTransactionTrait, EngineTransactionInputTrait, EngineTransactionOutputTrait
EngineTransactionTrait, EngineTransactionInputTrait, EngineTransactionOutputTrait,
Transaction
};
use crate::stack::ScriptStackTrait;
use crate::scriptflags::ScriptFlags;
use crate::signature::signature;
use crate::signature::sighash;
use crate::signature::signature::BaseSigVerifierTrait;
use crate::signature::signature::{BaseSigVerifierTrait, TaprootSigVerifierTrait};
use starknet::secp256_trait::{is_valid_signature};
use core::sha256::compute_sha256_byte_array;
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 @@ -95,29 +98,32 @@ pub fn opcode_checksig<
// 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 {
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 sig_verifier.verify(ref engine) {
is_valid = true;
} else {
is_valid = sig_verifier.verify(ref engine);
} else if engine.is_witness_active(BASE_SEGWIT_VERSION) {
// TODO: Implement
is_valid = false;
}
// else use BaseSigWitnessVerifier
// let mut sig_verifier: BaseSigWitnessVerifier = BaseSigWitnessVerifierTrait::new(ref engine,
// @full_sig_bytes, @pk_bytes)?;
} else if engine.use_taproot {
engine.taproot_context.use_ops_budget()?;
if pk_bytes.len() == 0 {
return Result::Err(Error::TAPROOT_EMPTY_PUBKEY);
}

// if sig_verifier.verify(ref engine) {
// is_valid = true;
// } else {
// is_valid = false;
// }
let mut verifier = TaprootSigVerifierTrait::<Transaction>::new_base(@full_sig_bytes, @pk_bytes)?;
is_valid = TaprootSigVerifierTrait::<Transaction>::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);
}
Expand All @@ -141,7 +147,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);
}

// Get number of public keys and construct array
let num_keys = engine.dstack.pop_int()?;
Expand Down Expand Up @@ -267,12 +275,12 @@ pub fn opcode_checkmultisig<
pub fn opcode_codeseparator<T, +Drop<T>>(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
45 changes: 45 additions & 0 deletions packages/engine/src/opcodes/opcodes.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -507,4 +507,49 @@ pub mod Opcode {
+ push_data_len
);
}

pub fn is_success_opcode(opcode: u8) -> bool {
// TODO: To map
if opcode > 186 {
// OP_UNKNOWNX
return true;
}
if opcode == OP_RESERVED ||
opcode == OP_VER ||
opcode == OP_CAT ||
opcode == OP_SUBSTR ||
opcode == OP_LEFT ||
opcode == OP_RIGHT ||
opcode == OP_INVERT ||
opcode == OP_AND ||
opcode == OP_OR ||
opcode == OP_XOR ||
opcode == OP_RESERVED1 ||
opcode == OP_RESERVED2 ||
opcode == OP_2MUL ||
opcode == OP_2DIV ||
opcode == OP_MUL ||
opcode == OP_DIV ||
opcode == OP_MOD ||
opcode == OP_LSHIFT ||
opcode == OP_RSHIFT {
return true;
}
return false;
}

pub fn has_success_opcode(script: @ByteArray) -> bool {
let mut i = 0;
let mut result = false;
while i < script.len() {
let opcode = script[i];
if is_success_opcode(opcode) {
result = true;
break;
}
let data_len = data_len(i, script).unwrap();
i += data_len + 1;
};
return result;
}
}
Loading

0 comments on commit 4fbe693

Please sign in to comment.