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,27 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# feefilter message
|
5
|
+
# https://bitcoin.org/en/developer-reference#feefilter
|
6
|
+
class FeeFilter < Base
|
7
|
+
|
8
|
+
COMMAND = 'feefilter'
|
9
|
+
|
10
|
+
# The fee rate (in satoshis per kilobyte)
|
11
|
+
attr_accessor :fee_rate
|
12
|
+
|
13
|
+
def initialize(fee_rate)
|
14
|
+
@fee_rate = fee_rate
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.parse_from_payload(payload)
|
18
|
+
new(payload.unpack('Q').first)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_payload
|
22
|
+
[fee_rate].pack('Q')
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# filteradd message
|
5
|
+
# https://bitcoin.org/en/developer-reference#filteradd
|
6
|
+
class FilterAdd < Base
|
7
|
+
|
8
|
+
COMMAND = 'filteradd'
|
9
|
+
|
10
|
+
# element must be sent in the byte order they would use when appearing in a raw transaction;
|
11
|
+
attr_accessor :element
|
12
|
+
|
13
|
+
def initialize(element)
|
14
|
+
@element = element
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.parse_from_payload(payload)
|
18
|
+
new(Bitcoin.unpack_var_string(payload).first.bth)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_payload
|
22
|
+
Bitcoin.pack_var_string(element.htb)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# filterload message
|
5
|
+
# https://bitcoin.org/en/developer-reference#filterload
|
6
|
+
class FilterLoad < Base
|
7
|
+
|
8
|
+
COMMAND = 'filterload'
|
9
|
+
|
10
|
+
BLOOM_UPDATE_NONE = 0
|
11
|
+
BLOOM_UPDATE_ALL = 1
|
12
|
+
BLOOM_UPDATE_P2PUBKEY_ONLY = 2
|
13
|
+
|
14
|
+
attr_accessor :filter # bin format
|
15
|
+
attr_accessor :func_count
|
16
|
+
attr_accessor :tweak
|
17
|
+
attr_accessor :flag
|
18
|
+
|
19
|
+
def initialize(filter, func_count, tweak = 0, flag = BLOOM_UPDATE_ALL)
|
20
|
+
@filter = filter
|
21
|
+
@func_count = func_count
|
22
|
+
@tweak = tweak
|
23
|
+
@flag = flag
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.parse_from_payload(payload)
|
27
|
+
buf = StringIO.new(payload)
|
28
|
+
filter_count = Bitcoin.unpack_var_int_from_io(buf)
|
29
|
+
filter = buf.read(filter_count).unpack('C*')
|
30
|
+
func_count = buf.read(4).unpack('V').first
|
31
|
+
tweak = buf.read(4).unpack('V').first
|
32
|
+
flag = buf.read(1).unpack('C').first
|
33
|
+
new(filter, func_count, tweak, flag)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_payload
|
37
|
+
Bitcoin.pack_var_int(filter.size) << filter.pack('C*') << [func_count, tweak, flag].pack('VVC')
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# block message
|
5
|
+
# https://bitcoin.org/en/developer-reference#getblocks
|
6
|
+
class GetBlocks < Base
|
7
|
+
include HeadersParser
|
8
|
+
extend HeadersParser
|
9
|
+
|
10
|
+
COMMAND = 'getblocks'
|
11
|
+
|
12
|
+
# protocol version
|
13
|
+
attr_accessor :version
|
14
|
+
|
15
|
+
# block header hashes
|
16
|
+
attr_accessor :hashes
|
17
|
+
|
18
|
+
attr_accessor :stop_hash
|
19
|
+
|
20
|
+
def initialize(version, hashes, stop_hash = DEFAULT_STOP_HASH)
|
21
|
+
@version = version
|
22
|
+
@hashes = hashes
|
23
|
+
@stop_hash = stop_hash
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# getdadta message
|
5
|
+
# https://bitcoin.org/en/developer-reference#getdata
|
6
|
+
class GetData < Base
|
7
|
+
include InventoriesParser
|
8
|
+
extend InventoriesParser
|
9
|
+
|
10
|
+
COMMAND ='getdata'
|
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,28 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# getheaders message
|
5
|
+
# https://bitcoin.org/en/developer-reference#getheaders
|
6
|
+
class GetHeaders < Base
|
7
|
+
include HeadersParser
|
8
|
+
extend HeadersParser
|
9
|
+
|
10
|
+
COMMAND = 'getheaders'
|
11
|
+
|
12
|
+
# protocol version
|
13
|
+
attr_accessor :version
|
14
|
+
|
15
|
+
# block header hashes
|
16
|
+
attr_accessor :hashes
|
17
|
+
|
18
|
+
attr_accessor :stop_hash
|
19
|
+
|
20
|
+
def initialize(version, hashes, stop_hash = DEFAULT_STOP_HASH)
|
21
|
+
@version = version
|
22
|
+
@hashes = hashes
|
23
|
+
@stop_hash = stop_hash
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# Default P2P message handler.
|
5
|
+
class Handler
|
6
|
+
|
7
|
+
attr_reader :logger
|
8
|
+
attr_reader :conn
|
9
|
+
|
10
|
+
def initialize(conn, logger = Bitcoin::Logger.create(:parser))
|
11
|
+
@conn = conn
|
12
|
+
@logger = logger
|
13
|
+
@message = ""
|
14
|
+
end
|
15
|
+
|
16
|
+
# handle p2p message.
|
17
|
+
def handle(message)
|
18
|
+
logger.info "handle message #{message.bth}"
|
19
|
+
begin
|
20
|
+
parse(message)
|
21
|
+
rescue Error => e
|
22
|
+
logger.error("invalid header magic. #{e.message}")
|
23
|
+
conn.close
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def parse(message)
|
30
|
+
@message += message
|
31
|
+
command, payload, rest = parse_header
|
32
|
+
return unless command
|
33
|
+
|
34
|
+
handle_command(command, payload)
|
35
|
+
@message = ""
|
36
|
+
parse(rest) if rest && rest.bytesize > 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse_header
|
40
|
+
head_magic = Bitcoin.chain_params.magic_head
|
41
|
+
return if @message.nil? || @message.size < HEADER_SIZE
|
42
|
+
|
43
|
+
magic, command, length, checksum = @message.unpack('a4A12Va4')
|
44
|
+
raise Error, "invalid header magic. #{magic.bth}" unless magic.bth == head_magic
|
45
|
+
|
46
|
+
payload = @message[HEADER_SIZE...(HEADER_SIZE + length)]
|
47
|
+
return if payload.size < length
|
48
|
+
raise Error, "header checksum mismatch. #{checksum.bth}" unless Bitcoin.double_sha256(payload)[0...4] == checksum
|
49
|
+
|
50
|
+
rest = @message[(HEADER_SIZE + length)..-1]
|
51
|
+
[command, payload, rest]
|
52
|
+
end
|
53
|
+
|
54
|
+
def handle_command(command, payload)
|
55
|
+
logger.info("process command #{command}. payload = #{payload.bth}")
|
56
|
+
case command
|
57
|
+
when Version::COMMAND
|
58
|
+
on_version(Version.parse_from_payload(payload))
|
59
|
+
when VerAck::COMMAND
|
60
|
+
on_ver_ack
|
61
|
+
when Addr::COMMAND
|
62
|
+
on_addr(Addr.parse_from_payload(payload))
|
63
|
+
when SendHeaders::COMMAND
|
64
|
+
on_send_headers
|
65
|
+
when FeeFilter::COMMAND
|
66
|
+
on_fee_filter(FeeFilter.parse_from_payload(payload))
|
67
|
+
when Ping::COMMAND
|
68
|
+
on_ping(Ping.parse_from_payload(payload))
|
69
|
+
when Pong::COMMAND
|
70
|
+
on_pong(Pong.parse_from_payload(payload))
|
71
|
+
when GetHeaders::COMMAND
|
72
|
+
on_get_headers(GetHeaders.parse_from_payload(payload))
|
73
|
+
when Headers::COMMAND
|
74
|
+
on_headers(Headers.parse_from_payload(payload))
|
75
|
+
when Block::COMMAND
|
76
|
+
on_block(Block.parse_from_payload(payload))
|
77
|
+
when Tx::COMMAND
|
78
|
+
on_tx(Tx.parse_from_payload(payload))
|
79
|
+
when NotFound::COMMAND
|
80
|
+
on_not_found(NotFound.parse_from_payload(payload))
|
81
|
+
when MemPool::COMMAND
|
82
|
+
on_mem_pool
|
83
|
+
when Reject::COMMAND
|
84
|
+
on_reject(Reject.parse_from_payload(payload))
|
85
|
+
when SendCmpct::COMMAND
|
86
|
+
on_send_cmpct(SendCmpct.parse_from_payload(payload))
|
87
|
+
else
|
88
|
+
logger.warn("unsupported command received. #{command}")
|
89
|
+
conn.close("with command #{command}")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def on_version(version)
|
94
|
+
logger.info("receive version message. #{version.to_json}")
|
95
|
+
conn.send_data(VerAck.new.to_pkt)
|
96
|
+
end
|
97
|
+
|
98
|
+
def on_ver_ack
|
99
|
+
logger.info('receive verack message.')
|
100
|
+
conn.handshake_done
|
101
|
+
end
|
102
|
+
|
103
|
+
def on_addr(addr)
|
104
|
+
logger.info("receive addr message. #{addr.to_json}")
|
105
|
+
# TODO
|
106
|
+
end
|
107
|
+
|
108
|
+
def on_send_headers
|
109
|
+
logger.info('receive sendheaders message.')
|
110
|
+
conn.sendheaders = true
|
111
|
+
end
|
112
|
+
|
113
|
+
def on_fee_filter(fee_filter)
|
114
|
+
logger.info("receive feefilter message. #{fee_filter.to_json}")
|
115
|
+
conn.fee_rate = fee_filter.fee_rate
|
116
|
+
end
|
117
|
+
|
118
|
+
def on_ping(ping)
|
119
|
+
logger.info("receive ping message. #{ping.to_json}")
|
120
|
+
conn.send_data(ping.to_response)
|
121
|
+
end
|
122
|
+
|
123
|
+
def on_pong(pong)
|
124
|
+
logger.info("receive pong message. #{pong.to_json}")
|
125
|
+
# TODO calculate response
|
126
|
+
end
|
127
|
+
|
128
|
+
def on_get_headers(headers)
|
129
|
+
logger.info("receive getheaders message. #{headers.to_json}")
|
130
|
+
# TODO
|
131
|
+
end
|
132
|
+
|
133
|
+
def on_headers(headers)
|
134
|
+
logger.info("receive headers message. #{headers.to_json}")
|
135
|
+
# TODO
|
136
|
+
end
|
137
|
+
|
138
|
+
def on_block(block)
|
139
|
+
logger.info("receive block message. #{block.to_json}")
|
140
|
+
# TODO
|
141
|
+
end
|
142
|
+
|
143
|
+
def on_tx(tx)
|
144
|
+
logger.info("receive tx message. #{tx.to_json}")
|
145
|
+
# TODO
|
146
|
+
end
|
147
|
+
|
148
|
+
def on_not_found(not_found)
|
149
|
+
logger.info("receive notfound message. #{not_found.to_json}")
|
150
|
+
# TODO
|
151
|
+
end
|
152
|
+
|
153
|
+
def on_mem_pool
|
154
|
+
logger.info('receive mempool message.')
|
155
|
+
# TODO return mempool tx
|
156
|
+
end
|
157
|
+
|
158
|
+
def on_reject(reject)
|
159
|
+
logger.warn("receive reject message. #{reject.to_json}")
|
160
|
+
# TODO
|
161
|
+
end
|
162
|
+
|
163
|
+
def on_send_cmpct(cmpct)
|
164
|
+
logger.info("receive sendcmpct message. #{cmpct.to_json}")
|
165
|
+
# TODO if mode is high and version is 1, relay block with cmpctblock message
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# headers message
|
5
|
+
# https://bitcoin.org/en/developer-reference#headers
|
6
|
+
class Headers < Base
|
7
|
+
|
8
|
+
COMMAND = 'headers'
|
9
|
+
|
10
|
+
attr_accessor :headers
|
11
|
+
|
12
|
+
def initialize(headers = [])
|
13
|
+
@headers = headers
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse_from_payload(payload)
|
17
|
+
buf = StringIO.new(payload)
|
18
|
+
header_count = Bitcoin.unpack_var_int_from_io(buf)
|
19
|
+
h = new
|
20
|
+
header_count.times do
|
21
|
+
h.headers << Bitcoin::BlockHeader.parse_from_payload(buf.read(80))
|
22
|
+
buf.read(1) # read tx count 0x00 (headers message doesn't include any tx.)
|
23
|
+
end
|
24
|
+
h
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_payload
|
28
|
+
Bitcoin.pack_var_int(headers.size) << headers.map { |h| h.to_payload << 0x00 }.join
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# Common message parser which handle multiple block headers as a payload.
|
5
|
+
module HeadersParser
|
6
|
+
|
7
|
+
def parse_from_payload(payload)
|
8
|
+
ver, payload = payload.unpack('Va*')
|
9
|
+
size, payload = Bitcoin.unpack_var_int(payload)
|
10
|
+
hashes = []
|
11
|
+
buf = StringIO.new(payload)
|
12
|
+
size.times do
|
13
|
+
hashes << buf.read(32).reverse.bth
|
14
|
+
end
|
15
|
+
new(ver, hashes, buf.read(32).reverse.bth)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_payload
|
19
|
+
[version].pack('V') << Bitcoin.pack_var_int(hashes.length) << hashes.map{|h|h.htb.reverse}.join << stop_hash.htb.reverse
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|