sibit 0.33.0 → 0.34.1

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.
data/lib/sibit/btc.rb CHANGED
@@ -28,22 +28,18 @@ class Sibit::Btc
28
28
 
29
29
  # Current price of BTC in USD (float returned).
30
30
  def price(_currency = 'USD')
31
- raise Sibit::NotSupportedError, 'Btc.com API doesn\'t provide prices'
31
+ raise(Sibit::NotSupportedError, 'Btc.com API doesn\'t provide prices')
32
32
  end
33
33
 
34
34
  # Gets the balance of the address, in satoshi.
35
35
  def balance(address)
36
- uri = Iri.new('https://chain.api.btc.com/v3/address').append(address).append('unspent')
37
- json = Sibit::Json.new(http: @http, log: @log).get(uri)
36
+ json = Sibit::Json.new(http: @http, log: @log).get(Iri.new('https://chain.api.btc.com/v3/address').append(address).append('unspent'))
38
37
  if json['err_no'] == 1
39
38
  @log.debug("The balance of #{address} is zero (not found)")
40
39
  return 0
41
40
  end
42
41
  data = json['data']
43
- if data.nil?
44
- @log.debug("The balance of #{address} is probably zero (not found)")
45
- return 0
46
- end
42
+ raise(Sibit::Error, "The address #{address} not found") if data.nil?
47
43
  txns = data['list']
48
44
  if txns.nil?
49
45
  @log.debug("The balance of #{address} is probably zero (not found)")
@@ -56,11 +52,10 @@ class Sibit::Btc
56
52
 
57
53
  # Get hash of the block after this one, or NIL if it's the last one in Blockchain.
58
54
  def next_of(hash)
59
- head = Sibit::Json.new(http: @http, log: @log).get(
55
+ data = Sibit::Json.new(http: @http, log: @log).get(
60
56
  Iri.new('https://chain.api.btc.com/v3/block').append(hash)
61
- )
62
- data = head['data']
63
- raise Sibit::Error, "The block #{hash} not found" if data.nil?
57
+ )['data']
58
+ raise(Sibit::Error, "The block #{hash} not found") if data.nil?
64
59
  nxt = data['next_block_hash']
65
60
  nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
66
61
  @log.debug("In BTC.com the block #{hash} is the latest, there is no next block") if nxt.nil?
@@ -70,28 +65,25 @@ class Sibit::Btc
70
65
 
71
66
  # The height of the block.
72
67
  def height(hash)
73
- json = Sibit::Json.new(http: @http, log: @log).get(
68
+ data = Sibit::Json.new(http: @http, log: @log).get(
74
69
  Iri.new('https://chain.api.btc.com/v3/block').append(hash)
75
- )
76
- data = json['data']
77
- raise Sibit::Error, "The block #{hash} not found" if data.nil?
70
+ )['data']
71
+ raise(Sibit::Error, "The block #{hash} not found") if data.nil?
78
72
  h = data['height']
79
- raise Sibit::Error, "The block #{hash} found but the height is absent" if h.nil?
73
+ raise(Sibit::Error, "The block #{hash} found but the height is absent") if h.nil?
80
74
  @log.debug("The height of #{hash} is #{h}")
81
75
  h
82
76
  end
83
77
 
84
78
  # Get recommended fees, in satoshi per byte.
85
79
  def fees
86
- raise Sibit::NotSupportedError, 'Btc.com doesn\'t provide recommended fees'
80
+ raise(Sibit::NotSupportedError, 'Btc.com doesn\'t provide recommended fees')
87
81
  end
88
82
 
89
83
  # Gets the hash of the latest block.
90
84
  def latest
91
- uri = Iri.new('https://chain.api.btc.com/v3/block/latest')
92
- json = Sibit::Json.new(http: @http, log: @log).get(uri)
93
- data = json['data']
94
- raise Sibit::Error, 'The latest block not found' if data.nil?
85
+ data = Sibit::Json.new(http: @http, log: @log).get(Iri.new('https://chain.api.btc.com/v3/block/latest'))['data']
86
+ raise(Sibit::Error, 'The latest block not found') if data.nil?
95
87
  hash = data['hash']
96
88
  @log.debug("The hash of the latest block is #{hash}")
97
89
  hash
@@ -99,46 +91,44 @@ class Sibit::Btc
99
91
 
100
92
  # Fetch all unspent outputs per address.
101
93
  def utxos(sources)
102
- txns = []
94
+ results = []
103
95
  sources.each do |hash|
104
- json = Sibit::Json.new(http: @http, log: @log).get(
96
+ data = Sibit::Json.new(http: @http, log: @log).get(
105
97
  Iri.new('https://chain.api.btc.com/v3/address').append(hash).append('unspent')
106
- )
107
- data = json['data']
108
- raise Sibit::Error, "The address #{hash} not found" if data.nil?
109
- txns = data['list']
110
- next if txns.nil?
111
- txns.each do |u|
112
- outs = Sibit::Json.new(http: @http, log: @log).get(
98
+ )['data']
99
+ raise(Sibit::Error, "The address #{hash} not found") if data.nil?
100
+ list = data['list']
101
+ next if list.nil?
102
+ list.each do |u|
103
+ index = u['tx_output_n']
104
+ raise(Sibit::Error, "The unspent output of #{hash} has no tx_output_n") if index.nil?
105
+ out = Sibit::Json.new(http: @http, log: @log).get(
113
106
  Iri.new('https://chain.api.btc.com/v3/tx').append(u['tx_hash']).add(verbose: 3)
114
- )['data']['outputs']
115
- outs.each_with_index do |o, i|
116
- next unless o['addresses'].include?(hash)
117
- txns << {
118
- value: o['value'],
119
- hash: u['tx_hash'],
120
- index: i,
121
- confirmations: u['confirmations'],
122
- script: [o['script_hex']].pack('H*')
123
- }
124
- end
107
+ )['data']['outputs'][index]
108
+ raise(Sibit::Error, "The output #{index} of #{u['tx_hash']} is absent") if out.nil?
109
+ results << {
110
+ value: u['value'],
111
+ hash: u['tx_hash'],
112
+ index: index,
113
+ confirmations: u['confirmations'],
114
+ script: [out['script_hex']].pack('H*')
115
+ }
125
116
  end
126
117
  end
127
- txns
118
+ results
128
119
  end
129
120
 
130
121
  # Push this transaction (in hex format) to the network.
131
122
  def push(_hex)
132
- raise Sibit::NotSupportedError, 'Btc.com doesn\'t provide payment gateway'
123
+ raise(Sibit::NotSupportedError, 'Btc.com doesn\'t provide payment gateway')
133
124
  end
134
125
 
135
126
  # This method should fetch a Blockchain block and return as a hash.
136
127
  def block(hash)
137
- head = Sibit::Json.new(http: @http, log: @log).get(
128
+ data = Sibit::Json.new(http: @http, log: @log).get(
138
129
  Iri.new('https://chain.api.btc.com/v3/block').append(hash)
139
- )
140
- data = head['data']
141
- raise Sibit::Error, "The block #{hash} not found" if data.nil?
130
+ )['data']
131
+ raise(Sibit::Error, "The block #{hash} not found") if data.nil?
142
132
  nxt = data['next_block_hash']
143
133
  nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
144
134
  {
@@ -162,20 +152,21 @@ class Sibit::Btc
162
152
  Iri.new('https://chain.api.btc.com/v3/block')
163
153
  .append(hash).append('tx').add(page: page, pagesize: psize)
164
154
  )['data']
165
- raise Sibit::Error, "The block #{hash} has no data at page #{page}" if data.nil?
155
+ raise(Sibit::Error, "The block #{hash} has no data at page #{page}") if data.nil?
166
156
  list = data['list']
167
- raise Sibit::Error, "The list is empty for block #{hash} at page #{page}" if list.nil?
168
- txns = list.map do |t|
169
- {
170
- hash: t['hash'],
171
- outputs: t['outputs'].reject { |o| o['spent_by_tx'] }.map do |o|
172
- {
173
- address: o['addresses'][0],
174
- value: o['value']
175
- }
176
- end
177
- }
178
- end
157
+ raise(Sibit::Error, "The list is empty for block #{hash} at page #{page}") if list.nil?
158
+ txns =
159
+ list.map do |t|
160
+ {
161
+ hash: t['hash'],
162
+ outputs: t['outputs'].reject { |o| o['spent_by_tx'] }.map do |o|
163
+ {
164
+ address: o['addresses'][0],
165
+ value: o['value']
166
+ }
167
+ end
168
+ }
169
+ end
179
170
  all += txns
180
171
  page += 1
181
172
  break if txns.length < psize
data/lib/sibit/cex.rb CHANGED
@@ -25,51 +25,52 @@ class Sibit::Cex
25
25
 
26
26
  # Current price of BTC in USD (float returned).
27
27
  def price(currency = 'USD')
28
- json = Sibit::Json.new(http: @http, log: @log).get(
29
- Iri.new('https://cex.io/api/last_price/BTC').append(currency)
28
+ p = Float(
29
+ Sibit::Json.new(http: @http, log: @log).get(
30
+ Iri.new('https://cex.io/api/last_price/BTC').append(currency)
31
+ )['lprice']
30
32
  )
31
- p = json['lprice'].to_f
32
33
  @log.debug("The price of BTC is #{p} #{currency}")
33
34
  p
34
35
  end
35
36
 
36
37
  # Get hash of the block after this one.
37
38
  def next_of(_hash)
38
- raise Sibit::NotSupportedError, 'Cex.io API doesn\'t provide next_of()'
39
+ raise(Sibit::NotSupportedError, 'Cex.io API doesn\'t provide next_of()')
39
40
  end
40
41
 
41
42
  # Gets the balance of the address, in satoshi.
42
43
  def balance(_address)
43
- raise Sibit::NotSupportedError, 'Cex.io doesn\'t implement balance()'
44
+ raise(Sibit::NotSupportedError, 'Cex.io doesn\'t implement balance()')
44
45
  end
45
46
 
46
47
  # The height of the block.
47
48
  def height(_hash)
48
- raise Sibit::NotSupportedError, 'Cex.io doesn\'t implement height()'
49
+ raise(Sibit::NotSupportedError, 'Cex.io doesn\'t implement height()')
49
50
  end
50
51
 
51
52
  # Get recommended fees, in satoshi per byte.
52
53
  def fees
53
- raise Sibit::NotSupportedError, 'Cex.io doesn\'t implement fees()'
54
+ raise(Sibit::NotSupportedError, 'Cex.io doesn\'t implement fees()')
54
55
  end
55
56
 
56
57
  # Gets the hash of the latest block.
57
58
  def latest
58
- raise Sibit::NotSupportedError, 'Cex.io doesn\'t implement latest()'
59
+ raise(Sibit::NotSupportedError, 'Cex.io doesn\'t implement latest()')
59
60
  end
60
61
 
61
62
  # Fetch all unspent outputs per address.
62
63
  def utxos(_sources)
63
- raise Sibit::NotSupportedError, 'Cex.io doesn\'t implement utxos()'
64
+ raise(Sibit::NotSupportedError, 'Cex.io doesn\'t implement utxos()')
64
65
  end
65
66
 
66
67
  # Push this transaction (in hex format) to the network.
67
68
  def push(_hex)
68
- raise Sibit::NotSupportedError, 'Cex.io doesn\'t implement push()'
69
+ raise(Sibit::NotSupportedError, 'Cex.io doesn\'t implement push()')
69
70
  end
70
71
 
71
72
  # This method should fetch a Blockchain block and return as a hash.
72
73
  def block(_hash)
73
- raise Sibit::NotSupportedError, 'Cex.io doesn\'t implement block()'
74
+ raise(Sibit::NotSupportedError, 'Cex.io doesn\'t implement block()')
74
75
  end
75
76
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require 'bigdecimal'
7
+ require_relative 'error'
8
+
9
+ # Sibit main class.
10
+ class Sibit
11
+ # An amount of bitcoins, convertible to an exact number of satoshi.
12
+ #
13
+ # It parses the amount through +BigDecimal+ instead of a binary +Float+,
14
+ # so decimal values like 0.29 map to exactly 29,000,000 satoshi. An amount
15
+ # with sub-satoshi precision is rejected instead of being truncated.
16
+ #
17
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
18
+ # Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
19
+ # License:: MIT
20
+ class Coins
21
+ def initialize(btc)
22
+ @btc = btc
23
+ end
24
+
25
+ def satoshi
26
+ sat = BigDecimal(@btc.to_s) * 100_000_000
27
+ unless sat.frac.zero?
28
+ raise(Sibit::Error, "The amount #{@btc.inspect} is finer than one satoshi")
29
+ end
30
+ Integer(sat)
31
+ rescue ArgumentError, TypeError
32
+ raise(Sibit::Error, "The amount #{@btc.inspect} is not a valid number of bitcoins")
33
+ end
34
+ end
35
+ end
@@ -7,6 +7,7 @@ require 'iri'
7
7
  require 'json'
8
8
  require 'loog'
9
9
  require 'uri'
10
+ require_relative 'coins'
10
11
  require_relative 'error'
11
12
  require_relative 'http'
12
13
  require_relative 'json'
@@ -27,15 +28,18 @@ class Sibit::Cryptoapis
27
28
 
28
29
  # Current price of BTC in USD (float returned).
29
30
  def price(_currency = 'USD')
30
- raise Sibit::NotSupportedError, 'Cryptoapis doesn\'t provide BTC price'
31
+ raise(Sibit::NotSupportedError, 'Cryptoapis doesn\'t provide BTC price')
31
32
  end
32
33
 
33
34
  # Get hash of the block after this one.
34
35
  def next_of(hash)
35
- nxt = Sibit::Json.new(http: @http, log: @log).get(
36
+ head = Sibit::Json.new(http: @http, log: @log).get(
36
37
  Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/blocks').append(hash),
37
38
  headers: headers
38
- )['payload']['hash']
39
+ )['payload']
40
+ raise(Sibit::Error, "The block #{hash} not found") if head.nil?
41
+ nxt = head['nextblockhash']
42
+ nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
39
43
  @log.debug("The block #{hash} is the latest, there is no next block") if nxt.nil?
40
44
  @log.debug("The next block of #{hash} is #{nxt}") unless nxt.nil?
41
45
  nxt
@@ -43,29 +47,29 @@ class Sibit::Cryptoapis
43
47
 
44
48
  # The height of the block.
45
49
  def height(hash)
46
- json = Sibit::Json.new(http: @http, log: @log).get(
50
+ h = Sibit::Json.new(http: @http, log: @log).get(
47
51
  Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/blocks').append(hash),
48
52
  headers: headers
49
- )['payload']
50
- h = json['height']
53
+ )['payload']['height']
51
54
  @log.debug("The height of #{hash} is #{h}")
52
55
  h
53
56
  end
54
57
 
55
58
  # Gets the balance of the address, in satoshi.
56
59
  def balance(address)
57
- json = Sibit::Json.new(http: @http, log: @log).get(
58
- Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/address').append(address),
59
- headers: headers
60
- )['payload']
61
- b = (json['balance'].to_f * 100_000_000).to_i
60
+ b = Sibit::Coins.new(
61
+ Sibit::Json.new(http: @http, log: @log).get(
62
+ Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/address').append(address),
63
+ headers: headers
64
+ )['payload']['balance']
65
+ ).satoshi
62
66
  @log.debug("The balance of #{address} is #{b} satoshi")
63
67
  b
64
68
  end
65
69
 
66
70
  # Get recommended fees, in satoshi per byte.
67
71
  def fees
68
- raise Sibit::NotSupportedError, 'Cryptoapis doesn\'t provide recommended fees'
72
+ raise(Sibit::NotSupportedError, 'Cryptoapis doesn\'t provide recommended fees')
69
73
  end
70
74
 
71
75
  # Gets the hash of the latest block.
@@ -80,15 +84,15 @@ class Sibit::Cryptoapis
80
84
 
81
85
  # Fetch all unspent outputs per address.
82
86
  def utxos(_sources)
83
- raise Sibit::NotSupportedError, 'Not implemented yet'
87
+ raise(Sibit::NotSupportedError, 'Not implemented yet')
84
88
  end
85
89
 
86
90
  # Push this transaction (in hex format) to the network.
87
91
  def push(hex)
88
92
  Sibit::Json.new(http: @http, log: @log).post(
89
- Iri.new('https://api.cryptoapis.io/v1/bc/btc/testnet/txs/send'),
93
+ Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/txs/send'),
90
94
  JSON.pretty_generate(hex: hex),
91
- headers: headers
95
+ headers: headers.merge('Content-Type' => 'application/json')
92
96
  )
93
97
  end
94
98
 
@@ -122,21 +126,22 @@ class Sibit::Cryptoapis
122
126
  limit = 200
123
127
  all = []
124
128
  loop do
125
- txns = Sibit::Json.new(http: @http, log: @log).get(
126
- Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/txs/block/')
127
- .append(hash).add(index: index, limit: limit),
128
- headers: headers
129
- )['payload'].map do |t|
130
- {
131
- hash: t['hash'],
132
- outputs: t['txouts'].map do |o|
133
- {
134
- address: o['addresses'][0],
135
- value: o['amount'].to_f * 100_000_000
136
- }
137
- end
138
- }
139
- end
129
+ txns =
130
+ Sibit::Json.new(http: @http, log: @log).get(
131
+ Iri.new('https://api.cryptoapis.io/v1/bc/btc/mainnet/txs/block/')
132
+ .append(hash).add(index: index, limit: limit),
133
+ headers: headers
134
+ )['payload'].map do |t|
135
+ {
136
+ hash: t['hash'],
137
+ outputs: t['txouts'].map do |o|
138
+ {
139
+ address: o['addresses'][0],
140
+ value: Sibit::Coins.new(o['amount'] || 0).satoshi
141
+ }
142
+ end
143
+ }
144
+ end
140
145
  all += txns
141
146
  index += txns.length
142
147
  break if txns.length < limit
data/lib/sibit/firstof.rb CHANGED
@@ -83,18 +83,18 @@ class Sibit::FirstOf
83
83
  private
84
84
 
85
85
  def first_of(method)
86
- return yield @list unless @list.is_a?(Array)
86
+ return yield(@list) unless @list.is_a?(Array)
87
87
  errors = []
88
88
  done = false
89
89
  result = nil
90
90
  @list.each do |api|
91
91
  @log.debug("Calling #{api.class.name}##{method}()...")
92
92
  begin
93
- result = yield api
93
+ result = yield(api)
94
94
  done = true
95
95
  break
96
96
  rescue Sibit::NotSupportedError
97
- # Just ignore it
97
+ nil
98
98
  rescue Sibit::Error => e
99
99
  errors << e
100
100
  @log.debug("The API #{api.class.name} failed at #{method}(): #{e.message}") if @verbose
@@ -102,8 +102,11 @@ class Sibit::FirstOf
102
102
  end
103
103
  unless done
104
104
  errors.each { |e| @log.debug(Backtrace.new(e).to_s) }
105
- raise Sibit::Error, "No APIs out of #{@list.length} managed to succeed at #{method}(): \
106
- #{@list.map { |a| a.class.name }.join(', ')}"
105
+ raise(
106
+ Sibit::Error,
107
+ "No APIs out of #{@list.length} managed to succeed at #{method}(): " \
108
+ "#{@list.map { |a| a.class.name }.join(', ')}"
109
+ )
107
110
  end
108
111
  result
109
112
  end
@@ -22,7 +22,7 @@ class Sibit
22
22
  attr_reader :host
23
23
 
24
24
  def client(uri)
25
- http = Net::HTTP.new(uri.host, uri.port, @host, @port.to_i, @user, @password)
25
+ http = Net::HTTP.new(uri.host, uri.port, @host, Integer(@port, 10), @user, @password)
26
26
  http.use_ssl = true
27
27
  http.read_timeout = 240
28
28
  http
data/lib/sibit/json.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require 'cgi'
7
6
  require 'elapsed'
8
7
  require 'json'
9
8
  require 'loog'
@@ -43,16 +42,16 @@ class Sibit::Json
43
42
  'Accept-Encoding' => ''
44
43
  }.merge(headers)
45
44
  )
46
- unless accept.include?(res.code.to_i)
47
- raise Sibit::Error, "Failed to retrieve #{uri} (#{res.code}): #{res.body}"
45
+ unless accept.include?(Integer(res.code, 10))
46
+ raise(Sibit::Error, "Failed to retrieve #{uri} (#{res.code}): #{res.body}")
48
47
  end
49
48
  ret =
50
49
  begin
51
50
  JSON.parse(res.body)
52
51
  rescue JSON::ParserError => e
53
- raise Sibit::Error, "Can't parse JSON: #{e.message}"
52
+ raise(Sibit::Error, "Can't parse JSON: #{e.message}")
54
53
  end
55
- throw :"GET #{uri}: #{res.code}/#{length(res.body.length)}"
54
+ throw(:"GET #{uri}: #{res.code}/#{length(res.body.length)}")
56
55
  end
57
56
  ret
58
57
  end
@@ -61,8 +60,8 @@ class Sibit::Json
61
60
  uri = URI(address.to_s)
62
61
  elapsed(@log) do
63
62
  res = @http.client(uri).post(
64
- "#{uri.path}?#{uri.query}",
65
- "tx=#{CGI.escape(body)}",
63
+ "#{uri.path.empty? ? '/' : uri.path}#{"?#{uri.query}" if uri.query}",
64
+ body,
66
65
  {
67
66
  'Accept' => 'text/plain',
68
67
  'User-Agent' => user_agent,
@@ -72,9 +71,9 @@ class Sibit::Json
72
71
  }.merge(headers)
73
72
  )
74
73
  unless res.code == '200'
75
- raise Sibit::Error, "Failed to post tx to #{uri}: #{res.code}\n#{res.body}"
74
+ raise(Sibit::Error, "Failed to post tx to #{uri}: #{res.code}\n#{res.body}")
76
75
  end
77
- throw :"POST #{uri}: #{res.code}"
76
+ throw(:"POST #{uri}: #{res.code}")
78
77
  end
79
78
  end
80
79
 
data/lib/sibit/key.rb CHANGED
@@ -32,13 +32,14 @@ class Sibit
32
32
  def self.generate(network: :mainnet)
33
33
  key = OpenSSL::PKey::EC.generate('secp256k1')
34
34
  pvt = key.private_key
35
- raise 'Invalid private key: zero' if pvt.zero?
36
- raise 'Invalid private key: out of range' if pvt >= SECP256K1_N
37
- raise 'Invalid public key: not on curve' unless key.public_key.on_curve?
35
+ raise(Sibit::Error, 'Invalid private key: zero') if pvt.zero?
36
+ raise(Sibit::Error, 'Invalid private key: out of range') if pvt >= SECP256K1_N
37
+ raise(Sibit::Error, 'Invalid public key: not on curve') unless key.public_key.on_curve?
38
38
  hex = key.private_key.to_s(16).rjust(64, '0').downcase
39
- raise 'Invalid private key encoding' unless hex.match?(/\A[0-9a-f]{64}\z/)
40
- trip = OpenSSL::BN.new(hex, 16)
41
- raise 'Private key serialization is lossy' unless trip == pvt
39
+ raise(Sibit::Error, 'Invalid private key encoding') unless hex.match?(/\A[0-9a-f]{64}\z/)
40
+ unless OpenSSL::BN.new(hex, 16) == pvt
41
+ raise(Sibit::Error, 'Private key serialization is lossy')
42
+ end
42
43
  new(hex, network: network)
43
44
  end
44
45
 
@@ -55,42 +56,38 @@ class Sibit
55
56
  end
56
57
 
57
58
  def pub
58
- point = @key.public_key
59
- point.to_octet_string(@compressed ? :compressed : :uncompressed).unpack1('H*')
59
+ @key.public_key.to_octet_string(@compressed ? :compressed : :uncompressed).unpack1('H*')
60
60
  end
61
61
 
62
62
  def bech32
63
63
  hrp = { mainnet: 'bc', testnet: 'tb', regtest: 'bcrt' }[@network]
64
64
  hex = pub
65
- raise Error, 'Invalid public key: not on curve' unless @key.public_key.on_curve?
66
- raise Error, 'Invalid public key format' unless hex.match?(/\A0[23][0-9a-f]{64}\z/)
65
+ raise(Error, 'Invalid public key: not on curve') unless @key.public_key.on_curve?
66
+ raise(Error, 'Invalid public key format') unless hex.match?(/\A0[23][0-9a-f]{64}\z/)
67
67
  addr = Bech32.encode(hrp, 0, hash160(hex))
68
- expected = /\A#{hrp}1q[a-z0-9]{38,58}\z/
69
- raise Error, "Invalid bech32 address: #{addr}" unless addr.match?(expected)
68
+ unless addr.match?(/\A#{hrp}1q[a-z0-9]{38,58}\z/)
69
+ raise(Error, "Invalid bech32 address: #{addr}")
70
+ end
70
71
  addr
71
72
  end
72
73
 
73
74
  def base58
74
75
  hex = pub
75
- raise Error, 'Invalid public key: not on curve' unless @key.public_key.on_curve?
76
- raise Error, 'Invalid public key format' unless hex.match?(/\A0[23][0-9a-f]{64}\z/)
77
- hash = hash160(hex)
78
- prefix = @network == :mainnet ? '00' : '6f'
79
- versioned = "#{prefix}#{hash}"
80
- checksum = Base58.new(versioned).check
81
- addr = Base58.new(versioned + checksum).encode
76
+ raise(Error, 'Invalid public key: not on curve') unless @key.public_key.on_curve?
77
+ raise(Error, 'Invalid public key format') unless hex.match?(/\A0[23][0-9a-f]{64}\z/)
78
+ versioned = "#{@network == :mainnet ? '00' : '6f'}#{hash160(hex)}"
79
+ addr = Base58.new(versioned + Base58.new(versioned).check).encode
82
80
  mainnet = /\A1[1-9A-HJ-NP-Za-km-z]{25,34}\z/
83
81
  testnet = /\A[mn][1-9A-HJ-NP-Za-km-z]{25,34}\z/
84
82
  unless addr.match?(@network == :mainnet ? mainnet : testnet)
85
- raise Error,
86
- "Invalid base58 address: #{addr}"
83
+ raise(Error, "Invalid base58 address: #{addr}")
87
84
  end
88
85
  addr
89
86
  end
90
87
 
91
88
  def sign(data)
92
89
  sig = @key.dsa_sign_asn1(data)
93
- raise Error, 'Signature verification failed' unless verify(data, sig)
90
+ raise(Error, 'Signature verification failed') unless verify(data, sig)
94
91
  sig
95
92
  end
96
93
 
@@ -103,25 +100,29 @@ class Sibit
103
100
  private
104
101
 
105
102
  def build(privkey)
106
- value = privkey.to_i(16)
107
- raise Error, 'Private key is not on curve' unless value.between?(MIN_PRIV, MAX_PRIV)
108
- group = OpenSSL::PKey::EC::Group.new('secp256k1')
103
+ raise(Error, 'Private key is not on curve') unless privkey.to_i(16).between?(
104
+ MIN_PRIV,
105
+ MAX_PRIV
106
+ )
109
107
  bn = OpenSSL::BN.new(privkey, 16)
110
- pubkey = group.generator.mul(bn)
111
- asn1 = OpenSSL::ASN1::Sequence(
112
- [
113
- OpenSSL::ASN1::Integer.new(1),
114
- OpenSSL::ASN1::OctetString(bn.to_s(2)),
115
- OpenSSL::ASN1::ObjectId('secp256k1', 0, :EXPLICIT),
116
- OpenSSL::ASN1::BitString(pubkey.to_octet_string(:uncompressed), 1, :EXPLICIT)
117
- ]
108
+ OpenSSL::PKey::EC.new(
109
+ OpenSSL::ASN1::Sequence(
110
+ [
111
+ OpenSSL::ASN1::Integer.new(1),
112
+ OpenSSL::ASN1::OctetString(bn.to_s(2)),
113
+ OpenSSL::ASN1::ObjectId('secp256k1', 0, :EXPLICIT),
114
+ OpenSSL::ASN1::BitString(
115
+ OpenSSL::PKey::EC::Group.new('secp256k1').generator.mul(bn)
116
+ .to_octet_string(:uncompressed),
117
+ 1, :EXPLICIT
118
+ )
119
+ ]
120
+ ).to_der
118
121
  )
119
- OpenSSL::PKey::EC.new(asn1.to_der)
120
122
  end
121
123
 
122
124
  def hash160(hex)
123
- bytes = [hex].pack('H*')
124
- Digest::RMD160.hexdigest(Digest::SHA256.digest(bytes))
125
+ Digest::RMD160.hexdigest(Digest::SHA256.digest([hex].pack('H*')))
125
126
  end
126
127
 
127
128
  def decode(key)
@@ -130,12 +131,9 @@ class Sibit
130
131
  return key.downcase
131
132
  end
132
133
  raw = Base58.new(key).decode
133
- payload = raw[0..-9]
134
- checksum = raw[-8..]
135
- expected = Base58.new(payload).check
136
- raise Error, 'Invalid WIF checksum' unless checksum == expected
134
+ raise(Error, 'Invalid WIF checksum') unless raw[-8..] == Base58.new(raw[0..-9]).check
137
135
  version = raw[0, 2]
138
- raise Error, "Invalid WIF version: #{version}" unless %w[80 ef].include?(version)
136
+ raise(Error, "Invalid WIF version: #{version}") unless %w[80 ef].include?(version)
139
137
  detected = version == '80' ? :mainnet : :testnet
140
138
  @network = @override || detected
141
139
  body = raw[2..-9]
data/lib/sibit/script.rb CHANGED
@@ -59,17 +59,14 @@ class Sibit
59
59
  def p2pkh_address(network)
60
60
  h = hash160
61
61
  return nil unless h
62
- prefix = network == :mainnet ? '00' : '6f'
63
- versioned = "#{prefix}#{h}"
64
- checksum = Base58.new(versioned).check
65
- Base58.new(versioned + checksum).encode
62
+ versioned = "#{network == :mainnet ? '00' : '6f'}#{h}"
63
+ Base58.new(versioned + Base58.new(versioned).check).encode
66
64
  end
67
65
 
68
66
  def p2wpkh_address(network)
69
67
  h = hash160
70
68
  return nil unless h
71
- hrp = { mainnet: 'bc', testnet: 'tb', regtest: 'bcrt' }[network]
72
- Bech32.encode(hrp, 0, h)
69
+ Bech32.encode({ mainnet: 'bc', testnet: 'tb', regtest: 'bcrt' }[network], 0, h)
73
70
  end
74
71
  end
75
72
  end