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.
- 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
|