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,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