diff --git a/lib/bitcoin/script.rb b/lib/bitcoin/script.rb index 08aafa5..a52194f 100644 --- a/lib/bitcoin/script.rb +++ b/lib/bitcoin/script.rb @@ -1,5 +1,6 @@ require_relative '../bitcoin_data_io' require_relative '../encoding_helper' +require_relative '../hash_helper' require_relative './op' module Bitcoin @@ -88,14 +89,14 @@ def serialize encode_varint(raw.length) + raw end - def evaluate(z) # rubocop:disable Metrics/MethodLength + def evaluate(z, witness: nil) cmds = @cmds.clone stack = [] altstack = [] while cmds.any? cmd = cmds.shift - return false unless resolve_cmd(cmd, cmds, stack, altstack, z) + return false unless resolve_cmd(cmd, cmds, stack, altstack, z, witness: witness) end return false if stack.empty? || stack.pop.empty? @@ -110,9 +111,33 @@ def p2sh?(cmds = @cmds) && cmds[2] == 135 end + def p2wpkh?(cmds = @cmds) + cmds.length == 2 \ + && cmds[0] == 0 \ + && cmds[1].is_a?(String) && cmds[1].length == 20 + end + + def p2wsh?(cmds = @cmds) + cmds.length == 2 \ + && cmds[0] == 0 \ + && cmds[1].is_a?(String) && cmds[1].length == 32 + end + + def self.p2pkh(hash160) + Script.new([118, 169, hash160, 136, 172]) + end + + def self.p2wpkh(hash160) + Script.new([0, hash160]) + end + + def self.p2wsh(hash256) + Script.new([0, hash256]) + end + private - def resolve_cmd(cmd, cmds, stack, altstack, z) + def resolve_cmd(cmd, cmds, stack, altstack, z, witness: nil) if cmd.is_a? Integer return execute_operation(cmd, cmds, stack, altstack, z) else @@ -120,6 +145,10 @@ def resolve_cmd(cmd, cmds, stack, altstack, z) if p2sh?(cmds) return execute_p2sh(cmd, cmds, stack) + elsif p2wpkh? + return execute_p2wpkh(cmds, stack, witness) + elsif p2wsh? + return execute_p2wsh(cmds, stack, witness) end end @@ -142,6 +171,30 @@ def execute_p2sh(cmd, cmds, stack) cmds.concat self.class.parse(stream).cmds end + def execute_p2wpkh(cmds, stack, witness) + h160 = stack.pop + stack.pop + cmds.concat witness + cmds.concat self.class.p2wpkh(h160).cmds + end + + def execute_p2wsh(cmds, stack, witness) + s256_stack = stack.pop + stack.pop + cmds.concat witness[0...-1] + witness_script = witness.last + s256_script = HashHelper.hash256(witness_script) + + unless s256_stack == s256_script + raise "Witness script hash mismatch: \n"\ + "stack: #{bytes_to_hex(s256_stack)} \n"\ + "script: #{bytes_to_hex(s256_script)}" + end + + stream = encode_varint(witness_script.size) + witness_script + cmds.concat parse(stream).cmds + end + def raw_serialize @cmds.map do |cmd| cmd.is_a?(Integer) ? int_to_little_endian(cmd, 1) : serialized_element_prefix(cmd) + cmd diff --git a/lib/bitcoin/tx.rb b/lib/bitcoin/tx.rb index 83ba2a9..fa27a66 100644 --- a/lib/bitcoin/tx.rb +++ b/lib/bitcoin/tx.rb @@ -17,12 +17,13 @@ class Tx class TxIn include EncodingHelper - def initialize(prev_tx, prev_index, script_sig = nil, sequence = 0xffffffff) + def initialize(prev_tx, prev_index, script_sig = nil, sequence = 0xffffffff, witness = nil) @prev_tx = prev_tx @prev_index = prev_index @script_sig = script_sig || Script.new @sequence = sequence @tx_fetcher = UriFetcher.new + @witness = witness end def self.parse(_io) @@ -42,6 +43,11 @@ def fetch_tx(testnet: false) @tx_fetcher.fetch tx_id, testnet: testnet end + def value(testnet: false) + tx = fetch_tx testnet: testnet + tx.outs[prev_index].amount + end + def script_pubkey(testnet: false) tx = fetch_tx testnet: testnet tx.outs[prev_index].script_pubkey @@ -54,7 +60,19 @@ def serialize result << to_bytes(sequence, 4, 'little') end - attr_accessor :prev_tx, :prev_index, :script_sig, :sequence + def serialize_witness + result = int_to_little_endian(@witness.size, 1) + @witness.each do |wt| + result << if wt.is_a?(Integer) + int_to_little_endian(wt, 1) + else + encode_varint(wt.size) + wt + end + end + result + end + + attr_accessor :prev_tx, :prev_index, :script_sig, :sequence, :witness end class TxOut @@ -77,6 +95,10 @@ def serialize end def self.parse(_io, _options = {}) + segwit?(_io) ? parse_segwit(_io, _options) : parse_legacy(_io, _options) + end + + def self.parse_legacy(_io, _options) io = BitcoinDataIO(_io) new(_options).tap do |tx| @@ -87,11 +109,69 @@ def self.parse(_io, _options = {}) end end + def self.parse_segwit(_io, _options) + io = BitcoinDataIO(_io) + + new(_options).tap do |tx| + tx.version = io.read_le_int32 + marker = io.read(2) + raise "Not a segwit transaction #{marker}" unless marker == "\x00\x01" + + io.read_varint.times { tx.ins << TxIn.parse(io) } + io.read_varint.times { tx.outs << TxOut.parse(io) } + tx.read_witness_items(io) + tx.locktime = io.read_le_int32 + tx.segwit = true + end + end + + def self.segwit?(_io) + _io.read(4) + flag_byte = _io.read(1) + _io.rewind + + flag_byte == "\x00" + end + + def read_witness_items(_io) + @ins.each do |tx_in| + items = [] + _io.read_varint.times do + item_len = _io.read_varint + items << if item_len.zero? + 0 + else + _io.read(item_len) + end + end + tx_in.witness = items + end + end + def id - HashHelper.hash256(serialize).reverse.unpack('H*') + HashHelper.hash256(serialize_legacy).reverse.unpack('H*') end def serialize + segwit ? serialize_segwit : serialize_legacy + end + + # rubocop:disable Metrics/AbcSize + def serialize_segwit + result = to_bytes(version, 4, 'little') + result << "\x00\x01" + result << encode_varint(ins.size) + result << ins.map(&:serialize).join + result << encode_varint(outs.size) + result << outs.map(&:serialize).join + result << ins.map(&:serialize_witness).join + result << to_bytes(locktime, 4, 'little') + + result + end + # rubocop:enable Metrics/AbcSize + + def serialize_legacy result = to_bytes(version, 4, 'little') result << encode_varint(ins.size) result << ins.map(&:serialize).join @@ -102,13 +182,18 @@ def serialize result end - attr_accessor :version, :locktime, :ins, :outs + attr_accessor :version, :locktime, :ins, :outs, :segwit, + :_hash_prevouts, :_hash_sequence, :_hash_outputs - def initialize(tx_fetcher: nil, testnet: false) + def initialize(tx_fetcher: nil, testnet: false, segwit: false) @tx_fetcher = tx_fetcher @ins = [] @outs = [] @testnet = testnet + @segwit = segwit + @_hash_prevouts = nil + @_hash_sequence = nil + @_hash_outputs = nil end def fee @@ -127,20 +212,92 @@ def sig_hash(input_index, redeem_script = nil) from_bytes hash256, 'big' end + # rubocop:disable Metrics/AbcSize + def sig_hash_bip143(input_index, redeem_script: nil, witness_script: nil) + tx_in = @ins[input_index] + result = int_to_little_endian(version, 4) + + result += hash_prevouts + hash_sequence + result += tx_in.prev_tx.reverse + int_to_little_endian(tx_in.prev_index, 4) + result += build_script_raw(redeem_script, witness_script, tx_in) + result += int_to_little_endian(tx_in.value, 8) + result += int_to_little_endian(tx_in.sequence, 4) + result += hash_outputs + result << int_to_little_endian(locktime, 4) + result << int_to_little_endian(SIGHASH_ALL, 4) + + hash256 = HashHelper.hash256 result + + from_bytes hash256, 'big' + end + # rubocop:enable Metrics/AbcSize + + def hash_prevouts + unless @_hash_prevouts + all_prevouts = '' + all_sequence = '' + @ins.each do |tx_in| + all_prevouts += tx_in.prev_tx.reverse + int_to_little_endian(tx_in.prev_index, 4) + all_sequence += int_to_little_endian(tx_in.sequence, 4) + end + @_hash_prevouts = HashHelper.hash256(all_prevouts) + @_hash_sequence = HashHelper.hash256(all_sequence) + end + @_hash_prevouts + end + + def hash_sequence + hash_prevouts unless @_hash_sequence + @_hash_sequence + end + + def hash_outputs + unless @_hash_outputs + all_outputs = '' + @outs.each { |tx_out| all_outputs += tx_out.serialize } + @_hash_outputs = HashHelper.hash256(all_outputs) + end + @_hash_outputs + end + def verify_input(input_index) tx_in = ins[input_index] script_pubkey = tx_in.script_pubkey testnet: @testnet + z, witness = build_z_and_witness(script_pubkey, tx_in, input_index) + combined = tx_in.script_sig + script_pubkey + combined.evaluate(z, witness: witness) + end + + def build_z_and_witness(script_pubkey, tx_in, input_index) # rubocop:disable Metrics/MethodLength if script_pubkey.p2sh? cmd = tx_in.script_sig.cmds[-1] raw_redeem = encode_varint(cmd.length) + cmd redeem_script = Script.parse(StringIO.new(raw_redeem)) + + if redeem_script.p2wpkh? + [sig_hash_bip143(input_index, redeem_script), tx_in.witness] + elsif redeem_script.p2wsh? + build_z_from_witness(tx_in, input_index) + else + [sig_hash(input_index, redeem_script), nil] + end + + elsif script_pubkey.p2wpkh? + [sig_hash_bip143(input_index, redeem_script: redeem_script), tx_in.witness] + elsif script_pubkey.p2wsh? + build_z_from_witness(tx_in, input_index) else - redeem_script = nil + [sig_hash(input_index), nil] end - z = sig_hash(input_index, redeem_script) - combined = tx_in.script_sig + script_pubkey - combined.evaluate(z) + end + + def build_z_from_witness(tx_in, input_index) + cmd = tx_in.witness.last + raw_witness = encode_varint(cmd.size) + cmd + witness_script = Script.parse(StringIO.new(raw_witness)) + + [sig_hash_bip143(input_index, witness_script: witness_script), tx_in.witness] end def verify? @@ -204,6 +361,16 @@ def encode_outs encode_varint(outs.size) + outs.map(&:serialize).join end + def build_script_raw(redeem_script, witness_script, tx_in) + if witness_script + witness_script.serialize + elsif redeem_script + Script.p2pkh(redeem_script.cms[1]).serialize + else + Script.p2pkh(tx_in.script_pubkey(testnet: @testnet).cmds[1]).serialize + end + end + def calculate_fee raise 'transaction fetcher not provided' if @tx_fetcher.nil? diff --git a/spec/bitcoin/script_spec.rb b/spec/bitcoin/script_spec.rb index b6065ef..96cc1b0 100644 --- a/spec/bitcoin/script_spec.rb +++ b/spec/bitcoin/script_spec.rb @@ -1,6 +1,7 @@ require 'bitcoin/script' require 'bitcoin/op' require 'encoding_helper' +require 'pry' RSpec.describe Bitcoin::Script do def _raw_script(hex_script) @@ -222,6 +223,75 @@ def _raw_script(hex_script) end end + describe '#p2wpkh?' do + let!(:script) { described_class.new(commands) } + let(:commands) { [] } + + context "when the script matches the p2wpkh pattern" do + let(:hash160) { HashHelper.hash160('') } + let(:commands) do + [ + 0, + hash160 + ] + end + let!(:script) { described_class.new(commands) } + + it 'returns true' do + expect(script.p2wpkh?).to be true + end + end + + context "when the script does not match the p2wpkh pattern" do + let(:commands) do + [ + '', + 18, + 135 + ] + end + let!(:script) { described_class.new(commands) } + + it 'returns false' do + expect(script.p2wpkh?).to be false + end + end + end + + describe '#p2wsh?' do + let!(:script) { described_class.new(commands) } + let(:commands) { [] } + + context "when the script matches the p2wsh pattern" do + let(:hash256) { HashHelper.hash256('') } + let(:commands) do + [ + 0, + hash256 + ] + end + let!(:script) { described_class.new(commands) } + + it 'returns true' do + expect(script.p2wsh?).to be true + end + end + + context "when the script does not match the p2wsh pattern" do + let(:commands) do + [ + '', + 18 + ] + end + let!(:script) { described_class.new(commands) } + + it 'returns false' do + expect(script.p2wsh?).to be false + end + end + end + describe "#+" do it "adds the commands arrays" do script1 = described_class.new([1, 2]) diff --git a/spec/bitcoin/tx_spec.rb b/spec/bitcoin/tx_spec.rb index f9c091a..de253a0 100644 --- a/spec/bitcoin/tx_spec.rb +++ b/spec/bitcoin/tx_spec.rb @@ -1,59 +1,125 @@ require 'bitcoin/tx' +require 'bitcoin/script' +require 'encoding_helper' require_relative '../support/fixture_macros' require_relative '../../lib/ecc/private_key' +require 'pry' RSpec.describe Bitcoin::Tx do load_transaction_set 'transactions' + include EncodingHelper let(:raw_tx) { resolve_tx '452c629d67e41baec3ac6f04fe744b4b9617f8f859c63b3002f8684e7a4fee03' } + let(:raw_tx_sw) { resolve_tx '9b4fc533a9a69ed0eb030b08e40150999a8aa871e918345cb19855298c103ba3' } describe ".parse" do def parse(*_args) described_class.parse(*_args) end - it "properly parses the version" do - expect(parse(raw_tx).version).to eq(1) - end + context 'when tx is legacy' do + it "properly parses the version" do + expect(parse(raw_tx).version).to eq(1) + end - it "properly parses input count" do - expect(parse(raw_tx).ins.count).to eq(1) - end + it "properly parses input count" do + expect(parse(raw_tx).ins.count).to eq(1) + end - it "properly parses each input prev_tx" do - expect(bytes_to_hex(parse(raw_tx).ins.first.prev_tx)).to eq "d1c789a9c60383bf715f3f6ad9d14b91\ + it "properly parses each input prev_tx" do + expect(bytes_to_hex(parse(raw_tx).ins.first.prev_tx)).to eq "d1c789a9c60383bf715f3f6ad9d14b91\ fe55f3deb369fe5d9280cb1a01793f81" - end + end - it "properly parses each input prev_index" do - expect(parse(raw_tx).ins.first.prev_index).to eq 0 - end + it "properly parses each input prev_index" do + expect(parse(raw_tx).ins.first.prev_index).to eq 0 + end - it "properly parses each input script_sig" do - expect(bytes_to_hex(parse(raw_tx).ins.first.script_sig.serialize)).to eq "6b483045022100ed81ff192e75a\ -3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c3\ + it "properly parses each input script_sig" do + expect(bytes_to_hex(parse(raw_tx).ins.first.script_sig.serialize)) + .to eq "6b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320\ +b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c3\ 1967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a" - end + end - it "properly parses each input sequence" do - expect(parse(raw_tx).ins.first.sequence).to eq 0xfffffffe - end + it "properly parses each input sequence" do + expect(parse(raw_tx).ins.first.sequence).to eq 0xfffffffe + end - it "properly parses output count" do - expect(parse(raw_tx).outs.count).to eq(2) - end + it "properly parses output count" do + expect(parse(raw_tx).outs.count).to eq(2) + end + + it "properly parses each output amount" do + expect(parse(raw_tx).outs.map(&:amount)).to eq([32454049, 10011545]) + end - it "properly parses each output amount" do - expect(parse(raw_tx).outs.map(&:amount)).to eq([32454049, 10011545]) + it "properly parses each output script_pubkey" do + expect(parse(raw_tx).outs.map { |o| bytes_to_hex(o.script_pubkey.serialize) }).to eq( + [ + '1976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac', + '1976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac' + ] + ) + end end - it "properly parses each output script_pubkey" do - expect(parse(raw_tx).outs.map { |o| bytes_to_hex(o.script_pubkey.serialize) }).to eq( - [ - '1976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac', - '1976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac' - ] - ) + context 'when tx is segwit' do + it "properly parses the version" do + expect(parse(raw_tx_sw).version).to eq(2) + end + + it "properly parses input count" do + expect(parse(raw_tx_sw).ins.count).to eq(1) + end + + it "properly parses each input prev_tx" do + expect(bytes_to_hex(parse(raw_tx_sw).ins.first.prev_tx)) + .to eq "2cbe83a5efe4d6582eb8f5a029c65f74832c734c89234081a25ad3f997ba74e8" + end + + it "properly parses each input prev_index" do + expect(parse(raw_tx_sw).ins.first.prev_index).to eq 0 + end + + it "properly parses each input script_sig" do + expect(parse(raw_tx_sw).ins.first.script_sig.serialize).to eq "\x00" + end + + it "properly parses each input sequence" do + expect(parse(raw_tx_sw).ins.first.sequence).to eq 0xffffffff + end + + it "properly parses output count" do + expect(parse(raw_tx_sw).outs.count).to eq(2) + end + + it "properly parses each output amount" do + expect(parse(raw_tx_sw).outs.map(&:amount)).to eq([176500, 2178124]) + end + + it "properly parses each output script_pubkey" do + skip + # todo : check correct script_pub. should be : [76a91433e73d0a40a60d02d19c8b8d38ad6da14306683b88ac, 001424ca8b17be9dfa365929dc9251da7d1533ca8b5e] + expect(parse(raw_tx_sw).outs.map { |o| bytes_to_hex(o.script_pubkey.serialize) }).to eq( + [ + '1976a91433e73d0a40a60d02d19c8b8d38ad6da14306683b88ac', + '16001424ca8b17be9dfa365929dc9251da7d1533ca8b5e' + ] + ) + end + + it "properly parses each input witness" do + witness = parse(raw_tx_sw).ins.first.witness.map { |w| w.unpack1("H*") } + + expect(witness).to eq( + [ + "30440220635a9629c9eb17f7ebbfc200e674aff253ec4213c3e6d3207e86886af9f6a75"\ + "1022072e2d54a9b8079e37cefd856ccf3c2383858346f55197d32c3a7fad18eac48e001", + "02550e51f143d27ed811e8cdf9008a21211bf26c73866a4b077117496b432421bd" + ] + ) + end end end @@ -116,6 +182,24 @@ def bytes_to_hex(_bytes) end end + describe '#sig_hash_bip143' do + context 'when tx is segwit' do + let(:tx_sw) { described_class.parse raw_tx_sw, tx_fetcher: tx_fetcher } + + it 'returns the correct sig_hash' do + skip + # todo: give correct witness as input + + cmd = tx_sw.ins.first.witness.last + raw_witness = encode_varint(cmd.size) + cmd + witness_script = Bitcoin::Script.parse(StringIO.new(raw_witness)) + + expect(tx_sw.sig_hash_bip143(0, witness_script: witness_script)) + .to eq 999999 + end + end + end + describe '#verify_input' do let(:tx) { described_class.parse raw_tx, tx_fetcher: tx_fetcher } let(:raw_tx) do @@ -141,6 +225,27 @@ def bytes_to_hex(_bytes) expect(tx.verify_input(0)).to be true end end + + context 'when tx is segwit and the script pubkey is p2wpkh' do + let(:tx_p2wpkh) { described_class.parse raw_tx_sw, tx_fetcher: tx_fetcher } + + it 'verifies unlocking script unlocks the script' do + skip + expect(tx_p2wpkh.verify_input(0)).to be true + end + end + + context 'when tx is segwit and the script pubkey is p2wsh' do + let(:raw_tx_p2wsh) do + resolve_tx '98abf6f18cedc5e527775ff2b0d4235b16fd40774c33ab7c599e20099fd11259' + end + let(:tx_p2wsh) { described_class.parse raw_tx_p2wsh, tx_fetcher: tx_fetcher } + + it 'verifies unlocking script unlocks the script' do + skip + expect(tx_p2wsh.verify_input(0)).to be true + end + end end describe '#sign_input' do @@ -218,4 +323,50 @@ def bytes_to_hex(_bytes) end end end + + describe '#segwit?' do + context 'when tx is legacy' do + it 'returns false' do + expect(described_class.segwit?(raw_tx)).to be false + end + end + + context 'when tx is segwit' do + it 'returns true' do + expect(described_class.segwit?(raw_tx_sw)).to be true + end + end + end + + describe '#serialize' do + let(:tx) { described_class.parse raw_tx, tx_fetcher: tx_fetcher } + + context 'when tx is legacy' do + it 'returns serialized tx' do + expect(bytes_to_hex(tx.serialize)).to eq( + "0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b48"\ + "3045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d95"\ + "5c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f"\ + "5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56"\ + "b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40d"\ + "f79fea1288ac19430600" + ) + end + end + + context 'when tx is segwit' do + let(:tx_sw) { described_class.parse raw_tx_sw, tx_fetcher: tx_fetcher } + + it 'returns serialized tx' do + expect(bytes_to_hex(tx_sw.serialize)).to eq( + "02000000000101e874ba97f9d35aa2814023894c732c83745fc629a0f5b82e58d6e4efa583be2c"\ + "0000000000ffffffff0274b10200000000001976a91433e73d0a40a60d02d19c8b8d38ad6da1430"\ + "6683b88ac4c3c21000000000016001424ca8b17be9dfa365929dc9251da7d1533ca8b5e024730440"\ + "220635a9629c9eb17f7ebbfc200e674aff253ec4213c3e6d3207e86886af9f6a751022072e2d54a9"\ + "b8079e37cefd856ccf3c2383858346f55197d32c3a7fad18eac48e0012102550e51f143d27ed811e8"\ + "cdf9008a21211bf26c73866a4b077117496b432421bd00000000" + ) + end + end + end end diff --git a/spec/fixtures/transactions.json b/spec/fixtures/transactions.json index 067b248..9d8f0c0 100644 --- a/spec/fixtures/transactions.json +++ b/spec/fixtures/transactions.json @@ -15,5 +15,9 @@ "c586389e5e4b3acb9d6c8be1c19ae8ab2795397633176f5a6442a261bbdefc3a": "0200000000010140d43a99926d43eb0e619bf0b3d83b4a31f60c176beecfb9d35bf45e54d0f7420100000017160014a4b4ca48de0b3fffc15404a1acdc8dbaae226955ffffffff0100e1f5050000000017a9144a1154d50b03292b3024370901711946cb7cccc387024830450221008604ef8f6d8afa892dee0f31259b6ce02dd70c545cfcfed8148179971876c54a022076d771d6e91bed212783c9b06e0de600fab2d518fad6f15a2b191d7fbd262a3e0121039d25ab79f41f75ceaf882411fd41fa670a4c672c23ffaf0e361a969cde0692e800000000", "d1c789a9c60383bf715f3f6ad9d14b91fe55f3deb369fe5d9280cb1a01793f81": "0100000002137c53f0fb48f83666fcfd2fe9f12d13e94ee109c5aeabbfa32bb9e02538f4cb000000006a47304402207e6009ad86367fc4b166bc80bf10cf1e78832a01e9bb491c6d126ee8aa436cb502200e29e6dd7708ed419cd5ba798981c960f0cc811b24e894bff072fea8074a7c4c012103bc9e7397f739c70f424aa7dcce9d2e521eb228b0ccba619cd6a0b9691da796a1ffffffff517472e77bc29ae59a914f55211f05024556812a2dd7d8df293265acd8330159010000006b483045022100f4bfdb0b3185c778cf28acbaf115376352f091ad9e27225e6f3f350b847579c702200d69177773cd2bb993a816a5ae08e77a6270cf46b33f8f79d45b0cd1244d9c4c0121031c0b0b95b522805ea9d0225b1946ecaeb1727c0b36c7e34165769fd8ed860bf5ffffffff027a958802000000001976a914a802fc56c704ce87c42d7c92eb75e7896bdc41ae88aca5515e00000000001976a914e82bd75c9c662c3f5700b33fec8a676b6e9391d588ac00000000", "d37f9e7282f81b7fd3af0fde8b462a1c28024f1d83cf13637ec18d03f4518feb": "0100000001b74780c0b9903472f84f8697a7449faebbfb1af659ecb8148ce8104347f3f72d010000006b483045022100bb8792c98141bcf4dab4fd4030743b4eff9edde59cec62380c60ffb90121ab7802204b439e3572b51382540c3b652b01327ee8b14cededc992fbc69b1e077a2c3f9f0121027c975c8bdc9717de310998494a2ae63f01b7a390bd34ef5b4c346fa717cba012ffffffff01a627c901000000001976a914af24b3f3e987c23528b366122a7ed2af199b36bc88ac00000000", - "d869f854e1f8788bcff294cc83b280942a8c728de71eb709a2c29d10bfe21b7c": "0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560100000000ffffffff0100b4f505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02483045022100df7b7e5cda14ddf91290e02ea10786e03eb11ee36ec02dd862fe9a326bbcb7fd02203f5b4496b667e6e281cc654a2da9e4f08660c620a1051337fa8965f727eb19190121038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990ac00000000" + "d869f854e1f8788bcff294cc83b280942a8c728de71eb709a2c29d10bfe21b7c": "0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560100000000ffffffff0100b4f505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02483045022100df7b7e5cda14ddf91290e02ea10786e03eb11ee36ec02dd862fe9a326bbcb7fd02203f5b4496b667e6e281cc654a2da9e4f08660c620a1051337fa8965f727eb19190121038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990ac00000000", + "9b4fc533a9a69ed0eb030b08e40150999a8aa871e918345cb19855298c103ba3": + "02000000000101e874ba97f9d35aa2814023894c732c83745fc629a0f5b82e58d6e4efa583be2c0000000000ffffffff0274b10200000000001976a91433e73d0a40a60d02d19c8b8d38ad6da14306683b88ac4c3c21000000000016001424ca8b17be9dfa365929dc9251da7d1533ca8b5e024730440220635a9629c9eb17f7ebbfc200e674aff253ec4213c3e6d3207e86886af9f6a751022072e2d54a9b8079e37cefd856ccf3c2383858346f55197d32c3a7fad18eac48e0012102550e51f143d27ed811e8cdf9008a21211bf26c73866a4b077117496b432421bd00000000", + "98abf6f18cedc5e527775ff2b0d4235b16fd40774c33ab7c599e20099fd11259": + "01000000000101a7fe0333427c213e06447a1d44c3a47389654ab41d3e258aa0f6dcd2e51e59440100000000ffffffff0308c712000000000017a914f5926cf4b6797b550bd3d28ab3ec74fd84a01a1387103bcd00000000001976a9145ff42d2ae630e66ddd2cb29094e05c9710d3db9e88ac1407130300000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d040047304402202157e477ddd81dc2be1089de72d5d7ad30a29daa1ed710d685ee8bc731afbee102207078681d21f6a03a8a6210f4c44af20a1478e469c3f27b540ba53121c4c980620147304402206338f38f78deef81a3d2fdf0359a8e685cb0087d76a58477f0c5fce44f70b784022075f892d5662fd63d8e35c1b93f9729130ca70e9145a736d18bc24be964344f73016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000" }