cryptocoin 0.0.1b
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +85 -0
- data/Rakefile +2 -0
- data/cryptocoin.gemspec +24 -0
- data/lib/cryptocoin/core_ext/integer.rb +24 -0
- data/lib/cryptocoin/core_ext/string.rb +30 -0
- data/lib/cryptocoin/digest.rb +36 -0
- data/lib/cryptocoin/merkle_tree.rb +78 -0
- data/lib/cryptocoin/network/bitcoin.rb +9 -0
- data/lib/cryptocoin/network/dogecoin.rb +9 -0
- data/lib/cryptocoin/network/litecoin.rb +9 -0
- data/lib/cryptocoin/network.rb +53 -0
- data/lib/cryptocoin/protocol/block_header.rb +58 -0
- data/lib/cryptocoin/protocol/inventory_vector.rb +28 -0
- data/lib/cryptocoin/protocol/message/addr.rb +29 -0
- data/lib/cryptocoin/protocol/message/alert.rb +0 -0
- data/lib/cryptocoin/protocol/message/block.rb +13 -0
- data/lib/cryptocoin/protocol/message/getaddr.rb +17 -0
- data/lib/cryptocoin/protocol/message/getblocks.rb +44 -0
- data/lib/cryptocoin/protocol/message/getdata.rb +31 -0
- data/lib/cryptocoin/protocol/message/getheaders.rb +44 -0
- data/lib/cryptocoin/protocol/message/headers.rb +30 -0
- data/lib/cryptocoin/protocol/message/inv.rb +31 -0
- data/lib/cryptocoin/protocol/message/mempool.rb +16 -0
- data/lib/cryptocoin/protocol/message/notfound.rb +31 -0
- data/lib/cryptocoin/protocol/message/ping.rb +24 -0
- data/lib/cryptocoin/protocol/message/pong.rb +24 -0
- data/lib/cryptocoin/protocol/message/reject.rb +54 -0
- data/lib/cryptocoin/protocol/message/tx.rb +11 -0
- data/lib/cryptocoin/protocol/message/verack.rb +16 -0
- data/lib/cryptocoin/protocol/message/version.rb +59 -0
- data/lib/cryptocoin/protocol/message.rb +27 -0
- data/lib/cryptocoin/protocol/net_addr.rb +55 -0
- data/lib/cryptocoin/protocol/packet.rb +74 -0
- data/lib/cryptocoin/protocol/var_len_int.rb +85 -0
- data/lib/cryptocoin/protocol/var_len_str.rb +18 -0
- data/lib/cryptocoin/protocol.rb +8 -0
- data/lib/cryptocoin/script/op_code/constants.rb +157 -0
- data/lib/cryptocoin/script/op_code/functions.rb +515 -0
- data/lib/cryptocoin/script/op_code.rb +59 -0
- data/lib/cryptocoin/script.rb +234 -0
- data/lib/cryptocoin/structure/address.rb +64 -0
- data/lib/cryptocoin/structure/block.rb +109 -0
- data/lib/cryptocoin/structure/key_pair.rb +57 -0
- data/lib/cryptocoin/structure/merkle_branch.rb +37 -0
- data/lib/cryptocoin/structure/transaction/input.rb +80 -0
- data/lib/cryptocoin/structure/transaction/output.rb +49 -0
- data/lib/cryptocoin/structure/transaction.rb +94 -0
- data/lib/cryptocoin/version.rb +3 -0
- data/lib/cryptocoin.rb +29 -0
- data/spec/script_spec.rb +42 -0
- data/spec/spec_helper.rb +20 -0
- 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
|