sibit 0.27.1 → 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.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -3
- data/README.md +46 -47
- data/bin/sibit +12 -14
- data/lib/sibit/bestof.rb +68 -71
- data/lib/sibit/bitcoin/base58.rb +33 -35
- data/lib/sibit/bitcoin/key.rb +64 -66
- data/lib/sibit/bitcoin/script.rb +45 -47
- data/lib/sibit/bitcoin/tx.rb +162 -164
- data/lib/sibit/bitcoin/txbuilder.rb +1 -1
- data/lib/sibit/bitcoinchain.rb +93 -96
- data/lib/sibit/blockchain.rb +115 -118
- data/lib/sibit/blockchair.rb +62 -65
- data/lib/sibit/btc.rb +147 -150
- data/lib/sibit/cex.rb +49 -50
- data/lib/sibit/cryptoapis.rb +113 -116
- data/lib/sibit/fake.rb +42 -47
- data/lib/sibit/firstof.rb +73 -76
- data/lib/sibit/http.rb +17 -20
- data/lib/sibit/json.rb +63 -66
- data/lib/sibit/version.rb +1 -1
- data/lib/sibit.rb +9 -11
- metadata +1 -1
data/lib/sibit/bitcoin/script.rb
CHANGED
|
@@ -6,53 +6,51 @@
|
|
|
6
6
|
require 'digest'
|
|
7
7
|
require_relative 'base58'
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@bytes
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Base58.encode(versioned + checksum)
|
|
55
|
-
end
|
|
9
|
+
module Sibit::Bitcoin
|
|
10
|
+
# Bitcoin Script parser.
|
|
11
|
+
#
|
|
12
|
+
# Parses standard P2PKH scripts to extract addresses.
|
|
13
|
+
#
|
|
14
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
15
|
+
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
|
16
|
+
# License:: MIT
|
|
17
|
+
class Script
|
|
18
|
+
OP_DUP = 0x76
|
|
19
|
+
OP_HASH160 = 0xa9
|
|
20
|
+
OP_EQUALVERIFY = 0x88
|
|
21
|
+
OP_CHECKSIG = 0xac
|
|
22
|
+
|
|
23
|
+
def initialize(hex)
|
|
24
|
+
@bytes = [hex].pack('H*').bytes
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def address
|
|
28
|
+
return p2pkh_address if p2pkh?
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def p2pkh?
|
|
33
|
+
@bytes.length == 25 &&
|
|
34
|
+
@bytes[0] == OP_DUP &&
|
|
35
|
+
@bytes[1] == OP_HASH160 &&
|
|
36
|
+
@bytes[2] == 20 &&
|
|
37
|
+
@bytes[23] == OP_EQUALVERIFY &&
|
|
38
|
+
@bytes[24] == OP_CHECKSIG
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def hash160
|
|
42
|
+
return nil unless p2pkh?
|
|
43
|
+
@bytes[3, 20].pack('C*').unpack1('H*')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def p2pkh_address
|
|
49
|
+
h = hash160
|
|
50
|
+
return nil unless h
|
|
51
|
+
versioned = "00#{h}"
|
|
52
|
+
checksum = Base58.check(versioned)
|
|
53
|
+
Base58.encode(versioned + checksum)
|
|
56
54
|
end
|
|
57
55
|
end
|
|
58
56
|
end
|
data/lib/sibit/bitcoin/tx.rb
CHANGED
|
@@ -8,205 +8,203 @@ require_relative 'base58'
|
|
|
8
8
|
require_relative 'key'
|
|
9
9
|
require_relative 'script'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
def add_input(hash:, index:, script:, key:)
|
|
30
|
+
@inputs << Input.new(hash, index, script, key)
|
|
31
|
+
end
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
def add_output(value, address)
|
|
34
|
+
@outputs << Output.new(value, address)
|
|
35
|
+
end
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
def hash
|
|
38
|
+
Digest::SHA256.hexdigest(Digest::SHA256.digest(payload)).reverse.scan(/../).join
|
|
39
|
+
end
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
def payload
|
|
42
|
+
sign_inputs
|
|
43
|
+
serialize
|
|
44
|
+
end
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
def hex
|
|
47
|
+
payload.unpack1('H*')
|
|
48
|
+
end
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
def in
|
|
51
|
+
@inputs
|
|
52
|
+
end
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
def out
|
|
55
|
+
@outputs
|
|
56
|
+
end
|
|
58
57
|
|
|
59
|
-
|
|
58
|
+
private
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
def sign(key, hash)
|
|
76
|
+
der = key.sign(hash)
|
|
77
|
+
repack(der)
|
|
78
|
+
end
|
|
80
79
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
def der_sig(sig)
|
|
100
|
+
data = sig + [SIGHASH_ALL].pack('C')
|
|
101
|
+
[data.length].pack('C') + data
|
|
102
|
+
end
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
def pubkey_script(pubkey)
|
|
105
|
+
[pubkey.length].pack('C') + pubkey
|
|
106
|
+
end
|
|
108
107
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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 += [
|
|
153
|
-
result
|
|
142
|
+
result += [SEQUENCE].pack('V')
|
|
154
143
|
end
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
def prev_out
|
|
177
|
+
[@hash].pack('H*')
|
|
178
|
+
end
|
|
180
179
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
end
|
|
180
|
+
def prev_out_index
|
|
181
|
+
@index
|
|
184
182
|
end
|
|
183
|
+
end
|
|
185
184
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
# Transaction output.
|
|
186
|
+
class Output
|
|
187
|
+
attr_reader :value
|
|
189
188
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
189
|
+
def initialize(value, address)
|
|
190
|
+
@value = value
|
|
191
|
+
@address = address
|
|
192
|
+
end
|
|
194
193
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
def script_hex
|
|
200
|
+
script.unpack1('H*')
|
|
201
|
+
end
|
|
203
202
|
|
|
204
|
-
|
|
203
|
+
private
|
|
205
204
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|