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,44 @@
1
+ module Bitcoin
2
+ class TxChecker
3
+
4
+ attr_reader :tx
5
+ attr_reader :input_index
6
+ attr_reader :amount
7
+
8
+ def initialize(tx: nil, amount: 0, input_index: nil)
9
+ @tx = tx
10
+ @amount = amount
11
+ @input_index = input_index
12
+ end
13
+
14
+ # check signature
15
+ # @param [String] script_sig
16
+ # @param [String] pubkey
17
+ # @param [Bitcoin::Script] script_code
18
+ # @param [Integer] sig_version
19
+ def check_sig(script_sig, pubkey, script_code, sig_version)
20
+ return false if script_sig.empty?
21
+ script_sig = script_sig.htb
22
+ hash_type = script_sig[-1].unpack('C').first
23
+ sig = script_sig[0..-2]
24
+ if sig_version == ScriptInterpreter::SIG_VERSION[:witness_v0]
25
+ sighash = tx.sighash_for_input(input_index: input_index, hash_type: hash_type,
26
+ script_code: script_code, amount: amount, sig_version: sig_version)
27
+ else
28
+ sighash = tx.sighash_for_input(input_index: input_index, hash_type: hash_type,
29
+ script_code: script_code, sig_version: sig_version)
30
+ end
31
+ key = Bitcoin::Key.new(pubkey: pubkey)
32
+ key.verify(sig, sighash)
33
+ end
34
+
35
+ def check_locktime
36
+ # TODO
37
+ end
38
+
39
+ def check_sequence
40
+ # TODO
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,29 @@
1
+ module Bitcoin
2
+
3
+ # witness
4
+ class ScriptWitness
5
+
6
+ attr_reader :stack
7
+
8
+ def initialize(stack = [])
9
+ @stack = stack
10
+ end
11
+
12
+ def empty?
13
+ stack.empty?
14
+ end
15
+
16
+ def to_payload
17
+ p = Bitcoin.pack_var_int(stack.size)
18
+ p << stack.map { |s|
19
+ Bitcoin.pack_var_int(s.bytesize) << s
20
+ }.join
21
+ end
22
+
23
+ def to_s
24
+ stack.map{|s|s.bth}.join(' ')
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,10 @@
1
+ module Bitcoin
2
+
3
+ module Secp256k1
4
+
5
+ autoload :Ruby, 'bitcoin/secp256k1/ruby'
6
+ autoload :Native, 'bitcoin/secp256k1/native'
7
+
8
+ end
9
+
10
+ end
@@ -0,0 +1,22 @@
1
+ module Bitcoin
2
+ module Secp256k1
3
+
4
+ # secp256k1 module using libsecp256k1
5
+ module Native
6
+
7
+ def generate_key_pair(compressed: true)
8
+ # TODO
9
+ end
10
+
11
+ def generate_key(compressed: true)
12
+ # TODO
13
+ end
14
+
15
+ def sign_data(privkey, data)
16
+ # TODO
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,96 @@
1
+ module Bitcoin
2
+ module Secp256k1
3
+
4
+ GROUP = ECDSA::Group::Secp256k1
5
+
6
+ # secp256 module using ecdsa gem
7
+ # https://github.com/DavidEGrayson/ruby_ecdsa
8
+ module Ruby
9
+
10
+ module_function
11
+
12
+ # generate ecdsa private key and public key
13
+ def generate_key_pair(compressed: true)
14
+ private_key = 1 + SecureRandom.random_number(GROUP.order - 1)
15
+ public_key = GROUP.generator.multiply_by_scalar(private_key)
16
+ privkey = ECDSA::Format::IntegerOctetString.encode(private_key, 32)
17
+ pubkey = ECDSA::Format::PointOctetString.encode(public_key, compression: compressed)
18
+ [privkey.bth, pubkey.bth]
19
+ end
20
+
21
+ # generate bitcoin key
22
+ def generate_key(compressed: true)
23
+ privkey, pubkey = generate_key_pair(compressed: compressed)
24
+ Bitcoin::Key.new(priv_key: privkey, pubkey: pubkey, compressed: compressed)
25
+ end
26
+
27
+ # generate publick key from private key
28
+ # @param [String] privkey a private key with string format
29
+ # @param [Boolean] compressed pubkey compressed?
30
+ # @return [String] a pubkey which generate from privkey
31
+ def generate_pubkey(privkey, compressed: true)
32
+ private_key = ECDSA::Format::IntegerOctetString.decode(privkey.htb)
33
+ public_key = GROUP.generator.multiply_by_scalar(private_key)
34
+ pubkey = ECDSA::Format::PointOctetString.encode(public_key, compression: compressed)
35
+ pubkey.bth
36
+ end
37
+
38
+ # sign data.
39
+ # @param [String] data a data to be signed
40
+ # @param [String] privkey a private key using sign
41
+ # @return [String] signature data with binary format
42
+ def sign_data(data, privkey)
43
+ digest = Digest::SHA2.digest(data)
44
+ private_key = ECDSA::Format::IntegerOctetString.decode(privkey.htb)
45
+ signature = nil
46
+ while signature.nil?
47
+ # TODO support rfc 6979 https://tools.ietf.org/html/rfc6979
48
+ temp_key = 1 + SecureRandom.random_number(GROUP.order - 1)
49
+ signature = ECDSA.sign(GROUP, private_key, digest, temp_key)
50
+ end
51
+ ECDSA::Format::SignatureDerString.encode(signature) # signature with DER format
52
+ end
53
+
54
+ # verify signature using public key
55
+ # @param [String] digest a SHA-256 message digest with binary format
56
+ # @param [String] sig a signature for +data+ with binary format
57
+ # @param [String] pubkey a public key corresponding to the private key used for sign
58
+ # @return [Boolean] verify result
59
+ def verify_sig(digest, sig, pubkey)
60
+ begin
61
+ k = ECDSA::Format::PointOctetString.decode(repack_pubkey(pubkey), GROUP)
62
+ signature = repack_sig(sig)
63
+ ECDSA.valid_signature?(k, digest, signature)
64
+ rescue Exception
65
+ false
66
+ end
67
+ end
68
+
69
+ # repack signature for OpenSSL 1.0.1k handling of DER signatures
70
+ # https://github.com/bitcoin/bitcoin/pull/5634/files
71
+ def repack_sig(sig)
72
+ sig_array = sig.unpack('C*')
73
+ len_r = sig_array[3]
74
+ r = sig_array[4...(len_r+4)].pack('C*').bth
75
+ len_s = sig_array[len_r + 5]
76
+ s = sig_array[(len_r + 6)...(len_r + 6 + len_s)].pack('C*').bth
77
+ ECDSA::Signature.new(r.to_i(16), s.to_i(16))
78
+ end
79
+
80
+ # if +pubkey+ is hybrid public key format, it convert uncompressed format.
81
+ # https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2012-June/001578.html
82
+ def repack_pubkey(pubkey)
83
+ p = pubkey.htb
84
+ case p[0]
85
+ when "\x06", "\x07"
86
+ p[0] = "\x04"
87
+ p
88
+ else
89
+ pubkey.htb
90
+ end
91
+ end
92
+
93
+ end
94
+
95
+ end
96
+ end
data/lib/bitcoin/tx.rb ADDED
@@ -0,0 +1,191 @@
1
+ module Bitcoin
2
+
3
+ # Transaction class
4
+ class Tx
5
+
6
+ MARKER = 0x00
7
+ FLAG = 0x01
8
+
9
+ attr_accessor :version
10
+ attr_accessor :marker
11
+ attr_accessor :flag
12
+ attr_reader :inputs
13
+ attr_reader :outputs
14
+ attr_accessor :lock_time
15
+
16
+ def initialize
17
+ @inputs = []
18
+ @outputs = []
19
+ @version = 1
20
+ @lock_time = 0
21
+ end
22
+
23
+ def self.parse_from_payload(payload)
24
+ buf = payload.is_a?(String) ? StringIO.new(payload) : payload
25
+ tx = new
26
+ tx.version = buf.read(4).unpack('V').first
27
+
28
+ in_count = Bitcoin.unpack_var_int_from_io(buf)
29
+ witness = false
30
+ if in_count.zero?
31
+ tx.marker = 0
32
+ tx.flag = buf.read(1).unpack('c').first
33
+ in_count = Bitcoin.unpack_var_int_from_io(buf)
34
+ witness = true
35
+ end
36
+
37
+ in_count.times do
38
+ tx.inputs << TxIn.parse_from_payload(buf)
39
+ end
40
+
41
+ out_count = Bitcoin.unpack_var_int_from_io(buf)
42
+ out_count.times do
43
+ tx.outputs << TxOut.parse_from_payload(buf)
44
+ end
45
+
46
+ if witness
47
+ in_count.times do |i|
48
+ witness_count = Bitcoin.unpack_var_int_from_io(buf)
49
+ witness_count.times do
50
+ size = Bitcoin.unpack_var_int_from_io(buf)
51
+ tx.inputs[i].script_witness.stack << buf.read(size)
52
+ end
53
+ end
54
+ end
55
+
56
+ tx.lock_time = buf.read(4).unpack('V').first
57
+
58
+ tx
59
+ end
60
+
61
+ def txid
62
+ Bitcoin.double_sha256(serialize_old_format).reverse.bth
63
+ end
64
+
65
+ def wtxid
66
+ Bitcoin.double_sha256(to_payload).reverse.bth
67
+ end
68
+
69
+ def to_payload
70
+ witness? ? serialize_witness_format : serialize_old_format
71
+ end
72
+
73
+ def coinbase_tx?
74
+ inputs.length == 1 && inputs.first.coinbase?
75
+ end
76
+
77
+ def witness?
78
+ !inputs.find { |i| !i.script_witness.empty? }.nil?
79
+ end
80
+
81
+ # serialize tx with old tx format
82
+ def serialize_old_format
83
+ buf = [version].pack('V')
84
+ buf << Bitcoin.pack_var_int(inputs.length) << inputs.map(&:to_payload).join
85
+ buf << Bitcoin.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
86
+ buf << [lock_time].pack('V')
87
+ buf
88
+ end
89
+
90
+ # serialize tx with segwit tx format
91
+ # https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki
92
+ def serialize_witness_format
93
+ buf = [version, MARKER, FLAG].pack('Vcc')
94
+ buf << Bitcoin.pack_var_int(inputs.length) << inputs.map(&:to_payload).join
95
+ buf << Bitcoin.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
96
+ buf << witness_payload << [lock_time].pack('V')
97
+ buf
98
+ end
99
+
100
+ def witness_payload
101
+ inputs.map { |i| i.script_witness.to_payload }.join
102
+ end
103
+
104
+ # get signature hash
105
+ # @param [Integer] input_index input index.
106
+ # @param [Integer] hash_type signature hash type
107
+ # @param [Bitcoin::Script] script_code script code
108
+ # @param [Integer] amount bitcoin amount locked in input. required for witness input only.
109
+ def sighash_for_input(input_index: nil, hash_type: Script::SIGHASH_TYPE[:all], script_code: nil,
110
+ sig_version: ScriptInterpreter::SIG_VERSION[:base], amount: nil)
111
+ raise ArgumentError, 'input_index must be specified.' unless input_index
112
+ raise ArgumentError, 'does not exist input corresponding to input_index.' if input_index >= inputs.size
113
+ raise ArgumentError, 'script_pubkey must be specified.' unless script_code
114
+
115
+ if sig_version == ScriptInterpreter::SIG_VERSION[:witness_v0]
116
+ raise ArgumentError, 'amount must be specified.' unless amount
117
+ sighash_for_witness(input_index, script_code, hash_type, amount)
118
+ else
119
+ sighash_for_legacy(input_index, script_code, hash_type)
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ # generate sighash with legacy format
126
+ def sighash_for_legacy(index, script_code, hash_type)
127
+ ins = inputs.map.with_index do |i, idx|
128
+ if idx == index
129
+ i.to_payload(script_code)
130
+ else
131
+ case hash_type & 0x1f
132
+ when Script::SIGHASH_TYPE[:none], Script::SIGHASH_TYPE[:single]
133
+ i.to_payload(Bitcoin::Script.new, 0)
134
+ else
135
+ i.to_payload(Bitcoin::Script.new)
136
+ end
137
+ end
138
+ end
139
+
140
+ outs = outputs.map(&:to_payload)
141
+ out_size = Bitcoin.pack_var_int(outputs.size)
142
+
143
+ case hash_type & 0x1f
144
+ when Script::SIGHASH_TYPE[:none]
145
+ outs = ''
146
+ out_size = Bitcoin.pack_var_int(0)
147
+ when Script::SIGHASH_TYPE[:single]
148
+ return "\x01".ljust(32, "\x00") if index >= outputs.size
149
+ outs = outputs[0...(index + 1)].map.with_index { |o, idx| (idx == index) ? o.to_payload : o.to_empty_payload }.join
150
+ out_size = Bitcoin.pack_var_int(index + 1)
151
+ end
152
+
153
+ if hash_type & Script::SIGHASH_TYPE[:anyonecanpay] != 0
154
+ ins = [ins[index]]
155
+ end
156
+
157
+ buf = [[version].pack('V'), Bitcoin.pack_var_int(ins.size),
158
+ ins, out_size, outs, [lock_time, hash_type].pack('VV')].join
159
+
160
+ Bitcoin.double_sha256(buf)
161
+ end
162
+
163
+ # generate sighash with BIP-143 format
164
+ # https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
165
+ def sighash_for_witness(index, script_code, hash_type, amount)
166
+ hash_prevouts = Bitcoin.double_sha256(inputs.map{|i|i.out_point.to_payload}.join)
167
+ hash_sequence = Bitcoin.double_sha256(inputs.map{|i|[i.sequence].pack('V')}.join)
168
+ outpoint = inputs[index].out_point.to_payload
169
+ amount = [amount].pack('Q')
170
+ nsequence = [inputs[index].sequence].pack('V')
171
+ hash_outputs = Bitcoin.double_sha256(outputs.map{|o|o.to_payload}.join)
172
+
173
+ case (hash_type & 0x1f)
174
+ when Script::SIGHASH_TYPE[:single]
175
+ hash_outputs = index >= outputs.size ? "\x00".ljust(32, "\x00") : Bitcoin.double_sha256(outputs[index].to_payload)
176
+ hash_sequence = "\x00".ljust(32, "\x00")
177
+ when Script::SIGHASH_TYPE[:none]
178
+ hash_sequence = hash_outputs = "\x00".ljust(32, "\x00")
179
+ end
180
+
181
+ if (hash_type & Script::SIGHASH_TYPE[:anyonecanpay]) != 0
182
+ hash_prevouts = hash_sequence ="\x00".ljust(32, "\x00")
183
+ end
184
+ buf = [ [version].pack('V'), hash_prevouts, hash_sequence, outpoint, Bitcoin::Script.pack_pushdata(script_code.to_payload),
185
+ amount, nsequence, hash_outputs, [@lock_time, hash_type].pack('VV')].join
186
+ Bitcoin.double_sha256(buf)
187
+ end
188
+
189
+ end
190
+
191
+ end
@@ -0,0 +1,45 @@
1
+ module Bitcoin
2
+
3
+ # transaction input
4
+ class TxIn
5
+
6
+ attr_accessor :out_point
7
+ attr_accessor :script_sig
8
+ attr_accessor :sequence
9
+ attr_accessor :script_witness
10
+
11
+ DEFAULT_SEQUENCE = 0xFFFFFFFF
12
+
13
+ def initialize(out_point: nil, script_sig: nil, script_witness: ScriptWitness.new, sequence: DEFAULT_SEQUENCE)
14
+ @out_point = out_point
15
+ @script_sig = script_sig
16
+ @script_witness = script_witness
17
+ @sequence = sequence
18
+ end
19
+
20
+ def self.parse_from_payload(payload)
21
+ buf = payload.is_a?(String) ? StringIO.new(payload) : payload
22
+ i = new
23
+ hash, index = buf.read(36).unpack('a32V')
24
+ i.out_point = OutPoint.new(hash.reverse.bth, index)
25
+ sig_length = Bitcoin.unpack_var_int_from_io(buf)
26
+ sig = buf.read(sig_length)
27
+ i.script_sig = Script.new
28
+ i.script_sig.chunks[0] = sig
29
+ i.sequence = buf.read(4).unpack('V').first
30
+ i
31
+ end
32
+
33
+ def coinbase?
34
+ out_point.coinbase?
35
+ end
36
+
37
+ def to_payload(script_sig = @script_sig, sequence = @sequence)
38
+ p = out_point.to_payload
39
+ p << Bitcoin.pack_var_int(script_sig.to_payload.bytesize)
40
+ p << script_sig.to_payload << [sequence].pack('V')
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,32 @@
1
+ module Bitcoin
2
+
3
+ # transaction output
4
+ class TxOut
5
+
6
+ attr_accessor :value
7
+ attr_accessor :script_pubkey
8
+
9
+ def initialize(value: 0, script_pubkey: nil)
10
+ @value = value
11
+ @script_pubkey = script_pubkey
12
+ end
13
+
14
+ def self.parse_from_payload(payload)
15
+ buf = payload.is_a?(String) ? StringIO.new(payload) : payload
16
+ value = buf.read(8).unpack('Q').first
17
+ script_size = Bitcoin.unpack_var_int_from_io(buf)
18
+ new(value: value, script_pubkey: Script.parse_from_payload(buf.read(script_size)))
19
+ end
20
+
21
+ def to_payload
22
+ s = script_pubkey.to_payload
23
+ [value].pack('Q') << Bitcoin.pack_var_int(s.length) << s
24
+ end
25
+
26
+ def to_empty_payload
27
+ 'ffffffffffffffff00'.htb
28
+ end
29
+
30
+ end
31
+
32
+ end