sibit 0.28.0 → 0.29.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.
@@ -8,205 +8,203 @@ require_relative 'base58'
8
8
  require_relative 'key'
9
9
  require_relative 'script'
10
10
 
11
- class Sibit
12
- module Bitcoin
13
- # Bitcoin Transaction structure.
14
- #
15
- # Author:: Yegor Bugayenko (yegor256@gmail.com)
16
- # Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
17
- # License:: MIT
18
- class Tx
19
- SIGHASH_ALL = 0x01
20
- VERSION = 1
21
- SEQUENCE = 0xffffffff
22
-
23
- attr_reader :inputs, :outputs
24
-
25
- def initialize
26
- @inputs = []
27
- @outputs = []
28
- end
11
+ module Sibit::Bitcoin
12
+ # Bitcoin Transaction structure.
13
+ #
14
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
15
+ # Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
16
+ # License:: MIT
17
+ class Tx
18
+ SIGHASH_ALL = 0x01
19
+ VERSION = 1
20
+ SEQUENCE = 0xffffffff
21
+
22
+ attr_reader :inputs, :outputs
23
+
24
+ def initialize
25
+ @inputs = []
26
+ @outputs = []
27
+ end
29
28
 
30
- def add_input(hash:, index:, script:, key:)
31
- @inputs << Input.new(hash, index, script, key)
32
- end
29
+ def add_input(hash:, index:, script:, key:)
30
+ @inputs << Input.new(hash, index, script, key)
31
+ end
33
32
 
34
- def add_output(value, address)
35
- @outputs << Output.new(value, address)
36
- end
33
+ def add_output(value, address)
34
+ @outputs << Output.new(value, address)
35
+ end
37
36
 
38
- def hash
39
- Digest::SHA256.hexdigest(Digest::SHA256.digest(payload)).reverse.scan(/../).join
40
- end
37
+ def hash
38
+ Digest::SHA256.hexdigest(Digest::SHA256.digest(payload)).reverse.scan(/../).join
39
+ end
41
40
 
42
- def payload
43
- sign_inputs
44
- serialize
45
- end
41
+ def payload
42
+ sign_inputs
43
+ serialize
44
+ end
46
45
 
47
- def hex
48
- payload.unpack1('H*')
49
- end
46
+ def hex
47
+ payload.unpack1('H*')
48
+ end
50
49
 
51
- def in
52
- @inputs
53
- end
50
+ def in
51
+ @inputs
52
+ end
54
53
 
55
- def out
56
- @outputs
57
- end
54
+ def out
55
+ @outputs
56
+ end
58
57
 
59
- private
58
+ private
60
59
 
61
- def sign_inputs
62
- @inputs.each_with_index do |input, idx|
63
- sighash = signature_hash(idx)
64
- sig = sign(input.key, sighash)
65
- pubkey = [input.key.pub].pack('H*')
66
- input.script_sig = der_sig(sig) + pubkey_script(pubkey)
67
- end
60
+ def sign_inputs
61
+ @inputs.each_with_index do |input, idx|
62
+ sighash = signature_hash(idx)
63
+ sig = sign(input.key, sighash)
64
+ pubkey = [input.key.pub].pack('H*')
65
+ input.script_sig = der_sig(sig) + pubkey_script(pubkey)
68
66
  end
67
+ end
69
68
 
70
- def signature_hash(idx)
71
- tx_copy = serialize_for_signing(idx)
72
- hash_type = [SIGHASH_ALL].pack('V')
73
- Digest::SHA256.digest(Digest::SHA256.digest(tx_copy + hash_type))
74
- end
69
+ def signature_hash(idx)
70
+ tx_copy = serialize_for_signing(idx)
71
+ hash_type = [SIGHASH_ALL].pack('V')
72
+ Digest::SHA256.digest(Digest::SHA256.digest(tx_copy + hash_type))
73
+ end
75
74
 
76
- def sign(key, hash)
77
- der = key.sign(hash)
78
- repack(der)
79
- end
75
+ def sign(key, hash)
76
+ der = key.sign(hash)
77
+ repack(der)
78
+ end
80
79
 
81
- def repack(der)
82
- return der if low_s?(der)
83
- seq = OpenSSL::ASN1.decode(der)
84
- r = seq.value[0].value.to_i
85
- s = seq.value[1].value.to_i
86
- order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
87
- s = order - s if s > order / 2
88
- OpenSSL::ASN1::Sequence.new(
89
- [OpenSSL::ASN1::Integer.new(r), OpenSSL::ASN1::Integer.new(s)]
90
- ).to_der
91
- end
80
+ def repack(der)
81
+ return der if low_s?(der)
82
+ seq = OpenSSL::ASN1.decode(der)
83
+ r = seq.value[0].value.to_i
84
+ s = seq.value[1].value.to_i
85
+ order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
86
+ s = order - s if s > order / 2
87
+ OpenSSL::ASN1::Sequence.new(
88
+ [OpenSSL::ASN1::Integer.new(r), OpenSSL::ASN1::Integer.new(s)]
89
+ ).to_der
90
+ end
92
91
 
93
- def low_s?(der)
94
- seq = OpenSSL::ASN1.decode(der)
95
- s = seq.value[1].value.to_i
96
- order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
97
- s <= order / 2
98
- end
92
+ def low_s?(der)
93
+ seq = OpenSSL::ASN1.decode(der)
94
+ s = seq.value[1].value.to_i
95
+ order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
96
+ s <= order / 2
97
+ end
99
98
 
100
- def der_sig(sig)
101
- data = sig + [SIGHASH_ALL].pack('C')
102
- [data.length].pack('C') + data
103
- end
99
+ def der_sig(sig)
100
+ data = sig + [SIGHASH_ALL].pack('C')
101
+ [data.length].pack('C') + data
102
+ end
104
103
 
105
- def pubkey_script(pubkey)
106
- [pubkey.length].pack('C') + pubkey
107
- end
104
+ def pubkey_script(pubkey)
105
+ [pubkey.length].pack('C') + pubkey
106
+ end
108
107
 
109
- def serialize
110
- result = [VERSION].pack('V')
111
- result += varint(@inputs.length)
112
- @inputs.each do |input|
113
- result += [input.hash].pack('H*').reverse
114
- result += [input.index].pack('V')
115
- result += varint(input.script_sig.length)
116
- result += input.script_sig
117
- result += [SEQUENCE].pack('V')
118
- end
119
- result += varint(@outputs.length)
120
- @outputs.each do |output|
121
- result += [output.value].pack('Q<')
122
- script = output.script
123
- result += varint(script.length)
124
- result += script
125
- end
126
- result += [0].pack('V')
127
- result
128
- end
108
+ def serialize
109
+ result = [VERSION].pack('V')
110
+ result += varint(@inputs.length)
111
+ @inputs.each do |input|
112
+ result += [input.hash].pack('H*').reverse
113
+ result += [input.index].pack('V')
114
+ result += varint(input.script_sig.length)
115
+ result += input.script_sig
116
+ result += [SEQUENCE].pack('V')
117
+ end
118
+ result += varint(@outputs.length)
119
+ @outputs.each do |output|
120
+ result += [output.value].pack('Q<')
121
+ script = output.script
122
+ result += varint(script.length)
123
+ result += script
124
+ end
125
+ result += [0].pack('V')
126
+ result
127
+ end
129
128
 
130
- def serialize_for_signing(idx)
131
- result = [VERSION].pack('V')
132
- result += varint(@inputs.length)
133
- @inputs.each_with_index do |input, i|
134
- result += [input.hash].pack('H*').reverse
135
- result += [input.index].pack('V')
136
- if i == idx
137
- script = [input.prev_script].pack('H*')
138
- result += varint(script.length)
139
- result += script
140
- else
141
- result += varint(0)
142
- end
143
- result += [SEQUENCE].pack('V')
144
- end
145
- result += varint(@outputs.length)
146
- @outputs.each do |output|
147
- result += [output.value].pack('Q<')
148
- script = output.script
129
+ def serialize_for_signing(idx)
130
+ result = [VERSION].pack('V')
131
+ result += varint(@inputs.length)
132
+ @inputs.each_with_index do |input, i|
133
+ result += [input.hash].pack('H*').reverse
134
+ result += [input.index].pack('V')
135
+ if i == idx
136
+ script = [input.prev_script].pack('H*')
149
137
  result += varint(script.length)
150
138
  result += script
139
+ else
140
+ result += varint(0)
151
141
  end
152
- result += [0].pack('V')
153
- result
142
+ result += [SEQUENCE].pack('V')
154
143
  end
155
-
156
- def varint(num)
157
- return [num].pack('C') if num < 0xfd
158
- return [0xfd, num].pack('Cv') if num <= 0xffff
159
- return [0xfe, num].pack('CV') if num <= 0xffffffff
160
- [0xff, num].pack('CQ<')
144
+ result += varint(@outputs.length)
145
+ @outputs.each do |output|
146
+ result += [output.value].pack('Q<')
147
+ script = output.script
148
+ result += varint(script.length)
149
+ result += script
161
150
  end
151
+ result += [0].pack('V')
152
+ result
162
153
  end
163
154
 
164
- # Transaction input.
165
- class Input
166
- attr_reader :hash, :index, :prev_script, :key
167
- attr_accessor :script_sig
155
+ def varint(num)
156
+ return [num].pack('C') if num < 0xfd
157
+ return [0xfd, num].pack('Cv') if num <= 0xffff
158
+ return [0xfe, num].pack('CV') if num <= 0xffffffff
159
+ [0xff, num].pack('CQ<')
160
+ end
161
+ end
168
162
 
169
- def initialize(hash, index, script, key)
170
- @hash = hash
171
- @index = index
172
- @prev_script = script
173
- @key = key
174
- @script_sig = ''
175
- end
163
+ # Transaction input.
164
+ class Input
165
+ attr_reader :hash, :index, :prev_script, :key
166
+ attr_accessor :script_sig
167
+
168
+ def initialize(hash, index, script, key)
169
+ @hash = hash
170
+ @index = index
171
+ @prev_script = script
172
+ @key = key
173
+ @script_sig = ''
174
+ end
176
175
 
177
- def prev_out
178
- [@hash].pack('H*')
179
- end
176
+ def prev_out
177
+ [@hash].pack('H*')
178
+ end
180
179
 
181
- def prev_out_index
182
- @index
183
- end
180
+ def prev_out_index
181
+ @index
184
182
  end
183
+ end
185
184
 
186
- # Transaction output.
187
- class Output
188
- attr_reader :value
185
+ # Transaction output.
186
+ class Output
187
+ attr_reader :value
189
188
 
190
- def initialize(value, address)
191
- @value = value
192
- @address = address
193
- end
189
+ def initialize(value, address)
190
+ @value = value
191
+ @address = address
192
+ end
194
193
 
195
- def script
196
- hash160 = address_to_hash160(@address)
197
- [0x76, 0xa9, 0x14].pack('C*') + [hash160].pack('H*') + [0x88, 0xac].pack('C*')
198
- end
194
+ def script
195
+ hash160 = address_to_hash160(@address)
196
+ [0x76, 0xa9, 0x14].pack('C*') + [hash160].pack('H*') + [0x88, 0xac].pack('C*')
197
+ end
199
198
 
200
- def script_hex
201
- script.unpack1('H*')
202
- end
199
+ def script_hex
200
+ script.unpack1('H*')
201
+ end
203
202
 
204
- private
203
+ private
205
204
 
206
- def address_to_hash160(addr)
207
- decoded = Base58.decode(addr)
208
- decoded[2..41]
209
- end
205
+ def address_to_hash160(addr)
206
+ decoded = Base58.decode(addr)
207
+ decoded[2..41]
210
208
  end
211
209
  end
212
210
  end
@@ -3,8 +3,8 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require_relative 'tx'
7
6
  require_relative 'key'
7
+ require_relative 'tx'
8
8
 
9
9
  class Sibit
10
10
  module Bitcoin
@@ -5,10 +5,10 @@
5
5
 
6
6
  require 'iri'
7
7
  require 'json'
8
+ require 'loog'
8
9
  require 'uri'
9
10
  require_relative 'error'
10
11
  require_relative 'http'
11
- require 'loog'
12
12
  require_relative 'json'
13
13
  require_relative 'version'
14
14
 
@@ -17,110 +17,107 @@ require_relative 'version'
17
17
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
18
18
  # Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
19
19
  # License:: MIT
20
- class Sibit
21
- # Btc.com API.
22
- class Bitcoinchain
23
- # Constructor.
24
- def initialize(log: Loog::NULL, http: Sibit::Http.new, dry: false)
25
- @http = http
26
- @log = log
27
- @dry = dry
28
- end
20
+ class Sibit::Bitcoinchain
21
+ # Constructor.
22
+ def initialize(log: Loog::NULL, http: Sibit::Http.new, dry: false)
23
+ @http = http
24
+ @log = log
25
+ @dry = dry
26
+ end
29
27
 
30
- # Current price of BTC in USD (float returned).
31
- def price(_currency = 'USD')
32
- raise Sibit::NotSupportedError, 'Bitcoinchain API doesn\'t provide BTC price'
33
- end
28
+ # Current price of BTC in USD (float returned).
29
+ def price(_currency = 'USD')
30
+ raise Sibit::NotSupportedError, 'Bitcoinchain API doesn\'t provide BTC price'
31
+ end
34
32
 
35
- # The height of the block.
36
- def height(_hash)
37
- raise Sibit::NotSupportedError, 'Bitcoinchain API doesn\'t provide height()'
38
- end
33
+ # The height of the block.
34
+ def height(_hash)
35
+ raise Sibit::NotSupportedError, 'Bitcoinchain API doesn\'t provide height()'
36
+ end
39
37
 
40
- # Get hash of the block after this one.
41
- def next_of(hash)
42
- block = Sibit::Json.new(http: @http, log: @log).get(
43
- Iri.new('https://api-r.bitcoinchain.com/v1/block').append(hash)
44
- )[0]
45
- raise Sibit::Error, "Block #{hash} not found" if block.nil?
46
- nxt = block['next_block']
47
- nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
48
- @log.info("The block #{hash} is the latest, there is no next block") if nxt.nil?
49
- @log.info("The next block of #{hash} is #{nxt}") unless nxt.nil?
50
- nxt
51
- end
38
+ # Get hash of the block after this one.
39
+ def next_of(hash)
40
+ block = Sibit::Json.new(http: @http, log: @log).get(
41
+ Iri.new('https://api-r.bitcoinchain.com/v1/block').append(hash)
42
+ )[0]
43
+ raise Sibit::Error, "Block #{hash} not found" if block.nil?
44
+ nxt = block['next_block']
45
+ nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
46
+ @log.info("The block #{hash} is the latest, there is no next block") if nxt.nil?
47
+ @log.info("The next block of #{hash} is #{nxt}") unless nxt.nil?
48
+ nxt
49
+ end
52
50
 
53
- # Gets the balance of the address, in satoshi.
54
- def balance(address)
55
- json = Sibit::Json.new(http: @http, log: @log).get(
56
- Iri.new('https://api-r.bitcoinchain.com/v1/address').append(address),
57
- accept: [200, 409]
58
- )[0]
59
- b = json['balance']
60
- if b.nil?
61
- @log.info("The balance of #{address} is not visible")
62
- return 0
63
- end
64
- b *= 100_000_000
65
- b = b.to_i
66
- @log.info("The balance of #{address} is #{b} satoshi (#{json['transactions']} txns)")
67
- b
51
+ # Gets the balance of the address, in satoshi.
52
+ def balance(address)
53
+ json = Sibit::Json.new(http: @http, log: @log).get(
54
+ Iri.new('https://api-r.bitcoinchain.com/v1/address').append(address),
55
+ accept: [200, 409]
56
+ )[0]
57
+ b = json['balance']
58
+ if b.nil?
59
+ @log.info("The balance of #{address} is not visible")
60
+ return 0
68
61
  end
62
+ b *= 100_000_000
63
+ b = b.to_i
64
+ @log.info("The balance of #{address} is #{b} satoshi (#{json['transactions']} txns)")
65
+ b
66
+ end
69
67
 
70
- # Get recommended fees, in satoshi per byte.
71
- def fees
72
- raise Sibit::NotSupportedError, 'Not implemented yet'
73
- end
68
+ # Get recommended fees, in satoshi per byte.
69
+ def fees
70
+ raise Sibit::NotSupportedError, 'Not implemented yet'
71
+ end
74
72
 
75
- # Gets the hash of the latest block.
76
- def latest
77
- hash = Sibit::Json.new(http: @http, log: @log).get(
78
- Iri.new('https://api-r.bitcoinchain.com/v1/status')
79
- )['hash']
80
- @log.info("The latest block hash is #{hash}")
81
- hash
82
- end
73
+ # Gets the hash of the latest block.
74
+ def latest
75
+ hash = Sibit::Json.new(http: @http, log: @log).get(
76
+ Iri.new('https://api-r.bitcoinchain.com/v1/status')
77
+ )['hash']
78
+ @log.info("The latest block hash is #{hash}")
79
+ hash
80
+ end
83
81
 
84
- # Fetch all unspent outputs per address.
85
- def utxos(_sources)
86
- raise Sibit::NotSupportedError, 'Not implemented yet'
87
- end
82
+ # Fetch all unspent outputs per address.
83
+ def utxos(_sources)
84
+ raise Sibit::NotSupportedError, 'Not implemented yet'
85
+ end
88
86
 
89
- # Push this transaction (in hex format) to the network.
90
- def push(_hex)
91
- raise Sibit::NotSupportedError, 'Not implemented yet'
92
- end
87
+ # Push this transaction (in hex format) to the network.
88
+ def push(_hex)
89
+ raise Sibit::NotSupportedError, 'Not implemented yet'
90
+ end
93
91
 
94
- # This method should fetch a Blockchain block and return as a hash. Raises
95
- # an exception if the block is not found.
96
- def block(hash)
97
- head = Sibit::Json.new(http: @http, log: @log).get(
98
- Iri.new('https://api-r.bitcoinchain.com/v1/block').append(hash)
99
- )[0]
100
- raise Sibit::Error, "The block #{hash} is not found" if head.nil?
101
- txs = Sibit::Json.new(http: @http, log: @log).get(
102
- Iri.new('https://api-r.bitcoinchain.com/v1/block/txs').append(hash)
103
- )
104
- nxt = head['next_block']
105
- nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
106
- {
107
- provider: self.class.name,
108
- hash: head['hash'],
109
- orphan: !head['is_main'],
110
- next: nxt,
111
- previous: head['prev_block'],
112
- txns: txs[0]['txs'].map do |t|
113
- {
114
- hash: t['self_hash'],
115
- outputs: t['outputs'].map do |o|
116
- {
117
- address: o['receiver'],
118
- value: o['value'] * 100_000_000
119
- }
120
- end
121
- }
122
- end
123
- }
124
- end
92
+ # This method should fetch a Blockchain block and return as a hash. Raises
93
+ # an exception if the block is not found.
94
+ def block(hash)
95
+ head = Sibit::Json.new(http: @http, log: @log).get(
96
+ Iri.new('https://api-r.bitcoinchain.com/v1/block').append(hash)
97
+ )[0]
98
+ raise Sibit::Error, "The block #{hash} is not found" if head.nil?
99
+ txs = Sibit::Json.new(http: @http, log: @log).get(
100
+ Iri.new('https://api-r.bitcoinchain.com/v1/block/txs').append(hash)
101
+ )
102
+ nxt = head['next_block']
103
+ nxt = nil if nxt == '0000000000000000000000000000000000000000000000000000000000000000'
104
+ {
105
+ provider: self.class.name,
106
+ hash: head['hash'],
107
+ orphan: !head['is_main'],
108
+ next: nxt,
109
+ previous: head['prev_block'],
110
+ txns: txs[0]['txs'].map do |t|
111
+ {
112
+ hash: t['self_hash'],
113
+ outputs: t['outputs'].map do |o|
114
+ {
115
+ address: o['receiver'],
116
+ value: o['value'] * 100_000_000
117
+ }
118
+ end
119
+ }
120
+ end
121
+ }
125
122
  end
126
123
  end