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

feat: add support to deserialize transaction with witness data #259

Merged
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
63 changes: 55 additions & 8 deletions packages/engine/src/transaction.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,12 @@ pub impl EngineInternalTransactionImpl of EngineInternalTransactionTrait {
fn btc_decode(raw: ByteArray, encoding: u32) -> EngineTransaction {
let mut offset: usize = 0;
let version: i32 = byte_array_value_at_le(@raw, ref offset, 4).try_into().unwrap();
if encoding == WITNESS_ENCODING {
// consume flags
offset += 2;
}
// TODO: ReadVerIntBuf
let input_len: u8 = byte_array_value_at_le(@raw, ref offset, 1).try_into().unwrap();
// TODO: input_len = 0 -> segwit
// TODO: Error handling and bounds checks
// TODO: Byte orderings
let mut i = 0;
Expand Down Expand Up @@ -211,18 +214,62 @@ pub impl EngineInternalTransactionImpl of EngineInternalTransactionTrait {
outputs.append(output);
i += 1;
};
// TODO: Witness

let mut inputs_with_witness: Array<EngineTransactionInput> = array![];

if encoding == WITNESS_ENCODING {
// one witness for each input
i = 0;
while i != input_len {
let witness_count: u8 = byte_array_value_at_le(@raw, ref offset, 1)
.try_into()
.unwrap();
let mut j = 0;
let mut witness: Array<ByteArray> = array![];
while j != witness_count {
let script_len = byte_array_value_at_le(@raw, ref offset, 1)
.try_into()
.unwrap();
let script = sub_byte_array(@raw, ref offset, script_len);
witness.append(script);
j += 1;
};
// update Transaction Input
let input = inputs.at(i.into());
let mut input_with_witness = input.clone();
input_with_witness.witness = witness;
inputs_with_witness.append(input_with_witness);
i += 1;
};
}
let locktime: u32 = byte_array_value_at_le(@raw, ref offset, 4).try_into().unwrap();
EngineTransaction {
version: version,
transaction_inputs: inputs,
transaction_outputs: outputs,
locktime: locktime,

if encoding == WITNESS_ENCODING {
EngineTransaction {
version: version,
transaction_inputs: inputs_with_witness,
transaction_outputs: outputs,
locktime: locktime,
}
} else {
EngineTransaction {
version: version,
transaction_inputs: inputs,
transaction_outputs: outputs,
locktime: locktime,
}
}
}

fn deserialize(raw: ByteArray) -> EngineTransaction {
Self::btc_decode(raw, WITNESS_ENCODING)
let mut offset: usize = 0;
let _version: i32 = byte_array_value_at_le(@raw, ref offset, 4).try_into().unwrap();
let flags: u16 = byte_array_value_at_le(@raw, ref offset, 2).try_into().unwrap();
if flags == 0x100 {
Self::btc_decode(raw, WITNESS_ENCODING)
} else {
Self::btc_decode(raw, BASE_ENCODING)
}
}

fn deserialize_no_witness(raw: ByteArray) -> EngineTransaction {
Expand Down
40 changes: 40 additions & 0 deletions packages/tests/src/tests/test_transactions.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,46 @@ fn test_deserialize_first_p2pkh_transaction() {
assert_eq!(transaction.locktime, 0, "Lock time is not correct");
}

#[test]
fn test_deserialize_p2wsh_transaction() {
// https://learnmeabitcoin.com/explorer/tx/64f427122f7951687aea608b5474509a30616d4e5773a83bc1ed8b8271ad1991
let raw_transaction_hex =
"0x020000000001018a39b5cdd48c7d45a31a89cd675a95f5de78aebeeda1e55ac35d7110c3bacfc60000000000ffffffff01204e0000000000001976a914ee63c8c790952de677d1f8019c9474d84098d6e188ac0202123423aa20a23421f2ba909c885a3077bb6f8eb4312487797693bbcfe7e311f797e3c5b8fa8700000000";
let raw_transaction = hex_to_bytecode(@raw_transaction_hex);
let transaction = EngineInternalTransactionTrait::deserialize(raw_transaction);

assert_eq!(transaction.version, 2, "Version is not correct");
assert_eq!(transaction.transaction_inputs.len(), 1, "Transaction inputs length is not correct");
let input0 = transaction.transaction_inputs[0];
let expected_txid_hex = "0x8a39b5cdd48c7d45a31a89cd675a95f5de78aebeeda1e55ac35d7110c3bacfc6";
let expected_txid = hex_to_bytecode(@expected_txid_hex);
let expected_witness_1_hex = "0x1234";
let expected_witness_1 = hex_to_bytecode(@expected_witness_1_hex);
let expected_witness_2_hex =
"0xaa20a23421f2ba909c885a3077bb6f8eb4312487797693bbcfe7e311f797e3c5b8fa87";
let expected_witness_2 = hex_to_bytecode(@expected_witness_2_hex);

assert_eq!(input0.previous_outpoint.vout, @0, "Outpoint vout on input 1 is not correct");
assert_eq!(
input0.previous_outpoint.txid,
@u256_from_byte_array_with_offset(@expected_txid, 0, 32),
"Outpoint txid on input 1 is not correct"
);
assert_eq!(input0.signature_script.len(), 0, "Script sig on input 1 is not empty");
assert_eq!(input0.witness.len(), 2, "Witness length on input 1 is not correct");
assert_eq!(input0.witness[0], @expected_witness_1, "Witness 1 on input 1 is not correct");
assert_eq!(input0.witness[1], @expected_witness_2, "Witness 2 on input 1 is not correct");
assert_eq!(input0.sequence, @0xFFFFFFFF, "Sequence on input 1 is not correct");

let output0 = transaction.transaction_outputs[0];
assert_eq!(output0.value, @20000, "Output 1 value is not correct");
let expected_pk_script_hex = "0x76a914ee63c8c790952de677d1f8019c9474d84098d6e188ac";
let expected_pk_script = hex_to_bytecode(@expected_pk_script_hex);
assert_eq!(output0.publickey_script, @expected_pk_script, "Output 1 pk_script is not correct");

assert_eq!(transaction.locktime, 0, "Lock time is not correct");
}

#[test]
fn test_deserialize_coinbase_transaction() { // TODO
}
Expand Down