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,131 @@
1
+ module Bitcoin
2
+
3
+ # bitcoin key class
4
+ class Key
5
+
6
+ attr_accessor :priv_key
7
+ attr_accessor :pubkey
8
+ attr_accessor :compressed
9
+
10
+ def initialize(priv_key: nil, pubkey: nil, compressed: true)
11
+ extend Bitcoin.secp_impl
12
+ @priv_key = priv_key
13
+ if pubkey
14
+ @pubkey = pubkey
15
+ else
16
+ @pubkey = generate_pubkey(priv_key, compressed: compressed) if priv_key
17
+ end
18
+ @compressed = compressed
19
+ end
20
+
21
+ # import private key from wif format
22
+ # https://en.bitcoin.it/wiki/Wallet_import_format
23
+ def self.from_wif(wif)
24
+ compressed = wif.size == 52
25
+ hex = Base58.decode(wif)
26
+ version, key, flag, checksum = hex.unpack("a2a64a#{compressed ? 2 : 0}a8")
27
+ raise ArgumentError, 'invalid version' unless version == Bitcoin.chain_params.privkey_version
28
+ raise ArgumentError, 'invalid checksum' unless Bitcoin.calc_checksum(version + key + flag) == checksum
29
+ new(priv_key: key, compressed: compressed)
30
+ end
31
+
32
+ # export private key with wif format
33
+ def to_wif
34
+ version = Bitcoin.chain_params.privkey_version
35
+ hex = version + priv_key
36
+ hex += '01' if compressed?
37
+ hex += Bitcoin.calc_checksum(hex)
38
+ Base58.encode(hex)
39
+ end
40
+
41
+ # sign +data+ with private key
42
+ def sign(data)
43
+ sign_data(data, priv_key)
44
+ end
45
+
46
+ # verify signature using public key
47
+ # @param [String] sig signature data with binary format
48
+ # @param [String] origin original message
49
+ # @return [Boolean] verify result
50
+ def verify(sig, origin)
51
+ verify_sig(origin, sig, pubkey)
52
+ end
53
+
54
+ # get pay to pubkey hash address
55
+ def to_p2pkh
56
+ Bitcoin::Script.to_p2pkh(Bitcoin.hash160(pubkey)).to_addr
57
+ end
58
+
59
+ # get pay to witness pubkey hash address
60
+ def to_p2wpkh
61
+ Bitcoin::Script.to_p2wpkh(Bitcoin.hash160(pubkey)).to_addr
62
+ end
63
+
64
+ def compressed?
65
+ @compressed
66
+ end
67
+
68
+ # generate pubkey ec point
69
+ # @return [ECDSA::Point]
70
+ def to_point
71
+ p = pubkey
72
+ p ||= generate_pubkey(priv_key, compressed: compressed)
73
+ ECDSA::Format::PointOctetString.decode(p.htb, Bitcoin::Secp256k1::GROUP)
74
+ end
75
+
76
+ # check +pubkey+ (hex) is compress or uncompress pubkey.
77
+ def self.compress_or_uncompress_pubkey?(pubkey)
78
+ p = pubkey.htb
79
+ return false if p.bytesize < 33
80
+ case p[0]
81
+ when "\x04"
82
+ return false unless p.bytesize == 65
83
+ when "\x02", "\x03"
84
+ return false unless p.bytesize == 33
85
+ else
86
+ return false
87
+ end
88
+ true
89
+ end
90
+
91
+ # check +pubkey+ (hex) is compress pubkey.
92
+ def self.compress_pubkey?(pubkey)
93
+ p = pubkey.htb
94
+ p.bytesize == 33 && ["\x02", "\x03"].include?(p[0])
95
+ end
96
+
97
+ # check +sig+ is low.
98
+ def self.low_signature?(sig)
99
+ s = sig.unpack('C*')
100
+ len_r = s[3]
101
+ len_s = s[5 + len_r]
102
+ val_s = s.slice(6 + len_r, len_s)
103
+ max_mod_half_order = [
104
+ 0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
105
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
106
+ 0x5d,0x57,0x6e,0x73,0x57,0xa4,0x50,0x1d,
107
+ 0xdf,0xe9,0x2f,0x46,0x68,0x1b,0x20,0xa0]
108
+ compare_big_endian(val_s, [0]) > 0 &&
109
+ compare_big_endian(val_s, max_mod_half_order) <= 0
110
+ end
111
+
112
+ private
113
+
114
+ def self.compare_big_endian(c1, c2)
115
+ c1, c2 = c1.dup, c2.dup # Clone the arrays
116
+
117
+ while c1.size > c2.size
118
+ return 1 if c1.shift > 0
119
+ end
120
+
121
+ while c2.size > c1.size
122
+ return -1 if c2.shift > 0
123
+ end
124
+
125
+ c1.size.times{|idx| return c1[idx] - c2[idx] if c1[idx] != c2[idx] }
126
+ 0
127
+ end
128
+
129
+ end
130
+
131
+ end
@@ -0,0 +1,18 @@
1
+ require 'logger'
2
+
3
+ module Bitcoin
4
+
5
+ # Simple Logger module
6
+ module Logger
7
+
8
+ # Create a logger with given +name+.log in $HOME/.bitcoinrb/log.
9
+ def self.create(name, level = ::Logger::INFO)
10
+ dir = "#{Bitcoin.base_dir}/log"
11
+ FileUtils.mkdir_p(dir)
12
+ logger = ::Logger.new(dir + "/#{name}.log", 10)
13
+ logger.level = level
14
+ logger
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,120 @@
1
+ module Bitcoin
2
+
3
+ # merkle tree
4
+ class MerkleTree
5
+
6
+ attr_accessor :root
7
+
8
+ def initialize(root = nil)
9
+ @root = root
10
+ end
11
+
12
+ def merkle_root
13
+ root.hash
14
+ end
15
+
16
+ def self.build_from_leaf(txids)
17
+ nodes = txids.each_slice(2).map{ |m|
18
+ left = Node.new(m[0])
19
+ right = Node.new(m[1] ? m[1] : m[0])
20
+ [left, right]
21
+ }.flatten
22
+ new(build_initial_tree(nodes))
23
+ end
24
+
25
+ # https://bitcoin.org/en/developer-reference#creating-a-merkleblock-message
26
+ def self.build_partial(tx_count, hashes, flags)
27
+ flags = flags.each_char.map(&:to_i)
28
+ root = build_initial_tree( Array.new(tx_count) { Node.new })
29
+ current_node = root
30
+ hash_index = 0
31
+ flags.each do |f|
32
+ current_node.flag = f
33
+ if f.zero? || current_node.leaf?
34
+ current_node.hash = hashes[hash_index]
35
+ hash_index += 1
36
+ end
37
+ current_node = current_node.next_partial
38
+ break if hash_index == hashes.size
39
+ end
40
+ new(root)
41
+ end
42
+
43
+ def self.build_initial_tree(nodes)
44
+ while nodes.size != 1
45
+ nodes = nodes.each_slice(2).map { |m|
46
+ parent = Node.new
47
+ parent.left = m[0]
48
+ parent.right = m[1] ? m[1] : m[0].dup
49
+ parent
50
+ }
51
+ end
52
+ nodes.first
53
+ end
54
+
55
+ end
56
+
57
+ # node of merkle tree
58
+ class Node
59
+
60
+ attr_accessor :flag
61
+ attr_accessor :hash
62
+ attr_accessor :parent
63
+ attr_accessor :left
64
+ attr_accessor :right
65
+
66
+ def initialize(hash = nil)
67
+ @hash = hash
68
+ end
69
+
70
+ def left=(node)
71
+ node.parent = self
72
+ @left = node
73
+ end
74
+
75
+ def right=(node)
76
+ node.parent = self
77
+ @right = node
78
+ end
79
+
80
+ def hash
81
+ return @hash if @hash
82
+ self.right = left.dup unless right
83
+ Digest::SHA256.digest(Digest::SHA256.digest(
84
+ [right.hash + left.hash].pack('H*').reverse )).reverse.bth
85
+ end
86
+
87
+ def root?
88
+ parent.nil?
89
+ end
90
+
91
+ def leaf?
92
+ right.nil? && left.nil?
93
+ end
94
+
95
+ def partial?
96
+ !flag.nil?
97
+ end
98
+
99
+ def next_partial
100
+ return nil if root? && (flag.zero? || (left.partial? && right.partial?))
101
+ return parent.next_partial if flag.zero? || leaf?
102
+ return left unless left.partial?
103
+ self.right = left.dup unless right
104
+ right.partial? ? parent.next_partial : right
105
+ end
106
+
107
+ # calculate the depth of this node in the tree.
108
+ def depth
109
+ d = 0
110
+ current_node = self
111
+ until current_node.root? do
112
+ current_node = current_node.parent
113
+ d += 1
114
+ end
115
+ d
116
+ end
117
+
118
+ end
119
+
120
+ end
@@ -0,0 +1,42 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ autoload :Handler, 'bitcoin/message/handler'
5
+ autoload :Base, 'bitcoin/message/base'
6
+ autoload :Inventory, 'bitcoin/message/inventory'
7
+ autoload :InventoriesParser, 'bitcoin/message/inventories_parser'
8
+ autoload :HeadersParser, 'bitcoin/message/headers_parser'
9
+ autoload :Version, 'bitcoin/message/version'
10
+ autoload :VerAck, 'bitcoin/message/ver_ack'
11
+ autoload :Addr, 'bitcoin/message/addr'
12
+ autoload :Block, 'bitcoin/message/block'
13
+ autoload :FilterLoad, 'bitcoin/message/filter_load'
14
+ autoload :FilterAdd, 'bitcoin/message/filter_add'
15
+ autoload :FilterClear, 'bitcoin/message/filter_clear'
16
+ autoload :MerkleBlock, 'bitcoin/message/merkle_block'
17
+ autoload :Tx, 'bitcoin/message/tx'
18
+ autoload :Ping, 'bitcoin/message/ping'
19
+ autoload :Pong, 'bitcoin/message/pong'
20
+ autoload :Inv, 'bitcoin/message/inv'
21
+ autoload :GetBlocks, 'bitcoin/message/get_blocks'
22
+ autoload :GetHeaders, 'bitcoin/message/get_headers'
23
+ autoload :Headers, 'bitcoin/message/headers'
24
+ autoload :GetAddr, 'bitcoin/message/get_addr'
25
+ autoload :GetData, 'bitcoin/message/get_data'
26
+ autoload :SendHeaders, 'bitcoin/message/send_headers'
27
+ autoload :FeeFilter, 'bitcoin/message/fee_filter'
28
+ autoload :MemPool, 'bitcoin/message/mem_pool'
29
+ autoload :NotFound, 'bitcoin/message/not_found'
30
+ autoload :Error, 'bitcoin/message/error'
31
+ autoload :Reject, 'bitcoin/message/reject'
32
+ autoload :SendCmpct, 'bitcoin/message/send_cmpct'
33
+
34
+ HEADER_SIZE = 24
35
+ USER_AGENT = "/bitcoinrb:#{Bitcoin::VERSION}/"
36
+
37
+ SERVICE_FLAGS = {none: 0, network: 1 << 0, getutxo: 1 << 1, bloom: 1 << 2, witness: 1 << 3, xthin: 1 << 4}
38
+
39
+ DEFAULT_STOP_HASH = "00"*32
40
+
41
+ end
42
+ end
@@ -0,0 +1,74 @@
1
+ require 'ipaddr'
2
+
3
+ module Bitcoin
4
+ module Message
5
+
6
+ # addr message
7
+ # https://bitcoin.org/en/developer-reference#addr
8
+ class Addr < Base
9
+
10
+ COMMAND = 'addr'
11
+
12
+ attr_reader :addrs
13
+
14
+ def initialize(addrs = [])
15
+ @addrs = addrs
16
+ end
17
+
18
+ def self.parse_from_payload(payload)
19
+ buf = StringIO.new(payload)
20
+ addr_count = Bitcoin.unpack_var_int_from_io(buf)
21
+ addr = new
22
+ addr_count.times do
23
+ addr.addrs << NetworkAddr.parse_from_payload(buf)
24
+ end
25
+ addr
26
+ end
27
+
28
+ def to_payload
29
+ Bitcoin.pack_var_int(addrs.length) << addrs.map(&:to_payload).join
30
+ end
31
+
32
+ end
33
+
34
+ class NetworkAddr
35
+
36
+ # unix time.
37
+ # Nodes advertising their own IP address set this to the current time.
38
+ # Nodes advertising IP addresses they’ve connected to set this to the last time they connected to that node.
39
+ # Other nodes just relaying the IP address should not change the time. Nodes can use the time field to avoid relaying old addr messages.
40
+ attr_accessor :time
41
+
42
+ # The services the node advertised in its version message.
43
+ attr_accessor :services
44
+
45
+ attr_accessor :ip
46
+
47
+ attr_accessor :port
48
+
49
+ def initialize
50
+ @time = Time.now.to_i
51
+ @services = Bitcoin::Message::SERVICE_FLAGS[:network]
52
+ end
53
+
54
+ def self.parse_from_payload(payload)
55
+ buf = payload.is_a?(String) ? StringIO.new(payload) : payload
56
+ addr = new
57
+ addr.time = buf.read(4).unpack('V').first
58
+ addr.services = buf.read(8).unpack('Q').first
59
+ ip = IPAddr::new_ntoh(buf.read(16))
60
+ addr.ip = ip.ipv4_mapped? ? ip.native : ip.to_s
61
+ addr.port = buf.read(2).unpack('n').first
62
+ addr
63
+ end
64
+
65
+ def to_payload
66
+ ip_addr = IPAddr.new (ip)
67
+ ip_addr = ip_addr.ipv4_mapped if ip_addr.ipv4?
68
+ [time, services].pack('VQ') << ip_addr.hton << [port].pack('n')
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,40 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # Base message class
5
+ class Base
6
+ include Bitcoin::Util
7
+ extend Bitcoin::Util
8
+
9
+ # generate message header (binary format)
10
+ # https://bitcoin.org/en/developer-reference#message-headers
11
+ def to_pkt
12
+ payload = to_payload
13
+ magic = Bitcoin.chain_params.magic_head.htb
14
+ command_name = self.class.const_get(:COMMAND, false).ljust(12, "\x00")
15
+ payload_size = [payload.bytesize].pack('V')
16
+ checksum = Bitcoin.double_sha256(payload)[0...4]
17
+ magic << command_name << payload_size << checksum << payload
18
+ end
19
+
20
+ # abstract method
21
+ def to_payload
22
+ raise 'to_payload must be implemented in a child class.'
23
+ end
24
+
25
+ def to_json
26
+ to_h.to_json
27
+ end
28
+
29
+ def to_h
30
+ instance_variables.inject({}) do |result, var|
31
+ key = var.to_s
32
+ key.slice!(0) if key.start_with?('@')
33
+ result.update(key => instance_variable_get(var))
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,41 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # block message
5
+ # https://bitcoin.org/en/developer-reference#block
6
+ class Block < Base
7
+
8
+ attr_accessor :header
9
+ attr_accessor :transactions
10
+ attr_accessor :use_segwit
11
+
12
+ COMMAND = 'block'
13
+
14
+ def initialize(header, transactions = [], use_segwit = false)
15
+ @header = header
16
+ @transactions = transactions
17
+ @use_segwit = use_segwit
18
+ end
19
+
20
+ def self.parse_from_payload(payload)
21
+ buf = StringIO.new(payload)
22
+ header = Bitcoin::BlockHeader.parse_from_payload(buf.read(80))
23
+ transactions = []
24
+ unless buf.eof?
25
+ tx_count = Bitcoin.unpack_var_int_from_io(buf)
26
+ tx_count.times do
27
+ transactions << Bitcoin::Tx.parse_from_payload(buf)
28
+ end
29
+ end
30
+ new(header, transactions)
31
+ end
32
+
33
+ def to_payload
34
+ header.to_payload << Bitcoin.pack_var_int(transactions.size) <<
35
+ transactions.map{|t|use_segwit ? t.to_payload : t.serialize_old_format}.join
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+ end