bitcoinrb 0.2.9 → 0.3.0

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