sibit 0.28.0 → 0.29.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7846df06f5fbb2cae2ee00122f75f640901fc2d2d5321d810ae0848ee56c01e2
4
- data.tar.gz: 1ff75f919747d146c038c8cdfcc2cefbbd5dbf45136a5102691740ab7224dbd2
3
+ metadata.gz: 16d749128c92e0a715c1545d8cb6aa6a41a4afcbd7fc39db0e708fa87ea7383d
4
+ data.tar.gz: c522bc105501aedd9f3ef6140ac99bb1b18cec23fc060ff8dd6ceed4d4e69dff
5
5
  SHA512:
6
- metadata.gz: 892d1c222af88708788f791a8508c7622f3e148190c7aa2cf4a8c6fbb3a47fdd2dd78ebd0190512fb11c0a512ac4d474153c2467bf54a4cb4f7afa260d732b1f
7
- data.tar.gz: b9d78f193a4e81d5c9659528927c6526f6d2413f65303bd016b6fdd220c0afac25c0555f05ddeaf34ab5e0d9fd257ba8d0f5f70b31667eeb1ebc549cab232bc6
6
+ metadata.gz: 854ede7b60a6cd9e1334341b14f1b712e002b23499a5a192d61b53380b68d3203f4d7c3d0aaa87499d59a885190345e776410e1eb133f26f85b8aaac260772ed
7
+ data.tar.gz: f99cee0f4d8615d0a39ee7609c697b4238e0e867193934564d5f2d29cfffe454b630b8da0f4e7f1bda9620d57d395bdf4b365614cf318c6079b808dab330cc2f
data/Gemfile CHANGED
@@ -14,6 +14,8 @@ gem 'logger', '~>1.7', require: false
14
14
  gem 'minitest', '~>6.0', require: false
15
15
  gem 'minitest-reporters', '~>1.7', require: false
16
16
  gem 'nokogiri', '~>1.18', require: false
17
+ gem 'os', '~>1.1', require: false
18
+ gem 'qbash', '~>0.0', require: false
17
19
  gem 'rake', '~>13.2', require: false
18
20
  gem 'rdoc', '~>7.0', require: false
19
21
  gem 'rubocop', '~>1.62', require: false
data/Gemfile.lock CHANGED
@@ -59,6 +59,9 @@ GEM
59
59
  date (3.5.1)
60
60
  diff-lcs (1.6.2)
61
61
  docile (1.4.1)
62
+ elapsed (0.2.1)
63
+ loog (~> 0.6)
64
+ tago (~> 0.1)
62
65
  erb (6.0.1)
63
66
  ffi (1.17.2-arm64-darwin)
64
67
  ffi (1.17.2-x86_64-linux-gnu)
@@ -72,6 +75,7 @@ GEM
72
75
  logger (~> 1.0)
73
76
  memoist3 (1.0.0)
74
77
  mini_mime (1.1.5)
78
+ mini_portile2 (2.8.9)
75
79
  minitest (6.0.1)
76
80
  prism (~> 1.5)
77
81
  minitest-reporters (1.7.1)
@@ -80,11 +84,11 @@ GEM
80
84
  minitest (>= 5.0)
81
85
  ruby-progressbar
82
86
  multi_test (1.1.0)
83
- nokogiri (1.18.10-arm64-darwin)
84
- racc (~> 1.4)
85
- nokogiri (1.18.10-x86_64-linux-gnu)
87
+ nokogiri (1.18.10)
88
+ mini_portile2 (~> 2.8.2)
86
89
  racc (~> 1.4)
87
90
  openssl (4.0.0)
91
+ os (1.1.4)
88
92
  parallel (1.27.0)
89
93
  parser (3.3.10.0)
90
94
  ast (~> 2.4.1)
@@ -94,6 +98,11 @@ GEM
94
98
  date
95
99
  stringio
96
100
  public_suffix (7.0.0)
101
+ qbash (0.4.8)
102
+ backtrace (> 0)
103
+ elapsed (> 0)
104
+ loog (> 0)
105
+ tago (> 0)
97
106
  racc (1.8.1)
98
107
  rainbow (3.1.1)
99
108
  rake (13.3.1)
@@ -150,6 +159,7 @@ GEM
150
159
  sys-uname (1.4.1)
151
160
  ffi (~> 1.1)
152
161
  memoist3 (~> 1.0.0)
162
+ tago (0.6.0)
153
163
  thor (1.4.0)
154
164
  tsort (0.2.0)
155
165
  unicode-display_width (3.2.0)
@@ -173,6 +183,8 @@ DEPENDENCIES
173
183
  minitest (~> 6.0)
174
184
  minitest-reporters (~> 1.7)
175
185
  nokogiri (~> 1.18)
186
+ os (~> 1.1)
187
+ qbash (~> 0.0)
176
188
  rake (~> 13.2)
177
189
  rdoc (~> 7.0)
178
190
  rubocop (~> 1.62)
data/README.md CHANGED
@@ -7,15 +7,13 @@
7
7
  [![rake](https://github.com/yegor256/sibit/actions/workflows/rake.yml/badge.svg)](https://github.com/yegor256/sibit/actions/workflows/rake.yml)
8
8
  [![PDD status](https://www.0pdd.com/svg?name=yegor256/sibit)](https://www.0pdd.com/p?name=yegor256/sibit)
9
9
  [![Gem Version](https://badge.fury.io/rb/sibit.svg)](https://badge.fury.io/rb/sibit)
10
- [![Maintainability](https://api.codeclimate.com/v1/badges/74c909f06d4afa0d8001/maintainability)](https://codeclimate.com/github/yegor256/sibit/maintainability)
11
10
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/yegor256/takes/sibit/master/LICENSE.txt)
12
11
  [![Test Coverage](https://img.shields.io/codecov/c/github/yegor256/sibit.svg)](https://codecov.io/github/yegor256/sibit?branch=master)
13
12
  [![Hits-of-Code](https://hitsofcode.com/github/yegor256/sibit)](https://hitsofcode.com/view/github/yegor256/sibit)
14
13
 
15
- To understand how the Bitcoin protocol works,
16
- I recommend you watching this [short video] and
17
- then reading this blog post of mine:
18
- [_Sibit Demonstrates How Bitcoin Works_][blog].
14
+ To understand how the Bitcoin protocol works, I recommend you watching
15
+ this [short video] and then reading this blog post of mine:
16
+ [_Sibit Demonstrates How Bitcoin Works_][blog].
19
17
 
20
18
  This is a simple Bitcoin client for use from the command line
21
19
  or from your Ruby app.
@@ -26,10 +24,10 @@ If you need something more complex, I would recommend using
26
24
  [bitcoin-ruby] for Ruby and [Electrum] as a GUI client.
27
25
 
28
26
  You may want to discuss this tool at [Bitcointalk]
29
- and give the thread a few merits.
27
+ and give the thread a few merits.
30
28
 
31
29
  This is a Ruby gem, install it first (if it doesn't work, there are
32
- some hints at the bottom of this page):
30
+ some hints at the bottom of this page):
33
31
 
34
32
  ```bash
35
33
  gem install sibit
@@ -82,18 +80,16 @@ Say, you are sending 0.5 BTC and the fee is 0.0001 BTC.
82
80
  Totally, you spend 0.5001.
83
81
  However, you can make Sibit deduct the fee from the payment amount.
84
82
  In this case you should provide a negative amount
85
- of the fee or one of `-S`, `-M`, `-L`, `-XL`.
86
- You can also say `+S`, if you want the opposite, which is the default.
83
+ of the fee or one of `S-`, `M-`, `L-`, `XL-`.
84
+ You can also say `S+`, if you want the opposite, which is the default.
87
85
 
88
86
  It is recommended to run it with `--dry --verbose` options first,
89
87
  to see what's going to be sent to the network.
90
88
  If everything looks correct, remove the `--dry` and run again,
91
89
  the transaction is pushed to the network.
92
90
 
93
- All operations are performed through the
94
- [Blockchain API].
95
- Transactions are pushed to the Bitcoin network via
96
- [this relay].
91
+ All operations are performed through the [Blockchain API].
92
+ Transactions are pushed to the Bitcoin network via [this relay].
97
93
 
98
94
  ## Ruby SDK
99
95
 
@@ -115,8 +111,8 @@ It should work.
115
111
  ## APIs
116
112
 
117
113
  The library works through one (or a few) public APIs for fetching
118
- Bitcoin data and pushing transactions to the network. At the moment we
119
- work with the following APIs:
114
+ Bitcoin data and pushing transactions to the network.
115
+ At the moment we work with the following APIs:
120
116
 
121
117
  * [Blockchain.com] - `Sibit::Blockchain`
122
118
  * [BTC.com] - `Sibit::Btc`
@@ -125,8 +121,9 @@ work with the following APIs:
125
121
  * [Blockchair.com] - `Sibit::Blockchair`
126
122
  * [Cex.io] - `Sibit::Cex`
127
123
 
128
- The first one in this list is used by default. If you want to use a different
129
- one, you just specify it in the constructor of `Sibit` object:
124
+ The first one in this list is used by default.
125
+ If you want to use a different one,
126
+ you just specify it in the constructor of `Sibit` object:
130
127
 
131
128
  ```ruby
132
129
  require 'sibit'
data/Rakefile CHANGED
@@ -3,10 +3,13 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require 'rubygems'
6
+ require 'os'
7
+ require 'qbash'
7
8
  require 'rake'
8
- require 'rdoc'
9
9
  require 'rake/clean'
10
+ require 'rdoc'
11
+ require 'rubygems'
12
+ require 'shellwords'
10
13
 
11
14
  def name
12
15
  @name ||= File.basename(Dir['*.gemspec'].first, '.*')
@@ -16,7 +19,7 @@ def version
16
19
  Gem::Specification.load(Dir['*.gemspec'].first).version
17
20
  end
18
21
 
19
- task default: %i[clean test features rubocop]
22
+ task default: %i[clean test picks features rubocop]
20
23
 
21
24
  require 'rake/testtask'
22
25
  Rake::TestTask.new(:test) do |test|
@@ -27,6 +30,16 @@ Rake::TestTask.new(:test) do |test|
27
30
  test.verbose = false
28
31
  end
29
32
 
33
+ desc 'Run them via Ruby, one by one'
34
+ task :picks do
35
+ next if OS.windows?
36
+ %w[test lib].each do |d|
37
+ Dir["#{d}/**/*.rb"].each do |f|
38
+ qbash("bundle exec ruby #{Shellwords.escape(f)}", log: $stdout, env: { 'PICKS' => 'yes' })
39
+ end
40
+ end
41
+ end
42
+
30
43
  require 'rdoc/task'
31
44
  Rake::RDocTask.new do |rdoc|
32
45
  rdoc.rdoc_dir = 'rdoc'
data/bin/sibit CHANGED
@@ -6,15 +6,12 @@
6
6
 
7
7
  $stdout.sync = true
8
8
 
9
- # see https://stackoverflow.com/a/6048451/187141
10
- require 'openssl'
11
- OpenSSL::SSL::VERIFY_PEER ||= OpenSSL::SSL::VERIFY_NONE
12
-
13
9
  require 'backtrace'
14
10
  require 'loog'
15
11
  require 'retriable_proxy'
16
12
  require 'slop'
17
13
  require_relative '../lib/sibit'
14
+ require_relative '../lib/sibit/http'
18
15
  require_relative '../lib/sibit/bitcoinchain'
19
16
  require_relative '../lib/sibit/blockchain'
20
17
  require_relative '../lib/sibit/blockchair'
data/features/cli.feature CHANGED
@@ -1,7 +1,7 @@
1
1
  # SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
2
2
  # SPDX-License-Identifier: MIT
3
3
  Feature: Command Line Processing
4
- As a newsletter author I want to be able to send a newsletter
4
+ As a holder of BTC I want to be able to use sibit
5
5
 
6
6
  Scenario: Help can be printed
7
7
  When I run bin/sibit with "--help"
@@ -0,0 +1,25 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+ Feature: Command Line Processing
4
+ As a holder of BTC I want to use sibit in dry mode
5
+
6
+ Scenario: Bitcoin price can be retrieved
7
+ When I run bin/sibit with "price --dry --attempts=4"
8
+ Then Exit code is zero
9
+
10
+ Scenario: Bitcoin latest block hash can be retrieved
11
+ When I run bin/sibit with "latest --dry --api=blockchain"
12
+ Then Exit code is zero
13
+
14
+ Scenario: Bitcoin balance can be checked
15
+ When I run bin/sibit with "balance --dry 1MZT1fa6y8H9UmbZV6HqKF4UY41o9MGT5f --verbose --api=blockchain,btc"
16
+ Then Exit code is zero
17
+
18
+ Scenario: Bitcoin fees can be printed
19
+ When I run bin/sibit with "fees --dry --verbose --api=fake"
20
+ Then Exit code is zero
21
+
22
+ Scenario: Bitcoin payment can be sent
23
+ When I run bin/sibit with "pay --dry --verbose --api=fake --proxy=localhost:3128 999999 XL- 46feba063e9b59a8ae0dba68abd39a3cb8f52089e776576d6eb1bb5bfec123d1 1MZT1fa6y8H9UmbZV6HqKF4UY41o9MGT5f 1Fsyq5YGe8zbSjLS8YsDnZWM8U6AYMR6ZD"
24
+ Then Exit code is not zero
25
+ Then Stdout contains "UTXO arrived to 1JvCsJtLmCxEk7ddZFnVkGXpr9uhxZPmJi is incorrect"
data/lib/sibit/bestof.rb CHANGED
@@ -12,96 +12,93 @@ require_relative 'error'
12
12
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
13
13
  # Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
14
14
  # License:: MIT
15
- class Sibit
16
- # Best of API.
17
- class BestOf
18
- # Constructor.
19
- def initialize(list, log: Loog::NULL, verbose: false)
20
- @list = list
21
- @log = log
22
- @verbose = verbose
23
- end
15
+ class Sibit::BestOf
16
+ # Constructor.
17
+ def initialize(list, log: Loog::NULL, verbose: false)
18
+ @list = list
19
+ @log = log
20
+ @verbose = verbose
21
+ end
24
22
 
25
- # Current price of BTC in USD (float returned).
26
- def price(currency = 'USD')
27
- best_of('price') do |api|
28
- api.price(currency)
29
- end
23
+ # Current price of BTC in USD (float returned).
24
+ def price(currency = 'USD')
25
+ best_of('price') do |api|
26
+ api.price(currency)
30
27
  end
28
+ end
31
29
 
32
- # Gets the balance of the address, in satoshi.
33
- def balance(address)
34
- best_of('balance') do |api|
35
- api.balance(address)
36
- end
30
+ # Gets the balance of the address, in satoshi.
31
+ def balance(address)
32
+ best_of('balance') do |api|
33
+ api.balance(address)
37
34
  end
35
+ end
38
36
 
39
- # Get the height of the block.
40
- def height(hash)
41
- best_of('height') do |api|
42
- api.height(hash)
43
- end
37
+ # Get the height of the block.
38
+ def height(hash)
39
+ best_of('height') do |api|
40
+ api.height(hash)
44
41
  end
42
+ end
45
43
 
46
- # Get the hash of the next block.
47
- def next_of(hash)
48
- best_of('next_of') do |api|
49
- api.next_of(hash)
50
- end
44
+ # Get the hash of the next block.
45
+ def next_of(hash)
46
+ best_of('next_of') do |api|
47
+ api.next_of(hash)
51
48
  end
49
+ end
52
50
 
53
- # Get recommended fees, in satoshi per byte. The method returns
54
- # a hash: { S: 12, M: 45, L: 100, XL: 200 }
55
- def fees
56
- best_of('fees', &:fees)
57
- end
51
+ # Get recommended fees, in satoshi per byte. The method returns
52
+ # a hash: { S: 12, M: 45, L: 100, XL: 200 }
53
+ def fees
54
+ best_of('fees', &:fees)
55
+ end
58
56
 
59
- # Fetch all unspent outputs per address.
60
- def utxos(keys)
61
- best_of('utxos') do |api|
62
- api.utxos(keys)
63
- end
57
+ # Fetch all unspent outputs per address.
58
+ def utxos(keys)
59
+ best_of('utxos') do |api|
60
+ api.utxos(keys)
64
61
  end
62
+ end
65
63
 
66
- # Latest block hash.
67
- def latest
68
- best_of('latest', &:latest)
69
- end
64
+ # Latest block hash.
65
+ def latest
66
+ best_of('latest', &:latest)
67
+ end
70
68
 
71
- # Push this transaction (in hex format) to the network.
72
- def push(hex)
73
- best_of('push') do |api|
74
- api.push(hex)
75
- end
69
+ # Push this transaction (in hex format) to the network.
70
+ def push(hex)
71
+ best_of('push') do |api|
72
+ api.push(hex)
76
73
  end
74
+ end
77
75
 
78
- # This method should fetch a block and return as a hash.
79
- def block(hash)
80
- best_of('block') do |api|
81
- api.block(hash)
82
- end
76
+ # This method should fetch a block and return as a hash.
77
+ def block(hash)
78
+ best_of('block') do |api|
79
+ api.block(hash)
83
80
  end
81
+ end
84
82
 
85
- private
83
+ private
86
84
 
87
- def best_of(method)
88
- return yield @list unless @list.is_a?(Array)
89
- results = []
90
- errors = []
91
- @list.each do |api|
92
- results << yield(api)
93
- rescue Sibit::NotSupportedError
94
- # Just ignore it
95
- rescue Sibit::Error => e
96
- errors << e
97
- @log.info("The API #{api.class.name} failed at #{method}(): #{e.message}") if @verbose
98
- end
99
- if results.empty?
100
- errors.each { |e| @log.info(Backtrace.new(e).to_s) }
101
- raise Sibit::Error, "No APIs out of #{@list.length} managed to succeed at #{method}(): \
85
+ def best_of(method)
86
+ return yield @list unless @list.is_a?(Array)
87
+ results = []
88
+ errors = []
89
+ @list.each do |api|
90
+ results << yield(api)
91
+ rescue Sibit::NotSupportedError
92
+ # Just ignore it
93
+ rescue Sibit::Error => e
94
+ errors << e
95
+ @log.info("The API #{api.class.name} failed at #{method}(): #{e.message}") if @verbose
96
+ end
97
+ if results.empty?
98
+ errors.each { |e| @log.info(Backtrace.new(e).to_s) }
99
+ raise Sibit::Error, "No APIs out of #{@list.length} managed to succeed at #{method}(): \
102
100
  #{@list.map { |a| a.class.name }.join(', ')}"
103
- end
104
- results.group_by(&:to_s).values.max_by(&:size)[0]
105
101
  end
102
+ results.group_by(&:to_s).values.max_by(&:size)[0]
106
103
  end
107
104
  end
@@ -5,46 +5,45 @@
5
5
 
6
6
  require 'digest'
7
7
 
8
+ # Sibit main class.
8
9
  class Sibit
9
- # Bitcoin primitives module.
10
+ # Base58 encoding for Bitcoin addresses.
10
11
  #
11
- # Pure Ruby implementation of Bitcoin functionality using OpenSSL 3.0+.
12
- # Replaces the bitcoin-ruby dependency which is incompatible with OpenSSL 3.0.
13
- module Bitcoin
14
- MIN_TX_FEE = 10_000
12
+ # Encapsulates hex data and provides encoding/decoding functionality.
13
+ #
14
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
15
+ # Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
16
+ # License:: MIT
17
+ class Base58
18
+ ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
15
19
 
16
- # Base58 encoding for Bitcoin addresses.
17
- #
18
- # Author:: Yegor Bugayenko (yegor256@gmail.com)
19
- # Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
20
- # License:: MIT
21
- module Base58
22
- ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
20
+ def initialize(data)
21
+ @data = data
22
+ end
23
23
 
24
- def self.encode(hex)
25
- bytes = [hex].pack('H*')
26
- leading = bytes.match(/^\x00*/)[0].length
27
- num = hex.to_i(16)
28
- result = ''
29
- while num.positive?
30
- num, remainder = num.divmod(58)
31
- result = ALPHABET[remainder] + result
32
- end
33
- ('1' * leading) + result
24
+ def encode
25
+ bytes = [@data].pack('H*')
26
+ leading = bytes.match(/^\x00*/)[0].length
27
+ num = @data.to_i(16)
28
+ result = ''
29
+ while num.positive?
30
+ num, remainder = num.divmod(58)
31
+ result = ALPHABET[remainder] + result
34
32
  end
33
+ ('1' * leading) + result
34
+ end
35
35
 
36
- def self.decode(str)
37
- leading = str.match(/^1*/)[0].length
38
- num = 0
39
- str.each_char { |c| num = (num * 58) + ALPHABET.index(c) }
40
- hex = num.zero? ? '' : num.to_s(16)
41
- hex = "0#{hex}" if hex.length.odd?
42
- ('00' * leading) + hex
43
- end
36
+ def decode
37
+ leading = @data.match(/^1*/)[0].length
38
+ num = 0
39
+ @data.each_char { |c| num = (num * 58) + ALPHABET.index(c) }
40
+ hex = num.zero? ? '' : num.to_s(16)
41
+ hex = "0#{hex}" if hex.length.odd?
42
+ ('00' * leading) + hex
43
+ end
44
44
 
45
- def self.check(hex)
46
- Digest::SHA256.hexdigest(Digest::SHA256.digest([hex].pack('H*')))[0...8]
47
- end
45
+ def check
46
+ Digest::SHA256.hexdigest(Digest::SHA256.digest([@data].pack('H*')))[0...8]
48
47
  end
49
48
  end
50
49
  end
@@ -3,85 +3,84 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require 'openssl'
7
6
  require 'digest'
7
+ require 'openssl'
8
8
  require_relative 'base58'
9
9
 
10
+ # Sibit main class.
10
11
  class Sibit
11
- module Bitcoin
12
- # Bitcoin ECDSA key using secp256k1 curve.
13
- #
14
- # Supports OpenSSL 3.0+ by constructing keys via DER encoding instead
15
- # of using deprecated mutable key APIs.
16
- #
17
- # Author:: Yegor Bugayenko (yegor256@gmail.com)
18
- # Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
19
- # License:: MIT
20
- class Key
21
- MIN_PRIV = 0x01
22
- MAX_PRIV = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140
12
+ # Bitcoin ECDSA key using secp256k1 curve.
13
+ #
14
+ # Supports OpenSSL 3.0+ by constructing keys via DER encoding instead
15
+ # of using deprecated mutable key APIs.
16
+ #
17
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
18
+ # Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
19
+ # License:: MIT
20
+ class Key
21
+ MIN_PRIV = 0x01
22
+ MAX_PRIV = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140
23
23
 
24
- def self.generate
25
- key = OpenSSL::PKey::EC.generate('secp256k1')
26
- pvt = key.private_key.to_s(16).rjust(64, '0').downcase
27
- new(pvt)
28
- end
24
+ def self.generate
25
+ key = OpenSSL::PKey::EC.generate('secp256k1')
26
+ pvt = key.private_key.to_s(16).rjust(64, '0').downcase
27
+ new(pvt)
28
+ end
29
29
 
30
- def initialize(privkey)
31
- @privkey = privkey
32
- @compressed = true
33
- @key = build(privkey)
34
- end
30
+ def initialize(privkey)
31
+ @privkey = privkey
32
+ @compressed = true
33
+ @key = build(privkey)
34
+ end
35
35
 
36
- def priv
37
- @privkey
38
- end
36
+ def priv
37
+ @privkey
38
+ end
39
39
 
40
- def pub
41
- point = @key.public_key
42
- point.to_octet_string(@compressed ? :compressed : :uncompressed).unpack1('H*')
43
- end
40
+ def pub
41
+ point = @key.public_key
42
+ point.to_octet_string(@compressed ? :compressed : :uncompressed).unpack1('H*')
43
+ end
44
44
 
45
- def addr
46
- hash = hash160(pub)
47
- versioned = "00#{hash}"
48
- checksum = Base58.check(versioned)
49
- Base58.encode(versioned + checksum)
50
- end
45
+ def addr
46
+ hash = hash160(pub)
47
+ versioned = "00#{hash}"
48
+ checksum = Base58.new(versioned).check
49
+ Base58.new(versioned + checksum).encode
50
+ end
51
51
 
52
- def sign(data)
53
- @key.sign('SHA256', data)
54
- end
52
+ def sign(data)
53
+ @key.sign('SHA256', data)
54
+ end
55
55
 
56
- def verify(data, sig)
57
- @key.verify('SHA256', sig, data)
58
- rescue OpenSSL::PKey::PKeyError
59
- false
60
- end
56
+ def verify(data, sig)
57
+ @key.verify('SHA256', sig, data)
58
+ rescue OpenSSL::PKey::PKeyError
59
+ false
60
+ end
61
61
 
62
- private
62
+ private
63
63
 
64
- def build(privkey)
65
- value = privkey.to_i(16)
66
- raise 'private key is not on curve' unless value.between?(MIN_PRIV, MAX_PRIV)
67
- group = OpenSSL::PKey::EC::Group.new('secp256k1')
68
- bn = OpenSSL::BN.new(privkey, 16)
69
- pubkey = group.generator.mul(bn)
70
- asn1 = OpenSSL::ASN1::Sequence(
71
- [
72
- OpenSSL::ASN1::Integer.new(1),
73
- OpenSSL::ASN1::OctetString(bn.to_s(2)),
74
- OpenSSL::ASN1::ObjectId('secp256k1', 0, :EXPLICIT),
75
- OpenSSL::ASN1::BitString(pubkey.to_octet_string(:uncompressed), 1, :EXPLICIT)
76
- ]
77
- )
78
- OpenSSL::PKey::EC.new(asn1.to_der)
79
- end
64
+ def build(privkey)
65
+ value = privkey.to_i(16)
66
+ raise 'private key is not on curve' unless value.between?(MIN_PRIV, MAX_PRIV)
67
+ group = OpenSSL::PKey::EC::Group.new('secp256k1')
68
+ bn = OpenSSL::BN.new(privkey, 16)
69
+ pubkey = group.generator.mul(bn)
70
+ asn1 = OpenSSL::ASN1::Sequence(
71
+ [
72
+ OpenSSL::ASN1::Integer.new(1),
73
+ OpenSSL::ASN1::OctetString(bn.to_s(2)),
74
+ OpenSSL::ASN1::ObjectId('secp256k1', 0, :EXPLICIT),
75
+ OpenSSL::ASN1::BitString(pubkey.to_octet_string(:uncompressed), 1, :EXPLICIT)
76
+ ]
77
+ )
78
+ OpenSSL::PKey::EC.new(asn1.to_der)
79
+ end
80
80
 
81
- def hash160(hex)
82
- bytes = [hex].pack('H*')
83
- Digest::RMD160.hexdigest(Digest::SHA256.digest(bytes))
84
- end
81
+ def hash160(hex)
82
+ bytes = [hex].pack('H*')
83
+ Digest::RMD160.hexdigest(Digest::SHA256.digest(bytes))
85
84
  end
86
85
  end
87
86
  end