bitcoinrb 0.2.9 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db45450b62847b14f755e93063e237e6f521a30de9125659dd2325b1b445d5b2
4
- data.tar.gz: b1e446e457bdad6cc7d5449ffd7a97cf26fba50f0ec85659aa53584a01ac998d
3
+ metadata.gz: 52fe21b51d659b4d5a45abbe8207727101813f1f30d7e0cf3438dad43ad3be8d
4
+ data.tar.gz: b8d94902ad2733c963f23507bdde7f018560cf8cd2ceef17bc349f78373b67ec
5
5
  SHA512:
6
- metadata.gz: 67767d3f68b7b1656730a43e1dce32c9dff0b43cdd603f813e6de1c4a1b3fe4c5c51784d6afb356db42a2cc230c2fa559e1ebc2f0992afe3bb55b3d635a05ffe
7
- data.tar.gz: 4d38a01b81e90b79836c47cfd4912b6f70aba0ca257627fc0aba368ace227ee475b3205263e011c84fc00b43de4e33719b72c65fdddcb81210f5f35b19ba3bbd
6
+ metadata.gz: 62f1c0c37657b0f3a1df74d902c42d53a0134cbf254968db70d3814bd1190937c049fc9362bc2f7a6b7570958909bf8ce7fb080ee40d1ff3a6ce410f77305ff7
7
+ data.tar.gz: 7306ece331df7d9fffd781d0d8bcabcd1f6fc0d6ca64271acbc6138de6ebcb1e7bd843021ef432067136c5ec28bdad4613cb42356daeb6246bf17c3f51ea4526
@@ -1 +1 @@
1
- 2.6.2
1
+ 2.6.3
@@ -1,8 +1,8 @@
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
6
  addons:
7
7
  apt:
8
8
  packages:
@@ -52,6 +52,8 @@ module Bitcoin
52
52
  autoload :BitStreamWriter, 'bitcoin/bit_stream'
53
53
  autoload :BitStreamReader, 'bitcoin/bit_stream'
54
54
  autoload :KeyPath, 'bitcoin/key_path'
55
+ autoload :Descriptor, 'bitcoin/descriptor'
56
+ autoload :SLIP39, 'bitcoin/slip39'
55
57
 
56
58
  require_relative 'bitcoin/constants'
57
59
 
@@ -110,6 +112,11 @@ module Bitcoin
110
112
  [self].pack('H*')
111
113
  end
112
114
 
115
+ # binary convert to integer
116
+ def bti
117
+ bth.to_i(16)
118
+ end
119
+
113
120
  # reverse hex string endian
114
121
  def rhex
115
122
  htb.reverse.bth
@@ -154,6 +161,12 @@ module Bitcoin
154
161
  self[offset..-1]
155
162
  end
156
163
 
164
+ # whether value is hex or not hex
165
+ # @return [Boolean] return true if data is hex
166
+ def valid_hex?
167
+ !self[/\H/]
168
+ end
169
+
157
170
  end
158
171
 
159
172
  class ::Object
@@ -191,6 +204,15 @@ module Bitcoin
191
204
  def itb
192
205
  to_even_length_hex.htb
193
206
  end
207
+
208
+ # convert bit string
209
+ def to_bits(length = nil )
210
+ if length
211
+ to_s(2).rjust(length, '0')
212
+ else
213
+ to_s(2)
214
+ end
215
+ end
194
216
  end
195
217
 
196
218
  class ::ECDSA::Signature
@@ -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
@@ -227,8 +227,12 @@ module Bitcoin
227
227
  # fully validate whether this is a valid public key (more expensive than IsValid())
228
228
  def fully_valid_pubkey?
229
229
  return false unless valid_pubkey?
230
- point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1)
231
- ECDSA::Group::Secp256k1.valid_public_key?(point)
230
+ begin
231
+ point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1)
232
+ ECDSA::Group::Secp256k1.valid_public_key?(point)
233
+ rescue ECDSA::Format::DecodeError
234
+ false
235
+ end
232
236
  end
233
237
 
234
238
  private
@@ -1,3 +1,5 @@
1
+ require 'ipaddr'
2
+
1
3
  module Bitcoin
2
4
  module Message
3
5
 
@@ -12,30 +14,47 @@ module Bitcoin
12
14
  # The services the node advertised in its version message.
13
15
  attr_accessor :services
14
16
 
15
- attr_accessor :ip
17
+ attr_accessor :ip_addr # IPAddr
16
18
 
17
19
  attr_accessor :port
18
20
 
19
- def initialize
20
- @time = Time.now.to_i
21
- @services = Bitcoin::Message::SERVICE_FLAGS[:network]
21
+ attr_reader :skip_time
22
+
23
+ def initialize(ip: '127.0.0.1', port: Bitcoin.chain_params.default_port, services: DEFAULT_SERVICE_FLAGS, time: Time.now.to_i)
24
+ @time = time
25
+ @ip_addr = IPAddr.new(ip)
26
+ @port = port
27
+ @services = services
22
28
  end
23
29
 
24
30
  def self.parse_from_payload(payload)
25
31
  buf = payload.is_a?(String) ? StringIO.new(payload) : payload
26
- addr = new
27
- addr.time = buf.read(4).unpack('V').first
32
+ has_time = buf.size > 26
33
+ addr = new(time: nil)
34
+ addr.time = buf.read(4).unpack('V').first if has_time
28
35
  addr.services = buf.read(8).unpack('Q').first
29
- ip = IPAddr::new_ntoh(buf.read(16))
30
- addr.ip = ip.ipv4_mapped? ? ip.native : ip.to_s
36
+ addr.ip_addr = IPAddr::new_ntoh(buf.read(16))
31
37
  addr.port = buf.read(2).unpack('n').first
32
38
  addr
33
39
  end
34
40
 
35
- def to_payload
36
- ip_addr = IPAddr.new (ip)
37
- ip_addr = ip_addr.ipv4_mapped if ip_addr.ipv4?
38
- [time, services].pack('VQ') << ip_addr.hton << [port].pack('n')
41
+ def self.local_addr
42
+ addr = new
43
+ addr.ip_addr = IPAddr.new('127.0.0.1')
44
+ addr.port = Bitcoin.chain_params.default_port
45
+ addr.services = DEFAULT_SERVICE_FLAGS
46
+ addr
47
+ end
48
+
49
+ def ip
50
+ ip_addr.ipv4_mapped? ? ip_addr.native : ip_addr.to_s
51
+ end
52
+
53
+ def to_payload(skip_time = false)
54
+ p = ''
55
+ p << [time].pack('V') unless skip_time
56
+ addr = ip_addr.ipv4? ? ip_addr.ipv4_mapped : ip_addr
57
+ p << [services].pack('Q') << addr.hton << [port].pack('n')
39
58
  end
40
59
 
41
60
  end
@@ -22,8 +22,8 @@ module Bitcoin
22
22
  @version = Bitcoin.chain_params.protocol_version
23
23
  @services = DEFAULT_SERVICE_FLAGS
24
24
  @timestamp = Time.now.to_i
25
- @local_addr = "127.0.0.1:#{Bitcoin.chain_params.default_port}"
26
- @remote_addr = "127.0.0.1:#{Bitcoin.chain_params.default_port}"
25
+ @local_addr = NetworkAddr.local_addr
26
+ @remote_addr = NetworkAddr.local_addr
27
27
  @nonce = SecureRandom.random_number(0xffffffffffffffff)
28
28
  @user_agent = Bitcoin::Message::USER_AGENT
29
29
  @start_height = 0
@@ -32,13 +32,13 @@ module Bitcoin
32
32
  end
33
33
 
34
34
  def self.parse_from_payload(payload)
35
- version, services, timestamp, remote_addr, local_addr, nonce, rest = payload.unpack('VQQa26a26Qa*')
35
+ version, services, timestamp, local_addr, remote_addr, nonce, rest = payload.unpack('VQQa26a26Qa*')
36
36
  v = new
37
37
  v.version = version
38
38
  v.services = services
39
39
  v.timestamp = timestamp
40
- v.remote_addr = v.unpack_addr(remote_addr)
41
- v.local_addr = v.unpack_addr(local_addr)
40
+ v.local_addr = NetworkAddr.parse_from_payload(local_addr)
41
+ v.remote_addr = NetworkAddr.parse_from_payload(remote_addr)
42
42
  v.nonce = nonce
43
43
  user_agent, rest = unpack_var_string(rest)
44
44
  start_height, rest = rest.unpack('Va*')
@@ -51,8 +51,8 @@ module Bitcoin
51
51
  def to_payload
52
52
  [
53
53
  [version, services, timestamp].pack('VQQ'),
54
- pack_addr(local_addr),
55
- pack_addr(remote_addr),
54
+ local_addr.to_payload(true),
55
+ remote_addr.to_payload(true),
56
56
  [nonce].pack('Q'),
57
57
  pack_var_string(user_agent),
58
58
  [start_height].pack('V'),
@@ -60,21 +60,6 @@ module Bitcoin
60
60
  ].join
61
61
  end
62
62
 
63
- def pack_addr(addr)
64
- separator = addr.rindex(':')
65
- ip = addr[0...separator]
66
- port = addr[separator + 1..-1].to_i
67
- ip_addr = IPAddr.new(ip)
68
- ip_addr = ip_addr.ipv4_mapped if ip_addr.ipv4?
69
- [1].pack('Q') << ip_addr.hton << [port].pack('n')
70
- # [[1].pack('Q'), "\x00" * 10, "\xFF\xFF", sockaddr[4...8], sockaddr[2...4]].join
71
- end
72
-
73
- def unpack_addr(addr)
74
- host, port = addr.unpack('x8x12a4n')
75
- "#{host.unpack('C*').join('.')}:#{port}"
76
- end
77
-
78
63
  def unpack_relay_field(payload)
79
64
  ( version >= 70001 && payload ) ? unpack_boolean(payload) : [ true, nil ]
80
65
  end
@@ -50,7 +50,8 @@ module Bitcoin
50
50
  @bytes_recv = 0
51
51
  @relay = configuration.conf[:relay]
52
52
  current_height = @chain.latest_block.height
53
- @local_version = Bitcoin::Message::Version.new(remote_addr: addr, start_height: current_height, relay: @relay)
53
+ remote_addr = Bitcoin::Message::NetworkAddr.new(ip: host, port: port, time: nil)
54
+ @local_version = Bitcoin::Message::Version.new(remote_addr: remote_addr, start_height: current_height, relay: @relay)
54
55
  end
55
56
 
56
57
  def connect
@@ -164,12 +165,7 @@ module Bitcoin
164
165
  # @return [Bitcoin::Message::NetworkAddr]
165
166
  def to_network_addr
166
167
  v = remote_version
167
- addr = Bitcoin::Message::NetworkAddr.new
168
- addr.time = v.timestamp
169
- addr.services = v.services
170
- addr.ip = host
171
- addr.port = port
172
- addr
168
+ Bitcoin::Message::NetworkAddr.new(ip: host, port: port, services: v.services, time: v.timestamp)
173
169
  end
174
170
 
175
171
  # send +addr+ message to remote peer
@@ -6,11 +6,11 @@ module Bitcoin
6
6
  COINBASE_HASH = '0000000000000000000000000000000000000000000000000000000000000000'
7
7
  COINBASE_INDEX = 4294967295
8
8
 
9
- attr_reader :hash
9
+ attr_reader :tx_hash
10
10
  attr_reader :index
11
11
 
12
- def initialize(hash, index = -1)
13
- @hash = hash
12
+ def initialize(tx_hash, index = -1)
13
+ @tx_hash = tx_hash
14
14
  @index = index
15
15
  end
16
16
 
@@ -19,11 +19,11 @@ module Bitcoin
19
19
  end
20
20
 
21
21
  def coinbase?
22
- hash == COINBASE_HASH && index == COINBASE_INDEX
22
+ tx_hash == COINBASE_HASH && index == COINBASE_INDEX
23
23
  end
24
24
 
25
25
  def to_payload
26
- [hash.htb, index].pack('a32V')
26
+ [tx_hash.htb, index].pack('a32V')
27
27
  end
28
28
 
29
29
  def self.create_coinbase_outpoint
@@ -31,12 +31,12 @@ module Bitcoin
31
31
  end
32
32
 
33
33
  def valid?
34
- index >= 0 && (!coinbase? && hash != COINBASE_HASH)
34
+ index >= 0 && (!coinbase? && tx_hash != COINBASE_HASH)
35
35
  end
36
36
 
37
37
  # convert hash to txid
38
38
  def txid
39
- hash.rhex
39
+ tx_hash.rhex
40
40
  end
41
41
 
42
42
  end
@@ -12,13 +12,15 @@ module Bitcoin
12
12
 
13
13
  # constants for PSBT
14
14
  PSBT_MAGIC_BYTES = 0x70736274
15
- PSBT_GLOBAL_TYPES = {unsigned_tx: 0x00, xpub: 0x01}
15
+ PSBT_GLOBAL_TYPES = {unsigned_tx: 0x00, xpub: 0x01, ver: 0xfb}
16
16
  PSBT_IN_TYPES = {non_witness_utxo: 0x00, witness_utxo: 0x01, partial_sig: 0x02,
17
17
  sighash: 0x03, redeem_script: 0x04, witness_script: 0x05,
18
18
  bip32_derivation: 0x06, script_sig: 0x07, script_witness: 0x08}
19
19
  PSBT_OUT_TYPES = {redeem_script: 0x00, witness_script: 0x01, bip32_derivation: 0x02}
20
20
  PSBT_SEPARATOR = 0x00
21
21
 
22
+ SUPPORT_VERSION = 0
23
+
22
24
  module_function
23
25
 
24
26
  def self.serialize_to_vector(key_type, key: nil, value: nil)