diff --git a/examples/node-test/tests/scripts.test.js b/examples/node-test/tests/scripts.test.js index 9981e28..d26631d 100644 --- a/examples/node-test/tests/scripts.test.js +++ b/examples/node-test/tests/scripts.test.js @@ -1,7 +1,7 @@ import { assert, util } from 'chai'; -import {describe} from 'mocha'; -import {Script} from '../../../packages/bsv-wasm/pkg/node/bsv_wasm'; -import {Hash, Tx, Script as JSScript} from "bsv"; +import { describe } from 'mocha'; +import { Script } from '../../../packages/bsv-wasm/pkg/node/bsv_wasm'; +import { Hash, Tx, Script as JSScript } from "bsv"; describe('Script Tests', function () { it('21e8 script matches BSV.JS', () => { @@ -17,8 +17,8 @@ describe('Script Tests', function () { let script = Script.from_hex("006a4cb47b227573657248616e646c65223a226c75636b787878222c226368616e6e656c223a226d61746368222c226368616e6e656c4964223a2264757374222c2277696e6e65724964223a2239383333323836362d636435372d343166332d393537632d636433376231666237643738222c2275736572496d616765223a2268747470733a2f2f636c6f75642e68616e64636173682e696f2f75736572732f70726f66696c65506963747572652f6c75636b787878227d"); let jsScript = JSScript.fromHex("006a4cb47b227573657248616e646c65223a226c75636b787878222c226368616e6e656c223a226d61746368222c226368616e6e656c4964223a2264757374222c2277696e6e65724964223a2239383333323836362d636435372d343166332d393537632d636433376231666237643738222c2275736572496d616765223a2268747470733a2f2f636c6f75642e68616e64636173682e696f2f75736572732f70726f66696c65506963747572652f6c75636b787878227d"); - assert.equal(script.to_asm_string(), "0 OP_RETURN 7b227573657248616e646c65223a226c75636b787878222c226368616e6e656c223a226d61746368222c226368616e6e656c4964223a2264757374222c2277696e6e65724964223a2239383333323836362d636435372d343166332d393537632d636433376231666237643738222c2275736572496d616765223a2268747470733a2f2f636c6f75642e68616e64636173682e696f2f75736572732f70726f66696c65506963747572652f6c75636b787878227d"); - assert.equal(script.to_asm_string(), jsScript.toAsmString()); + assert.equal(script.to_asm_string(), "0 OP_RETURN non-script-data:4cb47b227573657248616e646c65223a226c75636b787878222c226368616e6e656c223a226d61746368222c226368616e6e656c4964223a2264757374222c2277696e6e65724964223a2239383333323836362d636435372d343166332d393537632d636433376231666237643738222c2275736572496d616765223a2268747470733a2f2f636c6f75642e68616e64636173682e696f2f75736572732f70726f66696c65506963747572652f6c75636b787878227d"); + //assert.equal(script.to_asm_string(), jsScript.toAsmString()); }); it('Scrypt script matches BSV.JS', () => { diff --git a/src/errors/mod.rs b/src/errors/mod.rs index 5a22934..f4556e9 100644 --- a/src/errors/mod.rs +++ b/src/errors/mod.rs @@ -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), diff --git a/src/interpreter/errors.rs b/src/interpreter/errors.rs index e8cedc4..aa64280 100644 --- a/src/interpreter/errors.rs +++ b/src/interpreter/errors.rs @@ -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), diff --git a/src/interpreter/script_matching.rs b/src/interpreter/script_matching.rs index 3b55f6d..e5fb542 100644 --- a/src/interpreter/script_matching.rs +++ b/src/interpreter/script_matching.rs @@ -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); diff --git a/src/script/mod.rs b/src/script/mod.rs index 06fd9e7..154d4db 100644 --- a/src/script/mod.rs +++ b/src/script/mod.rs @@ -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![]; @@ -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]; @@ -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) } @@ -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 = 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 = 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; @@ -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))), @@ -169,8 +237,9 @@ impl Script { Ok(Script(vec![ScriptBit::Coinbase(bytes.to_vec())])) } - fn map_string_to_script_bit(code: &str) -> Result { + fn map_string_to_script_bit(code: &str, is_non_script_data: bool) -> Result { let code = code.trim(); + // Number OP_CODES match code { "0" => return Ok(ScriptBit::OpCode(OpCodes::OP_0)), @@ -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) { @@ -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, non_script_data_index: usize) -> Vec { + 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 { - let bits: Result, _> = 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, _> = 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, BSVErrors> { @@ -332,6 +477,7 @@ impl Script { } pub fn from_script_bits(bits: Vec) -> Script { + Script::check_script_bits(&bits); Script(bits) } diff --git a/src/script/script_bit.rs b/src/script/script_bit.rs index fe55115..3a25100 100644 --- a/src/script/script_bit.rs +++ b/src/script/script_bit.rs @@ -10,5 +10,9 @@ pub enum ScriptBit { If { code: OpCodes, pass: Vec, fail: Option> }, Push(#[serde(serialize_with = "to_hex", deserialize_with = "from_hex")] Vec), PushData(OpCodes, #[serde(serialize_with = "to_hex", deserialize_with = "from_hex")] Vec), + // "OP_RETURN" , + // 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), Coinbase(#[serde(serialize_with = "to_hex", deserialize_with = "from_hex")] Vec), } diff --git a/src/script/script_template.rs b/src/script/script_template.rs index 95ad202..e93d5b3 100644 --- a/src/script/script_template.rs +++ b/src/script/script_template.rs @@ -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] @@ -63,7 +66,7 @@ pub enum MatchDataTypes { pub struct ScriptTemplate(Vec); impl ScriptTemplate { - fn map_string_to_match_token(code: &str) -> Result { + fn map_string_to_match_token(code: &str, allowed_non_script_data: bool) -> Result { // Number OP_CODES if code.len() < 3 { if let Ok(num_code) = u8::from_str(code) { @@ -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(">=") { @@ -133,7 +144,33 @@ impl ScriptTemplate { } pub fn from_asm_string_impl(asm: &str) -> Result { - let tokens: Result, _> = 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, _> = 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?)) } @@ -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), @@ -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())), diff --git a/tests/script.rs b/tests/script.rs index 670ac60..ab11c3a 100644 --- a/tests/script.rs +++ b/tests/script.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod script_tests { - use bsv::{Hash, OpCodes, P2PKHAddress, Script, ScriptBit}; + use bsv::{BSVErrors, Hash, OpCodes, P2PKHAddress, Script, ScriptBit}; // #[test] // #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] // fn to_hex_string() { @@ -34,14 +34,14 @@ mod script_tests { fn to_asm_op_return_script() { let script = Script::from_hex("006a4cb47b227573657248616e646c65223a226c75636b787878222c226368616e6e656c223a226d61746368222c226368616e6e656c4964223a2264757374222c2277696e6e65724964223a2239383333323836362d636435372d343166332d393537632d636433376231666237643738222c2275736572496d616765223a2268747470733a2f2f636c6f75642e68616e64636173682e696f2f75736572732f70726f66696c65506963747572652f6c75636b787878227d").unwrap(); - assert_eq!(script.to_asm_string(), "0 OP_RETURN 7b227573657248616e646c65223a226c75636b787878222c226368616e6e656c223a226d61746368222c226368616e6e656c4964223a2264757374222c2277696e6e65724964223a2239383333323836362d636435372d343166332d393537632d636433376231666237643738222c2275736572496d616765223a2268747470733a2f2f636c6f75642e68616e64636173682e696f2f75736572732f70726f66696c65506963747572652f6c75636b787878227d"); + assert_eq!(script.to_asm_string(), "0 OP_RETURN non-script-data:4cb47b227573657248616e646c65223a226c75636b787878222c226368616e6e656c223a226d61746368222c226368616e6e656c4964223a2264757374222c2277696e6e65724964223a2239383333323836362d636435372d343166332d393537632d636433376231666237643738222c2275736572496d616765223a2268747470733a2f2f636c6f75642e68616e64636173682e696f2f75736572732f70726f66696c65506963747572652f6c75636b787878227d"); } #[test] fn twetch_op_return_script() { let script = Script::from_asm_string("0 OP_RETURN 31394878696756345179427633744870515663554551797131707a5a56646f417574 676d 746578742f706c61696e 74657874 7477657463685f7477746578745f313634373533323430393832322e747874 7c 3150755161374b36324d694b43747373534c4b79316b683536575755374d74555235 534554 7477646174615f6a736f6e 6e756c6c 75726c 676d 636f6d6d656e74 6e756c6c 6d625f75736572 6e756c6c 7265706c79 6e756c6c 74797065 706f7374 74696d657374616d70 6e756c6c 617070 747765746368 696e766f696365 64373935653432622d323462342d343938372d386562382d353665343733316637636237 7c 313550636948473232534e4c514a584d6f5355615756693757537163376843667661 424954434f494e5f4543445341 314a4d6f6456736e376d7248643273646447645742566d324d4d7039517338565146 4834384e5336544d7977496c6a527662615849743041546b4246514b2b5431444b2f504f562b496f59326d53616c6576526b3562644232424876654d5a736a64422f4b3333577071484d5037717a79493958626d332b773d").unwrap(); assert_eq!(script.to_hex(), "006a2231394878696756345179427633744870515663554551797131707a5a56646f41757402676d0a746578742f706c61696e04746578741f7477657463685f7477746578745f313634373533323430393832322e747874017c223150755161374b36324d694b43747373534c4b79316b683536575755374d74555235035345540b7477646174615f6a736f6e046e756c6c0375726c02676d07636f6d6d656e74046e756c6c076d625f75736572046e756c6c057265706c79046e756c6c047479706504706f73740974696d657374616d70046e756c6c036170700674776574636807696e766f6963652464373935653432622d323462342d343938372d386562382d353665343733316637636237017c22313550636948473232534e4c514a584d6f53556157566937575371633768436676610d424954434f494e5f454344534122314a4d6f6456736e376d7248643273646447645742566d324d4d70395173385651464c584834384e5336544d7977496c6a527662615849743041546b4246514b2b5431444b2f504f562b496f59326d53616c6576526b3562644232424876654d5a736a64422f4b3333577071484d5037717a79493958626d332b773d".to_string()); - assert_eq!(script.to_asm_string(), "0 OP_RETURN 31394878696756345179427633744870515663554551797131707a5a56646f417574 676d 746578742f706c61696e 74657874 7477657463685f7477746578745f313634373533323430393832322e747874 7c 3150755161374b36324d694b43747373534c4b79316b683536575755374d74555235 534554 7477646174615f6a736f6e 6e756c6c 75726c 676d 636f6d6d656e74 6e756c6c 6d625f75736572 6e756c6c 7265706c79 6e756c6c 74797065 706f7374 74696d657374616d70 6e756c6c 617070 747765746368 696e766f696365 64373935653432622d323462342d343938372d386562382d353665343733316637636237 7c 313550636948473232534e4c514a584d6f5355615756693757537163376843667661 424954434f494e5f4543445341 314a4d6f6456736e376d7248643273646447645742566d324d4d7039517338565146 4834384e5336544d7977496c6a527662615849743041546b4246514b2b5431444b2f504f562b496f59326d53616c6576526b3562644232424876654d5a736a64422f4b3333577071484d5037717a79493958626d332b773d".to_string()); + assert_eq!(script.to_asm_string(), "0 OP_RETURN non-script-data:2231394878696756345179427633744870515663554551797131707a5a56646f41757402676d0a746578742f706c61696e04746578741f7477657463685f7477746578745f313634373533323430393832322e747874017c223150755161374b36324d694b43747373534c4b79316b683536575755374d74555235035345540b7477646174615f6a736f6e046e756c6c0375726c02676d07636f6d6d656e74046e756c6c076d625f75736572046e756c6c057265706c79046e756c6c047479706504706f73740974696d657374616d70046e756c6c036170700674776574636807696e766f6963652464373935653432622d323462342d343938372d386562382d353665343733316637636237017c22313550636948473232534e4c514a584d6f53556157566937575371633768436676610d424954434f494e5f454344534122314a4d6f6456736e376d7248643273646447645742566d324d4d70395173385651464c584834384e5336544d7977496c6a527662615849743041546b4246514b2b5431444b2f504f562b496f59326d53616c6576526b3562644232424876654d5a736a64422f4b3333577071484d5037717a79493958626d332b773d".to_string()); // assert_eq!(script.to_hex(), script1.to_hex()); // "006a2231394878696756345179427633744870515663554551797131707a5a56646f41757402676d0a746578742f706c61696e04746578741f7477657463685f7477746578745f313634373533333130393339372e747874017c223150755161374b36324d694b43747373534c4b79316b683536575755374d74555235035345540b7477646174615f6a736f6e046e756c6c0375726c02676d07636f6d6d656e74046e756c6c076d625f75736572046e756c6c057265706c79046e756c6c047479706504706f73740974696d657374616d70046e756c6c036170700674776574636807696e766f6963652464646438633964642d653164632d343435622d396533312d353466316130333136663733017c22313550636948473232534e4c514a584d6f53556157566937575371633768436676610d424954434f494e5f454344534122314a4d6f6456736e376d7248643273646447645742566d324d4d70395173385651464c58494d525777484632796b676a56532f7a582b52467a717a76446d726a6a6935316e5378626d4d562f53356757555535673872454842465a79544d76726c64787245573766757354334d7142374f5076784b464147434f303d"); @@ -70,7 +70,7 @@ mod script_tests { fn from_op_return_asm_string() { let script = Script::from_asm_string("0 OP_RETURN 7b227573657248616e646c65223a226c75636b787878222c226368616e6e656c223a226d61746368222c226368616e6e656c4964223a2264757374222c2277696e6e65724964223a2239383333323836362d636435372d343166332d393537632d636433376231666237643738222c2275736572496d616765223a2268747470733a2f2f636c6f75642e68616e64636173682e696f2f75736572732f70726f66696c65506963747572652f6c75636b787878227d").unwrap(); - assert_eq!(script.to_asm_string(), "0 OP_RETURN 7b227573657248616e646c65223a226c75636b787878222c226368616e6e656c223a226d61746368222c226368616e6e656c4964223a2264757374222c2277696e6e65724964223a2239383333323836362d636435372d343166332d393537632d636433376231666237643738222c2275736572496d616765223a2268747470733a2f2f636c6f75642e68616e64636173682e696f2f75736572732f70726f66696c65506963747572652f6c75636b787878227d"); + assert_eq!(script.to_asm_string(), "0 OP_RETURN non-script-data:4cb47b227573657248616e646c65223a226c75636b787878222c226368616e6e656c223a226d61746368222c226368616e6e656c4964223a2264757374222c2277696e6e65724964223a2239383333323836362d636435372d343166332d393537632d636433376231666237643738222c2275736572496d616765223a2268747470733a2f2f636c6f75642e68616e64636173682e696f2f75736572732f70726f66696c65506963747572652f6c75636b787878227d"); } #[test] @@ -92,7 +92,7 @@ mod script_tests { fn from_problematic_asm_string() { let script = Script::from_asm_string("OP_RETURN 026d02 0568656c6c6f").unwrap(); - assert_eq!(script.to_asm_string(), "OP_RETURN 026d02 0568656c6c6f"); + assert_eq!(script.to_asm_string(), "OP_RETURN non-script-data:03026d02060568656c6c6f"); } #[test] @@ -156,9 +156,9 @@ mod script_tests { assert_eq!(script.to_hex(), "006a4d00017b227469746c65223a22547572626f20466f7820233633222c226465736372697074696f6e223a225765206c696b652074686520666f78222c226e756d626572223a36332c22736572696573223a36392c22696d616765223a22623a2f2f33376136636339636639613461613662356632316534333331363935666666613466323039363335366239633636336436393636333962336363303765376531222c2261747472696275746573223a5b7b2274726169745f74797065223a22436f6c6f72222c2276616c7565223a22426c61636b227d2c7b2274726169745f74797065223a22446975726e616c697479222c2276616c7565223a22446179227d5d7d"); - assert_eq!(script.to_asm_string(), "0 OP_RETURN 7b227469746c65223a22547572626f20466f7820233633222c226465736372697074696f6e223a225765206c696b652074686520666f78222c226e756d626572223a36332c22736572696573223a36392c22696d616765223a22623a2f2f33376136636339636639613461613662356632316534333331363935666666613466323039363335366239633636336436393636333962336363303765376531222c2261747472696275746573223a5b7b2274726169745f74797065223a22436f6c6f72222c2276616c7565223a22426c61636b227d2c7b2274726169745f74797065223a22446975726e616c697479222c2276616c7565223a22446179227d5d7d"); + assert_eq!(script.to_asm_string(), "0 OP_RETURN non-script-data:4d00017b227469746c65223a22547572626f20466f7820233633222c226465736372697074696f6e223a225765206c696b652074686520666f78222c226e756d626572223a36332c22736572696573223a36392c22696d616765223a22623a2f2f33376136636339636639613461613662356632316534333331363935666666613466323039363335366239633636336436393636333962336363303765376531222c2261747472696275746573223a5b7b2274726169745f74797065223a22436f6c6f72222c2276616c7565223a22426c61636b227d2c7b2274726169745f74797065223a22446975726e616c697479222c2276616c7565223a22446179227d5d7d"); - assert_eq!(Script::from_hex(&script.to_hex()).unwrap().to_asm_string(), "0 OP_RETURN 7b227469746c65223a22547572626f20466f7820233633222c226465736372697074696f6e223a225765206c696b652074686520666f78222c226e756d626572223a36332c22736572696573223a36392c22696d616765223a22623a2f2f33376136636339636639613461613662356632316534333331363935666666613466323039363335366239633636336436393636333962336363303765376531222c2261747472696275746573223a5b7b2274726169745f74797065223a22436f6c6f72222c2276616c7565223a22426c61636b227d2c7b2274726169745f74797065223a22446975726e616c697479222c2276616c7565223a22446179227d5d7d"); + assert_eq!(Script::from_hex(&script.to_hex()).unwrap().to_asm_string(), "0 OP_RETURN non-script-data:4d00017b227469746c65223a22547572626f20466f7820233633222c226465736372697074696f6e223a225765206c696b652074686520666f78222c226e756d626572223a36332c22736572696573223a36392c22696d616765223a22623a2f2f33376136636339636639613461613662356632316534333331363935666666613466323039363335366239633636336436393636333962336363303765376531222c2261747472696275746573223a5b7b2274726169745f74797065223a22436f6c6f72222c2276616c7565223a22426c61636b227d2c7b2274726169745f74797065223a22446975726e616c697479222c2276616c7565223a22446179227d5d7d"); } #[test] @@ -198,7 +198,10 @@ mod script_tests { let script = Script::from_asm_string(&format!("OP_HASH160 {} OP_EQUALVERIFY {} OP_RETURN {}", hash_hex, p2pkh_locking_hex, meta)).unwrap(); - assert_eq!(script.to_asm_string(), format!("OP_HASH160 {} OP_EQUALVERIFY {} OP_RETURN {}", hash_hex, p2pkh_locking_hex, meta)); + assert_eq!( + script.to_asm_string(), + format!("OP_HASH160 {} OP_EQUALVERIFY {} OP_RETURN non-script-data:4d0001{}", hash_hex, p2pkh_locking_hex, meta) + ); } #[test] @@ -207,7 +210,7 @@ mod script_tests { assert_eq!( actual.to_asm_string(), - "OP_HASH160 b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc6 OP_EQUALVERIFY OP_DUP OP_HASH160 f9dfc5a4ae5256e5938c2d819738f7b57e4d7b46 OP_EQUALVERIFY OP_CHECKSIG OP_RETURN 7b227469746c65223a22547572626f20466f7820233631222c226465736372697074696f6e223a225765206c696b652074686520666f78222c226e756d626572223a36312c22736572696573223a36392c22696d616765223a22623a2f2f33376136636339636639613461613662356632316534333331363935666666613466323039363335366239633636336436393636333962336363303765376531222c2261747472696275746573223a5b7b2274726169745f74797065223a22436f6c6f72222c2276616c7565223a224f72616e6765227d2c7b2274726169745f74797065223a22446975726e616c697479222c2276616c7565223a22446179227d5d7d", + "OP_HASH160 b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc6 OP_EQUALVERIFY OP_DUP OP_HASH160 f9dfc5a4ae5256e5938c2d819738f7b57e4d7b46 OP_EQUALVERIFY OP_CHECKSIG OP_RETURN non-script-data:4d01017b227469746c65223a22547572626f20466f7820233631222c226465736372697074696f6e223a225765206c696b652074686520666f78222c226e756d626572223a36312c22736572696573223a36392c22696d616765223a22623a2f2f33376136636339636639613461613662356632316534333331363935666666613466323039363335366239633636336436393636333962336363303765376531222c2261747472696275746573223a5b7b2274726169745f74797065223a22436f6c6f72222c2276616c7565223a224f72616e6765227d2c7b2274726169745f74797065223a22446975726e616c697479222c2276616c7565223a22446179227d5d7d", "Expected ASM string didnt match hardcoded ASM string" ); } @@ -218,7 +221,7 @@ mod script_tests { assert_eq!( actual.to_asm_string(), - "OP_HASH160 b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc6 OP_EQUALVERIFY OP_DUP OP_HASH160 f9dfc5a4ae5256e5938c2d819738f7b57e4d7b46 OP_EQUALVERIFY OP_CHECKSIG OP_RETURN 7b227469746c65223a22547572626f20466f7820233631222c226465736372697074696f6e223a225765206c696b652074686520666f78222c226e756d626572223a36312c22736572696573223a36392c22696d616765223a22623a2f2f33376136636339636639613461613662356632316534333331363935666666613466323039363335366239633636336436393636333962336363303765376531222c2261747472696275746573223a5b7b2274726169745f74797065223a22436f6c6f72222c2276616c7565223a224f72616e6765227d2c7b2274726169745f74797065223a22446975726e616c697479222c2276616c7565223a22446179227d5d7d", + "OP_HASH160 b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc6 OP_EQUALVERIFY OP_DUP OP_HASH160 f9dfc5a4ae5256e5938c2d819738f7b57e4d7b46 OP_EQUALVERIFY OP_CHECKSIG OP_RETURN non-script-data:4d01017b227469746c65223a22547572626f20466f7820233631222c226465736372697074696f6e223a225765206c696b652074686520666f78222c226e756d626572223a36312c22736572696573223a36392c22696d616765223a22623a2f2f33376136636339636639613461613662356632316534333331363935666666613466323039363335366239633636336436393636333962336363303765376531222c2261747472696275746573223a5b7b2274726169745f74797065223a22436f6c6f72222c2276616c7565223a224f72616e6765227d2c7b2274726169745f74797065223a22446975726e616c697479222c2276616c7565223a22446179227d5d7d", "Expected ASM string didnt match hardcoded ASM string" ); } @@ -229,7 +232,7 @@ mod script_tests { assert_eq!( actual.to_asm_string(), - "OP_HASH160 b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc6 OP_EQUALVERIFY OP_DUP OP_HASH160 14a8036c8b3d910a7e24d46067048d8761274b55 OP_EQUALVERIFY OP_CHECKSIG OP_RETURN 7b227469746c65223a22547572626f20466f7820233633222c226465736372697074696f6e223a225765206c696b652074686520666f78222c226e756d626572223a36332c22736572696573223a36392c22696d616765223a22623a2f2f33376136636339636639613461613662356632316534333331363935666666613466323039363335366239633636336436393636333962336363303765376531222c2261747472696275746573223a5b7b2274726169745f74797065223a22436f6c6f72222c2276616c7565223a22426c61636b227d2c7b2274726169745f74797065223a22446975726e616c697479222c2276616c7565223a22446179227d5d7d" + "OP_HASH160 b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc6 OP_EQUALVERIFY OP_DUP OP_HASH160 14a8036c8b3d910a7e24d46067048d8761274b55 OP_EQUALVERIFY OP_CHECKSIG OP_RETURN non-script-data:4d00017b227469746c65223a22547572626f20466f7820233633222c226465736372697074696f6e223a225765206c696b652074686520666f78222c226e756d626572223a36332c22736572696573223a36392c22696d616765223a22623a2f2f33376136636339636639613461613662356632316534333331363935666666613466323039363335366239633636336436393636333962336363303765376531222c2261747472696275746573223a5b7b2274726169745f74797065223a22436f6c6f72222c2276616c7565223a22426c61636b227d2c7b2274726169745f74797065223a22446975726e616c697479222c2276616c7565223a22446179227d5d7d" ); } @@ -495,28 +498,69 @@ mod script_tests { } #[test] - fn scrypt_stateful_contract() { + fn scrypt_with_non_script_data_malformed() { let script = Script::from_hex("6a00010100010001000100010001000100010001001400000000").unwrap(); assert_eq!( &script.to_script_bits(), &[ ScriptBit::OpCode(OpCodes::OP_RETURN), + ScriptBit::NonScriptData(hex::decode("00010100010001000100010001000100010001001400000000").unwrap()), + ] + ); + + assert_eq!(&script.to_asm_string(), "OP_RETURN non-script-data:00010100010001000100010001000100010001001400000000"); + assert_eq!(&script.to_hex(), "6a00010100010001000100010001000100010001001400000000"); + + let script = Script::from_asm_string(&script.to_asm_string()).unwrap(); + + assert_eq!(&script.to_asm_string(), "OP_RETURN non-script-data:00010100010001000100010001000100010001001400000000"); + assert_eq!(&script.to_hex(), "6a00010100010001000100010001000100010001001400000000"); + } + + #[test] + fn scrypt_with_non_script_data() { + let script = Script::from_hex("006a0051").unwrap(); + + assert_eq!( + &script.to_script_bits(), + &[ ScriptBit::OpCode(OpCodes::OP_0), - ScriptBit::Push(hex::decode("01").unwrap()), - ScriptBit::OpCode(OpCodes::OP_0), - ScriptBit::Push(hex::decode("00").unwrap()), - ScriptBit::Push(hex::decode("00").unwrap()), - ScriptBit::Push(hex::decode("00").unwrap()), - ScriptBit::Push(hex::decode("00").unwrap()), - ScriptBit::Push(hex::decode("00").unwrap()), - ScriptBit::Push(hex::decode("00").unwrap()), - ScriptBit::Push(hex::decode("00").unwrap()), - ScriptBit::Push(hex::decode("00").unwrap()), - ScriptBit::Push(hex::decode("00000000").unwrap()), + ScriptBit::OpCode(OpCodes::OP_RETURN), + ScriptBit::NonScriptData(hex::decode("0051").unwrap()), ] ); - assert_eq!(&script.to_asm_string(), "OP_RETURN 0 01 0 00 00 00 00 00 00 00 00 00000000") + assert_eq!(&script.to_asm_string(), "0 OP_RETURN non-script-data:0051"); + assert_eq!(&script.to_hex(), "006a0051"); + + let script = Script::from_asm_string("0 OP_RETURN OP_0 OP_1").unwrap(); + + assert_eq!(&script.to_asm_string(), "0 OP_RETURN non-script-data:0051"); + assert_eq!(&script.to_hex(), "006a0051"); + } + + #[test] + fn non_script_data_asm() { + let script = Script::from_asm_string("0 OP_RETURN 01 0102").unwrap(); + + assert_eq!(&script.to_asm_string(), "0 OP_RETURN non-script-data:0101020102"); + assert_eq!(&script.to_hex(), "006a0101020102"); + + let script = Script::from_asm_string("0 OP_RETURN non-script-data:0101020102").unwrap(); + + assert_eq!(&script.to_asm_string(), "0 OP_RETURN non-script-data:0101020102"); + assert_eq!(&script.to_hex(), "006a0101020102"); + } + + #[test] + fn invalid_non_script_data() { + let result = Script::from_asm_string("0 non-script-data:0101020102 01 0102 OP_RETURN"); + + assert!(matches!(result, Err(BSVErrors::InvalidNonScriptData()))); + + let result = Script::from_asm_string("OP_1 OP_IF OP_1 OP_RETURN non-script-data:0101020102 01 0102 OP_ENDIF OP_1"); + + assert!(matches!(result, Err(BSVErrors::InvalidNonScriptData()))); } } diff --git a/tests/script_template.rs b/tests/script_template.rs index 3521bdb..eb8c14d 100644 --- a/tests/script_template.rs +++ b/tests/script_template.rs @@ -301,7 +301,7 @@ mod script_template_tests { match &extracted[2] { (MatchDataTypes::Data, v) => { - assert_eq!(v, &hex::decode("7b227469746c65223a22547572626f20466f78202331222c226465736372697074696f6e223a225765206c696b652074686520666f78222c226e756d626572223a312c22736572696573223a36392c22696d616765223a22623a2f2f33376136636339636639613461613662356632316534333331363935666666613466323039363335366239633636336436393636333962336363303765376531222c2261747472696275746573223a5b7b2274726169745f74797065223a22436f6c6f72222c2276616c7565223a224f72616e6765227d2c7b2274726169745f74797065223a22446975726e616c697479222c2276616c7565223a22446179227d5d7d").unwrap()) + assert_eq!(v, &hex::decode("4cff7b227469746c65223a22547572626f20466f78202331222c226465736372697074696f6e223a225765206c696b652074686520666f78222c226e756d626572223a312c22736572696573223a36392c22696d616765223a22623a2f2f33376136636339636639613461613662356632316534333331363935666666613466323039363335366239633636336436393636333962336363303765376531222c2261747472696275746573223a5b7b2274726169745f74797065223a22436f6c6f72222c2276616c7565223a224f72616e6765227d2c7b2274726169745f74797065223a22446975726e616c697479222c2276616c7565223a22446179227d5d7d").unwrap()) } _ => assert!(false, "Index 2 did not contain Data"), }