bitcoinrb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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