bitcoinrb 0.2.9 → 0.5.0

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +3 -2
  4. data/README.md +7 -6
  5. data/bitcoinrb.gemspec +4 -4
  6. data/exe/bitcoinrbd +5 -0
  7. data/lib/bitcoin.rb +33 -1
  8. data/lib/bitcoin/bip85_entropy.rb +111 -0
  9. data/lib/bitcoin/block_header.rb +2 -0
  10. data/lib/bitcoin/chain_params.rb +0 -8
  11. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  12. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  13. data/lib/bitcoin/constants.rb +3 -10
  14. data/lib/bitcoin/descriptor.rb +147 -0
  15. data/lib/bitcoin/ext.rb +5 -0
  16. data/lib/bitcoin/ext/json_parser.rb +46 -0
  17. data/lib/bitcoin/ext_key.rb +19 -4
  18. data/lib/bitcoin/key.rb +9 -5
  19. data/lib/bitcoin/key_path.rb +12 -5
  20. data/lib/bitcoin/message.rb +7 -0
  21. data/lib/bitcoin/message/base.rb +1 -0
  22. data/lib/bitcoin/message/cf_parser.rb +16 -0
  23. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  24. data/lib/bitcoin/message/cfheaders.rb +40 -0
  25. data/lib/bitcoin/message/cfilter.rb +35 -0
  26. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  27. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  28. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  29. data/lib/bitcoin/message/network_addr.rb +31 -12
  30. data/lib/bitcoin/message/version.rb +14 -22
  31. data/lib/bitcoin/mnemonic.rb +5 -5
  32. data/lib/bitcoin/network/peer.rb +12 -11
  33. data/lib/bitcoin/network/peer_discovery.rb +3 -1
  34. data/lib/bitcoin/node/cli.rb +14 -10
  35. data/lib/bitcoin/node/spv.rb +1 -1
  36. data/lib/bitcoin/out_point.rb +14 -7
  37. data/lib/bitcoin/payment_code.rb +92 -0
  38. data/lib/bitcoin/psbt.rb +3 -1
  39. data/lib/bitcoin/psbt/input.rb +7 -16
  40. data/lib/bitcoin/psbt/tx.rb +18 -12
  41. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  42. data/lib/bitcoin/rpc/request_handler.rb +3 -3
  43. data/lib/bitcoin/script/script.rb +18 -10
  44. data/lib/bitcoin/script/script_interpreter.rb +3 -5
  45. data/lib/bitcoin/secp256k1.rb +1 -0
  46. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  47. data/lib/bitcoin/secp256k1/ruby.rb +4 -35
  48. data/lib/bitcoin/slip39.rb +93 -0
  49. data/lib/bitcoin/slip39/share.rb +122 -0
  50. data/lib/bitcoin/slip39/sss.rb +245 -0
  51. data/lib/bitcoin/slip39/wordlist/english.txt +1024 -0
  52. data/lib/bitcoin/store.rb +2 -1
  53. data/lib/bitcoin/store/chain_entry.rb +1 -0
  54. data/lib/bitcoin/store/db/level_db.rb +2 -2
  55. data/lib/bitcoin/store/utxo_db.rb +226 -0
  56. data/lib/bitcoin/tx.rb +6 -10
  57. data/lib/bitcoin/tx_in.rb +4 -5
  58. data/lib/bitcoin/util.rb +29 -1
  59. data/lib/bitcoin/version.rb +1 -1
  60. data/lib/bitcoin/wallet.rb +1 -0
  61. data/lib/bitcoin/wallet/account.rb +1 -0
  62. data/lib/bitcoin/wallet/base.rb +3 -3
  63. data/lib/bitcoin/wallet/db.rb +1 -1
  64. data/lib/bitcoin/wallet/master_key.rb +1 -0
  65. data/lib/bitcoin/wallet/utxo.rb +37 -0
  66. metadata +45 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db45450b62847b14f755e93063e237e6f521a30de9125659dd2325b1b445d5b2
4
- data.tar.gz: b1e446e457bdad6cc7d5449ffd7a97cf26fba50f0ec85659aa53584a01ac998d
3
+ metadata.gz: a5da63d0663778eba1816d008ebf369348bc75d214965b68c2ff7ce564e95ac3
4
+ data.tar.gz: 2a7e31c9f5b29b72b14e93103f1b69111a2283e390fb61fdad517a5aa764f9f4
5
5
  SHA512:
6
- metadata.gz: 67767d3f68b7b1656730a43e1dce32c9dff0b43cdd603f813e6de1c4a1b3fe4c5c51784d6afb356db42a2cc230c2fa559e1ebc2f0992afe3bb55b3d635a05ffe
7
- data.tar.gz: 4d38a01b81e90b79836c47cfd4912b6f70aba0ca257627fc0aba368ace227ee475b3205263e011c84fc00b43de4e33719b72c65fdddcb81210f5f35b19ba3bbd
6
+ metadata.gz: b062f7c6e944cac7787fdca7be284224961b9a675467bd6f55fa0d49ed502157507e098aee9b28998f600826d677ddc3f8b26947134591240b5d8ada5e004686
7
+ data.tar.gz: b32d9705400ac5bfb1cc252e4a79c66ff9fb2d8dcba1f530060b5c130e14726e412a5434d5769dd6824588374cbae99c797ae65a2478a92d53cc5fa664ab5a4e
@@ -1 +1 @@
1
- 2.6.2
1
+ 2.7.0
@@ -1,8 +1,9 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.4.5
3
+ - 2.4.6
4
4
  - 2.5.5
5
- - 2.6.2
5
+ - 2.6.3
6
+ - 2.7.0
6
7
  addons:
7
8
  apt:
8
9
  packages:
data/README.md CHANGED
@@ -9,14 +9,15 @@ NOTE: Bitcoinrb work in progress, and there is a possibility of incompatible cha
9
9
 
10
10
  Bitcoinrb supports following feature:
11
11
 
12
- * Bitcoin script interpreter(including [BIP-65](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki), [BIP-68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki), [BIP-112](https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki))
13
- * De/serialization of Bitcoin protocol network messages
14
- * De/serialization of blocks and transactions
12
+ * [Bitcoin script interpreter](https://github.com/chaintope/bitcoinrb/wiki/Script)(including [BIP-65](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki), [BIP-68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki), [BIP-112](https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki))
13
+ * [De/serialization of Bitcoin protocol network messages](https://github.com/chaintope/bitcoinrb/wiki/P2P-Message)
14
+ * De/serialization of blocks and [transactions](https://github.com/chaintope/bitcoinrb/wiki/Transaction)
15
15
  * Key generation and verification for ECDSA, including [BIP-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) and [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) supports.
16
16
  * ECDSA signature(RFC6979 -Deterministic ECDSA, LOW-S, LOW-R support)
17
17
  * Segwit support (parsing segwit payload, Bech32 address, sign for segwit tx, [BIP-141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki), [BIP-143](https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki), [BIP-144](https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki))
18
18
  * [BIP-173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) Bech32 address support
19
19
  * [BIP-174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki) PSBT(Partially Signed Bitcoin Transaction) support
20
+ * [BIP-85](https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki) Deterministic Entropy From BIP32 Keychains support by `Bitcoin::BIP85Entropy` class.
20
21
  * [WIP] SPV node
21
22
  * [WIP] 0ff-chain protocol
22
23
 
@@ -36,10 +37,10 @@ If you use node features, please install level DB as follows.
36
37
 
37
38
  $ brew install leveldb
38
39
 
39
- and put `leveldb-ruby` in your Gemfile and run bundle install.
40
+ and put `leveldb-native` in your Gemfile and run bundle install.
40
41
 
41
- ```
42
- gem leveldb-ruby
42
+ ```ruby
43
+ gem 'leveldb-native'
43
44
  ```
44
45
 
45
46
  ## Installation
@@ -29,19 +29,19 @@ Gem::Specification.new do |spec|
29
29
  spec.add_runtime_dependency 'ffi'
30
30
  spec.add_runtime_dependency 'leb128', '~> 1.0.0'
31
31
  spec.add_runtime_dependency 'eventmachine_httpserver'
32
- spec.add_runtime_dependency 'rest-client'
33
32
  spec.add_runtime_dependency 'iniparse'
34
33
  spec.add_runtime_dependency 'siphash'
35
34
  spec.add_runtime_dependency 'protobuf', '3.8.5'
36
35
  spec.add_runtime_dependency 'scrypt'
37
- spec.add_runtime_dependency 'activesupport', '~> 5.2.3'
36
+ spec.add_runtime_dependency 'json_pure', '>= 2.3.1'
38
37
 
39
38
  # for options
40
- spec.add_development_dependency 'leveldb-ruby'
39
+ spec.add_development_dependency 'leveldb-native'
41
40
 
42
41
  spec.add_development_dependency 'bundler'
43
- spec.add_development_dependency 'rake', '~> 10.0'
42
+ spec.add_development_dependency 'rake', '>= 12.3.3'
44
43
  spec.add_development_dependency 'rspec', '~> 3.0'
45
44
  spec.add_development_dependency 'timecop'
45
+ spec.add_development_dependency 'webmock', '~> 3.0'
46
46
 
47
47
  end
@@ -12,6 +12,11 @@ class BitcoinDaemon < DaemonSpawn::Base
12
12
  node.run
13
13
  end
14
14
 
15
+ def stop
16
+ puts "Stopping Bitcoinrb daemon : #{Time.now}"
17
+ node.shutdown
18
+ end
19
+
15
20
  end
16
21
 
17
22
  class Bitcoinrbd < Thor
@@ -14,6 +14,7 @@ require_relative 'openassets'
14
14
 
15
15
  module Bitcoin
16
16
 
17
+ autoload :Ext, 'bitcoin/ext'
17
18
  autoload :Util, 'bitcoin/util'
18
19
  autoload :ChainParams, 'bitcoin/chain_params'
19
20
  autoload :Message, 'bitcoin/message'
@@ -52,6 +53,11 @@ module Bitcoin
52
53
  autoload :BitStreamWriter, 'bitcoin/bit_stream'
53
54
  autoload :BitStreamReader, 'bitcoin/bit_stream'
54
55
  autoload :KeyPath, 'bitcoin/key_path'
56
+ autoload :Descriptor, 'bitcoin/descriptor'
57
+ autoload :SLIP39, 'bitcoin/slip39'
58
+ autoload :Aezeed, 'bitcoin/aezeed'
59
+ autoload :PaymentCode, 'bitcoin/payment_code'
60
+ autoload :BIP85Entropy, 'bitcoin/bip85_entropy'
55
61
 
56
62
  require_relative 'bitcoin/constants'
57
63
 
@@ -110,6 +116,11 @@ module Bitcoin
110
116
  [self].pack('H*')
111
117
  end
112
118
 
119
+ # binary convert to integer
120
+ def bti
121
+ bth.to_i(16)
122
+ end
123
+
113
124
  # reverse hex string endian
114
125
  def rhex
115
126
  htb.reverse.bth
@@ -154,6 +165,12 @@ module Bitcoin
154
165
  self[offset..-1]
155
166
  end
156
167
 
168
+ # whether value is hex or not hex
169
+ # @return [Boolean] return true if data is hex
170
+ def valid_hex?
171
+ !self[/\H/]
172
+ end
173
+
157
174
  end
158
175
 
159
176
  class ::Object
@@ -175,7 +192,7 @@ module Bitcoin
175
192
  if value.is_a?(Array)
176
193
  result.update(key => value.map{|v|v.to_h})
177
194
  else
178
- result.update(key => value)
195
+ result.update(key => value.class.to_s.start_with?("Bitcoin::") ? value.to_h : value)
179
196
  end
180
197
  end
181
198
  end
@@ -191,6 +208,15 @@ module Bitcoin
191
208
  def itb
192
209
  to_even_length_hex.htb
193
210
  end
211
+
212
+ # convert bit string
213
+ def to_bits(length = nil )
214
+ if length
215
+ to_s(2).rjust(length, '0')
216
+ else
217
+ to_s(2)
218
+ end
219
+ end
194
220
  end
195
221
 
196
222
  class ::ECDSA::Signature
@@ -200,4 +226,10 @@ module Bitcoin
200
226
  end
201
227
  end
202
228
 
229
+ class ::ECDSA::Point
230
+ def to_hex(compression = true)
231
+ ECDSA::Format::PointOctetString.encode(self, compression: compression).bth
232
+ end
233
+ end
234
+
203
235
  end
@@ -0,0 +1,111 @@
1
+ module Bitcoin
2
+
3
+ # Deterministic Entropy From BIP32 Keychains
4
+ # https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki
5
+ class BIP85Entropy
6
+
7
+ BIP85_PATH = 83696968 + HARDENED_THRESHOLD
8
+
9
+ include Bitcoin::KeyPath
10
+
11
+ attr_reader :root_key #hex format
12
+
13
+ # Import root key.
14
+ # @param [String] base58 master bip32 root key.
15
+ # @return [Bitcoin::BIP85Entropy]
16
+ def self.from_base58(base58)
17
+ key = Bitcoin::ExtKey.from_base58(base58)
18
+ self.new(key)
19
+ end
20
+
21
+ # derive entropy
22
+ # @param [String] path derive path.
23
+ # @return [Tuple(String, Object)] a tuple of entropy with hex format and results depending the application.
24
+ def derive(path)
25
+ raise ArgumentError, "Invalid BIP85 path format." unless path.start_with?("m/83696968'")
26
+ derived_key = root_key
27
+ parse_key_path(path).each{|num| derived_key = derived_key.derive(num)}
28
+ derived_key = derived_key.priv
29
+ entropy = Bitcoin.hmac_sha512("bip-entropy-from-k", derived_key.htb).bth
30
+ app_no = path.split('/')[2]
31
+ case app_no
32
+ when "39'"
33
+ bip39_entropy(path, entropy)
34
+ when "2'"
35
+ hd_seed_entropy(entropy)
36
+ when "32'"
37
+ xprv_entropy(entropy)
38
+ else
39
+ [entropy, entropy]
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def initialize(root_key)
46
+ @root_key = root_key
47
+ end
48
+
49
+ # derive BIP39 entropy.
50
+ def bip39_entropy(path, entropy)
51
+ params = path.split('/')
52
+ word_len = params[4]
53
+ language = code_to_language(params[3])
54
+ entropy = case word_len
55
+ when "12'"
56
+ entropy[0...32]
57
+ when "18'"
58
+ entropy[0...48]
59
+ when "24'"
60
+ entropy[0...64]
61
+ else
62
+ raise ArgumentError, "Word length #{word_len} does not supported."
63
+ end
64
+ mnemonic = Bitcoin::Mnemonic.new(language)
65
+ [entropy, mnemonic.to_mnemonic(entropy)]
66
+ end
67
+
68
+ # derive HD-Seed WIF entropy.
69
+ def hd_seed_entropy(entropy)
70
+ result = entropy[0...64]
71
+ [result, Bitcoin::Key.new(priv_key: result).to_wif]
72
+ end
73
+
74
+ # derive xprv entropy
75
+ def xprv_entropy(entropy)
76
+ chaincode = entropy[0...64]
77
+ private_key = Bitcoin::Key.new(priv_key: entropy[64..-1])
78
+ ext_key = Bitcoin::ExtKey.new
79
+ ext_key.key = private_key
80
+ ext_key.chain_code = chaincode.htb
81
+ ext_key.depth = 0
82
+ ext_key.number = 0
83
+ ext_key.parent_fingerprint = Bitcoin::ExtKey::MASTER_FINGERPRINT
84
+ [entropy, ext_key.to_base58]
85
+ end
86
+
87
+ # convert language code to language string.
88
+ def code_to_language(code)
89
+ case code
90
+ when "0'"
91
+ "english"
92
+ when "1'"
93
+ "japanese"
94
+ when "3'"
95
+ "spanish"
96
+ when "4'"
97
+ "chinese_simplified"
98
+ when "5'"
99
+ "chinese_traditional"
100
+ when "6'"
101
+ "french"
102
+ when "7'"
103
+ "italian"
104
+ else
105
+ raise ArgumentError, "bitcoinrb does not support language: #{code}"
106
+ end
107
+ end
108
+
109
+ end
110
+
111
+ end
@@ -3,6 +3,8 @@ module Bitcoin
3
3
  # Block Header
4
4
  class BlockHeader
5
5
 
6
+ include Bitcoin::HexConverter
7
+
6
8
  attr_accessor :version
7
9
  attr_accessor :prev_hash
8
10
  attr_accessor :merkle_root
@@ -36,9 +36,6 @@ module Bitcoin
36
36
 
37
37
  attr_accessor :dust_relay_fee
38
38
 
39
- # fork coin id.
40
- attr_accessor :fork_id
41
-
42
39
  # mainnet genesis
43
40
  def self.mainnet
44
41
  init('mainnet')
@@ -73,11 +70,6 @@ module Bitcoin
73
70
  Bitcoin::Block.new(header)
74
71
  end
75
72
 
76
- # whether fork coin.
77
- def fork_chain?
78
- !fork_id.nil?
79
- end
80
-
81
73
  def self.init(name)
82
74
  i = YAML.load(File.open("#{__dir__}/chainparams/#{name}.yml"))
83
75
  i.dust_relay_fee ||= Bitcoin::DUST_RELAY_TX_FEE
@@ -9,7 +9,7 @@ privkey_version: "ef"
9
9
  extended_privkey_version: "04358394"
10
10
  extended_pubkey_version: "043587cf"
11
11
  bip49_pubkey_p2wpkh_p2sh_version: "044a5262"
12
- bip49_pubkey_p2wsh_p2sh_version: "024285ef"
12
+ bip49_pubkey_p2wsh_p2sh_version: "024289ef"
13
13
  bip49_privkey_p2wpkh_p2sh_version: "044a4e28"
14
14
  bip49_privkey_p2wsh_p2sh_version: "024285b5"
15
15
  bip84_pubkey_p2wpkh_version: "045f1cf6"
@@ -9,7 +9,7 @@ privkey_version: "ef"
9
9
  extended_privkey_version: "04358394"
10
10
  extended_pubkey_version: "043587cf"
11
11
  bip49_pubkey_p2wpkh_p2sh_version: "044a5262"
12
- bip49_pubkey_p2wsh_p2sh_version: "024285ef"
12
+ bip49_pubkey_p2wsh_p2sh_version: "024289ef"
13
13
  bip49_privkey_p2wpkh_p2sh_version: "044a4e28"
14
14
  bip49_privkey_p2wsh_p2sh_version: "024285b5"
15
15
  bip84_pubkey_p2wpkh_version: "045f1cf6"
@@ -91,13 +91,6 @@ module Bitcoin
91
91
  # Signature hash types/flags
92
92
  SIGHASH_TYPE = { all: 1, none: 2, single: 3, anyonecanpay: 128 }
93
93
 
94
- # SIGHASH_FORK_ID for replay protection of the fork coin
95
- SIGHASH_FORK_ID = 0x40
96
-
97
- # fork coin id.
98
- FORK_ID_CASH = 0
99
- FORK_ID_GOLD = 79
100
-
101
94
  # Maximum number length in bytes
102
95
  DEFAULT_MAX_NUM_SIZE = 4
103
96
 
@@ -146,9 +139,9 @@ module Bitcoin
146
139
  SCRIPT_ERR_SIG_HIGH_S = 54
147
140
  SCRIPT_ERR_SIG_NULLDUMMY = 55
148
141
  SCRIPT_ERR_PUBKEYTYPE = 56
149
- SCRIPT_ERR_CLEANSTACK = 56
150
- SCRIPT_ERR_MINIMALIF = 57
151
- SCRIPT_ERR_SIG_NULLFAIL = 58
142
+ SCRIPT_ERR_CLEANSTACK = 57
143
+ SCRIPT_ERR_MINIMALIF = 58
144
+ SCRIPT_ERR_SIG_NULLFAIL = 59
152
145
 
153
146
  # softfork safeness
154
147
  SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS = 60
@@ -0,0 +1,147 @@
1
+ module Bitcoin
2
+
3
+ module Descriptor
4
+
5
+ include Bitcoin::Opcodes
6
+
7
+ # generate P2PK output for the given public key.
8
+ # @param [String] key private key or public key with hex format
9
+ # @return [Bitcoin::Script] P2PK script.
10
+ def pk(key)
11
+ Bitcoin::Script.new << extract_pubkey(key) << OP_CHECKSIG
12
+ end
13
+
14
+ # generate P2PKH output for the given public key.
15
+ # @param [String] key private key or public key with hex format.
16
+ # @return [Bitcoin::Script] P2PKH script.
17
+ def pkh(key)
18
+ Bitcoin::Script.to_p2pkh(Bitcoin.hash160(extract_pubkey(key)))
19
+ end
20
+
21
+ # generate P2PKH output for the given public key.
22
+ # @param [String] key private key or public key with hex format.
23
+ # @return [Bitcoin::Script] P2WPKH script.
24
+ def wpkh(key)
25
+ pubkey = extract_pubkey(key)
26
+ raise ArgumentError, "Uncompressed key are not allowed." unless compressed_key?(pubkey)
27
+ Bitcoin::Script.to_p2wpkh(Bitcoin.hash160(pubkey))
28
+ end
29
+
30
+ # generate P2SH embed the argument.
31
+ # @param [String or Script] script script to be embed.
32
+ # @return [Bitcoin::Script] P2SH script.
33
+ def sh(script)
34
+ script = script.to_hex if script.is_a?(Bitcoin::Script)
35
+ raise ArgumentError, "P2SH script is too large, 547 bytes is larger than #{Bitcoin::MAX_SCRIPT_ELEMENT_SIZE} bytes." if script.htb.bytesize > Bitcoin::MAX_SCRIPT_ELEMENT_SIZE
36
+ Bitcoin::Script.to_p2sh(Bitcoin.hash160(script))
37
+ end
38
+
39
+ # generate P2WSH embed the argument.
40
+ # @param [String or Script] script script to be embed.
41
+ # @return [Bitcoin::Script] P2WSH script.
42
+ def wsh(script)
43
+ script = Bitcoin::Script(script.htb) if script.is_a?(String)
44
+ raise ArgumentError, "P2SH script is too large, 547 bytes is larger than #{Bitcoin::MAX_SCRIPT_ELEMENT_SIZE} bytes." if script.to_payload.bytesize > Bitcoin::MAX_SCRIPT_ELEMENT_SIZE
45
+ raise ArgumentError, "Uncompressed key are not allowed." if script.get_pubkeys.any?{|p|!compressed_key?(p)}
46
+ Bitcoin::Script.to_p2wsh(script)
47
+ end
48
+
49
+ # an alias for the collection of `pk(KEY)` and `pkh(KEY)`.
50
+ # If the key is compressed, it also includes `wpkh(KEY)` and `sh(wpkh(KEY))`.
51
+ # @param [String] key private key or public key with hex format.
52
+ # @return [Array[Bitcoin::Script]]
53
+ def combo(key)
54
+ result = [pk(key), pkh(key)]
55
+ pubkey = extract_pubkey(key)
56
+ if compressed_key?(pubkey)
57
+ result << wpkh(key)
58
+ result << sh(result.last)
59
+ end
60
+ result
61
+ end
62
+
63
+ # generate multisig output for given keys.
64
+ # @param [Integer] threshold the threshold of multisig.
65
+ # @param [Array[String]] keys an array of keys.
66
+ # @return [Bitcoin::Script] multisig script.
67
+ def multi(threshold, *keys, sort: false)
68
+ raise ArgumentError, 'Multisig threshold is not valid.' unless threshold.is_a?(Integer)
69
+ raise ArgumentError, 'Multisig threshold cannot be 0, must be at least 1.' unless threshold > 0
70
+ raise ArgumentError, 'Multisig threshold cannot be larger than the number of keys.' if threshold > keys.size
71
+ raise ArgumentError, 'Multisig must have between 1 and 16 keys, inclusive.' if keys.size > 16
72
+ pubkeys = keys.map{|key| extract_pubkey(key) }
73
+ Bitcoin::Script.to_multisig_script(threshold, pubkeys, sort: sort)
74
+ end
75
+
76
+ # generate sorted multisig output for given keys.
77
+ # @param [Integer] threshold the threshold of multisig.
78
+ # @param [Array[String]] keys an array of keys.
79
+ # @return [Bitcoin::Script] multisig script.
80
+ def sortedmulti(threshold, *keys)
81
+ multi(threshold, *keys, sort: true)
82
+ end
83
+
84
+ private
85
+
86
+ # extract public key from KEY format.
87
+ # @param [String] key KEY string.
88
+ # @return [String] public key.
89
+ def extract_pubkey(key)
90
+ if key.start_with?('[') # BIP32 fingerprint
91
+ raise ArgumentError, 'Invalid key origin.' if key.count('[') > 1 || key.count(']') > 1
92
+ info = key[1...key.index(']')] # TODO
93
+ fingerprint, *paths = info.split('/')
94
+ raise ArgumentError, 'Fingerprint is not hex.' unless fingerprint.valid_hex?
95
+ raise ArgumentError, 'Fingerprint is not 4 bytes.' unless fingerprint.size == 8
96
+ key = key[(key.index(']') + 1)..-1]
97
+ else
98
+ raise ArgumentError, 'Invalid key origin.' if key.include?(']')
99
+ end
100
+
101
+ # check BIP32 derivation path
102
+ key, *paths = key.split('/')
103
+
104
+ if key.start_with?('xprv')
105
+ key = Bitcoin::ExtKey.from_base58(key)
106
+ key = derive_path(key, paths, true) if paths
107
+ elsif key.start_with?('xpub')
108
+ key = Bitcoin::ExtPubkey.from_base58(key)
109
+ key = derive_path(key, paths, false) if paths
110
+ else
111
+ begin
112
+ key = Bitcoin::Key.from_wif(key)
113
+ rescue ArgumentError
114
+ key_type = compressed_key?(key) ? Bitcoin::Key::TYPES[:compressed] : Bitcoin::Key::TYPES[:uncompressed]
115
+ key = Bitcoin::Key.new(pubkey: key, key_type: key_type)
116
+ end
117
+ end
118
+ key = key.is_a?(Bitcoin::Key) ? key : key.key
119
+ raise ArgumentError, 'Invalid pubkey.' unless key.fully_valid_pubkey?
120
+ key.pubkey
121
+ end
122
+
123
+ def compressed_key?(key)
124
+ %w(02 03).include?(key[0..1]) && [key].pack("H*").bytesize == 33
125
+ end
126
+
127
+ def derive_path(key, paths, is_private)
128
+ paths.each do |path|
129
+ raise ArgumentError, 'xpub can not derive hardened key.' if !is_private && path.end_with?("'")
130
+ if is_private
131
+ hardened = path.end_with?("'")
132
+ path = hardened ? path[0..-2] : path
133
+ raise ArgumentError, 'Key path value is not a valid value.' unless path =~ /^[0-9]+$/
134
+ raise ArgumentError, 'Key path value is out of range.' if !hardened && path.to_i >= Bitcoin::HARDENED_THRESHOLD
135
+ key = key.derive(path.to_i, hardened)
136
+ else
137
+ raise ArgumentError, 'Key path value is not a valid value.' unless path =~ /^[0-9]+$/
138
+ raise ArgumentError, 'Key path value is out of range.' if path.to_i >= Bitcoin::HARDENED_THRESHOLD
139
+ key = key.derive(path.to_i)
140
+ end
141
+ end
142
+ key
143
+ end
144
+
145
+ end
146
+
147
+ end