Skip to content

Commit

Permalink
Feat: Generics and Crates (keep-starknet-strange#220)
Browse files Browse the repository at this point in the history
<!-- enter the gh issue after hash -->
Generic engine and crates refactor

- [ ] issue #
- [ ] follows contribution
[guide](https://github.com/keep-starknet-strange/shinigami/blob/main/CONTRIBUTING.md)
- [ ] code change includes tests

<!-- PR description below -->
  • Loading branch information
b-j-roberts authored Sep 17, 2024
1 parent b42984e commit 0029571
Show file tree
Hide file tree
Showing 68 changed files with 1,572 additions and 1,092 deletions.
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
scarb 2.8.0
scarb 2.8.2
33 changes: 28 additions & 5 deletions Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,42 @@
version = 1

[[package]]
name = "ripemd160"
name = "cmds"
version = "0.1.0"
source = "git+https://github.com/j1mbo64/ripemd160_cairo.git#cdc5ab58b0acc64db87e0b03851fb18213977dc8"
dependencies = [
"compiler",
"engine",
"utils",
]

[[package]]
name = "sha1"
name = "compiler"
version = "0.1.0"
source = "git+https://github.com/j1mbo64/sha1_cairo.git#280b4c64ae457fdc4bd7cd807efd17e8dced654e"
dependencies = [
"engine",
"utils",
]

[[package]]
name = "shinigami"
name = "engine"
version = "0.1.0"
dependencies = [
"compiler",
"ripemd160",
"sha1",
"utils",
]

[[package]]
name = "ripemd160"
version = "0.1.0"
source = "git+https://github.com/j1mbo64/ripemd160_cairo.git#cdc5ab58b0acc64db87e0b03851fb18213977dc8"

[[package]]
name = "sha1"
version = "0.1.0"
source = "git+https://github.com/j1mbo64/sha1_cairo.git#280b4c64ae457fdc4bd7cd807efd17e8dced654e"

[[package]]
name = "utils"
version = "0.1.0"
16 changes: 11 additions & 5 deletions Scarb.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
[package]
[workspace]
members = ["packages/*"]

[workspace.package]
name = "shinigami"
description = "Bitcoin Script Engine"
version = "0.1.0"
cairo-version = "2.8.2"
edition = "2024_07"
readme = "README.md"
license-file = "LICENSE"
repository = "https://github.com/keep-starknet-strange/shinigami.git"

[dependencies]
[workspace.dependencies]
ripemd160 = { git = "https://github.com/j1mbo64/ripemd160_cairo.git" }
sha1 = { git = "https://github.com/j1mbo64/sha1_cairo.git" }

[dev-dependencies]
cairo_test = "2.8.0"
cairo_test = "2.8.2"
2 changes: 1 addition & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM --platform=linux/amd64 node:21.7.1-alpine
RUN apk add --no-cache bash curl git jq ncurses

SHELL ["/bin/bash", "-c"]
RUN curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash -s -- -v 2.8.0
RUN curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash -s -- -v 2.8.2
RUN curl https://get.starkli.sh | sh && \
source /root/.starkli/env && \
starkliup
Expand Down
Empty file added packages/cmds/README.md
Empty file.
15 changes: 15 additions & 0 deletions packages/cmds/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "cmds"
version = "0.1.0"
edition = "2024_07"

[dependencies]
compiler = { path = "../compiler" }
engine = { path = "../engine" }
utils = { path = "../utils" }

[dev-dependencies]
cairo_test.workspace = true

[scripts]
lint = "scarb fmt"
1 change: 1 addition & 0 deletions packages/cmds/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod main;
39 changes: 20 additions & 19 deletions src/main.cairo → packages/cmds/src/main.cairo
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::compiler::CompilerImpl;
use crate::engine::{EngineImpl, EngineTrait};
use crate::transaction::{TransactionImpl, TransactionTrait};
use crate::utxo::UTXO;
use crate::validate;
use crate::utils;
use crate::scriptflags;
use crate::witness;
use compiler::compiler::CompilerImpl;
use engine::engine::EngineInternalImpl;
use engine::transaction::{TransactionImpl, TransactionTrait};
use engine::utxo::UTXO;
use engine::validate;
use engine::scriptflags;
use engine::witness;
use utils::byte_array::felt252_to_byte_array;
use utils::bytecode::hex_to_bytecode;

#[derive(Clone, Drop)]
struct InputData {
Expand Down Expand Up @@ -41,7 +42,7 @@ fn run_with_flags(input: InputDataWithFlags) -> Result<(), felt252> {
let script_sig = compiler.compile(input.ScriptSig)?;
let tx = TransactionImpl::new_signed(script_sig);
let flags = scriptflags::parse_flags(input.Flags);
let mut engine = EngineImpl::new(@script_pubkey, tx, 0, flags, 0)?;
let mut engine = EngineInternalImpl::new(@script_pubkey, tx, 0, flags, 0)?;
let _ = engine.execute()?;
Result::Ok(())
}
Expand All @@ -61,7 +62,7 @@ fn run_with_witness(input: InputDataWithWitness) -> Result<(), felt252> {
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 = EngineImpl::new(@script_pubkey, tx, 0, flags, 0)?;
let mut engine = EngineInternalImpl::new(@script_pubkey, tx, 0, flags, 0)?;
let _ = engine.execute()?;
Result::Ok(())
}
Expand All @@ -77,7 +78,7 @@ fn run(input: InputData) -> Result<(), felt252> {
let compiler = CompilerImpl::new();
let script_sig = compiler.compile(input.ScriptSig)?;
let tx = TransactionImpl::new_signed(script_sig);
let mut engine = EngineImpl::new(@script_pubkey, tx, 0, 0, 0)?;
let mut engine = EngineInternalImpl::new(@script_pubkey, tx, 0, 0, 0)?;
let _ = engine.execute()?;
Result::Ok(())
}
Expand All @@ -93,7 +94,7 @@ fn run_with_json(input: InputData) -> Result<(), felt252> {
let compiler = CompilerImpl::new();
let script_sig = compiler.compile(input.ScriptSig)?;
let tx = TransactionImpl::new_signed(script_sig);
let mut engine = EngineImpl::new(@script_pubkey, tx, 0, 0, 0)?;
let mut engine = EngineInternalImpl::new(@script_pubkey, tx, 0, 0, 0)?;
let _ = engine.execute()?;
engine.json();
Result::Ok(())
Expand All @@ -110,7 +111,7 @@ fn debug(input: InputData) -> Result<bool, felt252> {
let compiler = CompilerImpl::new();
let script_sig = compiler.compile(input.ScriptSig)?;
let tx = TransactionImpl::new_signed(script_sig);
let mut engine = EngineImpl::new(@script_pubkey, tx, 0, 0, 0)?;
let mut engine = EngineInternalImpl::new(@script_pubkey, tx, 0, 0, 0)?;
let mut res = Result::Ok(true);
while true {
res = engine.step();
Expand All @@ -133,7 +134,7 @@ fn main(input: InputDataWithFlags) -> u8 {
1
},
Result::Err(e) => {
println!("Execution failed: {}", utils::felt252_to_byte_array(e));
println!("Execution failed: {}", felt252_to_byte_array(e));
0
}
}
Expand All @@ -147,7 +148,7 @@ fn main_with_witness(input: InputDataWithWitness) -> u8 {
1
},
Result::Err(e) => {
println!("Execution failed: {}", utils::felt252_to_byte_array(e));
println!("Execution failed: {}", felt252_to_byte_array(e));
0
}
}
Expand All @@ -161,7 +162,7 @@ fn backend_run(input: InputData) -> u8 {
1
},
Result::Err(e) => {
println!("Execution failed: {}", utils::felt252_to_byte_array(e));
println!("Execution failed: {}", felt252_to_byte_array(e));
0
}
}
Expand All @@ -175,7 +176,7 @@ fn backend_debug(input: InputData) -> u8 {
1
},
Result::Err(e) => {
println!("Execution failed: {}", utils::felt252_to_byte_array(e));
println!("Execution failed: {}", felt252_to_byte_array(e));
0
}
}
Expand All @@ -189,7 +190,7 @@ struct ValidateRawInput {

fn run_raw_transaction(input: ValidateRawInput) -> u8 {
println!("Running Bitcoin Script with raw transaction: '{}'", input.raw_transaction);
let raw_transaction = utils::hex_to_bytecode(@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);
match res {
Expand All @@ -198,7 +199,7 @@ fn run_raw_transaction(input: ValidateRawInput) -> u8 {
1
},
Result::Err(e) => {
println!("Execution failed: {}", utils::felt252_to_byte_array(e));
println!("Execution failed: {}", felt252_to_byte_array(e));
0
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Bitcoin Script Compiler in Cairo
14 changes: 14 additions & 0 deletions packages/compiler/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "compiler"
version = "0.1.0"
edition = "2024_07"

[dependencies]
engine = { path = "../engine" }
utils = { path = "../utils" }

[dev-dependencies]
cairo_test.workspace = true

[scripts]
lint = "scarb fmt"
104 changes: 93 additions & 11 deletions src/compiler.cairo → packages/compiler/src/compiler.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use core::dict::Felt252Dict;
use crate::opcodes::Opcode;
use crate::utils;
use engine::opcodes::Opcode;
use engine::scriptnum::ScriptNum;
use utils::bytecode::hex_to_bytecode;
use utils::byte_array::byte_array_to_felt252_be;
use crate::utils::{is_hex, is_number, is_string};

// Compiler that takes a Bitcoin Script program and compiles it into a bytecode
#[derive(Destruct)]
Expand Down Expand Up @@ -357,16 +360,14 @@ pub impl CompilerImpl of CompilerTrait {
let mut err = '';
while i != script_len {
let script_item = split_script.at(i);
if utils::is_hex(script_item) {
ByteArrayTrait::append(ref bytecode, @utils::hex_to_bytecode(script_item));
} else if utils::is_string(script_item) {
ByteArrayTrait::append(ref bytecode, @utils::string_to_bytecode(script_item));
} else if utils::is_number(script_item) {
ByteArrayTrait::append(ref bytecode, @utils::number_to_bytecode(script_item));
if is_hex(script_item) {
ByteArrayTrait::append(ref bytecode, @hex_to_bytecode(script_item));
} else if is_string(script_item) {
ByteArrayTrait::append(ref bytecode, @string_to_bytecode(script_item));
} else if is_number(script_item) {
ByteArrayTrait::append(ref bytecode, @number_to_bytecode(script_item));
} else {
let opcode_nullable = self
.opcodes
.get(utils::byte_array_to_felt252_be(script_item));
let opcode_nullable = self.opcodes.get(byte_array_to_felt252_be(script_item));
if opcode_nullable.is_null() {
err = 'Compiler error: unknown opcode';
break;
Expand All @@ -381,3 +382,84 @@ pub impl CompilerImpl of CompilerTrait {
Result::Ok(bytecode)
}
}

// Remove the surrounding quotes and add the corrent append opcodes to the front
// https://github.com/btcsuite/btcd/blob/b161cd6a199b4e35acec66afc5aad221f05fe1e3/txs
// cript/scriptbuilder.go#L159
pub fn string_to_bytecode(script_item: @ByteArray) -> ByteArray {
let mut bytecode = "";
let mut i = 1;
let word_len = script_item.len() - 2;
let end = script_item.len() - 1;
if word_len == 0 || (word_len == 1 && script_item[1] == 0) {
bytecode.append_byte(Opcode::OP_0);
return bytecode;
} else if word_len == 1 && script_item[1] <= 16 {
bytecode.append_byte(Opcode::OP_1 - 1 + script_item[1]);
return bytecode;
} else if word_len == 1 && script_item[1] == 0x81 {
bytecode.append_byte(Opcode::OP_1NEGATE);
return bytecode;
}

if word_len < Opcode::OP_PUSHDATA1.into() {
bytecode.append_byte(Opcode::OP_DATA_1 - 1 + word_len.try_into().unwrap());
} else if word_len < 0x100 {
bytecode.append_byte(Opcode::OP_PUSHDATA1);
bytecode.append_byte(word_len.try_into().unwrap());
} else if word_len < 0x10000 {
bytecode.append_byte(Opcode::OP_PUSHDATA2);
// TODO: Little-endian?
bytecode.append(@ScriptNum::wrap(word_len.into()));
} else {
bytecode.append_byte(Opcode::OP_PUSHDATA4);
bytecode.append(@ScriptNum::wrap(word_len.into()));
}
while i != end {
bytecode.append_byte(script_item[i]);
i += 1;
};
bytecode
}

// Convert a number to bytecode
pub fn number_to_bytecode(script_item: @ByteArray) -> ByteArray {
let mut bytecode = "";
let mut i = 0;
let script_item_len = script_item.len();
let zero = '0';
let negative = '-';
let mut is_negative = false;
if script_item[0] == negative {
is_negative = true;
i += 1;
}
let mut value: i64 = 0;
while i != script_item_len {
value = value * 10 + script_item[i].into() - zero;
i += 1;
};
if is_negative {
value = -value;
}
// TODO: Negative info lost before this
if value == -1 {
bytecode.append_byte(Opcode::OP_1NEGATE);
} else if value > 0 && value <= 16 {
bytecode.append_byte(Opcode::OP_1 - 1 + value.try_into().unwrap());
} else if value == 0 {
bytecode.append_byte(Opcode::OP_0);
} else {
// TODO: always script num?
let script_num = ScriptNum::wrap(value);
let script_num_len = script_num.len();
if script_num_len < Opcode::OP_PUSHDATA1.into() {
bytecode.append_byte(Opcode::OP_DATA_1 - 1 + script_num_len.try_into().unwrap());
} else if script_num_len < 0x100 {
bytecode.append_byte(Opcode::OP_PUSHDATA1);
bytecode.append_byte(script_num_len.try_into().unwrap());
}
bytecode.append(@script_num);
}
bytecode
}
7 changes: 7 additions & 0 deletions packages/compiler/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pub mod compiler;
pub mod utils;

#[cfg(test)]
mod tests {
mod test_compiler;
}
File renamed without changes.
Loading

0 comments on commit 0029571

Please sign in to comment.