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

Fix #62 incorrect hex of the locking script #64

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
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
10 changes: 5 additions & 5 deletions examples/node-test/tests/scripts.test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ pub enum BSVErrors {
#[error("Error deserialising TxOut field {0}: {1}")]
DeserialiseTxOut(String, #[source] std::io::Error),

#[error("NonScriptData can only appear after an OP_RETURN that is not in a branch block!")]
InvalidNonScriptData(),

#[error("Error serialising TxOut field {0}: {1}")]
SerialiseTxOut(String, #[source] std::io::Error),

Expand Down
3 changes: 3 additions & 0 deletions src/interpreter/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ pub enum InterpreterError {
#[error("Stack is empty")]
EmptyStack,

#[error("NonScriptData can not be pushed")]
NonScriptData,

#[error("Invalid OpCode: {0}")]
InvalidOpcode(OpCodes),

Expand Down
1 change: 1 addition & 0 deletions src/interpreter/script_matching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ impl Interpreter {
self.state.executed_opcodes.push(*size);
self.state.clone()
}
ScriptBit::NonScriptData(_) => return Err(InterpreterError::NonScriptData),
ScriptBit::If { code, pass, fail } => {
let predicate = self.state.stack.pop_bool()?;
self.state.executed_opcodes.push(*code);
Expand Down
164 changes: 155 additions & 9 deletions src/script/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ impl Script {
true => format!("{} {} {}", code, bytes.len(), hex::encode(bytes)),
false => hex::encode(bytes),
},
ScriptBit::NonScriptData(bytes) => format!("non-script-data:{}", hex::encode(bytes)),
ScriptBit::OpCode(code) => code.to_string(),
ScriptBit::If { code, pass, fail } => {
let mut string_parts = vec![];
Expand Down Expand Up @@ -96,6 +97,11 @@ impl Script {
pushbytes.extend(bytes);
pushbytes
}
ScriptBit::NonScriptData(bytes) => {
let mut pushbytes = vec![];
pushbytes.extend(bytes);
pushbytes
}
ScriptBit::If { code, pass, fail } => {
let mut bytes = vec![*code as u8];

Expand All @@ -116,6 +122,30 @@ impl Script {
bytes
}

fn check_script_bits(codes: &[ScriptBit]) -> () {
if codes.len() == 0 {
return;
}
let mut is_non_script_data = false;
for (i, scriptbit) in codes.iter().enumerate() {
match scriptbit {
ScriptBit::OpCode(OpCodes::OP_RETURN) => {
is_non_script_data = true;
}
ScriptBit::NonScriptData(_) => {
if is_non_script_data != true {
panic!("NonScriptData can only appear after OP_RETURN");
}

if i != codes.len() - 1 {
panic!("NonScriptData can only appear at the end of the script!");
}
}
_ => (),
}
}
}

pub fn to_asm_string_impl(&self, extended: bool) -> String {
Script::script_bits_to_asm_string(&self.0, extended)
}
Expand All @@ -128,11 +158,43 @@ impl Script {
let mut cursor = Cursor::new(bytes);

let mut bit_accumulator = vec![];
let mut scope_level = 0;
while let Ok(byte) = cursor.read_u8() {
if byte.eq(&(OpCodes::OP_IF as u8)) || byte.eq(&(OpCodes::OP_NOTIF as u8)) {
scope_level += 1;
} else if byte.eq(&(OpCodes::OP_ENDIF as u8)) {
scope_level -= 1;
} else if byte.eq(&(OpCodes::OP_RETURN as u8)) && scope_level == 0 {
bit_accumulator.push(ScriptBit::OpCode(OpCodes::OP_RETURN));

let len = cursor.get_ref().len();

let non_script_data_length = len - cursor.position() as usize;

if non_script_data_length > 0 {
let mut data: Vec<u8> = vec![0; non_script_data_length as usize];

match cursor.read(&mut data) {
Ok(_) => {
bit_accumulator.push(ScriptBit::NonScriptData(data));
}
Err(e) => return Err(BSVErrors::DeserialiseScript(format!("Failed to read OP_PUSH data {}", e))),
}
}

break;
}

if byte.ne(&(OpCodes::OP_0 as u8)) && byte.lt(&(OpCodes::OP_PUSHDATA1 as u8)) {
let mut data: Vec<u8> = vec![0; byte as usize];
match cursor.read(&mut data) {
Ok(len) => bit_accumulator.push(ScriptBit::Push(data[..len].to_vec())),
Ok(len) => {
if len == byte as usize {
bit_accumulator.push(ScriptBit::Push(data));
} else {
return Err(BSVErrors::DeserialiseScript(format!("Failed to read OP_PUSH data")));
}
}
Err(e) => return Err(BSVErrors::DeserialiseScript(format!("Failed to read OP_PUSH data {}", e))),
}
continue;
Expand All @@ -147,11 +209,17 @@ impl Script {
};

let mut data = vec![0; data_length];
if let Err(e) = cursor.read(&mut data) {
return Err(BSVErrors::DeserialiseScript(format!("Failed to read OP_PUSHDATA data {}", e)));
}

ScriptBit::PushData(v, data)
match cursor.read(&mut data) {
Ok(len) => {
if len == data_length as usize {
ScriptBit::PushData(v, data)
} else {
return Err(BSVErrors::DeserialiseScript(format!("Failed to read OP_PUSH data")));
}
}
Err(e) => return Err(BSVErrors::DeserialiseScript(format!("Failed to read OP_PUSH data {}", e))),
}
}
Some(v) => ScriptBit::OpCode(v),
None => return Err(BSVErrors::DeserialiseScript(format!("Unknown opcode {}", byte))),
Expand All @@ -169,8 +237,9 @@ impl Script {
Ok(Script(vec![ScriptBit::Coinbase(bytes.to_vec())]))
}

fn map_string_to_script_bit(code: &str) -> Result<ScriptBit, BSVErrors> {
fn map_string_to_script_bit(code: &str, is_non_script_data: bool) -> Result<ScriptBit, BSVErrors> {
let code = code.trim();

// Number OP_CODES
match code {
"0" => return Ok(ScriptBit::OpCode(OpCodes::OP_0)),
Expand Down Expand Up @@ -198,6 +267,15 @@ impl Script {
return Ok(ScriptBit::OpCode(opcode));
}

if code.starts_with("non-script-data:") {
if is_non_script_data {
let non_script_data = hex::decode(code.trim_start_matches("non-script-data:"))?;
return Ok(ScriptBit::NonScriptData(non_script_data));
} else {
return Err(BSVErrors::InvalidNonScriptData());
}
}

// PUSHDATA OP_CODES
let data_bytes = hex::decode(code)?;
let bit = match VarInt::get_pushdata_opcode(data_bytes.len() as u64) {
Expand Down Expand Up @@ -263,11 +341,78 @@ impl Script {
Ok(nested_bits)
}

/**
* Ordinary ASM, (for example, OP_RETURN 01 01) does not contain ScriptBit::NonScriptData after being converted into ScriptBit.
* This function wraps all ScriptBit after OP_RETURN with ScriptBit::NonScriptData.
*/
fn wrap_with_non_script_data(bits_iter: &mut Iter<ScriptBit>, non_script_data_index: usize) -> Vec<ScriptBit> {
let mut bits = vec![];
let mut non_script_data_bits = vec![];
let mut index: usize = 0;
while let Some(thing) = bits_iter.next() {
if index >= non_script_data_index {
match thing {
ScriptBit::NonScriptData(b) => bits.push(ScriptBit::NonScriptData(b.to_vec())),
o => non_script_data_bits.push(o.clone()),
}
} else {
bits.push(thing.clone())
}
index += 1;
}

if non_script_data_bits.len() > 0 {
bits.push(ScriptBit::NonScriptData(Script::script_bits_to_bytes(&non_script_data_bits)))
}

bits
}

pub fn from_asm_string(asm: &str) -> Result<Script, BSVErrors> {
let bits: Result<Vec<ScriptBit>, _> = asm.split(' ').filter(|x| !(x.is_empty() || x == &"\n" || x == &"\r")).map(Script::map_string_to_script_bit).collect();
let bits = Script::if_statement_pass(&mut bits?.iter())?;
let mut scope_level = 0;

Ok(Script(bits))
let mut is_non_script_data = false;

let mut non_script_data_index: usize = usize::MAX;

let bits: Result<Vec<ScriptBit>, _> = asm
.split(' ')
.filter(|x| !(x.is_empty() || x == &"\n" || x == &"\r"))
.enumerate()
.map(|(i, x)| match Script::map_string_to_script_bit(x, is_non_script_data) {
Ok(bit) => {
match bit {
ScriptBit::OpCode(_v @ (OpCodes::OP_IF | OpCodes::OP_NOTIF | OpCodes::OP_VERIF | OpCodes::OP_VERNOTIF)) => {
scope_level += 1;
}
ScriptBit::OpCode(OpCodes::OP_ENDIF) => {
scope_level -= 1;
}
ScriptBit::OpCode(OpCodes::OP_RETURN) => {
if scope_level == 0 {
is_non_script_data = true;
non_script_data_index = i + 1;
}
}
_ => (),
}
Ok(bit)
}
Err(e) => Err(e),
})
.collect();

if non_script_data_index != usize::MAX {
let bits = Script::wrap_with_non_script_data(&mut bits?.iter(), non_script_data_index);

let bits = Script::if_statement_pass(&mut bits.iter())?;

return Ok(Script(bits));
} else {
let bits = Script::if_statement_pass(&mut bits?.iter())?;

return Ok(Script(bits));
}
}

pub fn get_pushdata_prefix_bytes(length: usize) -> Result<Vec<u8>, BSVErrors> {
Expand Down Expand Up @@ -332,6 +477,7 @@ impl Script {
}

pub fn from_script_bits(bits: Vec<ScriptBit>) -> Script {
Script::check_script_bits(&bits);
Script(bits)
}

Expand Down
4 changes: 4 additions & 0 deletions src/script/script_bit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@ pub enum ScriptBit {
If { code: OpCodes, pass: Vec<ScriptBit>, fail: Option<Vec<ScriptBit>> },
Push(#[serde(serialize_with = "to_hex", deserialize_with = "from_hex")] Vec<u8>),
PushData(OpCodes, #[serde(serialize_with = "to_hex", deserialize_with = "from_hex")] Vec<u8>),
// "OP_RETURN" <non-script-data>,
// Any bytes after an OP_RETURN that is not in a branch block are not evaluated and there are no grammatical requirements for those bytes.
// https://github.com/bitcoin-sv-specs/protocol/blob/master/updates/genesis-spec.md#formal-grammar-for-bitcoin-script
NonScriptData(#[serde(serialize_with = "to_hex", deserialize_with = "from_hex")] Vec<u8>),
Coinbase(#[serde(serialize_with = "to_hex", deserialize_with = "from_hex")] Vec<u8>),
}
45 changes: 41 additions & 4 deletions src/script/script_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ pub enum ScriptTemplateErrors {
#[error("Script Template and Script lengths do not match.")]
LengthsDiffer,

#[error("Script Template includes invalid non-script-data.")]
InvalidNonScriptData,

#[error("{0}")]
MalformedHex(
#[from]
Expand Down Expand Up @@ -63,7 +66,7 @@ pub enum MatchDataTypes {
pub struct ScriptTemplate(Vec<MatchToken>);

impl ScriptTemplate {
fn map_string_to_match_token(code: &str) -> Result<MatchToken, ScriptTemplateErrors> {
fn map_string_to_match_token(code: &str, allowed_non_script_data: bool) -> Result<MatchToken, ScriptTemplateErrors> {
// Number OP_CODES
if code.len() < 3 {
if let Ok(num_code) = u8::from_str(code) {
Expand All @@ -86,6 +89,14 @@ impl ScriptTemplate {
Err(_) => (),
}

if code.starts_with("non-script-data:") {
if allowed_non_script_data {
return Ok(MatchToken::AnyData);
} else {
return Err(ScriptTemplateErrors::InvalidNonScriptData);
}
}

if code.starts_with(&OpCodes::OP_DATA.to_string()) {
// Match on >=
if let Some((_, length_str)) = code.split_once(">=") {
Expand Down Expand Up @@ -133,7 +144,33 @@ impl ScriptTemplate {
}

pub fn from_asm_string_impl(asm: &str) -> Result<ScriptTemplate, ScriptTemplateErrors> {
let tokens: Result<Vec<_>, _> = asm.split(' ').map(ScriptTemplate::map_string_to_match_token).collect();
let mut scope_level = 0;

let mut allowed_non_script_data = false;

let tokens: Result<Vec<_>, _> = asm
.split(' ')
.map(|x| match ScriptTemplate::map_string_to_match_token(x, allowed_non_script_data) {
Ok(bit) => {
match bit {
MatchToken::OpCode(_v @ (OpCodes::OP_IF | OpCodes::OP_NOTIF | OpCodes::OP_VERIF | OpCodes::OP_VERNOTIF)) => {
scope_level += 1;
}
MatchToken::OpCode(OpCodes::OP_ENDIF) => {
scope_level -= 1;
}
MatchToken::OpCode(OpCodes::OP_RETURN) => {
if scope_level == 0 {
allowed_non_script_data = true;
}
}
_ => (),
}
Ok(bit)
}
Err(e) => Err(e),
})
.collect();

Ok(ScriptTemplate(tokens?))
}
Expand Down Expand Up @@ -188,7 +225,7 @@ impl Script {

(MatchToken::AnyData, ScriptBit::Push(_)) => Ok(true),
(MatchToken::AnyData, ScriptBit::PushData(_, _)) => Ok(true),

(MatchToken::AnyData, ScriptBit::NonScriptData(_)) => Ok(true),
(MatchToken::Signature, ScriptBit::Push(sig_buf)) => Signature::from_der_impl(sig_buf).map(|_| true),

(MatchToken::PublicKey, ScriptBit::Push(pubkey_buf)) => PublicKey::from_bytes_impl(pubkey_buf).map(|_| true),
Expand Down Expand Up @@ -219,7 +256,7 @@ impl Script {

(MatchToken::AnyData, ScriptBit::Push(data)) => matches.push((MatchDataTypes::Data, data.clone())),
(MatchToken::AnyData, ScriptBit::PushData(_, data)) => matches.push((MatchDataTypes::Data, data.clone())),

(MatchToken::AnyData, ScriptBit::NonScriptData(data)) => matches.push((MatchDataTypes::Data, data.clone())),
(MatchToken::Signature, ScriptBit::Push(data)) => matches.push((MatchDataTypes::Signature, data.clone())),

(MatchToken::PublicKey, ScriptBit::Push(data)) => matches.push((MatchDataTypes::PublicKey, data.clone())),
Expand Down
Loading
Loading