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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +4 -0
  7. data/CODE_OF_CONDUCT.md +49 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +41 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/bitcoinrb.gemspec +32 -0
  15. data/exe/bitcoinrb-cli +5 -0
  16. data/exe/bitcoinrbd +49 -0
  17. data/lib/bitcoin.rb +121 -0
  18. data/lib/bitcoin/base58.rb +40 -0
  19. data/lib/bitcoin/block_header.rb +41 -0
  20. data/lib/bitcoin/chain_params.rb +57 -0
  21. data/lib/bitcoin/chainparams/mainnet.yml +25 -0
  22. data/lib/bitcoin/chainparams/regtest.yml +20 -0
  23. data/lib/bitcoin/chainparams/testnet.yml +24 -0
  24. data/lib/bitcoin/connection.rb +66 -0
  25. data/lib/bitcoin/ext_key.rb +205 -0
  26. data/lib/bitcoin/key.rb +131 -0
  27. data/lib/bitcoin/logger.rb +18 -0
  28. data/lib/bitcoin/merkle_tree.rb +120 -0
  29. data/lib/bitcoin/message.rb +42 -0
  30. data/lib/bitcoin/message/addr.rb +74 -0
  31. data/lib/bitcoin/message/base.rb +40 -0
  32. data/lib/bitcoin/message/block.rb +41 -0
  33. data/lib/bitcoin/message/error.rb +10 -0
  34. data/lib/bitcoin/message/fee_filter.rb +27 -0
  35. data/lib/bitcoin/message/filter_add.rb +28 -0
  36. data/lib/bitcoin/message/filter_clear.rb +17 -0
  37. data/lib/bitcoin/message/filter_load.rb +43 -0
  38. data/lib/bitcoin/message/get_addr.rb +17 -0
  39. data/lib/bitcoin/message/get_blocks.rb +29 -0
  40. data/lib/bitcoin/message/get_data.rb +21 -0
  41. data/lib/bitcoin/message/get_headers.rb +28 -0
  42. data/lib/bitcoin/message/handler.rb +170 -0
  43. data/lib/bitcoin/message/headers.rb +34 -0
  44. data/lib/bitcoin/message/headers_parser.rb +24 -0
  45. data/lib/bitcoin/message/inv.rb +21 -0
  46. data/lib/bitcoin/message/inventories_parser.rb +23 -0
  47. data/lib/bitcoin/message/inventory.rb +47 -0
  48. data/lib/bitcoin/message/mem_pool.rb +17 -0
  49. data/lib/bitcoin/message/merkle_block.rb +42 -0
  50. data/lib/bitcoin/message/not_found.rb +29 -0
  51. data/lib/bitcoin/message/ping.rb +30 -0
  52. data/lib/bitcoin/message/pong.rb +26 -0
  53. data/lib/bitcoin/message/reject.rb +46 -0
  54. data/lib/bitcoin/message/send_cmpct.rb +43 -0
  55. data/lib/bitcoin/message/send_headers.rb +16 -0
  56. data/lib/bitcoin/message/tx.rb +30 -0
  57. data/lib/bitcoin/message/ver_ack.rb +17 -0
  58. data/lib/bitcoin/message/version.rb +79 -0
  59. data/lib/bitcoin/mnemonic.rb +76 -0
  60. data/lib/bitcoin/mnemonic/wordlist/chinese_simplified.txt +2048 -0
  61. data/lib/bitcoin/mnemonic/wordlist/chinese_traditional.txt +2048 -0
  62. data/lib/bitcoin/mnemonic/wordlist/english.txt +2048 -0
  63. data/lib/bitcoin/mnemonic/wordlist/french.txt +2048 -0
  64. data/lib/bitcoin/mnemonic/wordlist/italian.txt +2048 -0
  65. data/lib/bitcoin/mnemonic/wordlist/japanese.txt +2048 -0
  66. data/lib/bitcoin/mnemonic/wordlist/spanish.txt +2048 -0
  67. data/lib/bitcoin/nodes.rb +5 -0
  68. data/lib/bitcoin/nodes/spv.rb +13 -0
  69. data/lib/bitcoin/nodes/spv/cli.rb +12 -0
  70. data/lib/bitcoin/nodes/spv/daemon.rb +21 -0
  71. data/lib/bitcoin/opcodes.rb +172 -0
  72. data/lib/bitcoin/out_point.rb +31 -0
  73. data/lib/bitcoin/script/script.rb +347 -0
  74. data/lib/bitcoin/script/script_error.rb +168 -0
  75. data/lib/bitcoin/script/script_interpreter.rb +694 -0
  76. data/lib/bitcoin/script/tx_checker.rb +44 -0
  77. data/lib/bitcoin/script_witness.rb +29 -0
  78. data/lib/bitcoin/secp256k1.rb +10 -0
  79. data/lib/bitcoin/secp256k1/native.rb +22 -0
  80. data/lib/bitcoin/secp256k1/ruby.rb +96 -0
  81. data/lib/bitcoin/tx.rb +191 -0
  82. data/lib/bitcoin/tx_in.rb +45 -0
  83. data/lib/bitcoin/tx_out.rb +32 -0
  84. data/lib/bitcoin/util.rb +105 -0
  85. data/lib/bitcoin/version.rb +3 -0
  86. metadata +256 -0
@@ -0,0 +1,41 @@
1
+ module Bitcoin
2
+
3
+ # Block Header
4
+ class BlockHeader
5
+
6
+ attr_accessor :hash
7
+ attr_accessor :version
8
+ attr_accessor :prev_hash
9
+ attr_accessor :merkle_root
10
+ attr_accessor :time
11
+ attr_accessor :bits
12
+ attr_accessor :nonce
13
+
14
+ def initialize(version, prev_hash, merkle_root, time, bits, nonce)
15
+ @version = version
16
+ @prev_hash = prev_hash
17
+ @merkle_root = merkle_root
18
+ @time = time
19
+ @bits = bits
20
+ @nonce = nonce
21
+ @hash = calc_hash
22
+ end
23
+
24
+ def self.parse_from_payload(payload)
25
+ version, prev_hash, merkle_root, time, bits, nonce = payload.unpack('Va32a32VVV')
26
+ new(version, prev_hash.reverse.bth, merkle_root.reverse.bth, time, bits, nonce)
27
+ end
28
+
29
+ def to_payload
30
+ [version, prev_hash.htb.reverse, merkle_root.htb.reverse, time, bits, nonce].pack('Va32a32VVV')
31
+ end
32
+
33
+ private
34
+
35
+ def calc_hash
36
+ Bitcoin.double_sha256(to_payload).reverse.bth
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,57 @@
1
+ require 'yaml'
2
+
3
+ module Bitcoin
4
+
5
+ # Network parameter class
6
+ class ChainParams
7
+
8
+ attr_reader :network
9
+ attr_reader :magic_head
10
+ attr_reader :message_magic
11
+ attr_reader :address_version
12
+ attr_reader :p2sh_version
13
+ attr_reader :bech32_hrp
14
+ attr_reader :privkey_version
15
+ attr_reader :extended_privkey_version
16
+ attr_reader :extended_pubkey_version
17
+ attr_reader :default_port
18
+ attr_reader :protocol_version
19
+ attr_reader :retarget_interval
20
+ attr_reader :retarget_time
21
+ attr_reader :target_spacing
22
+ attr_reader :max_money
23
+ attr_reader :bip34_height
24
+ attr_reader :genesis_hash
25
+ attr_reader :proof_of_work_limit
26
+ attr_reader :dns_seeds
27
+
28
+ # mainnet params
29
+ def self.mainnet
30
+ YAML.load(File.open("#{__dir__}/chainparams/mainnet.yml"))
31
+ end
32
+
33
+ # testnet params
34
+ def self.testnet
35
+ YAML.load(File.open("#{__dir__}/chainparams/testnet.yml"))
36
+ end
37
+
38
+ # regtest params
39
+ def self.regtest
40
+ YAML.load(File.open("#{__dir__}/chainparams/regtest.yml"))
41
+ end
42
+
43
+ def mainnet?
44
+ network == 'mainnet'
45
+ end
46
+
47
+ def testnet?
48
+ network == 'testnet'
49
+ end
50
+
51
+ def regtest?
52
+ network == 'regtest'
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,25 @@
1
+ --- !ruby/object:Bitcoin::ChainParams
2
+ network: "mainnet"
3
+ magic_head: "f9beb4d9"
4
+ message_magic: "Bitcoin Signed Message:\n"
5
+ address_version: "00"
6
+ p2sh_version: "05"
7
+ bech32_hrp: 'bc'
8
+ privkey_version: "80"
9
+ extended_privkey_version: "0488ade4"
10
+ extended_pubkey_version: "0488b21e"
11
+ default_port: 8333
12
+ protocol_version: 70013
13
+ retarget_interval: 2016
14
+ retarget_time: 1209600 # 2 weeks
15
+ target_spacing: 600 # block interval
16
+ max_money: 21000000
17
+ bip34_height: 227931
18
+ genesis_hash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
19
+ proof_of_work_limit: 0x1d00ffff
20
+ dns_seeds:
21
+ - "seed.bitcoin.sipa.be"
22
+ - "dnsseed.bluematt.me"
23
+ - "dnsseed.bitcoin.dashjr.org"
24
+ - "seed.bitcoinstats.com"
25
+ - "seed.bitcoin.jonasschnelli.ch"
@@ -0,0 +1,20 @@
1
+ --- !ruby/object:Bitcoin::ChainParams
2
+ network: "regtest"
3
+ magic_head: "fabfb5da"
4
+ message_magic: "Bitcoin Signed Message:\n"
5
+ address_version: "6f"
6
+ p2sh_version: "c4"
7
+ bech32_hrp: 'tb'
8
+ privkey_version: "ef"
9
+ extended_privkey_version: "04358394"
10
+ extended_pubkey_version: "043587cf"
11
+ default_port: 18444
12
+ protocol_version: 70013
13
+ retarget_interval: 2016
14
+ retarget_time: 1209600 # 2 weeks
15
+ target_spacing: 600 # block interval
16
+ max_money: 21000000
17
+ bip34_height: 0
18
+ genesis_hash: "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
19
+ proof_of_work_limit: 0x207fffff
20
+ dns_seeds:
@@ -0,0 +1,24 @@
1
+ --- !ruby/object:Bitcoin::ChainParams
2
+ network: "testnet"
3
+ magic_head: "0b110907"
4
+ message_magic: "Bitcoin Signed Message:\n"
5
+ address_version: "6f"
6
+ p2sh_version: "c4"
7
+ bech32_hrp: 'tb'
8
+ privkey_version: "ef"
9
+ extended_privkey_version: "04358394"
10
+ extended_pubkey_version: "043587cf"
11
+ default_port: 18333
12
+ protocol_version: 70013
13
+ retarget_interval: 2016
14
+ retarget_time: 1209600 # 2 weeks
15
+ target_spacing: 600 # block interval
16
+ max_money: 21000000
17
+ bip34_height: 227931
18
+ genesis_hash: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
19
+ proof_of_work_limit: 0x1d00ffff
20
+ dns_seeds:
21
+ - "testnet-seed.bitcoin.jonasschnelli.ch"
22
+ - "seed.tbtc.petertodd.org"
23
+ - "testnet-seed.bluematt.me"
24
+ - "testnet-seed.bitcoin.schildbach.de"
@@ -0,0 +1,66 @@
1
+ module Bitcoin
2
+
3
+ # Basic Bitcoin P2P connection class
4
+ class Connection < EM::Connection
5
+
6
+ attr_reader :host, :port, :handler, :logger
7
+ attr_accessor :connected
8
+
9
+ # if true, this peer send new block announcements using a headers message rather than an inv message.
10
+ attr_accessor :sendheaders
11
+
12
+ # minimum fee(in satoshis per kilobyte) for relay tx
13
+ attr_accessor :fee_rate
14
+
15
+ def initialize(host, port)
16
+ @host = host
17
+ @port = port
18
+ @logger = Bitcoin::Logger.create(:connection)
19
+ @handler = Message::Handler.new(self, @logger)
20
+ @connected = false
21
+ @sendheaders = false
22
+ @attr_accessor = 0
23
+ end
24
+
25
+ def post_init
26
+ logger.info "connected. #{remote_node}"
27
+ begin_handshake
28
+ end
29
+
30
+ # handle receiving data from remote node.
31
+ def receive_data(data)
32
+ logger.info "receive data from #{remote_node}, data : #{data}"
33
+ handler.handle(data)
34
+ end
35
+
36
+ # close network connection.
37
+ def close(msg = '')
38
+ logger.info "close connection with #{remote_node}. #{msg}"
39
+ close_connection_after_writing
40
+ EM.stop
41
+ end
42
+
43
+ def handshake_done
44
+ logger.info 'handshake finished.'
45
+ @connected = true
46
+ # send_data(Message::SendCmpct.new(0, 1))
47
+
48
+ end
49
+
50
+ private
51
+
52
+ def remote_node
53
+ "#{host}:#{port}"
54
+ end
55
+
56
+ # start handshake
57
+ def begin_handshake
58
+ logger.info "begin handshake with #{remote_node}"
59
+ ver = Bitcoin::Message::Version.new(remote_addr: remote_node, start_height: 1150660)
60
+ logger.info "send version message. #{ver.to_json}"
61
+ send_data(ver.to_pkt)
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,205 @@
1
+ module Bitcoin
2
+
3
+ def self.hmac_sha512(key, data)
4
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA512'), key, data)
5
+ end
6
+
7
+ # Integers modulo the order of the curve(secp256k1)
8
+ CURVE_ORDER = 115792089237316195423570985008687907852837564279074904382605163141518161494337
9
+
10
+ # BIP32 Extended private key
11
+ class ExtKey
12
+
13
+ attr_accessor :depth
14
+ attr_accessor :number
15
+ attr_accessor :chain_code
16
+ attr_accessor :key
17
+ attr_accessor :parent_fingerprint
18
+
19
+ # generate master key from seed.
20
+ def self.generate_master(seed)
21
+ ext_key = ExtKey.new
22
+ ext_key.depth = ext_key.number = 0
23
+ ext_key.parent_fingerprint = '00000000'
24
+ l = Bitcoin.hmac_sha512('Bitcoin seed', seed)
25
+ left = l[0..31].bth.to_i(16)
26
+ raise 'invalid key' if left >= CURVE_ORDER || left == 0
27
+ ext_key.key = Bitcoin::Key.new(priv_key: l[0..31].bth)
28
+ ext_key.chain_code = l[32..-1]
29
+ ext_key
30
+ end
31
+
32
+ # get ExtPubkey from priv_key
33
+ def ext_pubkey
34
+ k = ExtPubkey.new
35
+ k.depth = depth
36
+ k.number = number
37
+ k.parent_fingerprint = parent_fingerprint
38
+ k.chain_code = chain_code
39
+ k.pubkey = key.pubkey
40
+ k
41
+ end
42
+
43
+ # serialize extended private key
44
+ def to_payload
45
+ Bitcoin.chain_params.extended_privkey_version.htb << [depth].pack('C') <<
46
+ parent_fingerprint.htb << [number].pack('N') << chain_code << [0x00].pack('C') << key.priv_key.htb
47
+ end
48
+
49
+ # Base58 encoded extended private key
50
+ def to_base58
51
+ h = to_payload.bth
52
+ hex = h + Bitcoin.calc_checksum(h)
53
+ Base58.encode(hex)
54
+ end
55
+
56
+ # get private key(hex)
57
+ def priv
58
+ key.priv_key
59
+ end
60
+
61
+ # get public key(hex)
62
+ def pub
63
+ key.pubkey
64
+ end
65
+
66
+ # get address
67
+ def addr
68
+ key.to_p2pkh
69
+ end
70
+
71
+ # get segwit p2wpkh address
72
+ def segwit_addr
73
+ ext_pubkey.segwit_addr
74
+ end
75
+
76
+ # get key identifier
77
+ def identifier
78
+ Bitcoin.hash160(key.pubkey)
79
+ end
80
+
81
+ # get fingerprint
82
+ def fingerprint
83
+ identifier.slice(0..7)
84
+ end
85
+
86
+ # derive new key
87
+ def derive(number)
88
+ new_key = ExtKey.new
89
+ new_key.depth = depth + 1
90
+ new_key.number = number
91
+ new_key.parent_fingerprint = fingerprint
92
+ if number > (2**31 -1)
93
+ data = [0x00].pack('C') << key.priv_key.htb << [number].pack('N')
94
+ else
95
+ data = key.pubkey.htb << [number].pack('N')
96
+ end
97
+ l = Bitcoin.hmac_sha512(chain_code, data)
98
+ left = l[0..31].bth.to_i(16)
99
+ raise 'invalid key' if left >= CURVE_ORDER
100
+ child_priv = (left + key.priv_key.to_i(16)) % CURVE_ORDER
101
+ raise 'invalid key ' if child_priv >= CURVE_ORDER
102
+ new_key.key = Bitcoin::Key.new(priv_key: child_priv.to_s(16).rjust(64, '0'))
103
+ new_key.chain_code = l[32..-1]
104
+ new_key
105
+ end
106
+
107
+ # import private key from Base58 private key address
108
+ def self.from_base58(address)
109
+ data = StringIO.new(Base58.decode(address).htb)
110
+ ext_key = ExtKey.new
111
+ data.read(4).bth # version
112
+ ext_key.depth = data.read(1).unpack('C').first
113
+ ext_key.parent_fingerprint = data.read(4).bth
114
+ ext_key.number = data.read(4).unpack('N').first
115
+ ext_key.chain_code = data.read(32)
116
+ data.read(1) # 0x00
117
+ ext_key.key = Bitcoin::Key.new(priv_key: data.read(32).bth)
118
+ ext_key
119
+ end
120
+
121
+ end
122
+
123
+ # BIP-32 Extended public key
124
+ class ExtPubkey
125
+ attr_accessor :depth
126
+ attr_accessor :number
127
+ attr_accessor :chain_code
128
+ attr_accessor :pubkey # hex format
129
+ attr_accessor :parent_fingerprint
130
+
131
+ # serialize extended pubkey
132
+ def to_payload
133
+ Bitcoin.chain_params.extended_pubkey_version.htb << [depth].pack('C') <<
134
+ parent_fingerprint.htb << [number].pack('N') << chain_code << pub.htb
135
+ end
136
+
137
+ def pub
138
+ pubkey
139
+ end
140
+
141
+ # get address
142
+ def addr
143
+ Bitcoin::Key.new(pubkey: pubkey).to_p2pkh
144
+ end
145
+
146
+ # get segwit p2wpkh address
147
+ def segwit_addr
148
+ hash160 = Bitcoin.hash160(pub)
149
+ p2wpkh = [ ["00", "14", hash160].join ].pack("H*").bth
150
+ segwit_addr = Bech32::SegwitAddr.new
151
+ segwit_addr.hrp = Bitcoin.chain_params.address_version == '00' ? 'bc' : 'tb'
152
+ segwit_addr.script_pubkey = p2wpkh
153
+ segwit_addr.addr
154
+ end
155
+
156
+ # get key identifier
157
+ def identifier
158
+ Bitcoin.hash160(pub)
159
+ end
160
+
161
+ # get fingerprint
162
+ def fingerprint
163
+ identifier.slice(0..7)
164
+ end
165
+
166
+ # Base58 encoded extended pubkey
167
+ def to_base58
168
+ h = to_payload.bth
169
+ hex = h + Bitcoin.calc_checksum(h)
170
+ Base58.encode(hex)
171
+ end
172
+
173
+ # derive child key
174
+ def derive(number)
175
+ new_key = ExtPubkey.new
176
+ new_key.depth = depth + 1
177
+ new_key.number = number
178
+ new_key.parent_fingerprint = fingerprint
179
+ raise 'hardened key is not support' if number > (2**31 -1)
180
+ data = pub.htb << [number].pack('N')
181
+ l = Bitcoin.hmac_sha512(chain_code, data)
182
+ left = l[0..31].bth.to_i(16)
183
+ raise 'invalid key' if left >= CURVE_ORDER
184
+ p1 = Bitcoin::Secp256k1::GROUP.generator.multiply_by_scalar(left)
185
+ p2 = Bitcoin::Key.new(pubkey: pubkey).to_point
186
+ new_key.pubkey = ECDSA::Format::PointOctetString.encode(p1 + p2, compression: true).bth
187
+ new_key.chain_code = l[32..-1]
188
+ new_key
189
+ end
190
+
191
+ # import pub key from Base58 private key address
192
+ def self.from_base58(address)
193
+ data = StringIO.new(Base58.decode(address).htb)
194
+ ext_pubkey = ExtPubkey.new
195
+ data.read(4).bth # version
196
+ ext_pubkey.depth = data.read(1).unpack('C').first
197
+ ext_pubkey.parent_fingerprint = data.read(4).bth
198
+ ext_pubkey.number = data.read(4).unpack('N').first
199
+ ext_pubkey.chain_code = data.read(32)
200
+ ext_pubkey.pubkey = data.read(33).bth
201
+ ext_pubkey
202
+ end
203
+ end
204
+
205
+ end