bitcoinrb 0.2.9 → 0.5.0

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