bitcoinrb 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bitcoinrb.gemspec +32 -0
- data/exe/bitcoinrb-cli +5 -0
- data/exe/bitcoinrbd +49 -0
- data/lib/bitcoin.rb +121 -0
- data/lib/bitcoin/base58.rb +40 -0
- data/lib/bitcoin/block_header.rb +41 -0
- data/lib/bitcoin/chain_params.rb +57 -0
- data/lib/bitcoin/chainparams/mainnet.yml +25 -0
- data/lib/bitcoin/chainparams/regtest.yml +20 -0
- data/lib/bitcoin/chainparams/testnet.yml +24 -0
- data/lib/bitcoin/connection.rb +66 -0
- data/lib/bitcoin/ext_key.rb +205 -0
- data/lib/bitcoin/key.rb +131 -0
- data/lib/bitcoin/logger.rb +18 -0
- data/lib/bitcoin/merkle_tree.rb +120 -0
- data/lib/bitcoin/message.rb +42 -0
- data/lib/bitcoin/message/addr.rb +74 -0
- data/lib/bitcoin/message/base.rb +40 -0
- data/lib/bitcoin/message/block.rb +41 -0
- data/lib/bitcoin/message/error.rb +10 -0
- data/lib/bitcoin/message/fee_filter.rb +27 -0
- data/lib/bitcoin/message/filter_add.rb +28 -0
- data/lib/bitcoin/message/filter_clear.rb +17 -0
- data/lib/bitcoin/message/filter_load.rb +43 -0
- data/lib/bitcoin/message/get_addr.rb +17 -0
- data/lib/bitcoin/message/get_blocks.rb +29 -0
- data/lib/bitcoin/message/get_data.rb +21 -0
- data/lib/bitcoin/message/get_headers.rb +28 -0
- data/lib/bitcoin/message/handler.rb +170 -0
- data/lib/bitcoin/message/headers.rb +34 -0
- data/lib/bitcoin/message/headers_parser.rb +24 -0
- data/lib/bitcoin/message/inv.rb +21 -0
- data/lib/bitcoin/message/inventories_parser.rb +23 -0
- data/lib/bitcoin/message/inventory.rb +47 -0
- data/lib/bitcoin/message/mem_pool.rb +17 -0
- data/lib/bitcoin/message/merkle_block.rb +42 -0
- data/lib/bitcoin/message/not_found.rb +29 -0
- data/lib/bitcoin/message/ping.rb +30 -0
- data/lib/bitcoin/message/pong.rb +26 -0
- data/lib/bitcoin/message/reject.rb +46 -0
- data/lib/bitcoin/message/send_cmpct.rb +43 -0
- data/lib/bitcoin/message/send_headers.rb +16 -0
- data/lib/bitcoin/message/tx.rb +30 -0
- data/lib/bitcoin/message/ver_ack.rb +17 -0
- data/lib/bitcoin/message/version.rb +79 -0
- data/lib/bitcoin/mnemonic.rb +76 -0
- data/lib/bitcoin/mnemonic/wordlist/chinese_simplified.txt +2048 -0
- data/lib/bitcoin/mnemonic/wordlist/chinese_traditional.txt +2048 -0
- data/lib/bitcoin/mnemonic/wordlist/english.txt +2048 -0
- data/lib/bitcoin/mnemonic/wordlist/french.txt +2048 -0
- data/lib/bitcoin/mnemonic/wordlist/italian.txt +2048 -0
- data/lib/bitcoin/mnemonic/wordlist/japanese.txt +2048 -0
- data/lib/bitcoin/mnemonic/wordlist/spanish.txt +2048 -0
- data/lib/bitcoin/nodes.rb +5 -0
- data/lib/bitcoin/nodes/spv.rb +13 -0
- data/lib/bitcoin/nodes/spv/cli.rb +12 -0
- data/lib/bitcoin/nodes/spv/daemon.rb +21 -0
- data/lib/bitcoin/opcodes.rb +172 -0
- data/lib/bitcoin/out_point.rb +31 -0
- data/lib/bitcoin/script/script.rb +347 -0
- data/lib/bitcoin/script/script_error.rb +168 -0
- data/lib/bitcoin/script/script_interpreter.rb +694 -0
- data/lib/bitcoin/script/tx_checker.rb +44 -0
- data/lib/bitcoin/script_witness.rb +29 -0
- data/lib/bitcoin/secp256k1.rb +10 -0
- data/lib/bitcoin/secp256k1/native.rb +22 -0
- data/lib/bitcoin/secp256k1/ruby.rb +96 -0
- data/lib/bitcoin/tx.rb +191 -0
- data/lib/bitcoin/tx_in.rb +45 -0
- data/lib/bitcoin/tx_out.rb +32 -0
- data/lib/bitcoin/util.rb +105 -0
- data/lib/bitcoin/version.rb +3 -0
- metadata +256 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# inv message
|
5
|
+
# https://bitcoin.org/en/developer-reference#inv
|
6
|
+
class Inv < Base
|
7
|
+
include InventoriesParser
|
8
|
+
extend InventoriesParser
|
9
|
+
|
10
|
+
COMMAND = 'inv'
|
11
|
+
|
12
|
+
attr_reader :inventories
|
13
|
+
|
14
|
+
def initialize(inventories = [])
|
15
|
+
@inventories = inventories
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# Common message parser which only handle multiple inventory as payload.
|
5
|
+
module InventoriesParser
|
6
|
+
|
7
|
+
def parse_from_payload(payload)
|
8
|
+
size, payload = Bitcoin.unpack_var_int(payload)
|
9
|
+
buf = StringIO.new(payload)
|
10
|
+
i = new
|
11
|
+
size.times do
|
12
|
+
i.inventories << Inventory.parse_from_payload(buf.read(36))
|
13
|
+
end
|
14
|
+
i
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_payload
|
18
|
+
Bitcoin.pack_var_int(inventories.length) << inventories.map(&:to_payload).join
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
# inventory class. inventory is a part of message.
|
4
|
+
# https://bitcoin.org/en/developer-reference#term-inventory
|
5
|
+
class Inventory
|
6
|
+
|
7
|
+
SEGWIT_FLAG = 1 << 30
|
8
|
+
|
9
|
+
MSG_TX = 1
|
10
|
+
MSG_BLOCK = 2
|
11
|
+
MSG_FILTERED_BLOCK = 3
|
12
|
+
MSG_CMPCT_BLOCK = 4
|
13
|
+
MSG_WITNESS_TX = SEGWIT_FLAG | MSG_TX
|
14
|
+
MSG_WITNESS_BLOCK = SEGWIT_FLAG | MSG_BLOCK
|
15
|
+
MSG_FILTERED_WITNESS_BLOCK = SEGWIT_FLAG | MSG_FILTERED_BLOCK
|
16
|
+
|
17
|
+
attr_accessor :identifier
|
18
|
+
attr_accessor :hash
|
19
|
+
|
20
|
+
def initialize(identifier, hash)
|
21
|
+
raise Error, "invalid type identifier specified. identifier = #{identifier}" unless valid_identifier?(identifier)
|
22
|
+
@identifier = identifier
|
23
|
+
@hash = hash
|
24
|
+
end
|
25
|
+
|
26
|
+
# parse inventory payload
|
27
|
+
def self.parse_from_payload(payload)
|
28
|
+
raise Error, 'invalid inventory size.' if payload.bytesize != 36
|
29
|
+
identifier = payload[0..4].unpack('V').first
|
30
|
+
hash = payload[4..-1].reverse.bth # internal byte order
|
31
|
+
new(identifier, hash)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_payload
|
35
|
+
[identifier].pack('V') << hash.htb.reverse
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def valid_identifier?(identifier)
|
41
|
+
[MSG_TX, MSG_BLOCK, MSG_FILTERED_BLOCK, MSG_CMPCT_BLOCK, MSG_WITNESS_TX,
|
42
|
+
MSG_WITNESS_BLOCK, MSG_FILTERED_WITNESS_BLOCK].include?(identifier)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# merckleblock message
|
5
|
+
# https://bitcoin.org/en/developer-reference#merkleblock
|
6
|
+
class MerkleBlock < Base
|
7
|
+
|
8
|
+
COMMAND = 'merkleblock'
|
9
|
+
|
10
|
+
attr_accessor :header
|
11
|
+
attr_accessor :tx_count
|
12
|
+
attr_accessor :hashes
|
13
|
+
attr_accessor :flags
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@hashes = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.parse_from_payload(payload)
|
20
|
+
m = new
|
21
|
+
buf = StringIO.new(payload)
|
22
|
+
m.header = Bitcoin::BlockHeader.parse_from_payload(buf.read(80))
|
23
|
+
m.tx_count = buf.read(4).unpack('V').first
|
24
|
+
hash_count = Bitcoin.unpack_var_int_from_io(buf)
|
25
|
+
hash_count.times do
|
26
|
+
m.hashes << buf.read(32).reverse.bth
|
27
|
+
end
|
28
|
+
flag_count = Bitcoin.unpack_var_int_from_io(buf)
|
29
|
+
# A sequence of bits packed eight in a byte with the least significant bit first.
|
30
|
+
m.flags = buf.read(flag_count).bth
|
31
|
+
m
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_payload
|
35
|
+
header.to_payload << [tx_count].pack('V') << Bitcoin.pack_var_int(hashes.size) <<
|
36
|
+
hashes.map{|h|h.htb.reverse}.join << Bitcoin.pack_var_int(flags.htb.bytesize) << flags.htb
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# notfound message
|
5
|
+
# https://bitcoin.org/en/developer-reference#notfound
|
6
|
+
class NotFound < Base
|
7
|
+
|
8
|
+
attr_accessor :inventory
|
9
|
+
|
10
|
+
COMMAND = 'notfound'
|
11
|
+
|
12
|
+
def initialize(identifier, hash)
|
13
|
+
@inventory = Inventory.new(identifier, hash)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse_from_payload(payload)
|
17
|
+
size, inventory_payload = Bitcoin.unpack_var_int(payload)
|
18
|
+
inventory = Inventory.parse_from_payload(inventory_payload)
|
19
|
+
new(inventory.identifier, inventory.hash)
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_payload
|
23
|
+
Bitcoin.pack_var_int(1) << inventory.to_payload
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# ping message class
|
5
|
+
# https://bitcoin.org/en/developer-reference#ping
|
6
|
+
class Ping < Base
|
7
|
+
|
8
|
+
COMMAND = 'ping'
|
9
|
+
|
10
|
+
attr_accessor :nonce
|
11
|
+
|
12
|
+
def initialize(nonce = SecureRandom.random_number(0xffffffff))
|
13
|
+
@nonce = nonce
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse_from_payload(payload)
|
17
|
+
new(payload.unpack('Q').first)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_payload
|
21
|
+
[nonce].pack('Q')
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_response
|
25
|
+
Pong.new(nonce)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# pong message
|
5
|
+
# https://bitcoin.org/en/developer-reference#pong
|
6
|
+
class Pong < Base
|
7
|
+
|
8
|
+
COMMAND = 'pong'
|
9
|
+
|
10
|
+
attr_reader :nonce
|
11
|
+
|
12
|
+
def initialize(nonce)
|
13
|
+
@nonce = nonce
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse_from_payload(payload)
|
17
|
+
new(payload.unpack('Q').first)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_payload
|
21
|
+
[nonce].pack('Q')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# reject message
|
5
|
+
# https://bitcoin.org/en/developer-reference#reject
|
6
|
+
class Reject < Base
|
7
|
+
|
8
|
+
attr_accessor :message
|
9
|
+
attr_accessor :code
|
10
|
+
attr_accessor :reason
|
11
|
+
attr_accessor :extra
|
12
|
+
|
13
|
+
COMMAND = 'reject'
|
14
|
+
|
15
|
+
CODE_MALFORMED = 0x01
|
16
|
+
CODE_INVALID = 0x10
|
17
|
+
CODE_OBSOLETE = 0x11
|
18
|
+
CODE_DUPLICATE = 0x12
|
19
|
+
CODE_NONSTANDARD = 0x40
|
20
|
+
CODE_DUST = 0x41
|
21
|
+
CODE_INSUFFICIENT_FEE = 0x42
|
22
|
+
CODE_CHECKPOINT = 0x43
|
23
|
+
|
24
|
+
def initialize(message, code, reason = '', extra = '')
|
25
|
+
@message = message
|
26
|
+
@code = code
|
27
|
+
@reason = reason
|
28
|
+
@extra = extra
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.parse_from_payload(payload)
|
32
|
+
message, payload = Bitcoin.unpack_var_string(payload)
|
33
|
+
code, payload = payload.unpack('Ca*')
|
34
|
+
reason, payload = Bitcoin.unpack_var_string(payload)
|
35
|
+
extra = ['tx', 'block'].include?(message) ? payload.reverse.bth : payload
|
36
|
+
new(message, code, reason, extra)
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_payload
|
40
|
+
e = ['tx', 'block'].include?(message) ? extra.htb.reverse : extra
|
41
|
+
Bitcoin.pack_var_string(message) << [code].pack('C') << Bitcoin.pack_var_string(reason) << e
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# sendcmpct message
|
5
|
+
# https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki
|
6
|
+
class SendCmpct < Base
|
7
|
+
|
8
|
+
COMMAND = 'sendcmpct'
|
9
|
+
|
10
|
+
MODE_HIGH = 1
|
11
|
+
MODE_LOW = 0
|
12
|
+
|
13
|
+
attr_accessor :mode
|
14
|
+
attr_accessor :version
|
15
|
+
# TODO support version 2
|
16
|
+
|
17
|
+
def initialize(mode, version)
|
18
|
+
@mode = mode
|
19
|
+
@version = version
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.parse_from_payload(payload)
|
23
|
+
buf = StringIO.new(payload)
|
24
|
+
mode = buf.read(1).unpack('c').first
|
25
|
+
version = buf.read(8).unpack('Q').first
|
26
|
+
new(mode, version)
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_payload
|
30
|
+
[mode, version].pack('cQ')
|
31
|
+
end
|
32
|
+
|
33
|
+
def high?
|
34
|
+
mode == 1
|
35
|
+
end
|
36
|
+
|
37
|
+
def low?
|
38
|
+
mode.zero?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# tx message
|
5
|
+
# https://bitcoin.org/en/developer-reference#tx
|
6
|
+
class Tx < Base
|
7
|
+
|
8
|
+
COMMAND = 'tx'
|
9
|
+
|
10
|
+
attr_accessor :tx
|
11
|
+
attr_accessor :use_segwit
|
12
|
+
|
13
|
+
def initialize(tx, use_segwit = false)
|
14
|
+
@tx = tx
|
15
|
+
@use_segwit = use_segwit
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.parse_from_payload(payload)
|
19
|
+
tx = Bitcoin::Tx.parse_from_payload(payload)
|
20
|
+
new(tx, tx.witness?)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_payload
|
24
|
+
use_segwit ? tx.to_payload : tx.serialize_old_format
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
module Bitcoin
|
3
|
+
module Message
|
4
|
+
|
5
|
+
# https://bitcoin.org/en/developer-reference#version
|
6
|
+
class Version < Base
|
7
|
+
|
8
|
+
COMMAND = 'version'
|
9
|
+
|
10
|
+
attr_accessor :version
|
11
|
+
attr_accessor :services
|
12
|
+
attr_accessor :timestamp
|
13
|
+
attr_accessor :local_addr
|
14
|
+
attr_accessor :remote_addr
|
15
|
+
attr_accessor :nonce
|
16
|
+
attr_accessor :user_agent
|
17
|
+
attr_accessor :start_height
|
18
|
+
attr_accessor :relay
|
19
|
+
|
20
|
+
def initialize(opts = {})
|
21
|
+
@version = Bitcoin.chain_params.protocol_version
|
22
|
+
@services = Bitcoin::Message::SERVICE_FLAGS[:network]
|
23
|
+
@timestamp = Time.now.to_i
|
24
|
+
@local_addr = "127.0.0.1:#{Bitcoin.chain_params.default_port}"
|
25
|
+
@remote_addr = "127.0.0.1:#{Bitcoin.chain_params.default_port}"
|
26
|
+
@nonce = SecureRandom.random_number(0xffffffffffffffff)
|
27
|
+
@user_agent = Bitcoin::Message::USER_AGENT
|
28
|
+
@start_height = 0
|
29
|
+
@relay = true
|
30
|
+
opts.each { |k, v| send "#{k}=", v }
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.parse_from_payload(payload)
|
34
|
+
version, services, timestamp, remote_addr, local_addr, nonce, rest = payload.unpack('VQQa26a26Qa*')
|
35
|
+
v = new
|
36
|
+
v.version = version
|
37
|
+
v.services = services
|
38
|
+
v.timestamp = timestamp
|
39
|
+
v.remote_addr = v.unpack_addr(remote_addr)
|
40
|
+
v.local_addr = v.unpack_addr(local_addr)
|
41
|
+
v.nonce = nonce
|
42
|
+
user_agent, rest = unpack_var_string(rest)
|
43
|
+
start_height, rest = rest.unpack('Va*')
|
44
|
+
v.user_agent = user_agent
|
45
|
+
v.start_height = start_height
|
46
|
+
v.relay = v.unpack_relay_field(rest).first
|
47
|
+
v
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_payload
|
51
|
+
[
|
52
|
+
[version, services, timestamp].pack('VQQ'),
|
53
|
+
pack_addr(local_addr),
|
54
|
+
pack_addr(remote_addr),
|
55
|
+
[nonce].pack('Q'),
|
56
|
+
pack_var_string(user_agent),
|
57
|
+
[start_height].pack('V'),
|
58
|
+
pack_boolean(relay)
|
59
|
+
].join
|
60
|
+
end
|
61
|
+
|
62
|
+
def pack_addr(addr)
|
63
|
+
host, port = addr.split(':')
|
64
|
+
sockaddr = Socket.pack_sockaddr_in(port.to_i, host)
|
65
|
+
[[1].pack('Q'), "\x00" * 10, "\xFF\xFF", sockaddr[4...8], sockaddr[2...4]].join
|
66
|
+
end
|
67
|
+
|
68
|
+
def unpack_addr(addr)
|
69
|
+
host, port = addr.unpack('x8x12a4n')
|
70
|
+
"#{host.unpack('C*').join('.')}:#{port}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def unpack_relay_field(payload)
|
74
|
+
( version >= 70001 && payload ) ? unpack_boolean(payload) : [ true, nil ]
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|