sibit 0.32.6 → 0.32.8
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 +4 -4
- data/Gemfile.lock +2 -2
- data/features/cli.feature +1 -1
- data/features/dry.feature +0 -16
- data/features/fake.feature +25 -0
- data/lib/sibit/key.rb +27 -9
- data/lib/sibit/version.rb +1 -1
- data/lib/sibit.rb +38 -16
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8dbaecce413d6a977eee8d4d440a066a666f2054ad7d2b4cec0b21c6da4efe8e
|
|
4
|
+
data.tar.gz: 39431e80439522e9d000404ee29856f558ba3b4406976e9345e550f4efbf3974
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dc62197de1ada4a3e155148fa09926ffd02861178f1049203b1212a363b60d01d11fe3881f3e61d86a57b666464084d511aec4b4664e8fd038904468b9fb315f
|
|
7
|
+
data.tar.gz: f1ec0c729a1ccca0b2b9728ea23348db8cb5e5d80b22ec06dc33a682fb04a76c47cd0b487d578d8b19d2e62dfb7ac3c4f638aac03a9f8fa7a4c791f882e10259
|
data/Gemfile.lock
CHANGED
|
@@ -102,7 +102,7 @@ GEM
|
|
|
102
102
|
parser (3.3.10.0)
|
|
103
103
|
ast (~> 2.4.1)
|
|
104
104
|
racc
|
|
105
|
-
prism (1.
|
|
105
|
+
prism (1.8.0)
|
|
106
106
|
psych (5.3.1)
|
|
107
107
|
date
|
|
108
108
|
stringio
|
|
@@ -115,7 +115,7 @@ GEM
|
|
|
115
115
|
racc (1.8.1)
|
|
116
116
|
rainbow (3.1.1)
|
|
117
117
|
rake (13.3.1)
|
|
118
|
-
rdoc (7.0
|
|
118
|
+
rdoc (7.1.0)
|
|
119
119
|
erb
|
|
120
120
|
psych (>= 4.0.0)
|
|
121
121
|
tsort
|
data/features/cli.feature
CHANGED
|
@@ -33,7 +33,7 @@ Feature: Command Line Processing
|
|
|
33
33
|
Then Exit code is zero
|
|
34
34
|
|
|
35
35
|
Scenario: Bitcoin balance can be checked
|
|
36
|
-
When I run bin/sibit with "balance 1MZT1fa6y8H9UmbZV6HqKF4UY41o9MGT5f --verbose
|
|
36
|
+
When I run bin/sibit with "balance 1MZT1fa6y8H9UmbZV6HqKF4UY41o9MGT5f --verbose"
|
|
37
37
|
Then Exit code is zero
|
|
38
38
|
|
|
39
39
|
Scenario: Bitcoin fees can be printed
|
data/features/dry.feature
CHANGED
|
@@ -3,22 +3,6 @@
|
|
|
3
3
|
Feature: Command Line Processing
|
|
4
4
|
As a holder of BTC I want to use sibit in dry mode
|
|
5
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
6
|
Scenario: Bitcoin payment can be sent
|
|
23
7
|
When I run bin/sibit with "pay --dry --verbose --api=fake --proxy=localhost:3128 999999 XL- 46feba063e9b59a8ae0dba68abd39a3cb8f52089e776576d6eb1bb5bfec123d1 1MZT1fa6y8H9UmbZV6HqKF4UY41o9MGT5f 1Fsyq5YGe8zbSjLS8YsDnZWM8U6AYMR6ZD"
|
|
24
8
|
Then Exit code is not zero
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
Feature: Command Line Processing
|
|
4
|
+
As a holder of BTC I want to use sibit with fake provider
|
|
5
|
+
|
|
6
|
+
Scenario: Bitcoin price can be retrieved
|
|
7
|
+
When I run bin/sibit with "price --attempts=4 --api=fake"
|
|
8
|
+
Then Exit code is zero
|
|
9
|
+
|
|
10
|
+
Scenario: Bitcoin latest block hash can be retrieved
|
|
11
|
+
When I run bin/sibit with "latest --api=fake"
|
|
12
|
+
Then Exit code is zero
|
|
13
|
+
|
|
14
|
+
Scenario: Bitcoin balance can be checked
|
|
15
|
+
When I run bin/sibit with "balance 1MZT1fa6y8H9UmbZV6HqKF4UY41o9MGT5f --verbose --api=fake"
|
|
16
|
+
Then Exit code is zero
|
|
17
|
+
|
|
18
|
+
Scenario: Bitcoin fees can be printed
|
|
19
|
+
When I run bin/sibit with "fees --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 --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/key.rb
CHANGED
|
@@ -7,6 +7,7 @@ require 'digest'
|
|
|
7
7
|
require 'openssl'
|
|
8
8
|
require_relative 'base58'
|
|
9
9
|
require_relative 'bech32'
|
|
10
|
+
require_relative 'error'
|
|
10
11
|
|
|
11
12
|
# Sibit main class.
|
|
12
13
|
class Sibit
|
|
@@ -61,24 +62,36 @@ class Sibit
|
|
|
61
62
|
def bech32
|
|
62
63
|
hrp = { mainnet: 'bc', testnet: 'tb', regtest: 'bcrt' }[@network]
|
|
63
64
|
hex = pub
|
|
64
|
-
raise 'Invalid public key: not on curve' unless @key.public_key.on_curve?
|
|
65
|
-
raise 'Invalid public key format' unless hex.match?(/\A0[23][0-9a-f]{64}\z/)
|
|
66
|
-
Bech32.encode(hrp, 0, hash160(hex))
|
|
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
|
+
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)
|
|
70
|
+
addr
|
|
67
71
|
end
|
|
68
72
|
|
|
69
73
|
def base58
|
|
70
74
|
hex = pub
|
|
71
|
-
raise 'Invalid public key: not on curve' unless @key.public_key.on_curve?
|
|
72
|
-
raise 'Invalid public key format' unless hex.match?(/\A0[23][0-9a-f]{64}\z/)
|
|
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/)
|
|
73
77
|
hash = hash160(hex)
|
|
74
78
|
prefix = @network == :mainnet ? '00' : '6f'
|
|
75
79
|
versioned = "#{prefix}#{hash}"
|
|
76
80
|
checksum = Base58.new(versioned).check
|
|
77
|
-
Base58.new(versioned + checksum).encode
|
|
81
|
+
addr = Base58.new(versioned + checksum).encode
|
|
82
|
+
mainnet = /\A1[1-9A-HJ-NP-Za-km-z]{25,34}\z/
|
|
83
|
+
testnet = /\A[mn][1-9A-HJ-NP-Za-km-z]{25,34}\z/
|
|
84
|
+
unless addr.match?(@network == :mainnet ? mainnet : testnet)
|
|
85
|
+
raise Error,
|
|
86
|
+
"Invalid base58 address: #{addr}"
|
|
87
|
+
end
|
|
88
|
+
addr
|
|
78
89
|
end
|
|
79
90
|
|
|
80
91
|
def sign(data)
|
|
81
|
-
@key.dsa_sign_asn1(data)
|
|
92
|
+
sig = @key.dsa_sign_asn1(data)
|
|
93
|
+
raise Error, 'Signature verification failed' unless verify(data, sig)
|
|
94
|
+
sig
|
|
82
95
|
end
|
|
83
96
|
|
|
84
97
|
def verify(data, sig)
|
|
@@ -91,7 +104,7 @@ class Sibit
|
|
|
91
104
|
|
|
92
105
|
def build(privkey)
|
|
93
106
|
value = privkey.to_i(16)
|
|
94
|
-
raise '
|
|
107
|
+
raise Error, 'Private key is not on curve' unless value.between?(MIN_PRIV, MAX_PRIV)
|
|
95
108
|
group = OpenSSL::PKey::EC::Group.new('secp256k1')
|
|
96
109
|
bn = OpenSSL::BN.new(privkey, 16)
|
|
97
110
|
pubkey = group.generator.mul(bn)
|
|
@@ -114,10 +127,15 @@ class Sibit
|
|
|
114
127
|
def decode(key)
|
|
115
128
|
if key.length == 64 && key.match?(/\A[0-9a-f]+\z/i)
|
|
116
129
|
@network = @override || :mainnet
|
|
117
|
-
return key
|
|
130
|
+
return key.downcase
|
|
118
131
|
end
|
|
119
132
|
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
|
|
120
137
|
version = raw[0, 2]
|
|
138
|
+
raise Error, "Invalid WIF version: #{version}" unless %w[80 ef].include?(version)
|
|
121
139
|
detected = version == '80' ? :mainnet : :testnet
|
|
122
140
|
@network = @override || detected
|
|
123
141
|
body = raw[2..-9]
|
data/lib/sibit/version.rb
CHANGED
data/lib/sibit.rb
CHANGED
|
@@ -53,13 +53,13 @@ class Sibit
|
|
|
53
53
|
# Generates new Bitcoin private key and returns in Hash160 format.
|
|
54
54
|
def generate
|
|
55
55
|
key = Key.generate.priv
|
|
56
|
-
@log.debug("Bitcoin private key generated: #{key
|
|
56
|
+
@log.debug("Bitcoin private key generated: #{key.ellipsized(8)}...")
|
|
57
57
|
key
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
# Creates Bitcoin address using the private key in Hash160 format.
|
|
61
61
|
def create(pvt)
|
|
62
|
-
raise Error, 'Invalid private key (must be 64 chars)' unless /^[0-9a-f]{64}$/.match?(pvt)
|
|
62
|
+
raise Error, 'Invalid private key (must be 64 hex chars)' unless /^[0-9a-f]{64}$/.match?(pvt)
|
|
63
63
|
Key.new(pvt).bech32
|
|
64
64
|
end
|
|
65
65
|
|
|
@@ -105,8 +105,21 @@ class Sibit
|
|
|
105
105
|
# +change+: the address where the change has to be sent to
|
|
106
106
|
# +network+: optional network override (:mainnet, :testnet, :regtest)
|
|
107
107
|
def pay(amount, fee, sources, target, change, skip_utxo: [], network: nil, base58: false)
|
|
108
|
+
unless amount.is_a?(Integer) || amount.is_a?(String)
|
|
109
|
+
raise Error, "The amount #{amount.inspect} must be Integer or String"
|
|
110
|
+
end
|
|
111
|
+
raise Error, 'The amount must be positive' if amount.is_a?(Integer) && amount.negative?
|
|
112
|
+
raise Error, 'The sources must be an Array' unless sources.is_a?(Array)
|
|
113
|
+
raise Error, 'The target must be a String' unless target.is_a?(String)
|
|
114
|
+
raise Error, 'The change must be a String' unless change.is_a?(String)
|
|
108
115
|
p = price('USD')
|
|
109
|
-
keys = sources.map
|
|
116
|
+
keys = sources.map do |k|
|
|
117
|
+
raise Error, 'Each source private key must be a String' unless k.is_a?(String)
|
|
118
|
+
hex = /^[0-9a-f]{64}$/i.match?(k)
|
|
119
|
+
wif = /^[5KLc][1-9A-HJ-NP-Za-km-z]{50,51}$/.match?(k)
|
|
120
|
+
raise Error, "Invalid private key format: #{k.inspect.ellipsized(8)}" unless hex || wif
|
|
121
|
+
Key.new(k, network: network)
|
|
122
|
+
end
|
|
110
123
|
network = keys.first&.network || :mainnet
|
|
111
124
|
sources = keys.to_h do |k|
|
|
112
125
|
pub =
|
|
@@ -168,19 +181,28 @@ class Sibit
|
|
|
168
181
|
change_address: change
|
|
169
182
|
)
|
|
170
183
|
left = unspent - tx.outputs.sum(&:value)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
+
has_change = tx.out.count > 1
|
|
185
|
+
@log.debug(
|
|
186
|
+
[
|
|
187
|
+
"A new Bitcoin transaction #{tx.hash} prepared:",
|
|
188
|
+
"#{tx.in.count} input#{'s' if tx.in.count > 1}:",
|
|
189
|
+
tx.inputs.map do |i|
|
|
190
|
+
" in: #{i.prev_out.unpack1('H*')}:#{i.prev_out_index}"
|
|
191
|
+
end,
|
|
192
|
+
"#{tx.out.count} output#{'s' if tx.out.count > 1}:",
|
|
193
|
+
tx.outputs.map do |o|
|
|
194
|
+
" out: #{o.script_hex} / #{num(o.value, p)}"
|
|
195
|
+
end,
|
|
196
|
+
"Min fee: #{num(MIN_SATOSHI_PER_BYTE, p)} /byte",
|
|
197
|
+
"Fee requested: #{num(f, p)} as \"#{fee}\"",
|
|
198
|
+
"Fee actually paid: #{num(left, p)}",
|
|
199
|
+
"Tx size: #{size} bytes",
|
|
200
|
+
"Unspent: #{num(unspent, p)}",
|
|
201
|
+
"Amount: #{num(satoshi, p)}",
|
|
202
|
+
"Target address: #{target}",
|
|
203
|
+
("Change address: #{change}" if has_change)
|
|
204
|
+
].flatten.compact.join("\n")
|
|
205
|
+
)
|
|
184
206
|
@api.push(tx.to_payload.bth)
|
|
185
207
|
tx.hash
|
|
186
208
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sibit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.32.
|
|
4
|
+
version: 0.32.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yegor Bugayenko
|
|
@@ -171,6 +171,7 @@ files:
|
|
|
171
171
|
- cucumber.yml
|
|
172
172
|
- features/cli.feature
|
|
173
173
|
- features/dry.feature
|
|
174
|
+
- features/fake.feature
|
|
174
175
|
- features/gem_package.feature
|
|
175
176
|
- features/step_definitions/steps.rb
|
|
176
177
|
- features/support/env.rb
|