cryptocoin 0.0.1b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +85 -0
  7. data/Rakefile +2 -0
  8. data/cryptocoin.gemspec +24 -0
  9. data/lib/cryptocoin/core_ext/integer.rb +24 -0
  10. data/lib/cryptocoin/core_ext/string.rb +30 -0
  11. data/lib/cryptocoin/digest.rb +36 -0
  12. data/lib/cryptocoin/merkle_tree.rb +78 -0
  13. data/lib/cryptocoin/network/bitcoin.rb +9 -0
  14. data/lib/cryptocoin/network/dogecoin.rb +9 -0
  15. data/lib/cryptocoin/network/litecoin.rb +9 -0
  16. data/lib/cryptocoin/network.rb +53 -0
  17. data/lib/cryptocoin/protocol/block_header.rb +58 -0
  18. data/lib/cryptocoin/protocol/inventory_vector.rb +28 -0
  19. data/lib/cryptocoin/protocol/message/addr.rb +29 -0
  20. data/lib/cryptocoin/protocol/message/alert.rb +0 -0
  21. data/lib/cryptocoin/protocol/message/block.rb +13 -0
  22. data/lib/cryptocoin/protocol/message/getaddr.rb +17 -0
  23. data/lib/cryptocoin/protocol/message/getblocks.rb +44 -0
  24. data/lib/cryptocoin/protocol/message/getdata.rb +31 -0
  25. data/lib/cryptocoin/protocol/message/getheaders.rb +44 -0
  26. data/lib/cryptocoin/protocol/message/headers.rb +30 -0
  27. data/lib/cryptocoin/protocol/message/inv.rb +31 -0
  28. data/lib/cryptocoin/protocol/message/mempool.rb +16 -0
  29. data/lib/cryptocoin/protocol/message/notfound.rb +31 -0
  30. data/lib/cryptocoin/protocol/message/ping.rb +24 -0
  31. data/lib/cryptocoin/protocol/message/pong.rb +24 -0
  32. data/lib/cryptocoin/protocol/message/reject.rb +54 -0
  33. data/lib/cryptocoin/protocol/message/tx.rb +11 -0
  34. data/lib/cryptocoin/protocol/message/verack.rb +16 -0
  35. data/lib/cryptocoin/protocol/message/version.rb +59 -0
  36. data/lib/cryptocoin/protocol/message.rb +27 -0
  37. data/lib/cryptocoin/protocol/net_addr.rb +55 -0
  38. data/lib/cryptocoin/protocol/packet.rb +74 -0
  39. data/lib/cryptocoin/protocol/var_len_int.rb +85 -0
  40. data/lib/cryptocoin/protocol/var_len_str.rb +18 -0
  41. data/lib/cryptocoin/protocol.rb +8 -0
  42. data/lib/cryptocoin/script/op_code/constants.rb +157 -0
  43. data/lib/cryptocoin/script/op_code/functions.rb +515 -0
  44. data/lib/cryptocoin/script/op_code.rb +59 -0
  45. data/lib/cryptocoin/script.rb +234 -0
  46. data/lib/cryptocoin/structure/address.rb +64 -0
  47. data/lib/cryptocoin/structure/block.rb +109 -0
  48. data/lib/cryptocoin/structure/key_pair.rb +57 -0
  49. data/lib/cryptocoin/structure/merkle_branch.rb +37 -0
  50. data/lib/cryptocoin/structure/transaction/input.rb +80 -0
  51. data/lib/cryptocoin/structure/transaction/output.rb +49 -0
  52. data/lib/cryptocoin/structure/transaction.rb +94 -0
  53. data/lib/cryptocoin/version.rb +3 -0
  54. data/lib/cryptocoin.rb +29 -0
  55. data/spec/script_spec.rb +42 -0
  56. data/spec/spec_helper.rb +20 -0
  57. metadata +145 -0
@@ -0,0 +1,59 @@
1
+ require 'cryptocoin/script/op_code/constants'
2
+
3
+ module Cryptocoin
4
+ class Script
5
+ # Taken from the Bitcoin official client
6
+ class OpCode
7
+ include Cryptocoin::Script::OpCode::Constants
8
+ def self.from_name(name)
9
+ c = Cryptocoin::Script::OpCode::Constants.const_get(name.upcase)
10
+ self.new([c].pack('C'))
11
+ rescue NameError
12
+ false
13
+ end
14
+
15
+ def initialize(bin)
16
+ @bin = bin
17
+ @hex = bin.unpack('H*')[0].to_i(16)
18
+
19
+ if const_by_val(@hex) or @hex.between?(OP_PUSHDATA0, OP_PUSHDATA1)
20
+ @valid = true
21
+ else
22
+ @valid = false
23
+ end
24
+ end
25
+
26
+ def name
27
+ # Return special case first
28
+ return nil if !@valid
29
+ return 'OP_PUSHDATA0' if @hex.between?(OP_PUSHDATA0, OP_PUSHDATA1)
30
+ const_by_val(hex).to_s
31
+ end
32
+
33
+ def hex
34
+ @hex
35
+ end
36
+
37
+ def raw
38
+ @bin
39
+ end
40
+
41
+ def valid?
42
+ @valid
43
+ end
44
+
45
+ def disabled?
46
+ return nil if !@valid
47
+ [OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT, OP_INVERT, OP_AND, OP_OR, OP_XOR, OP_2MUL, OP_2DIV, OP_MUL, OP_DIV, OP_MOD, OP_LSHIFT, OP_RSHIFT].include?(@hex)
48
+ end
49
+
50
+ private
51
+
52
+ def const_by_val(val)
53
+ Cryptocoin::Script::OpCode::Constants.constants.find{ |name|
54
+ Cryptocoin::Script::OpCode::Constants.const_get(name) == val
55
+ }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,234 @@
1
+ require 'cryptocoin/script/op_code'
2
+ require 'cryptocoin/script/op_code/functions'
3
+
4
+ module Cryptocoin
5
+ # Script, or as I prefer to stylize it, SCRIPT, is Satoshi's
6
+ # non-Turing complete scripting language for Bitcoin transactions
7
+ # It's similar to Forth and Postscript in that it's a stack based
8
+ # language, while missing certain features such as loops to prevent
9
+ # malicious code from being created within a transaction.
10
+ #
11
+ # This is the parser for a SCRIPT directive
12
+ #
13
+ # My thinking behind the design of this parser is that, while it is
14
+ # tied to transactions usually, it can be used to parse general statements
15
+ # that a user inputs
16
+ class Script
17
+ include Cryptocoin::Script::OpCode::Constants
18
+ include Cryptocoin::Script::OpCode::Functions
19
+
20
+ attr_accessor :stack
21
+
22
+ # Create binary script from string
23
+ # Returns false if unable to create binary string
24
+ def self.from_s(str)
25
+ # split each value from str
26
+ # look up value in constants
27
+ # push data if pushdata instruction, noting that pushdata0 will need to read the binary size of the next string and then add the size plus the string
28
+ instructions = str.split(' ')
29
+ raw = ''
30
+ instructions.each_index do |i|
31
+ code = Cryptocoin::Script::OpCode.from_name(instructions[i].upcase)
32
+ return false if !code
33
+ case code.hex
34
+ when OP_PUSHDATA0
35
+ data = instructions.slice!(i+1)
36
+ data = [data].pack('H*')
37
+ raw += [data.bytesize].pack('C') + data
38
+ when OP_PUSHDATA1..OP_PUSHDATA4
39
+ # Crude implementation because it ignores what data says about the length
40
+ bytes = instructions.slice!(i+1)
41
+ data = instructions.slice!(i+1)
42
+ raw += code.raw + [bytes].pack('H*') + [data].pack('H*')
43
+ else
44
+ raw += code.raw
45
+ end
46
+ end
47
+ self.new(raw)
48
+ end
49
+
50
+ # Validates that the script is okay within the ruleset of the
51
+ # Bitcoin protocol
52
+ def initialize(raw)
53
+ return false if raw.bytesize > 10000
54
+ @script = raw
55
+ @raw = raw
56
+ @transaction_valid = true
57
+ @valid = false
58
+ @subscript = false
59
+ @codeseparator = 0
60
+ @script_string = []
61
+ @stack = []
62
+ end
63
+
64
+ # Sets the script as subscript and parses it to remove
65
+ # OP_CODESEPARATOR and the signature
66
+ def is_subscript!(signature)
67
+ @sub_sig = signature
68
+ @subscript = true
69
+ end
70
+
71
+ def is_subscript?
72
+ @subscript
73
+ end
74
+
75
+ # Sets the transaction so that it can be used if
76
+ # OP_CHECKSIG is called within directive
77
+ # +tx+ is the current transaction and +input+ is the
78
+ # current input index
79
+ def set_transaction(tx, input)
80
+ @transaction = tx
81
+ @input_index = input
82
+ end
83
+
84
+ def raw
85
+ if is_subscript?
86
+ # Remove OP_CODESEPARTOR
87
+ parse(true)
88
+ # Remove signature
89
+ @raw = @raw.split(@sub_sig).join
90
+ end
91
+ @raw
92
+ end
93
+
94
+ def to_s
95
+ @script_string.join(' ')
96
+ end
97
+
98
+ # Parses directive according to current Bitcoin client rules
99
+ # Returns true if a valid script, returns valid is invalid
100
+ # The functions are also aware if it's subscript and don't
101
+ # parse as much (i.e. we don't actually check the validity)
102
+ # if so.
103
+ def parse!(is_subscript=false)
104
+ opcode_count = 0
105
+ @alt_stack, @exec_stack = [], [] # These stacks are not carried over
106
+ seek = 0
107
+ @raw = ""
108
+ while seek < @script.length
109
+ opcode_count+=1
110
+ _i = @script[seek]
111
+ seek += 1
112
+ opcode = Cryptocoin::Script::OpCode.new(_i)
113
+ return false if !opcode.valid?
114
+ return false if opcode_count > 201 and opcode.hex > OP_16
115
+ return false if opcode.disabled?
116
+
117
+ if is_subscript
118
+ if !(opcode.hex == OP_CODESEPARATOR)
119
+ @raw += _i
120
+ @script_string.push(opcode.name)
121
+ end
122
+ else
123
+ @raw += _i
124
+ @script_string.push(opcode.name)
125
+ end
126
+
127
+ if opcode.hex.between?(OP_PUSHDATA0, OP_PUSHDATA4)
128
+ @stack, @script_string, @raw, seek = op_pushdata(@stack, opcode, @script, @script_string, @raw, seek)
129
+ end
130
+
131
+ if !is_subscript?
132
+ case opcode.hex
133
+ when OP_1NEGATE, OP_1, OP_2..OP_16
134
+ @stack = op_numeric(@stack, opcode)
135
+ when OP_NOP, OP_NOP1..OP_NOP10
136
+ op_nop
137
+ when OP_IF, OP_NOTIF
138
+ @stack, @exec_stack = op_if(@stack, @exec_stack, opcode)
139
+ when OP_ELSE
140
+ @exec_stack = op_else(@exec_stack)
141
+ when OP_ENDIF
142
+ exec_stack = op_endif(exec_stack)
143
+ when OP_RETURN
144
+ @transaction_valid = op_return
145
+ when OP_CODESEPARATOR
146
+ @codeseparator = op_codeseparator(seek)
147
+ when OP_VERIFY
148
+ @stack = op_verify(@stack)
149
+ when OP_TOALTSTACK
150
+ @stack, alt_stack = op_toaltstack(@stack, alt_stack)
151
+ when OP_FROMALTSTACK
152
+ @stack, alt_stack = op_fromaltstack(@stack, alt_stack)
153
+ when OP_2DROP
154
+ @stack = op_2drop(@stack)
155
+ when OP_2DUP
156
+ @stack = op_2dup(@stack)
157
+ when OP_3DUP
158
+ @stack = op_3dup(@stack)
159
+ when OP_2OVER
160
+ @stack = op_2over(@stack)
161
+ when OP_2ROT
162
+ @stack = op_2rot(@stack)
163
+ when OP_2SWAP
164
+ @stack = op_2swap(@stack)
165
+ when OP_IFDUP
166
+ @stack = op_ifdup(@stack)
167
+ when OP_DEPTH
168
+ @stack = op_depth(@stack)
169
+ when OP_DROP
170
+ @stack = op_drop(@stack)
171
+ when OP_DUP
172
+ @stack = op_dup(@stack)
173
+ when OP_NIP
174
+ @stack = op_nip(@stack)
175
+ when OP_OVER
176
+ @stack = op_over(@stack)
177
+ when OP_PICK, OP_ROLL
178
+ @stack = op_pick_or_roll(@stack, opcode)
179
+ when OP_ROT
180
+ @stack = op_rot(@stack)
181
+ when OP_SWAP
182
+ @stack = op_swap(@stack)
183
+ when OP_TUCK
184
+ @stack = op_tuck(@stack)
185
+ when OP_SIZE
186
+ @stack = op_size(@stack)
187
+ when OP_RETURN
188
+ @transaction_valid = op_return
189
+ when OP_EQUAL
190
+ @stack = op_equal(@stack)
191
+ when OP_1ADD, OP_1SUB, OP_NEGATE, OP_ABS, OP_NOT, OP_0NOTEQUAL
192
+ @stack = single_stack_arithmetic(@stack)
193
+ when OP_ADD, OP_SUB, OP_BOOLAND, OP_BOOLOR, OP_NUMEQUAL, OP_NUMEQUALVERIFY, OP_NUMNOTEQUAL, OP_LESSTHAN, OP_GREATERTHAN, OP_LESSTHANOREQUAL, OP_GREATERTHANOREQUAL, OP_MIN, OP_MAX
194
+ @stack = double_stack_arithmetic(@stack)
195
+ when OP_WITHIN
196
+ @stack = op_within(@stack)
197
+ when OP_RIPEMD160, OP_SHA256, OP_SHA1, OP_HASH160, OP_HASH256
198
+ @stack = digest(@stack)
199
+ when OP_CHECKSIG
200
+ raise ArgumentError, "Transaction and input are not set" if !@transaction
201
+ op_checksig(@stack, @script, @code_separator, @transaction)
202
+ when OP_CHECKSIGVERIFY
203
+ raise ArgumentError, "Transaction and input are not set" if !@transaction
204
+ @stack = op_checksig(@stack, @script, @code_separator, @transaction)
205
+ @stack = op_verify(@stack)
206
+ when OP_CHECKMULTISIG
207
+ raise ArgumentError, "Transaction and input are not set" if !@transaction
208
+ @stack = check_multisig(@stack, @script, code_separator, @transaction, opcode_count)
209
+ when OP_CHECKMULTISIGVERIFY
210
+ raise ArgumentError, "Transaction and input are not set" if !@transaction
211
+ @stack = check_multisig(@stack, @script, code_separator, @transaction, opcode_count)
212
+ @stack = op_verify(@stack)
213
+ end
214
+ end
215
+ end
216
+
217
+ # if top of stack is truthy, true
218
+ # else, false
219
+ if @stack.last != 0 && !@stack.empty?
220
+ @valid = true
221
+ else
222
+ @valid = false
223
+ end
224
+ end
225
+
226
+ def valid?
227
+ @valid
228
+ end
229
+
230
+ def transaction_valid?
231
+ @transaction_valid
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,64 @@
1
+ require 'cryptocoin/core_ext/string'
2
+
3
+ module Cryptocoin
4
+ module Structure
5
+ # An address is just a specific way to represent a series of bytes within the network
6
+ class Address
7
+ attr_reader :network
8
+ # Creates a new Address structure from a given hash and network
9
+ # Will return false if the address is just 1 as this the representation of 0
10
+ def self.from_s(address_hash, network)
11
+ s = address_hash.from_base58.to_s(16)
12
+ '0'+s = s if s.bytesize.odd?
13
+ return false if s == '00'
14
+
15
+ i = (address_hash.match(/^([1]+)/) ? $1 : '').size
16
+ self.new(['00'*i + s].pack('H*'), network)
17
+ end
18
+
19
+ def initialize(address, network)
20
+ @address_raw = address
21
+ @network = network
22
+ end
23
+
24
+ # Returns identifier for address type
25
+ # Current address types are p2sh and hash160
26
+ def type
27
+ return :p2sh if digest[0..1] == network.p2sh_version
28
+ return :hash160 if digest[0..1] == network.hash160_version
29
+ return :private_key if digest[0..1] == network.private_key_version
30
+ :unknown
31
+ end
32
+
33
+ def to_s
34
+ digest.encode_to_base58
35
+ end
36
+
37
+ def checksum
38
+ digest[-8..-1]
39
+ end
40
+
41
+ def digest
42
+ @address_raw.unpack('H*')[0]
43
+ end
44
+
45
+ def valid?
46
+ valid_checksum? and valid_network_version?
47
+ end
48
+
49
+ private
50
+
51
+ def valid_checksum?
52
+ if type == :private_key
53
+ Cryptocoin::Digest.new([digest[0..-9]].pack('H*'), :binary).double_sha256[0..7] == checksum
54
+ else
55
+ Cryptocoin::Digest.new([digest[0..-9]].pack('H*'), :binary).double_sha256[0..7] == checksum
56
+ end
57
+ end
58
+
59
+ def valid_network_version?
60
+ type != :unknown
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,109 @@
1
+ require 'cryptocoin/structure/transaction'
2
+ require 'cryptocoin/protocol/var_len_int'
3
+ require 'stringio'
4
+
5
+ module Cryptocoin
6
+ module Structure
7
+ class Block
8
+ attr_reader :transactions_count, :transactions
9
+ def self.parse_from_raw(raw)
10
+ io = StringIO.new(raw)
11
+ self.parse_from_io(io)
12
+ end
13
+
14
+ def self.parse_from_io(io, is_aux_pow=false)
15
+ # Save file for future use
16
+ # File.open('block.dat', 'w') do |f|
17
+ # f.puts(io.read)
18
+ # io.seek(0)
19
+ # end
20
+ txs = []
21
+ version_raw = io.read(4)
22
+ prev_block_raw = io.read(32)
23
+ merkle_root_raw = io.read(32)
24
+ timestamp_raw = io.read(4)
25
+ bits_raw = io.read(4)
26
+ nonce_raw = io.read(4)
27
+
28
+ if is_aux_pow
29
+ # TODO: actually save this information
30
+ aux_coinbase_tx = Cryptocoin::Structure::Transaction.parse_from_io(io)
31
+ aux_block_hash = io.read(32)
32
+ aux_coinbase_branch = Cryptocoin::Structure::MerkleBranch.parse_from_io(io)
33
+ aux_blockchain_branch = Cryptocoin::Structure::MerkleBranch.parse_from_io(io)
34
+ aux_parent_block = io.read(80) #This is a block header, parse this later
35
+ end
36
+
37
+ tx_count = Cryptocoin::Protocol::VarLenInt.parse_from_io(io)
38
+ p "Transaction count: #{tx_count}, #{tx_count.to_i}"
39
+ tx_count.times do
40
+ txs.push(Cryptocoin::Structure::Transaction.parse_from_io(io))
41
+ end
42
+ self.new(version_raw, prev_block_raw, merkle_root_raw, timestamp_raw, bits_raw, nonce_raw, tx_count, txs)
43
+ end
44
+
45
+ # txs is an array to keep consistent with the transaction model
46
+ def initialize(version_raw, prev_block_raw, merkle_root_raw, timestamp_raw, bits_raw, nonce_raw, tx_count, txs)
47
+ @version_raw = version_raw
48
+ @prev_block_raw = prev_block_raw
49
+ @merkle_root_raw = merkle_root_raw
50
+ @timestamp_raw = timestamp_raw
51
+ @bits_raw = bits_raw
52
+ @nonce_raw = nonce_raw
53
+ @transactions_count = tx_count
54
+ @transactions = txs
55
+ end
56
+
57
+ def head
58
+ @version_raw + @prev_block_raw + @merkle_root_raw + @timestamp_raw + @bits_raw + @nonce_raw
59
+ end
60
+
61
+ def body
62
+ @transactions_count.raw + @transactions.reduce { |tx, memo| memo += tx }
63
+ end
64
+
65
+ def version
66
+ @version ||= @version_raw.unpack('L')[0]
67
+ end
68
+
69
+ def previous_block_digest
70
+ @previous_block_digest ||= @prev_block_raw.reverse.unpack('H*')[0]
71
+ end
72
+
73
+ def merkle_root_digest
74
+ @merkle_root_digest ||= @merkle_root_raw.reverse.unpack('H*')[0]
75
+ end
76
+
77
+ def timestamp
78
+ @timestamp ||= @timestamp_raw.unpack('L')[0]
79
+ end
80
+
81
+ def bits
82
+ @bits ||= @bits_raw.unpack('L')[0]
83
+ end
84
+
85
+ def target
86
+ require 'openssl'
87
+ @bytes if @bytes
88
+
89
+ puts @bits_raw.unpack('H*')[0]
90
+
91
+ bytes = OpenSSL::BN.new(@bits_raw.unpack('H*')[0].reverse_every(2), 16).to_s(0).unpack('C*')
92
+ size = bytes.size - 4
93
+ nbits = size << 24
94
+ nbits |= (bytes[4] << 16) if size >= 1
95
+ nbits |= (bytes[5] << 8) if size >= 2
96
+ nbits |= (bytes[6] ) if size >= 3
97
+ @bytes = nbits
98
+ end
99
+
100
+ def nonce
101
+ @nonce ||= @nonce_raw.unpack('L')[0]
102
+ end
103
+
104
+ def digest
105
+ @digest ||= Cryptocoin::Digest.new(self.head, :binary).double_sha256.reverse_every(2)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,57 @@
1
+ require 'cryptocoin/core_ext/string'
2
+ require 'cryptocoin/core_ext/integer'
3
+ require 'cryptocoin/structure/address'
4
+
5
+ module Cryptocoin
6
+ module Structure
7
+ class KeyPair
8
+ attr_reader :network, :public_key, :private_key
9
+
10
+ def self.generate(network)
11
+ key = OpenSSL::PKey::EC.new("secp256k1").generate_key
12
+ new(key, network)
13
+ end
14
+
15
+ def initialize(key_pair, network)
16
+ @network = network
17
+
18
+ @public_key = PublicKey.new(key_pair.public_key, network)
19
+ @private_key = PrivateKey.new(key_pair.private_key, network)
20
+ end
21
+
22
+ class PublicKey
23
+ def initialize(public_key, network)
24
+ @network = network
25
+ @public_key = public_key
26
+ end
27
+
28
+ def to_s
29
+ @public_key.to_bn.to_i.to_s(16)
30
+ end
31
+
32
+ def to_address
33
+ kh = @network.hash160_version + Cryptocoin::Digest.new(to_s, :string).hash160
34
+ checksum = Cryptocoin::Digest.new([kh].pack('H*'), :binary).double_sha256[0..7]
35
+ Cryptocoin::Structure::Address.from_s((kh + checksum).encode_to_base58, @network)
36
+ end
37
+ end
38
+
39
+ class PrivateKey
40
+ def initialize(private_key, network)
41
+ @network = network
42
+ @private_key = private_key
43
+ end
44
+
45
+ def to_s
46
+ @private_key.to_bn.to_i.to_s(16)
47
+ end
48
+
49
+ def to_address
50
+ kh = @network.private_key_version + Cryptocoin::Digest.new(to_s, :string).double_sha256
51
+ checksum = Cryptocoin::Digest.new([kh].pack('H*'), :binary).double_sha256[0..7]
52
+ Cryptocoin::Structure::Address.from_s((kh + checksum).to_i(16).to_base58, @network)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,37 @@
1
+ require 'cryptocoin/protocol/var_len_int'
2
+
3
+ module Cryptocoin
4
+ module Structure
5
+ class MerkleBranch
6
+ def self.parse_from_io(io)
7
+ branch_length = Cryptocoin::Protocol::VarLenInt.parse_from_io(io)
8
+
9
+ branch_hashes = branch_length.times.map { |i|
10
+ io.read(32)
11
+ }
12
+
13
+ branch_side_mark = io.read(4)
14
+
15
+ self.new(branch_length, branch_hashes, branch_side_mark)
16
+ end
17
+
18
+ def initialize(branch_length, branch_hashes, branch_side_mark)
19
+ @branch_length_raw = branch_length
20
+ @branch_hashes_raw = branch_hashes
21
+ @branch_side_mark_raw = branch_side_mark
22
+ end
23
+
24
+ def branch_length
25
+ @branch_length ||= @branch_length_raw.to_i
26
+ end
27
+
28
+ def branch_hashes
29
+ @branch_hashes ||= @branch_hashes_raw.map { |branch_hash| branch_hash.reverse.unpack('H*')[0] }
30
+ end
31
+
32
+ def branch_side_mark
33
+ @branch_side_mark ||= @branch_side_mark_raw.unpack('l')[0]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,80 @@
1
+ require 'cryptocoin/protocol/var_len_int'
2
+
3
+ module Cryptocoin
4
+ module Structure
5
+ class Transaction
6
+ class Input
7
+ attr_reader :index, :previous_output_hash, :previous_output_index, :script_sig_length, :script_sig, :sequence
8
+
9
+ # Parses a raw binary transaction input
10
+ # Takes a raw transaction input and outputs a
11
+ # Crytocoin::Structure::Transaction::Input object
12
+ def self.parse_from_raw(i, raw)
13
+ c = 0
14
+ previous_output_hash_raw = raw[c..32]
15
+ previous_output_index_raw = raw[c+32..36]
16
+ sequence = raw[-5..-1]
17
+ c += 36
18
+ script_sig_length = Cryptocoin::Protocol::VarLenInt.parse_from_raw(raw[c..-1])
19
+ script_sig = raw[c+script_sig_length..-5]
20
+ self.new(previous_output_hash_raw, previous_output_index_raw, script_sig_length, script_sig, sequence, i)
21
+ end
22
+
23
+ # Parses the top transaction input from io
24
+ # Returns a Cryptocoin::Structure::Transaction::Input object
25
+ def self.parse_from_io(i, io)
26
+ previous_output_hash_raw = io.read(32)
27
+ previous_output_index_raw = io.read(4)
28
+ script_sig_length = Cryptocoin::Protocol::VarLenInt.parse_from_io(io)
29
+ script_sig = io.read(script_sig_length.to_i)
30
+ sequence_raw = io.read(4)
31
+ self.new(previous_output_hash_raw, previous_output_index_raw, script_sig_length, script_sig, sequence_raw, i)
32
+ end
33
+
34
+ # Creates a new transaction input from supplied arguments, then freezes itself
35
+ # Types: (str) previous_output_hash, (str) previous_output_index
36
+ # (Cryptocoin::Protocol::VarLenInt) sig_script_length, (str) script_sig
37
+ # (str) sequence, (int) index
38
+ def initialize(previous_output_hash, previous_output_index, script_sig_length, script_sig_raw, sequence, index)
39
+ @previous_output_hash_raw = previous_output_hash
40
+ @previous_output_index_raw = previous_output_index
41
+ @script_sig_length = script_sig_length
42
+ @script_sig_raw = script_sig_raw
43
+ @sequence_raw = sequence
44
+ @index = index
45
+ end
46
+
47
+ def previous_output_hash
48
+ @previous_output_hash_raw.reverse.unpack('H*')[0]
49
+ end
50
+
51
+ def previous_output_index
52
+ @previous_output_index_raw.unpack('V')[0]
53
+ end
54
+
55
+ def script_sig
56
+ @script_sig ||= Cryptocoin::Script.new(@script_sig_raw)
57
+ end
58
+
59
+ def raw
60
+ @previous_output_hash_raw + @previous_output_index_raw + @script_sig_length.raw + @script_sig + @sequence
61
+ end
62
+
63
+ def size
64
+ raw.bytesize
65
+ end
66
+
67
+ # Provides a copy of the transaction in which you can set the raw binary values
68
+ def copy
69
+ r = self
70
+ ['previous_output_hash_raw', 'previous_output_index_raw', 'script_sig_length', 'script_sig', 'sequence_raw'].each do |i|
71
+ r.class.send(:define_method, "#{i}=") do |j|
72
+ instance_variable_set("@#{i}", j)
73
+ end
74
+ end
75
+ r
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,49 @@
1
+ require 'cryptocoin/script'
2
+ require 'cryptocoin/protocol/var_len_int'
3
+
4
+ module Cryptocoin
5
+ module Structure
6
+ class Transaction
7
+ class Output
8
+ attr_reader :index, :value, :pk_script_length, :pk_script
9
+
10
+ def self.parse_from_io(i, io)
11
+ value_raw = io.read(8)
12
+ puts "CURRENT IO POS: #{io.pos}"
13
+ pk_script_length = Cryptocoin::Protocol::VarLenInt.parse_from_io(io)
14
+ pk_script_raw = io.read(pk_script_length.to_i)
15
+ self.new(value_raw, pk_script_length, pk_script_raw, i)
16
+ end
17
+
18
+ def initialize(value_raw, pk_scipt_length, pk_script_raw, i)
19
+ @value_raw = value_raw
20
+ @pk_script_length = pk_script_length
21
+ @pk_script_raw = pk_script_raw
22
+ @index = i
23
+ end
24
+
25
+ def value
26
+ @value_raw.unpack('Q')[0]
27
+ end
28
+
29
+ def pk_script
30
+ @pk_script ||= Cryptocoin::Script.new(@pk_script_raw)
31
+ end
32
+
33
+ def raw
34
+ @value_raw + @pk_script_length.raw + @pk_script
35
+ end
36
+
37
+ def copy
38
+ r = self
39
+ ['value_raw', 'pk_script_length', 'pk_script_raw', 'index'].each do |i|
40
+ r.class.send(:define_method, "#{i}=") do |j|
41
+ instance_variable_set("@#{i}", j)
42
+ end
43
+ end
44
+ r
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end