bitcoinrb 0.0.1
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 +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
|