tapyrus 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +12 -0
  7. data/CODE_OF_CONDUCT.md +49 -0
  8. data/Gemfile +6 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +100 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/exe/tapyrusrb-cli +5 -0
  15. data/exe/tapyrusrbd +41 -0
  16. data/lib/openassets/marker_output.rb +20 -0
  17. data/lib/openassets/payload.rb +54 -0
  18. data/lib/openassets/util.rb +28 -0
  19. data/lib/openassets.rb +9 -0
  20. data/lib/tapyrus/base58.rb +38 -0
  21. data/lib/tapyrus/block.rb +77 -0
  22. data/lib/tapyrus/block_header.rb +88 -0
  23. data/lib/tapyrus/bloom_filter.rb +78 -0
  24. data/lib/tapyrus/chain_params.rb +90 -0
  25. data/lib/tapyrus/chainparams/mainnet.yml +41 -0
  26. data/lib/tapyrus/chainparams/regtest.yml +38 -0
  27. data/lib/tapyrus/chainparams/testnet.yml +41 -0
  28. data/lib/tapyrus/constants.rb +195 -0
  29. data/lib/tapyrus/descriptor.rb +147 -0
  30. data/lib/tapyrus/ext_key.rb +337 -0
  31. data/lib/tapyrus/key.rb +296 -0
  32. data/lib/tapyrus/key_path.rb +26 -0
  33. data/lib/tapyrus/logger.rb +42 -0
  34. data/lib/tapyrus/merkle_tree.rb +149 -0
  35. data/lib/tapyrus/message/addr.rb +35 -0
  36. data/lib/tapyrus/message/base.rb +28 -0
  37. data/lib/tapyrus/message/block.rb +46 -0
  38. data/lib/tapyrus/message/block_transaction_request.rb +45 -0
  39. data/lib/tapyrus/message/block_transactions.rb +31 -0
  40. data/lib/tapyrus/message/block_txn.rb +27 -0
  41. data/lib/tapyrus/message/cmpct_block.rb +42 -0
  42. data/lib/tapyrus/message/error.rb +10 -0
  43. data/lib/tapyrus/message/fee_filter.rb +27 -0
  44. data/lib/tapyrus/message/filter_add.rb +28 -0
  45. data/lib/tapyrus/message/filter_clear.rb +17 -0
  46. data/lib/tapyrus/message/filter_load.rb +39 -0
  47. data/lib/tapyrus/message/get_addr.rb +17 -0
  48. data/lib/tapyrus/message/get_block_txn.rb +27 -0
  49. data/lib/tapyrus/message/get_blocks.rb +29 -0
  50. data/lib/tapyrus/message/get_data.rb +21 -0
  51. data/lib/tapyrus/message/get_headers.rb +28 -0
  52. data/lib/tapyrus/message/header_and_short_ids.rb +57 -0
  53. data/lib/tapyrus/message/headers.rb +35 -0
  54. data/lib/tapyrus/message/headers_parser.rb +24 -0
  55. data/lib/tapyrus/message/inv.rb +21 -0
  56. data/lib/tapyrus/message/inventories_parser.rb +23 -0
  57. data/lib/tapyrus/message/inventory.rb +51 -0
  58. data/lib/tapyrus/message/mem_pool.rb +17 -0
  59. data/lib/tapyrus/message/merkle_block.rb +42 -0
  60. data/lib/tapyrus/message/network_addr.rb +63 -0
  61. data/lib/tapyrus/message/not_found.rb +21 -0
  62. data/lib/tapyrus/message/ping.rb +30 -0
  63. data/lib/tapyrus/message/pong.rb +26 -0
  64. data/lib/tapyrus/message/prefilled_tx.rb +29 -0
  65. data/lib/tapyrus/message/reject.rb +46 -0
  66. data/lib/tapyrus/message/send_cmpct.rb +43 -0
  67. data/lib/tapyrus/message/send_headers.rb +16 -0
  68. data/lib/tapyrus/message/tx.rb +30 -0
  69. data/lib/tapyrus/message/ver_ack.rb +17 -0
  70. data/lib/tapyrus/message/version.rb +69 -0
  71. data/lib/tapyrus/message.rb +70 -0
  72. data/lib/tapyrus/mnemonic/wordlist/chinese_simplified.txt +2048 -0
  73. data/lib/tapyrus/mnemonic/wordlist/chinese_traditional.txt +2048 -0
  74. data/lib/tapyrus/mnemonic/wordlist/english.txt +2048 -0
  75. data/lib/tapyrus/mnemonic/wordlist/french.txt +2048 -0
  76. data/lib/tapyrus/mnemonic/wordlist/italian.txt +2048 -0
  77. data/lib/tapyrus/mnemonic/wordlist/japanese.txt +2048 -0
  78. data/lib/tapyrus/mnemonic/wordlist/spanish.txt +2048 -0
  79. data/lib/tapyrus/mnemonic.rb +77 -0
  80. data/lib/tapyrus/network/connection.rb +73 -0
  81. data/lib/tapyrus/network/message_handler.rb +241 -0
  82. data/lib/tapyrus/network/peer.rb +223 -0
  83. data/lib/tapyrus/network/peer_discovery.rb +42 -0
  84. data/lib/tapyrus/network/pool.rb +135 -0
  85. data/lib/tapyrus/network.rb +13 -0
  86. data/lib/tapyrus/node/cli.rb +112 -0
  87. data/lib/tapyrus/node/configuration.rb +38 -0
  88. data/lib/tapyrus/node/spv.rb +79 -0
  89. data/lib/tapyrus/node.rb +7 -0
  90. data/lib/tapyrus/opcodes.rb +178 -0
  91. data/lib/tapyrus/out_point.rb +44 -0
  92. data/lib/tapyrus/rpc/http_server.rb +65 -0
  93. data/lib/tapyrus/rpc/request_handler.rb +150 -0
  94. data/lib/tapyrus/rpc/tapyrus_core_client.rb +72 -0
  95. data/lib/tapyrus/rpc.rb +7 -0
  96. data/lib/tapyrus/script/multisig.rb +92 -0
  97. data/lib/tapyrus/script/script.rb +551 -0
  98. data/lib/tapyrus/script/script_error.rb +111 -0
  99. data/lib/tapyrus/script/script_interpreter.rb +668 -0
  100. data/lib/tapyrus/script/tx_checker.rb +81 -0
  101. data/lib/tapyrus/script_witness.rb +38 -0
  102. data/lib/tapyrus/secp256k1/native.rb +174 -0
  103. data/lib/tapyrus/secp256k1/ruby.rb +123 -0
  104. data/lib/tapyrus/secp256k1.rb +12 -0
  105. data/lib/tapyrus/slip39/share.rb +122 -0
  106. data/lib/tapyrus/slip39/sss.rb +245 -0
  107. data/lib/tapyrus/slip39/wordlist/english.txt +1024 -0
  108. data/lib/tapyrus/slip39.rb +93 -0
  109. data/lib/tapyrus/store/chain_entry.rb +67 -0
  110. data/lib/tapyrus/store/db/level_db.rb +98 -0
  111. data/lib/tapyrus/store/db.rb +9 -0
  112. data/lib/tapyrus/store/spv_chain.rb +101 -0
  113. data/lib/tapyrus/store.rb +9 -0
  114. data/lib/tapyrus/tx.rb +347 -0
  115. data/lib/tapyrus/tx_in.rb +89 -0
  116. data/lib/tapyrus/tx_out.rb +74 -0
  117. data/lib/tapyrus/util.rb +133 -0
  118. data/lib/tapyrus/validation.rb +115 -0
  119. data/lib/tapyrus/version.rb +3 -0
  120. data/lib/tapyrus/wallet/account.rb +151 -0
  121. data/lib/tapyrus/wallet/base.rb +162 -0
  122. data/lib/tapyrus/wallet/db.rb +81 -0
  123. data/lib/tapyrus/wallet/master_key.rb +110 -0
  124. data/lib/tapyrus/wallet.rb +8 -0
  125. data/lib/tapyrus.rb +219 -0
  126. data/tapyrusrb.conf.sample +0 -0
  127. data/tapyrusrb.gemspec +47 -0
  128. metadata +451 -0
@@ -0,0 +1,150 @@
1
+ module Tapyrus
2
+ module RPC
3
+
4
+ # RPC server's request handler.
5
+ module RequestHandler
6
+
7
+ # Returns an object containing various state info regarding blockchain processing.
8
+ def getblockchaininfo
9
+ h = {}
10
+ h[:chain] = Tapyrus.chain_params.network
11
+ best_block = node.chain.latest_block
12
+ h[:headers] = best_block.height
13
+ h[:bestblockhash] = best_block.header.block_id
14
+ h[:chainwork] = best_block.header.work
15
+ h[:mediantime] = node.chain.mtp(best_block.block_hash)
16
+ h
17
+ end
18
+
19
+ # shutdown node
20
+ def stop
21
+ node.shutdown
22
+ end
23
+
24
+ # get block header information.
25
+ # @param [String] block_id block hash(big endian)
26
+ def getblockheader(block_id, verbose)
27
+ block_hash = block_id.rhex
28
+ entry = node.chain.find_entry_by_hash(block_hash)
29
+ raise ArgumentError.new('Block not found') unless entry
30
+ if verbose
31
+ {
32
+ hash: block_id,
33
+ height: entry.height,
34
+ version: entry.header.version,
35
+ versionHex: entry.header.version.to_even_length_hex,
36
+ merkleroot: entry.header.merkle_root.rhex,
37
+ time: entry.header.time,
38
+ mediantime: node.chain.mtp(block_hash),
39
+ nonce: entry.header.nonce,
40
+ bits: entry.header.bits.to_even_length_hex,
41
+ previousblockhash: entry.prev_hash.rhex,
42
+ nextblockhash: node.chain.next_hash(block_hash).rhex
43
+ }
44
+ else
45
+ entry.header.to_payload.bth
46
+ end
47
+ end
48
+
49
+ # Returns connected peer information.
50
+ def getpeerinfo
51
+ node.pool.peers.map do |peer|
52
+ local_addr = "#{peer.remote_version.remote_addr.ip}:18333"
53
+ {
54
+ id: peer.id,
55
+ addr: "#{peer.host}:#{peer.port}",
56
+ addrlocal: local_addr,
57
+ services: peer.remote_version.services.to_even_length_hex.rjust(16, '0'),
58
+ relaytxes: peer.remote_version.relay,
59
+ lastsend: peer.last_send,
60
+ lastrecv: peer.last_recv,
61
+ bytessent: peer.bytes_sent,
62
+ bytesrecv: peer.bytes_recv,
63
+ conntime: peer.conn_time,
64
+ pingtime: peer.ping_time,
65
+ minping: peer.min_ping,
66
+ version: peer.remote_version.version,
67
+ subver: peer.remote_version.user_agent,
68
+ inbound: !peer.outbound?,
69
+ startingheight: peer.remote_version.start_height,
70
+ best_hash: peer.best_hash,
71
+ best_height: peer.best_height
72
+ }
73
+ end
74
+ end
75
+
76
+ # broadcast transaction
77
+ def sendrawtransaction(hex_tx)
78
+ tx = Tapyrus::Tx.parse_from_payload(hex_tx.htb)
79
+ # TODO check wether tx is valid
80
+ node.broadcast(tx)
81
+ tx.txid
82
+ end
83
+
84
+ # decode tx data.
85
+ def decoderawtransaction(hex_tx)
86
+ begin
87
+ Tapyrus::Tx.parse_from_payload(hex_tx.htb).to_h
88
+ rescue Exception
89
+ raise ArgumentError.new('TX decode failed')
90
+ end
91
+ end
92
+
93
+ # decode script data.
94
+ def decodescript(hex_script)
95
+ begin
96
+ script = Tapyrus::Script.parse_from_payload(hex_script.htb)
97
+ h = script.to_h
98
+ h.delete(:hex)
99
+ h[:p2sh] = script.to_p2sh.addresses.first unless script.p2sh?
100
+ h
101
+ rescue Exception
102
+ raise ArgumentError.new('Script decode failed')
103
+ end
104
+ end
105
+
106
+ # wallet api
107
+
108
+ # create wallet
109
+ def createwallet(wallet_id = 1, wallet_path_prefix = Tapyrus::Wallet::Base.default_path_prefix)
110
+ wallet = Tapyrus::Wallet::Base.create(wallet_id, wallet_path_prefix)
111
+ node.wallet = wallet unless node.wallet
112
+ {wallet_id: wallet.wallet_id, mnemonic: wallet.master_key.mnemonic}
113
+ end
114
+
115
+ # get wallet list.
116
+ def listwallets(wallet_path_prefix = Tapyrus::Wallet::Base.default_path_prefix)
117
+ Tapyrus::Wallet::Base.wallet_paths(wallet_path_prefix)
118
+ end
119
+
120
+ # get current wallet information.
121
+ def getwalletinfo
122
+ node.wallet ? node.wallet.to_h : {}
123
+ end
124
+
125
+ # get the list of current Wallet accounts.
126
+ def listaccounts
127
+ return {} unless node.wallet
128
+ accounts = {}
129
+ node.wallet.accounts.each do |a|
130
+ accounts[a.name] = node.wallet.get_balance(a)
131
+ end
132
+ accounts
133
+ end
134
+
135
+ # encrypt wallet.
136
+ def encryptwallet(passphrase)
137
+ return nil unless node.wallet
138
+ node.wallet.encrypt(passphrase)
139
+ "The wallet 'wallet_id: #{node.wallet.wallet_id}' has been encrypted."
140
+ end
141
+
142
+ # create new tapyrus address for receiving payments.
143
+ def getnewaddress(account_name)
144
+ node.wallet.generate_new_address(account_name)
145
+ end
146
+
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,72 @@
1
+ require 'rest-client'
2
+
3
+ module Tapyrus
4
+ module RPC
5
+
6
+ # Client implementation for RPC to Bitcoin Core.
7
+ #
8
+ # [Usage]
9
+ # config = {schema: 'http', host: 'localhost', port: 18332, user: 'xxx', password: 'yyy'}
10
+ # client = Tapyrus::RPC::BitcoinCoreClient.new(config)
11
+ #
12
+ # You can execute the CLI command supported by Bitcoin Core as follows:
13
+ #
14
+ # client.listunspent
15
+ # client.getblockchaininfo
16
+ #
17
+ class TapyrusCoreClient
18
+
19
+ attr_reader :config
20
+
21
+ # @param [Hash] config a configuration required to connect to Bitcoin Core.
22
+ def initialize(config)
23
+ @config = config
24
+
25
+ commands = request(:help).split("\n").inject([]) do |memo_ary, line|
26
+ if !line.empty? && !line.start_with?('==')
27
+ memo_ary << line.split(' ').first.to_sym
28
+ end
29
+ memo_ary
30
+ end
31
+ TapyrusCoreClient.class_eval do
32
+ commands.each do |command|
33
+ define_method(command) do |*params|
34
+ request(command, *params)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def server_url
43
+ url = "#{config[:schema]}://#{config[:user]}:#{config[:password]}@#{config[:host]}:#{config[:port]}"
44
+ if !config[:wallet].nil? && !config[:wallet].empty?
45
+ url += "/wallet/#{config[:wallet]}"
46
+ end
47
+ url
48
+ end
49
+
50
+ def request(command, *params)
51
+ data = {
52
+ :method => command,
53
+ :params => params,
54
+ :id => 'jsonrpc'
55
+ }
56
+ post(server_url, @config[:timeout], @config[:open_timeout], data.to_json, content_type: :json) do |respdata, request, result|
57
+ raise result.message if !result.kind_of?(Net::HTTPSuccess) && respdata.empty?
58
+ response = JSON.parse(respdata.gsub(/\\u([\da-fA-F]{4})/) { [$1].pack('H*').unpack('n*').pack('U*').encode('ISO-8859-1').force_encoding('UTF-8') })
59
+ raise response['error'] if response['error']
60
+ response['result']
61
+ end
62
+ end
63
+
64
+ def post(url, timeout, open_timeout, payload, headers={}, &block)
65
+ RestClient::Request.execute(method: :post, url: url, timeout: timeout,
66
+ open_timeout: open_timeout, payload: payload, headers: headers, &block)
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,7 @@
1
+ module Tapyrus
2
+ module RPC
3
+ autoload :HttpServer, 'tapyrus/rpc/http_server'
4
+ autoload :RequestHandler, 'tapyrus/rpc/request_handler'
5
+ autoload :TapyrusCoreClient, 'tapyrus/rpc/tapyrus_core_client'
6
+ end
7
+ end
@@ -0,0 +1,92 @@
1
+ module Tapyrus
2
+
3
+ # utility for multisig
4
+ module Multisig
5
+ include Tapyrus::Opcodes
6
+
7
+ def self.prefix
8
+ [OP_0].pack("C*")
9
+ end
10
+
11
+ # generate input script sig spending a multisig output script.
12
+ # returns a raw binary script sig of the form:
13
+ # OP_0 <sig> [<sig> ...]
14
+ # @param [[String]] array of signatures
15
+ # @return [String] script_sig for multisig
16
+ def self.to_multisig_script_sig(*sigs)
17
+ hash_type = sigs.last.is_a?(Numeric) ? sigs.pop : SIGHASH_TYPE[:all]
18
+ sigs.reverse.inject(prefix) { |joined, sig| add_sig_to_multisig_script_sig(sig, joined, hash_type) }
19
+ end
20
+
21
+ # take a multisig script sig (or p2sh multisig script sig) and add
22
+ # another signature to it after the OP_0. Used to sign a tx by
23
+ # multiple parties. Signatures must be in the same order as the
24
+ # pubkeys in the output script being redeemed.
25
+ def self.add_sig_to_multisig_script_sig(sig_to_add, script_sig, hash_type = SIGHASH_TYPE[:all])
26
+ signature = sig_to_add + [hash_type].pack("C*")
27
+ offset = script_sig.empty? ? 0 : 1
28
+ script_sig.insert(offset, Tapyrus::Script.pack_pushdata(signature))
29
+ end
30
+
31
+ # generate input script sig spending a p2sh-multisig output script.
32
+ # returns a raw binary script sig of the form:
33
+ # OP_0 <sig> [<sig> ...] <redeem_script>
34
+ # @param [Script] redeem_script
35
+ # @param [[String]] array of signatures
36
+ # @return [String] script_sig for multisig
37
+ def self.to_p2sh_multisig_script_sig(redeem_script, *sigs)
38
+ to_multisig_script_sig(*sigs.flatten) + Tapyrus::Script.pack_pushdata(redeem_script)
39
+ end
40
+
41
+ # Sort signatures in the given +script_sig+ according to the order of pubkeys in
42
+ # the redeem script. Also needs the +sig_hash+ to match signatures to pubkeys.
43
+ # @param [String] signature for multisig.
44
+ # @param [String] sig_hash to be signed.
45
+ # @return [String] sorted sig_hash.
46
+ def self.sort_p2sh_multisig_signatures(script_sig, sig_hash)
47
+ script = Tapyrus::Script.parse_from_payload(script_sig)
48
+ redeem_script = Tapyrus::Script.parse_from_payload(script.chunks[-1].pushed_data)
49
+ pubkeys = redeem_script.get_multisig_pubkeys
50
+
51
+ # find the pubkey for each signature by trying to verify it
52
+ sigs = Hash[script.chunks[1...-1].map.with_index do |sig, idx|
53
+ sig = sig.pushed_data
54
+ pubkey = pubkeys.map do |key|
55
+ Tapyrus::Key.new(pubkey: key.bth).verify(sig, sig_hash) ? key : nil
56
+ end.compact.first
57
+ raise "Key for signature ##{idx} not found in redeem script!" unless pubkey
58
+ [pubkey, sig]
59
+ end]
60
+
61
+ prefix + pubkeys.map { |k| sigs[k] ? Tapyrus::Script.pack_pushdata(sigs[k]) : nil }.join +
62
+ Tapyrus::Script.pack_pushdata(script.chunks[-1].pushed_data)
63
+ end
64
+
65
+ def self.add_sig_to_multisig_script_witness(sig_to_add, script_witness, hash_type = SIGHASH_TYPE[:all])
66
+ signature = sig_to_add + [hash_type].pack("C*")
67
+ script_witness.stack << signature
68
+ end
69
+
70
+ # Sort signatures in the given +script_witness+ according to the order of pubkeys in
71
+ # the redeem script. Also needs the +sig_hash+ to match signatures to pubkeys.
72
+ # @param [ScriptWitness] script_witness for multisig.
73
+ # @param [String] sig_hash to be signed.
74
+ def self.sort_witness_multisig_signatures(script_witness, sig_hash)
75
+ redeem_script = Tapyrus::Script.parse_from_payload(script_witness.stack[-1])
76
+ pubkeys = redeem_script.get_multisig_pubkeys
77
+ sigs = Hash[script_witness.stack[1...-1].map.with_index do |sig, idx|
78
+ pubkey = pubkeys.map do |key|
79
+ Tapyrus::Key.new(pubkey: key.bth).verify(sig, sig_hash) ? key : nil
80
+ end.compact.first
81
+ raise "Key for signature ##{idx} not found in redeem script!" unless pubkey
82
+ [pubkey, sig]
83
+ end]
84
+ script_witness.stack.clear
85
+ script_witness.stack << ''
86
+ pubkeys.each do |pubkey|
87
+ script_witness.stack << sigs[pubkey] if sigs[pubkey]
88
+ end
89
+ script_witness.stack << redeem_script.to_payload
90
+ end
91
+ end
92
+ end