bitcoinrb 0.3.1 → 0.7.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/.ruby-version +1 -1
- data/.travis.yml +6 -3
- data/README.md +17 -6
- data/bitcoinrb.gemspec +9 -8
- data/exe/bitcoinrbd +5 -0
- data/lib/bitcoin.rb +35 -19
- data/lib/bitcoin/bip85_entropy.rb +111 -0
- data/lib/bitcoin/block_filter.rb +14 -0
- data/lib/bitcoin/block_header.rb +2 -0
- data/lib/bitcoin/chain_params.rb +9 -8
- data/lib/bitcoin/chainparams/regtest.yml +1 -1
- data/lib/bitcoin/chainparams/signet.yml +39 -0
- data/lib/bitcoin/chainparams/testnet.yml +1 -1
- data/lib/bitcoin/constants.rb +45 -12
- data/lib/bitcoin/descriptor.rb +1 -1
- data/lib/bitcoin/errors.rb +19 -0
- data/lib/bitcoin/ext.rb +5 -0
- data/lib/bitcoin/ext/ecdsa.rb +31 -0
- data/lib/bitcoin/ext/json_parser.rb +46 -0
- data/lib/bitcoin/ext_key.rb +50 -19
- data/lib/bitcoin/key.rb +46 -29
- data/lib/bitcoin/key_path.rb +12 -5
- data/lib/bitcoin/message.rb +79 -0
- data/lib/bitcoin/message/addr_v2.rb +34 -0
- data/lib/bitcoin/message/base.rb +17 -0
- data/lib/bitcoin/message/cf_parser.rb +16 -0
- data/lib/bitcoin/message/cfcheckpt.rb +36 -0
- data/lib/bitcoin/message/cfheaders.rb +40 -0
- data/lib/bitcoin/message/cfilter.rb +35 -0
- data/lib/bitcoin/message/fee_filter.rb +1 -1
- data/lib/bitcoin/message/filter_load.rb +3 -3
- data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
- data/lib/bitcoin/message/get_cfheaders.rb +24 -0
- data/lib/bitcoin/message/get_cfilters.rb +25 -0
- data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
- data/lib/bitcoin/message/inventory.rb +1 -1
- data/lib/bitcoin/message/merkle_block.rb +1 -1
- data/lib/bitcoin/message/network_addr.rb +141 -18
- data/lib/bitcoin/message/ping.rb +1 -1
- data/lib/bitcoin/message/pong.rb +1 -1
- data/lib/bitcoin/message/send_addr_v2.rb +13 -0
- data/lib/bitcoin/message/send_cmpct.rb +2 -2
- data/lib/bitcoin/message/version.rb +7 -0
- data/lib/bitcoin/mnemonic.rb +7 -7
- data/lib/bitcoin/network/peer.rb +9 -4
- data/lib/bitcoin/network/peer_discovery.rb +1 -1
- data/lib/bitcoin/node/cli.rb +14 -10
- data/lib/bitcoin/node/configuration.rb +3 -1
- data/lib/bitcoin/node/spv.rb +9 -1
- data/lib/bitcoin/opcodes.rb +14 -1
- data/lib/bitcoin/out_point.rb +7 -0
- data/lib/bitcoin/payment_code.rb +92 -0
- data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
- data/lib/bitcoin/psbt/input.rb +8 -17
- data/lib/bitcoin/psbt/output.rb +1 -1
- data/lib/bitcoin/psbt/tx.rb +11 -16
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
- data/lib/bitcoin/rpc/request_handler.rb +3 -3
- data/lib/bitcoin/script/script.rb +68 -28
- data/lib/bitcoin/script/script_error.rb +27 -1
- data/lib/bitcoin/script/script_interpreter.rb +164 -67
- data/lib/bitcoin/script/tx_checker.rb +64 -14
- data/lib/bitcoin/secp256k1.rb +1 -0
- data/lib/bitcoin/secp256k1/native.rb +138 -25
- data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
- data/lib/bitcoin/secp256k1/ruby.rb +82 -54
- data/lib/bitcoin/sighash_generator.rb +156 -0
- data/lib/bitcoin/store.rb +2 -1
- data/lib/bitcoin/store/chain_entry.rb +1 -0
- data/lib/bitcoin/store/db/level_db.rb +2 -2
- data/lib/bitcoin/store/utxo_db.rb +226 -0
- data/lib/bitcoin/tx.rb +17 -88
- data/lib/bitcoin/tx_in.rb +4 -5
- data/lib/bitcoin/tx_out.rb +2 -3
- data/lib/bitcoin/util.rb +34 -6
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet.rb +1 -0
- data/lib/bitcoin/wallet/account.rb +2 -1
- data/lib/bitcoin/wallet/base.rb +3 -3
- data/lib/bitcoin/wallet/db.rb +1 -1
- data/lib/bitcoin/wallet/master_key.rb +1 -0
- data/lib/bitcoin/wallet/utxo.rb +37 -0
- metadata +66 -32
data/lib/bitcoin/secp256k1.rb
CHANGED
@@ -50,6 +50,10 @@ module Bitcoin
|
|
50
50
|
attach_function(:secp256k1_ecdsa_signature_parse_der, [:pointer, :pointer, :pointer, :size_t], :int)
|
51
51
|
attach_function(:secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int)
|
52
52
|
attach_function(:secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int)
|
53
|
+
attach_function(:secp256k1_schnorrsig_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
54
|
+
attach_function(:secp256k1_schnorrsig_verify, [:pointer, :pointer, :pointer, :pointer], :int)
|
55
|
+
attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
|
56
|
+
attach_function(:secp256k1_xonly_pubkey_parse, [:pointer, :pointer, :pointer], :int)
|
53
57
|
end
|
54
58
|
|
55
59
|
def with_context(flags: (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN))
|
@@ -97,13 +101,117 @@ module Bitcoin
|
|
97
101
|
|
98
102
|
# sign data.
|
99
103
|
# @param [String] data a data to be signed with binary format
|
100
|
-
# @param [String] privkey a private key using sign
|
101
|
-
# @param [String] extra_entropy a extra entropy for rfc6979
|
104
|
+
# @param [String] privkey a private key with hex format using sign
|
105
|
+
# @param [String] extra_entropy a extra entropy with binary format for rfc6979
|
106
|
+
# @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
|
102
107
|
# @return [String] signature data with binary format
|
103
|
-
def sign_data(data, privkey, extra_entropy)
|
108
|
+
def sign_data(data, privkey, extra_entropy = nil, algo: :ecdsa)
|
109
|
+
case algo
|
110
|
+
when :ecdsa
|
111
|
+
sign_ecdsa(data, privkey, extra_entropy)
|
112
|
+
when :schnorr
|
113
|
+
sign_schnorr(data, privkey, extra_entropy)
|
114
|
+
else
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# verify signature
|
120
|
+
# @param [String] data a data with binary format.
|
121
|
+
# @param [String] sig signature data with binary format
|
122
|
+
# @param [String] pubkey a public key with hex format using verify.
|
123
|
+
# # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
|
124
|
+
# @return [Boolean] verification result.
|
125
|
+
def verify_sig(data, sig, pubkey, algo: :ecdsa)
|
126
|
+
case algo
|
127
|
+
when :ecdsa
|
128
|
+
verify_ecdsa(data, sig, pubkey)
|
129
|
+
when :schnorr
|
130
|
+
verify_schnorr(data, sig, pubkey)
|
131
|
+
else
|
132
|
+
false
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# # validate whether this is a valid public key (more expensive than IsValid())
|
137
|
+
# @param [String] pub_key public key with hex format.
|
138
|
+
# @param [Boolean] allow_hybrid whether support hybrid public key.
|
139
|
+
# @return [Boolean] If valid public key return true, otherwise false.
|
140
|
+
def parse_ec_pubkey?(pub_key, allow_hybrid = false)
|
141
|
+
pub_key = pub_key.htb
|
142
|
+
return false if !allow_hybrid && ![0x02, 0x03, 0x04].include?(pub_key[0].ord)
|
143
|
+
with_context do |context|
|
144
|
+
pubkey = FFI::MemoryPointer.new(:uchar, pub_key.bytesize).put_bytes(0, pub_key)
|
145
|
+
internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
146
|
+
result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pub_key.bytesize)
|
147
|
+
result == 1
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Create key pair data from private key.
|
152
|
+
# @param [String] priv_key with hex format
|
153
|
+
# @return [String] key pair data with hex format. data = private key(32 bytes) | public key(64 bytes).
|
154
|
+
def create_keypair(priv_key)
|
155
|
+
with_context do |context|
|
156
|
+
priv_key = priv_key.htb
|
157
|
+
secret = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key)
|
158
|
+
raise 'priv_key is invalid.' unless secp256k1_ec_seckey_verify(context, secret)
|
159
|
+
keypair = FFI::MemoryPointer.new(:uchar, 96)
|
160
|
+
raise 'priv_key is invalid.' unless secp256k1_keypair_create(context, keypair, secret) == 1
|
161
|
+
keypair.read_string(96).bth
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Check whether valid x-only public key or not.
|
166
|
+
# @param [String] pub_key x-only public key with hex format(32 bytes).
|
167
|
+
# @return [Boolean] result.
|
168
|
+
def valid_xonly_pubkey?(pub_key)
|
169
|
+
begin
|
170
|
+
full_pubkey_from_xonly_pubkey(pub_key)
|
171
|
+
rescue Exception
|
172
|
+
return false
|
173
|
+
end
|
174
|
+
true
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
# Calculate full public key(64 bytes) from public key(32 bytes).
|
180
|
+
# @param [String] pub_key x-only public key with hex format(32 bytes).
|
181
|
+
# @return [String] x-only public key with hex format(64 bytes).
|
182
|
+
def full_pubkey_from_xonly_pubkey(pub_key)
|
183
|
+
with_context do |context|
|
184
|
+
pubkey = pub_key.htb
|
185
|
+
raise ArgumentError, 'Pubkey size must be 32 bytes.' unless pubkey.bytesize == 32
|
186
|
+
xonly_pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
|
187
|
+
full_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
188
|
+
raise ArgumentError, 'An invalid public key was specified.' unless secp256k1_xonly_pubkey_parse(context, full_pubkey, xonly_pubkey) == 1
|
189
|
+
full_pubkey.read_string(64).bth
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def generate_pubkey_in_context(context, privkey, compressed: true)
|
194
|
+
internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
195
|
+
result = secp256k1_ec_pubkey_create(context, internal_pubkey, privkey.htb)
|
196
|
+
raise 'error creating pubkey' unless result
|
197
|
+
|
198
|
+
pubkey = FFI::MemoryPointer.new(:uchar, 65)
|
199
|
+
pubkey_len = FFI::MemoryPointer.new(:uint64)
|
200
|
+
result = if compressed
|
201
|
+
pubkey_len.put_uint64(0, 33)
|
202
|
+
secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_COMPRESSED)
|
203
|
+
else
|
204
|
+
pubkey_len.put_uint64(0, 65)
|
205
|
+
secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_UNCOMPRESSED)
|
206
|
+
end
|
207
|
+
raise 'error serialize pubkey' unless result || pubkey_len.read_uint64 > 0
|
208
|
+
pubkey.read_string(pubkey_len.read_uint64).bth
|
209
|
+
end
|
210
|
+
|
211
|
+
def sign_ecdsa(data, privkey, extra_entropy)
|
104
212
|
with_context do |context|
|
105
213
|
secret = FFI::MemoryPointer.new(:uchar, privkey.htb.bytesize).put_bytes(0, privkey.htb)
|
106
|
-
raise 'priv_key invalid' unless secp256k1_ec_seckey_verify(context, secret)
|
214
|
+
raise 'priv_key is invalid' unless secp256k1_ec_seckey_verify(context, secret)
|
107
215
|
|
108
216
|
internal_signature = FFI::MemoryPointer.new(:uchar, 64)
|
109
217
|
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
|
@@ -126,11 +234,23 @@ module Bitcoin
|
|
126
234
|
end
|
127
235
|
end
|
128
236
|
|
129
|
-
def
|
237
|
+
def sign_schnorr(data, privkey, aux_rand = nil)
|
130
238
|
with_context do |context|
|
131
|
-
|
239
|
+
keypair = create_keypair(privkey).htb
|
240
|
+
keypair = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
|
241
|
+
signature = FFI::MemoryPointer.new(:uchar, 64)
|
242
|
+
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
|
243
|
+
aux_rand = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, aux_rand) if aux_rand
|
244
|
+
raise 'Failed to generate schnorr signature.' unless secp256k1_schnorrsig_sign(context, signature, msg32, keypair, nil, aux_rand) == 1
|
245
|
+
signature.read_string(64)
|
246
|
+
end
|
247
|
+
end
|
132
248
|
|
133
|
-
|
249
|
+
def verify_ecdsa(data, sig, pubkey)
|
250
|
+
with_context do |context|
|
251
|
+
return false if data.bytesize == 0
|
252
|
+
pubkey = pubkey.htb
|
253
|
+
pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
|
134
254
|
internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
135
255
|
result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pubkey.size)
|
136
256
|
return false unless result
|
@@ -150,25 +270,18 @@ module Bitcoin
|
|
150
270
|
end
|
151
271
|
end
|
152
272
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
pubkey_len.put_uint64(0, 33)
|
164
|
-
secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_COMPRESSED)
|
165
|
-
else
|
166
|
-
pubkey_len.put_uint64(0, 65)
|
167
|
-
secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_UNCOMPRESSED)
|
168
|
-
end
|
169
|
-
raise 'error serialize pubkey' unless result || pubkey_len.read_uint64 > 0
|
170
|
-
pubkey.read_string(pubkey_len.read_uint64).bth
|
273
|
+
def verify_schnorr(data, sig, pubkey)
|
274
|
+
with_context do |context|
|
275
|
+
return false if data.bytesize == 0
|
276
|
+
pubkey = full_pubkey_from_xonly_pubkey(pubkey).htb
|
277
|
+
xonly_pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
|
278
|
+
signature = FFI::MemoryPointer.new(:uchar, sig.bytesize).put_bytes(0, sig)
|
279
|
+
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
|
280
|
+
result = secp256k1_schnorrsig_verify(context, signature, msg32, xonly_pubkey)
|
281
|
+
result == 1
|
282
|
+
end
|
171
283
|
end
|
284
|
+
|
172
285
|
end
|
173
286
|
end
|
174
287
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Secp256k1
|
3
|
+
module RFC6979
|
4
|
+
|
5
|
+
INITIAL_V = '0101010101010101010101010101010101010101010101010101010101010101'.htb
|
6
|
+
INITIAL_K = '0000000000000000000000000000000000000000000000000000000000000000'.htb
|
7
|
+
ZERO_B = '00'.htb
|
8
|
+
ONE_B = '01'.htb
|
9
|
+
|
10
|
+
module_function
|
11
|
+
|
12
|
+
# generate temporary key k to be used when ECDSA sign.
|
13
|
+
# https://tools.ietf.org/html/rfc6979#section-3.2
|
14
|
+
# @param [String] key_data a data contains private key and message.
|
15
|
+
# @param [String] extra_entropy extra entropy with binary format.
|
16
|
+
# @return [Integer] a nonce.
|
17
|
+
def generate_rfc6979_nonce(key_data, extra_entropy)
|
18
|
+
v = INITIAL_V # 3.2.b
|
19
|
+
k = INITIAL_K # 3.2.c
|
20
|
+
# 3.2.d
|
21
|
+
k = Bitcoin.hmac_sha256(k, v + ZERO_B + key_data + extra_entropy)
|
22
|
+
# 3.2.e
|
23
|
+
v = Bitcoin.hmac_sha256(k, v)
|
24
|
+
# 3.2.f
|
25
|
+
k = Bitcoin.hmac_sha256(k, v + ONE_B + key_data + extra_entropy)
|
26
|
+
# 3.2.g
|
27
|
+
v = Bitcoin.hmac_sha256(k, v)
|
28
|
+
# 3.2.h
|
29
|
+
t = ''
|
30
|
+
10000.times do
|
31
|
+
v = Bitcoin.hmac_sha256(k, v)
|
32
|
+
t = (t + v)
|
33
|
+
t_num = t.bth.to_i(16)
|
34
|
+
return t_num if 1 <= t_num && t_num < Bitcoin::Secp256k1::GROUP.order
|
35
|
+
k = Bitcoin.hmac_sha256(k, v + '00'.htb)
|
36
|
+
v = Bitcoin.hmac_sha256(k, v)
|
37
|
+
end
|
38
|
+
raise 'A valid nonce was not found.'
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -12,8 +12,8 @@ module Bitcoin
|
|
12
12
|
private_key = 1 + SecureRandom.random_number(GROUP.order - 1)
|
13
13
|
public_key = GROUP.generator.multiply_by_scalar(private_key)
|
14
14
|
privkey = ECDSA::Format::IntegerOctetString.encode(private_key, 32)
|
15
|
-
pubkey =
|
16
|
-
[privkey.bth, pubkey
|
15
|
+
pubkey = public_key.to_hex(compressed)
|
16
|
+
[privkey.bth, pubkey]
|
17
17
|
end
|
18
18
|
|
19
19
|
# generate bitcoin key object
|
@@ -24,18 +24,87 @@ module Bitcoin
|
|
24
24
|
|
25
25
|
def generate_pubkey(privkey, compressed: true)
|
26
26
|
public_key = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(privkey.to_i(16))
|
27
|
-
|
27
|
+
public_key.to_hex(compressed)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Check whether valid x-only public key or not.
|
31
|
+
# @param [String] pub_key x-only public key with hex format(32 bytes).
|
32
|
+
# @return [Boolean] result.
|
33
|
+
def valid_xonly_pubkey?(pub_key)
|
34
|
+
pubkey = pub_key.htb
|
35
|
+
return false unless pubkey.bytesize == 32
|
36
|
+
begin
|
37
|
+
ECDSA::Format::PointOctetString.decode(pubkey, ECDSA::Group::Secp256k1)
|
38
|
+
rescue Exception
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
true
|
28
42
|
end
|
29
43
|
|
30
44
|
# sign data.
|
31
45
|
# @param [String] data a data to be signed with binary format
|
32
46
|
# @param [String] privkey a private key using sign
|
47
|
+
# @param [String] extra_entropy a extra entropy with binary format for rfc6979
|
48
|
+
# @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
|
33
49
|
# @return [String] signature data with binary format
|
34
|
-
def sign_data(data, privkey, extra_entropy)
|
50
|
+
def sign_data(data, privkey, extra_entropy = nil, algo: :ecdsa)
|
51
|
+
case algo
|
52
|
+
when :ecdsa
|
53
|
+
sign_ecdsa(data, privkey, extra_entropy)
|
54
|
+
when :schnorr
|
55
|
+
sign_schnorr(data, privkey, extra_entropy)
|
56
|
+
else
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# verify signature using public key
|
62
|
+
# @param [String] data a SHA-256 message digest with binary format
|
63
|
+
# @param [String] sig a signature for +data+ with binary format
|
64
|
+
# @param [String] pubkey a public key with hex format.
|
65
|
+
# @return [Boolean] verify result
|
66
|
+
def verify_sig(data, sig, pubkey, algo: :ecdsa)
|
67
|
+
case algo
|
68
|
+
when :ecdsa
|
69
|
+
verify_ecdsa(data, sig, pubkey)
|
70
|
+
when :schnorr
|
71
|
+
verify_schnorr(data, sig, pubkey)
|
72
|
+
else
|
73
|
+
false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# if +pubkey+ is hybrid public key format, it convert uncompressed format.
|
78
|
+
# https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2012-June/001578.html
|
79
|
+
def repack_pubkey(pubkey)
|
80
|
+
p = pubkey.htb
|
81
|
+
case p[0]
|
82
|
+
when "\x06", "\x07"
|
83
|
+
p[0] = "\x04"
|
84
|
+
p
|
85
|
+
else
|
86
|
+
pubkey.htb
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# validate whether this is a valid public key (more expensive than IsValid())
|
91
|
+
# @param [String] pubkey public key with hex format.
|
92
|
+
# @param [Boolean] allow_hybrid whether support hybrid public key.
|
93
|
+
# @return [Boolean] If valid public key return true, otherwise false.
|
94
|
+
def parse_ec_pubkey?(pubkey, allow_hybrid = false)
|
95
|
+
begin
|
96
|
+
point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1, allow_hybrid: allow_hybrid)
|
97
|
+
ECDSA::Group::Secp256k1.valid_public_key?(point)
|
98
|
+
rescue ECDSA::Format::DecodeError
|
99
|
+
false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def sign_ecdsa(data, privkey, extra_entropy)
|
35
104
|
privkey = privkey.htb
|
36
105
|
private_key = ECDSA::Format::IntegerOctetString.decode(privkey)
|
37
106
|
extra_entropy ||= ''
|
38
|
-
nonce = generate_rfc6979_nonce(data,
|
107
|
+
nonce = RFC6979.generate_rfc6979_nonce(privkey + data, extra_entropy)
|
39
108
|
|
40
109
|
# port form ecdsa gem.
|
41
110
|
r_point = GROUP.new_point(nonce)
|
@@ -59,65 +128,24 @@ module Bitcoin
|
|
59
128
|
signature
|
60
129
|
end
|
61
130
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
def verify_sig(digest, sig, pubkey)
|
131
|
+
def sign_schnorr(data, privkey, aux_rand)
|
132
|
+
aux_rand ? Schnorr.sign(data, privkey.htb, aux_rand).encode : Schnorr.sign(data, privkey.htb).encode
|
133
|
+
end
|
134
|
+
|
135
|
+
def verify_ecdsa(data, sig, pubkey)
|
68
136
|
begin
|
69
137
|
k = ECDSA::Format::PointOctetString.decode(repack_pubkey(pubkey), GROUP)
|
70
138
|
signature = ECDSA::Format::SignatureDerString.decode(sig)
|
71
|
-
ECDSA.valid_signature?(k,
|
139
|
+
ECDSA.valid_signature?(k, data, signature)
|
72
140
|
rescue Exception
|
73
141
|
false
|
74
142
|
end
|
75
143
|
end
|
76
144
|
|
77
|
-
|
78
|
-
|
79
|
-
def repack_pubkey(pubkey)
|
80
|
-
p = pubkey.htb
|
81
|
-
case p[0]
|
82
|
-
when "\x06", "\x07"
|
83
|
-
p[0] = "\x04"
|
84
|
-
p
|
85
|
-
else
|
86
|
-
pubkey.htb
|
87
|
-
end
|
145
|
+
def verify_schnorr(data, sig, pubkey)
|
146
|
+
Schnorr.valid_sig?(data, pubkey.htb, sig)
|
88
147
|
end
|
89
148
|
|
90
|
-
INITIAL_V = '0101010101010101010101010101010101010101010101010101010101010101'.htb
|
91
|
-
INITIAL_K = '0000000000000000000000000000000000000000000000000000000000000000'.htb
|
92
|
-
ZERO_B = '00'.htb
|
93
|
-
ONE_B = '01'.htb
|
94
|
-
|
95
|
-
# generate temporary key k to be used when ECDSA sign.
|
96
|
-
# https://tools.ietf.org/html/rfc6979#section-3.2
|
97
|
-
def generate_rfc6979_nonce(data, privkey, extra_entropy)
|
98
|
-
v = INITIAL_V # 3.2.b
|
99
|
-
k = INITIAL_K # 3.2.c
|
100
|
-
# 3.2.d
|
101
|
-
k = Bitcoin.hmac_sha256(k, v + ZERO_B + privkey + data + extra_entropy)
|
102
|
-
# 3.2.e
|
103
|
-
v = Bitcoin.hmac_sha256(k, v)
|
104
|
-
# 3.2.f
|
105
|
-
k = Bitcoin.hmac_sha256(k, v + ONE_B + privkey + data + extra_entropy)
|
106
|
-
# 3.2.g
|
107
|
-
v = Bitcoin.hmac_sha256(k, v)
|
108
|
-
# 3.2.h
|
109
|
-
t = ''
|
110
|
-
10000.times do
|
111
|
-
v = Bitcoin.hmac_sha256(k, v)
|
112
|
-
t = (t + v)
|
113
|
-
t_num = t.bth.to_i(16)
|
114
|
-
return t_num if 1 <= t_num && t_num < GROUP.order
|
115
|
-
k = Bitcoin.hmac_sha256(k, v + '00'.htb)
|
116
|
-
v = Bitcoin.hmac_sha256(k, v)
|
117
|
-
end
|
118
|
-
raise 'A valid nonce was not found.'
|
119
|
-
end
|
120
149
|
end
|
121
|
-
|
122
150
|
end
|
123
151
|
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
|
3
|
+
module SigHashGenerator
|
4
|
+
|
5
|
+
def self.load(sig_ver)
|
6
|
+
case sig_ver
|
7
|
+
when :base
|
8
|
+
LegacySigHashGenerator.new
|
9
|
+
when :witness_v0
|
10
|
+
SegwitSigHashGenerator.new
|
11
|
+
when :taproot, :tapscript
|
12
|
+
SchnorrSigHashGenerator.new
|
13
|
+
else
|
14
|
+
raise ArgumentError, "Unsupported sig version specified. #{sig_ver}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Legacy SigHash Generator
|
19
|
+
class LegacySigHashGenerator
|
20
|
+
|
21
|
+
def generate(tx, input_index, hash_type, opts)
|
22
|
+
output_script = opts[:script_code]
|
23
|
+
ins = tx.inputs.map.with_index do |i, idx|
|
24
|
+
if idx == input_index
|
25
|
+
i.to_payload(output_script.delete_opcode(Bitcoin::Opcodes::OP_CODESEPARATOR))
|
26
|
+
else
|
27
|
+
case hash_type & 0x1f
|
28
|
+
when SIGHASH_TYPE[:none], SIGHASH_TYPE[:single]
|
29
|
+
i.to_payload(Bitcoin::Script.new, 0)
|
30
|
+
else
|
31
|
+
i.to_payload(Bitcoin::Script.new)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
outs = tx.outputs.map(&:to_payload)
|
37
|
+
out_size = Bitcoin.pack_var_int(tx.outputs.size)
|
38
|
+
|
39
|
+
case hash_type & 0x1f
|
40
|
+
when SIGHASH_TYPE[:none]
|
41
|
+
outs = ''
|
42
|
+
out_size = Bitcoin.pack_var_int(0)
|
43
|
+
when SIGHASH_TYPE[:single]
|
44
|
+
return "\x01".ljust(32, "\x00") if input_index >= tx.outputs.size
|
45
|
+
outs = tx.outputs[0...(input_index + 1)].map.with_index { |o, idx| (idx == input_index) ? o.to_payload : o.to_empty_payload }.join
|
46
|
+
out_size = Bitcoin.pack_var_int(input_index + 1)
|
47
|
+
end
|
48
|
+
|
49
|
+
ins = [ins[input_index]] unless hash_type & SIGHASH_TYPE[:anyonecanpay] == 0
|
50
|
+
|
51
|
+
buf = [[tx.version].pack('V'), Bitcoin.pack_var_int(ins.size),
|
52
|
+
ins, out_size, outs, [tx.lock_time, hash_type].pack('VV')].join
|
53
|
+
|
54
|
+
Bitcoin.double_sha256(buf)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
# V0 witness sighash generator.
|
60
|
+
# see: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
|
61
|
+
class SegwitSigHashGenerator
|
62
|
+
|
63
|
+
def generate(tx, input_index, hash_type, opts)
|
64
|
+
amount = opts[:amount]
|
65
|
+
output_script = opts[:script_code]
|
66
|
+
skip_separator_index = opts[:skip_separator_index]
|
67
|
+
hash_prevouts = Bitcoin.double_sha256(tx.inputs.map{|i|i.out_point.to_payload}.join)
|
68
|
+
hash_sequence = Bitcoin.double_sha256(tx.inputs.map{|i|[i.sequence].pack('V')}.join)
|
69
|
+
outpoint = tx.inputs[input_index].out_point.to_payload
|
70
|
+
amount = [amount].pack('Q')
|
71
|
+
nsequence = [tx.inputs[input_index].sequence].pack('V')
|
72
|
+
hash_outputs = Bitcoin.double_sha256(tx.outputs.map{|o|o.to_payload}.join)
|
73
|
+
|
74
|
+
script_code = output_script.to_script_code(skip_separator_index)
|
75
|
+
|
76
|
+
case (hash_type & 0x1f)
|
77
|
+
when SIGHASH_TYPE[:single]
|
78
|
+
hash_outputs = input_index >= tx.outputs.size ? "\x00".ljust(32, "\x00") : Bitcoin.double_sha256(tx.outputs[input_index].to_payload)
|
79
|
+
hash_sequence = "\x00".ljust(32, "\x00")
|
80
|
+
when SIGHASH_TYPE[:none]
|
81
|
+
hash_sequence = hash_outputs = "\x00".ljust(32, "\x00")
|
82
|
+
end
|
83
|
+
|
84
|
+
unless (hash_type & SIGHASH_TYPE[:anyonecanpay]) == 0
|
85
|
+
hash_prevouts = hash_sequence ="\x00".ljust(32, "\x00")
|
86
|
+
end
|
87
|
+
|
88
|
+
buf = [ [tx.version].pack('V'), hash_prevouts, hash_sequence, outpoint,
|
89
|
+
script_code ,amount, nsequence, hash_outputs, [tx.lock_time, hash_type].pack('VV')].join
|
90
|
+
Bitcoin.double_sha256(buf)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
# v1 witness sighash generator
|
96
|
+
# see: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki
|
97
|
+
class SchnorrSigHashGenerator
|
98
|
+
|
99
|
+
# generate signature hash for taproot and tapscript
|
100
|
+
# @param [Hash] opts some data using signature. This class requires following key params:
|
101
|
+
# - sig_version: sig version. :taproot or :tapscript
|
102
|
+
# - prevouts: array of all prevout[Txout]
|
103
|
+
# - annex: annex value with binary format if annex exist.
|
104
|
+
# - leaf_hash: leaf hash with binary format if sig_version is :tapscript, it required
|
105
|
+
# - last_code_separator_pos: the position of last code separator
|
106
|
+
# @return [String] signature hash with binary format.
|
107
|
+
def generate(tx, input_index, hash_type, opts)
|
108
|
+
raise ArgumentError, 'Invalid sig_version was specified.' unless [:taproot, :tapscript].include?(opts[:sig_version])
|
109
|
+
|
110
|
+
ext_flag = opts[:sig_version] == :taproot ? 0 : 1
|
111
|
+
key_version = 0
|
112
|
+
output_ype = hash_type == SIGHASH_TYPE[:default] ? SIGHASH_TYPE[:all] : (hash_type & 0x03)
|
113
|
+
input_type = hash_type & 0x80
|
114
|
+
epoc = '00'.htb
|
115
|
+
|
116
|
+
buf = epoc # EPOC
|
117
|
+
buf << [hash_type, tx.version, tx.lock_time].pack('CVV')
|
118
|
+
unless input_type == SIGHASH_TYPE[:anyonecanpay]
|
119
|
+
buf << Bitcoin.sha256(tx.in.map{|i|i.out_point.to_payload}.join) # sha_prevouts
|
120
|
+
buf << Bitcoin.sha256(opts[:prevouts].map(&:value).pack('Q*'))# sha_amounts
|
121
|
+
buf << Bitcoin.sha256(opts[:prevouts].map{|o|o.script_pubkey.to_payload(true)}.join) # sha_scriptpubkeys
|
122
|
+
buf << Bitcoin.sha256(tx.in.map(&:sequence).pack('V*')) # sha_sequences
|
123
|
+
end
|
124
|
+
|
125
|
+
buf << Bitcoin.sha256(tx.out.map(&:to_payload).join) if output_ype == SIGHASH_TYPE[:all]
|
126
|
+
|
127
|
+
spend_type = (ext_flag << 1) + (opts[:annex] ? 1 : 0)
|
128
|
+
buf << [spend_type].pack('C')
|
129
|
+
if input_type == SIGHASH_TYPE[:anyonecanpay]
|
130
|
+
buf << tx.in[input_index].out_point.to_payload
|
131
|
+
buf << opts[:prevouts][input_index].to_payload
|
132
|
+
buf << [tx.in[input_index].sequence].pack('V')
|
133
|
+
else
|
134
|
+
buf << [input_index].pack('V')
|
135
|
+
end
|
136
|
+
|
137
|
+
buf << Bitcoin.sha256(Bitcoin.pack_var_string(opts[:annex])) if opts[:annex]
|
138
|
+
|
139
|
+
if output_ype == SIGHASH_TYPE[:single]
|
140
|
+
raise ArgumentError, "Tx does not have #{input_index} th output." if input_index >= tx.out.size
|
141
|
+
buf << Bitcoin.sha256(tx.out[input_index].to_payload)
|
142
|
+
end
|
143
|
+
|
144
|
+
if opts[:sig_version] == :tapscript
|
145
|
+
buf << opts[:leaf_hash]
|
146
|
+
buf << [key_version, opts[:last_code_separator_pos]].pack("CV")
|
147
|
+
end
|
148
|
+
|
149
|
+
Bitcoin.tagged_hash('TapSighash', buf)
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|