btcruby 0.0.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +18 -0
- data/.travis.yml +7 -0
- data/FAQ.md +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +18 -0
- data/HOWTO.md +17 -0
- data/LICENSE +19 -0
- data/README.md +59 -0
- data/Rakefile +6 -0
- data/TODO.txt +40 -0
- data/bin/console +19 -0
- data/btcruby.gemspec +20 -0
- data/documentation/address.md +73 -0
- data/documentation/base58.md +52 -0
- data/documentation/block.md +127 -0
- data/documentation/block_header.md +120 -0
- data/documentation/constants.md +88 -0
- data/documentation/data.md +54 -0
- data/documentation/diagnostics.md +90 -0
- data/documentation/extensions.md +76 -0
- data/documentation/hash_functions.md +58 -0
- data/documentation/hash_id.md +22 -0
- data/documentation/index.md +230 -0
- data/documentation/key.md +177 -0
- data/documentation/keychain.md +180 -0
- data/documentation/network.md +75 -0
- data/documentation/opcode.md +220 -0
- data/documentation/openssl.md +7 -0
- data/documentation/p2pkh.md +71 -0
- data/documentation/p2sh.md +64 -0
- data/documentation/proof_of_work.md +84 -0
- data/documentation/script.md +280 -0
- data/documentation/signature.md +71 -0
- data/documentation/transaction.md +213 -0
- data/documentation/transaction_builder.md +188 -0
- data/documentation/transaction_input.md +133 -0
- data/documentation/transaction_output.md +130 -0
- data/documentation/wif.md +72 -0
- data/documentation/wire_format.md +70 -0
- data/lib/btcruby/address.rb +296 -0
- data/lib/btcruby/base58.rb +108 -0
- data/lib/btcruby/big_number.rb +47 -0
- data/lib/btcruby/block.rb +170 -0
- data/lib/btcruby/block_header.rb +231 -0
- data/lib/btcruby/constants.rb +59 -0
- data/lib/btcruby/currency_formatter.rb +64 -0
- data/lib/btcruby/data.rb +98 -0
- data/lib/btcruby/diagnostics.rb +92 -0
- data/lib/btcruby/errors.rb +8 -0
- data/lib/btcruby/extensions.rb +65 -0
- data/lib/btcruby/hash_functions.rb +54 -0
- data/lib/btcruby/hash_id.rb +18 -0
- data/lib/btcruby/key.rb +517 -0
- data/lib/btcruby/keychain.rb +464 -0
- data/lib/btcruby/network.rb +73 -0
- data/lib/btcruby/opcode.rb +197 -0
- data/lib/btcruby/open_assets/asset.rb +35 -0
- data/lib/btcruby/open_assets/asset_address.rb +49 -0
- data/lib/btcruby/open_assets/asset_definition.rb +75 -0
- data/lib/btcruby/open_assets/asset_id.rb +24 -0
- data/lib/btcruby/open_assets/asset_marker.rb +94 -0
- data/lib/btcruby/open_assets/asset_processor.rb +377 -0
- data/lib/btcruby/open_assets/asset_transaction.rb +184 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/errors.rb +15 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/provider.rb +32 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/result.rb +47 -0
- data/lib/btcruby/open_assets/asset_transaction_builder.rb +418 -0
- data/lib/btcruby/open_assets/asset_transaction_input.rb +64 -0
- data/lib/btcruby/open_assets/asset_transaction_output.rb +140 -0
- data/lib/btcruby/open_assets.rb +26 -0
- data/lib/btcruby/openssl.rb +536 -0
- data/lib/btcruby/proof_of_work.rb +110 -0
- data/lib/btcruby/safety.rb +26 -0
- data/lib/btcruby/script.rb +733 -0
- data/lib/btcruby/signature_hashtype.rb +37 -0
- data/lib/btcruby/transaction.rb +511 -0
- data/lib/btcruby/transaction_builder/errors.rb +15 -0
- data/lib/btcruby/transaction_builder/provider.rb +54 -0
- data/lib/btcruby/transaction_builder/result.rb +73 -0
- data/lib/btcruby/transaction_builder/signer.rb +28 -0
- data/lib/btcruby/transaction_builder.rb +520 -0
- data/lib/btcruby/transaction_input.rb +298 -0
- data/lib/btcruby/transaction_outpoint.rb +30 -0
- data/lib/btcruby/transaction_output.rb +315 -0
- data/lib/btcruby/version.rb +3 -0
- data/lib/btcruby/wif.rb +118 -0
- data/lib/btcruby/wire_format.rb +362 -0
- data/lib/btcruby.rb +44 -2
- data/sample_code/creating_a_p2sh_multisig_address.rb +21 -0
- data/sample_code/creating_a_transaction_manually.rb +44 -0
- data/sample_code/generating_an_address.rb +20 -0
- data/sample_code/using_transaction_builder.rb +49 -0
- data/spec/address_spec.rb +206 -0
- data/spec/all.rb +6 -0
- data/spec/base58_spec.rb +83 -0
- data/spec/block_header_spec.rb +18 -0
- data/spec/block_spec.rb +18 -0
- data/spec/currency_formatter_spec.rb +46 -0
- data/spec/data_spec.rb +50 -0
- data/spec/diagnostics_spec.rb +41 -0
- data/spec/key_spec.rb +205 -0
- data/spec/keychain_spec.rb +261 -0
- data/spec/network_spec.rb +48 -0
- data/spec/open_assets/asset_address_spec.rb +33 -0
- data/spec/open_assets/asset_id_spec.rb +15 -0
- data/spec/open_assets/asset_marker_spec.rb +47 -0
- data/spec/open_assets/asset_processor_spec.rb +567 -0
- data/spec/open_assets/asset_transaction_builder_spec.rb +273 -0
- data/spec/open_assets/asset_transaction_spec.rb +70 -0
- data/spec/proof_of_work_spec.rb +53 -0
- data/spec/script_spec.rb +66 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/transaction_builder_spec.rb +338 -0
- data/spec/transaction_spec.rb +162 -0
- data/spec/wire_format_spec.rb +283 -0
- metadata +141 -7
data/lib/btcruby/key.rb
ADDED
@@ -0,0 +1,517 @@
|
|
1
|
+
# Key encapsulates EC public and private keypair (or only public part) on curve secp256k1.
|
2
|
+
# You can sign data and verify signatures.
|
3
|
+
# When instantiated with a public key, only signature verification is possible.
|
4
|
+
# When instantiated with a private key, all operations are available.
|
5
|
+
module BTC
|
6
|
+
class Key
|
7
|
+
|
8
|
+
# Flag specifying if the public key should be compressed.
|
9
|
+
# Default is true.
|
10
|
+
attr_reader :public_key_compressed
|
11
|
+
|
12
|
+
# Returns a copy of BTC::Key instance with public_key_compressed == true
|
13
|
+
attr_reader :compressed_key
|
14
|
+
|
15
|
+
# Returns a copy of BTC::Key instance with public_key_compressed == false
|
16
|
+
attr_reader :uncompressed_key
|
17
|
+
|
18
|
+
# A binary string containing a private key. Returns nil if there is only public key.
|
19
|
+
attr_reader :private_key
|
20
|
+
|
21
|
+
# A binary string containing compressed or uncompressed public key (depends on public_key_compressed flag).
|
22
|
+
attr_reader :public_key
|
23
|
+
|
24
|
+
# A binary string containing a compressed public key.
|
25
|
+
attr_reader :compressed_public_key
|
26
|
+
|
27
|
+
# A binary string containing an uncompressed public key.
|
28
|
+
attr_reader :uncompressed_public_key
|
29
|
+
|
30
|
+
# A network to which this key belongs.
|
31
|
+
# Affects how addresses and WIFs are formatted.
|
32
|
+
# Default is BTC::Network.default (mainnet if not overriden).
|
33
|
+
attr_accessor :network
|
34
|
+
|
35
|
+
COMPRESSED_PUBKEY_LENGTH = 33
|
36
|
+
UNCOMPRESSED_PUBKEY_LENGTH = 65
|
37
|
+
|
38
|
+
# Initializes a key with one of the given keys (public or private).
|
39
|
+
# Usage:
|
40
|
+
# * Key.new(private_key: ...[, public_key_compressed: ...][, network: ...])
|
41
|
+
# * Key.new(public_key: ...[, network: ...])
|
42
|
+
# * Key.new(wif: ...)
|
43
|
+
def initialize(private_key: nil,
|
44
|
+
public_key: nil,
|
45
|
+
public_key_compressed: true,
|
46
|
+
wif: nil,
|
47
|
+
network: nil)
|
48
|
+
|
49
|
+
@public_key_compressed = public_key_compressed
|
50
|
+
@network = network || BTC::Network.default
|
51
|
+
|
52
|
+
if private_key
|
53
|
+
if !Key.validate_private_key_range(private_key)
|
54
|
+
raise FormatError, "Private key is outside the valid range"
|
55
|
+
end
|
56
|
+
@private_key = private_key
|
57
|
+
elsif public_key
|
58
|
+
if !Key.valid_pubkey?(public_key)
|
59
|
+
raise FormatError, "Invalid public key: #{public_key.inspect}"
|
60
|
+
end
|
61
|
+
@public_key_compressed = (self.class.length_for_pubkey(public_key) == COMPRESSED_PUBKEY_LENGTH)
|
62
|
+
@public_key = public_key
|
63
|
+
elsif wif
|
64
|
+
wif = wif.is_a?(WIF) ? wif : Address.parse(wif)
|
65
|
+
if !wif.is_a?(WIF)
|
66
|
+
raise FormatError, "Invalid WIF string"
|
67
|
+
end
|
68
|
+
key = wif.key
|
69
|
+
@private_key = key.private_key
|
70
|
+
@public_key = key.public_key
|
71
|
+
@public_key_compressed = key.public_key_compressed
|
72
|
+
@network = wif.network
|
73
|
+
else
|
74
|
+
raise ArgumentError, "Must specify either private_key or public_key"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Creates a randomly-generated key pair.
|
79
|
+
def self.random(public_key_compressed: true, network: nil)
|
80
|
+
# Chances that we'll enter the loop are below 2^-127.
|
81
|
+
privkey = BTC::Data.random_data(32)
|
82
|
+
while !self.validate_private_key_range(privkey)
|
83
|
+
privkey = BTC::Data.random_data(32)
|
84
|
+
end
|
85
|
+
return self.new(private_key: privkey,
|
86
|
+
public_key_compressed: public_key_compressed,
|
87
|
+
network: network)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Creates an instance with public key data (compressed or not).
|
91
|
+
# You can verify signatures with that instance, but cannot sign messages.
|
92
|
+
# If the public key is compressed, public_key_compressed attribute will be set to true.
|
93
|
+
def self.with_public_key(public_key, network: nil)
|
94
|
+
raise ArgumentError, "Use Key.new(public_key: ...) instead"
|
95
|
+
end
|
96
|
+
|
97
|
+
# Creates an instance with private key data (32 bytes).
|
98
|
+
# You can sign messages and verify signatures with that instance.
|
99
|
+
# public_key_compressed is set to true as a default.
|
100
|
+
def self.with_private_key(private_key, public_key_compressed: true, network: nil)
|
101
|
+
raise ArgumentError, "Use Key.new(private_key: ...) instead"
|
102
|
+
end
|
103
|
+
|
104
|
+
# Creates an instance with a private key encoded in WIF format.
|
105
|
+
# Uses WIF internally.
|
106
|
+
def self.with_wif(wif_string)
|
107
|
+
raise ArgumentError, "Use Key.new(wif: ...) instead"
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
# Accessors
|
112
|
+
# ---------
|
113
|
+
|
114
|
+
def network
|
115
|
+
@network || BTC::Network.default
|
116
|
+
end
|
117
|
+
|
118
|
+
def compressed_key
|
119
|
+
self.class.new(private_key: @private_key,
|
120
|
+
public_key: self.compressed_public_key,
|
121
|
+
public_key_compressed: true,
|
122
|
+
network: @network)
|
123
|
+
end
|
124
|
+
|
125
|
+
def uncompressed_key
|
126
|
+
self.class.new(private_key: @private_key,
|
127
|
+
public_key: self.uncompressed_public_key,
|
128
|
+
public_key_compressed: false,
|
129
|
+
network: @network)
|
130
|
+
end
|
131
|
+
|
132
|
+
def public_key
|
133
|
+
if !@public_key
|
134
|
+
regenerate_key_pair
|
135
|
+
end
|
136
|
+
@public_key
|
137
|
+
end
|
138
|
+
|
139
|
+
def compressed_public_key
|
140
|
+
BTC::OpenSSL.public_key_with_compression(self.public_key, true)
|
141
|
+
end
|
142
|
+
|
143
|
+
def uncompressed_public_key
|
144
|
+
BTC::OpenSSL.public_key_with_compression(self.public_key, false)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Returns a PublicKeyAddress instance that encodes a public key hash.
|
148
|
+
def address(network: nil)
|
149
|
+
PublicKeyAddress.new(public_key: self.public_key, network: network)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns a WIF instance that encodes private key.
|
153
|
+
def to_wif_object(network: nil)
|
154
|
+
return nil if !self.private_key
|
155
|
+
WIF.new(key: self, network: network)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns private key encoded in WIF format (aka Sipa format).
|
159
|
+
def to_wif(network: nil)
|
160
|
+
return nil if !self.private_key
|
161
|
+
self.to_wif_object(network: network).to_s
|
162
|
+
end
|
163
|
+
|
164
|
+
def dup
|
165
|
+
self.class.new(
|
166
|
+
private_key: @private_key,
|
167
|
+
public_key: @public_key,
|
168
|
+
public_key_compressed: @public_key_compressed,
|
169
|
+
network: @network)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Two keypairs are equal only when they are equally complete (both with or
|
173
|
+
# without a private key), have matching keys and compression.
|
174
|
+
def ==(other)
|
175
|
+
self.public_key == other.public_key &&
|
176
|
+
self.private_key == other.private_key
|
177
|
+
end
|
178
|
+
alias_method :eql?, :==
|
179
|
+
|
180
|
+
# Multiplies a public key of the receiver with a given private key and
|
181
|
+
# returns resulting curve point as BTC::Key object (pubkey only).
|
182
|
+
# Pubkey compression flag is the same as on receiver.
|
183
|
+
def diffie_hellman(key2)
|
184
|
+
|
185
|
+
lib = BTC::OpenSSL
|
186
|
+
lib.autorelease do |pool|
|
187
|
+
|
188
|
+
pk = pool.new_bn(key2.private_key)
|
189
|
+
n = lib.group_order
|
190
|
+
|
191
|
+
# Convert pubkey to a EC point
|
192
|
+
pubkey_x = pool.new_bn(self.compressed_public_key)
|
193
|
+
pubkey_point = pool.new_ec_point
|
194
|
+
lib.EC_POINT_bn2point(lib.group, pubkey_x, pubkey_point, pool.bn_ctx)
|
195
|
+
|
196
|
+
# Compute point = pubkey*pk + 0*G
|
197
|
+
point = pool.new_ec_point
|
198
|
+
# /** Computes r = generator * n + q * m
|
199
|
+
# * \param group underlying EC_GROUP object
|
200
|
+
# * \param r EC_POINT object for the result
|
201
|
+
# * \param n BIGNUM with the multiplier for the group generator (optional)
|
202
|
+
# * \param q EC_POINT object with the first factor of the second summand
|
203
|
+
# * \param m BIGNUM with the second factor of the second summand
|
204
|
+
# * \param ctx BN_CTX object (optional)
|
205
|
+
# * \return 1 on success and 0 if an error occured
|
206
|
+
# */
|
207
|
+
# int EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx);
|
208
|
+
lib.EC_POINT_mul(lib.group, point, nil, pubkey_point, pk, pool.bn_ctx)
|
209
|
+
|
210
|
+
# Check for invalid derivation.
|
211
|
+
if 1 == lib.EC_POINT_is_at_infinity(lib.group, point)
|
212
|
+
raise MathError, "Resulting point is at infinity."
|
213
|
+
end
|
214
|
+
|
215
|
+
lib.EC_POINT_point2bn(
|
216
|
+
lib.group,
|
217
|
+
point,
|
218
|
+
self.public_key_compressed ?
|
219
|
+
BTC::OpenSSL::POINT_CONVERSION_COMPRESSED :
|
220
|
+
BTC::OpenSSL::POINT_CONVERSION_UNCOMPRESSED,
|
221
|
+
pubkey_x,
|
222
|
+
pool.bn_ctx
|
223
|
+
)
|
224
|
+
|
225
|
+
result_pubkey = lib.data_from_bn(pubkey_x, required_length: 33)
|
226
|
+
return Key.new(public_key: result_pubkey)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
|
232
|
+
# Signatures
|
233
|
+
# ----------
|
234
|
+
|
235
|
+
|
236
|
+
# Standard ECDSA signature for a given hash. Used by OP_CHECKSIG and friends.
|
237
|
+
# Ensures canonical lower S value and makes a deterministic signature
|
238
|
+
# (k = HMAC-SHA256(key: privkey, data: hash))
|
239
|
+
def ecdsa_signature(hash, normalized: true)
|
240
|
+
BTC::OpenSSL.ecdsa_signature(hash, @private_key, normalized: normalized)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Returns true if ECDSA signature is valid for a given hash
|
244
|
+
def verify_ecdsa_signature(signature, hash)
|
245
|
+
BTC::OpenSSL.ecdsa_verify(signature, hash, self.public_key)
|
246
|
+
end
|
247
|
+
|
248
|
+
def self.normalized_signature(signature)
|
249
|
+
BTC::OpenSSL.ecdsa_normalized_signature(signature)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Validates and normalizes script signature to make it canonical.
|
253
|
+
# Note: signature must have hashtype byte appended.
|
254
|
+
# Returns nil if signature is invalid and cannot be normalized.
|
255
|
+
# Returns original signature if it is canonical.
|
256
|
+
# Returns normalized signature script if signature can be normalized.
|
257
|
+
def self.validate_and_normalize_script_signature(data)
|
258
|
+
raise ArgumentError, "Missing script signature data" if !data || data.size == 0
|
259
|
+
if validate_script_signature(data)
|
260
|
+
return data
|
261
|
+
end
|
262
|
+
data = BTC::Data.ensure_binary_encoding(data)
|
263
|
+
normalized_sig = normalized_signature(data[0, data.size-1])
|
264
|
+
return nil if !normalized_sig
|
265
|
+
return normalized_sig + data[data.size-1, 1]
|
266
|
+
end
|
267
|
+
|
268
|
+
# Checks if this signature with appended script hash type is well-formed.
|
269
|
+
# Logs detailed info using Diagnostics and returns true or false.
|
270
|
+
# Set verify_lower_s:false when processing incoming blocks.
|
271
|
+
def self.validate_script_signature(data, verify_lower_s: true)
|
272
|
+
# See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
|
273
|
+
# A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
|
274
|
+
# Where R and S are not negative (their first byte has its highest bit not set), and not
|
275
|
+
# excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
|
276
|
+
# in which case a single 0 byte is necessary and even required).
|
277
|
+
|
278
|
+
raise ArgumentError, "Missing script signature data" if !data
|
279
|
+
|
280
|
+
data = BTC::Data.ensure_binary_encoding(data) # so we can use #[] on byte level.
|
281
|
+
|
282
|
+
length = data.bytesize
|
283
|
+
|
284
|
+
# Non-canonical signature: too short
|
285
|
+
if length < 9
|
286
|
+
Diagnostics.current.add_message("Non-canonical signature: too short.")
|
287
|
+
return false
|
288
|
+
end
|
289
|
+
|
290
|
+
# Non-canonical signature: too long
|
291
|
+
if length > 73
|
292
|
+
Diagnostics.current.add_message("Non-canonical signature: too long.")
|
293
|
+
return false
|
294
|
+
end
|
295
|
+
|
296
|
+
bytes = data.bytes
|
297
|
+
|
298
|
+
hashtype = bytes[length - 1] & (~(SIGHASH_ANYONECANPAY))
|
299
|
+
|
300
|
+
if hashtype < SIGHASH_ALL || hashtype > SIGHASH_SINGLE
|
301
|
+
Diagnostics.current.add_message("Non-canonical signature: unknown hashtype byte.")
|
302
|
+
return false
|
303
|
+
end
|
304
|
+
|
305
|
+
if bytes[0] != 0x30
|
306
|
+
Diagnostics.current.add_message("Non-canonical signature: wrong type.")
|
307
|
+
return false
|
308
|
+
end
|
309
|
+
|
310
|
+
if bytes[1] != length-3
|
311
|
+
Diagnostics.current.add_message("Non-canonical signature: wrong length marker.")
|
312
|
+
return false
|
313
|
+
end
|
314
|
+
|
315
|
+
lenR = bytes[3]
|
316
|
+
|
317
|
+
if (5 + lenR) >= length
|
318
|
+
Diagnostics.current.add_message("Non-canonical signature: S length misplaced.")
|
319
|
+
return false
|
320
|
+
end
|
321
|
+
|
322
|
+
lenS = bytes[5 + lenR]
|
323
|
+
|
324
|
+
if (lenR + lenS + 7) != length
|
325
|
+
Diagnostics.current.add_message("Non-canonical signature: R+S length mismatch")
|
326
|
+
return false
|
327
|
+
end
|
328
|
+
|
329
|
+
bufR = bytes[4, lenR]
|
330
|
+
if bytes[4 - 2] != 0x02
|
331
|
+
Diagnostics.current.add_message("Non-canonical signature: R value type mismatch")
|
332
|
+
return false
|
333
|
+
end
|
334
|
+
|
335
|
+
if lenR == 0
|
336
|
+
Diagnostics.current.add_message("Non-canonical signature: R length is zero")
|
337
|
+
return false
|
338
|
+
end
|
339
|
+
|
340
|
+
if bufR[0] & 0x80 != 0
|
341
|
+
Diagnostics.current.add_message("Non-canonical signature: R value negative")
|
342
|
+
return false
|
343
|
+
end
|
344
|
+
|
345
|
+
if lenR > 1 && (bufR[0] == 0x00) && ((bufR[1] & 0x80) == 0)
|
346
|
+
Diagnostics.current.add_message("Non-canonical signature: R value excessively padded")
|
347
|
+
return false
|
348
|
+
end
|
349
|
+
|
350
|
+
bufS = bytes[6 + lenR, lenS]
|
351
|
+
s = data[6 + lenR, lenS]
|
352
|
+
if bytes[6 + lenR - 2] != 0x02
|
353
|
+
Diagnostics.current.add_message("Non-canonical signature: S value type mismatch")
|
354
|
+
return false
|
355
|
+
end
|
356
|
+
|
357
|
+
if lenS == 0
|
358
|
+
Diagnostics.current.add_message("Non-canonical signature: S length is zero")
|
359
|
+
return false
|
360
|
+
end
|
361
|
+
|
362
|
+
if bufS[0] & 0x80 != 0
|
363
|
+
return false
|
364
|
+
Diagnostics.current.add_message("Non-canonical signature: S value is negative")
|
365
|
+
end
|
366
|
+
|
367
|
+
if lenS > 1 && (bufS[0] == 0x00) && ((bufS[1] & 0x80) == 0)
|
368
|
+
Diagnostics.current.add_message("Non-canonical signature: S value excessively padded")
|
369
|
+
return false
|
370
|
+
end
|
371
|
+
|
372
|
+
if verify_lower_s
|
373
|
+
if !self_validate_signature_element(s, check_half: true)
|
374
|
+
Diagnostics.current.add_message("Non-canonical signature: S value is unnecessarily high")
|
375
|
+
return false
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
return true
|
380
|
+
end
|
381
|
+
|
382
|
+
# Zero-filled 32-byte buffer
|
383
|
+
KEY_ZERO = "\x00"*32
|
384
|
+
|
385
|
+
# Order of secp256k1's generator minus 1.
|
386
|
+
KEY_MAX_MOD_ORDER =("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" +
|
387
|
+
"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE" +
|
388
|
+
"\xBA\xAE\xDC\xE6\xAF\x48\xA0\x3B" +
|
389
|
+
"\xBF\xD2\x5E\x8C\xD0\x36\x41\x40").b.freeze
|
390
|
+
|
391
|
+
# Half of the order of secp256k1's generator minus 1.
|
392
|
+
KEY_MAX_MOD_HALF_ORDER =("\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF" +
|
393
|
+
"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" +
|
394
|
+
"\x5D\x57\x6E\x73\x57\xA4\x50\x1D" +
|
395
|
+
"\xDF\xE9\x2F\x46\x68\x1B\x20\xA0").b.freeze
|
396
|
+
|
397
|
+
# Private helper to compare two big numbers in big-endian notation.
|
398
|
+
# Higher byte has higher value, but strings can be of different length.
|
399
|
+
def self.compare_big_endian(s1, s2)
|
400
|
+
s1 = BTC::Data.ensure_binary_encoding(s1)
|
401
|
+
s2 = BTC::Data.ensure_binary_encoding(s2)
|
402
|
+
|
403
|
+
if s1.bytesize < s2.bytesize
|
404
|
+
s1 = "\x00"*(s2.bytesize - s1.bytesize) + s1
|
405
|
+
end
|
406
|
+
|
407
|
+
if s2.bytesize < s1.bytesize
|
408
|
+
s2 = "\x00"*(s1.bytesize - s2.bytesize) + s2
|
409
|
+
end
|
410
|
+
|
411
|
+
s1 <=> s2
|
412
|
+
end
|
413
|
+
|
414
|
+
# Private helper to validate portion of a signate. Follows style of bitcoind.
|
415
|
+
def self.self_validate_signature_element(data, check_half: false)
|
416
|
+
return self.compare_big_endian(data, KEY_ZERO) > 0 &&
|
417
|
+
self.compare_big_endian(data, check_half ? KEY_MAX_MOD_HALF_ORDER : KEY_MAX_MOD_ORDER) <= 0
|
418
|
+
end
|
419
|
+
|
420
|
+
# Returns true if data representing a private key is within a valid range.
|
421
|
+
def self.validate_private_key_range(private_key)
|
422
|
+
return self.compare_big_endian(private_key, KEY_ZERO) > 0 &&
|
423
|
+
self.compare_big_endian(private_key, KEY_MAX_MOD_ORDER) <= 0
|
424
|
+
end
|
425
|
+
|
426
|
+
# Checks if this public key is valid and well-formed.
|
427
|
+
# Logs detailed info using Diagnostics and returns true or false.
|
428
|
+
def self.validate_public_key(data)
|
429
|
+
raise ArgumentError, "Missing public key" if !data
|
430
|
+
|
431
|
+
length = data.bytesize
|
432
|
+
|
433
|
+
# Non-canonical public key: too short
|
434
|
+
if length < 33
|
435
|
+
Diagnostics.current.add_message("Non-canonical public key: too short.")
|
436
|
+
return false
|
437
|
+
end
|
438
|
+
|
439
|
+
bytes = data.bytes
|
440
|
+
|
441
|
+
if bytes[0] == 0x04
|
442
|
+
# Length of an uncompressed key must be 65 bytes.
|
443
|
+
return true if length == 65
|
444
|
+
Diagnostics.current.add_message("Non-canonical public key: length of uncompressed key must be 65 bytes.")
|
445
|
+
return false
|
446
|
+
elsif bytes[0] == 0x02 || bytes[0] == 0x03
|
447
|
+
# Length of compressed key must be 33 bytes.
|
448
|
+
return true if length == 33
|
449
|
+
Diagnostics.current.add_message("Non-canonical public key: length of compressed key must be 33 bytes.")
|
450
|
+
return false
|
451
|
+
end
|
452
|
+
|
453
|
+
# Unknown public key format.
|
454
|
+
Diagnostics.current.add_message("Unknown non-canonical public key.")
|
455
|
+
return false
|
456
|
+
end
|
457
|
+
|
458
|
+
# Non-standard "compact" signature used for Bitcoin signed messages.
|
459
|
+
# It features fixed length and allows efficient extraction of a public key from it.
|
460
|
+
def compact_signature(hash)
|
461
|
+
raise BTCError, "Not implemented"
|
462
|
+
end
|
463
|
+
|
464
|
+
# Returns true if compact signature is valid for a given hash
|
465
|
+
def verify_compact_signature(signature, hash)
|
466
|
+
raise BTCError, "Not implemented"
|
467
|
+
end
|
468
|
+
|
469
|
+
# Compact signature for a given message. Prepends it with standard prefix
|
470
|
+
# "\x18Bitcoin Signed Message:\n" and encodes message in wire format.
|
471
|
+
def message_signature(message)
|
472
|
+
raise BTCError, "Not implemented"
|
473
|
+
end
|
474
|
+
|
475
|
+
def verify_message_signature(signature, message)
|
476
|
+
raise BTCError, "Not implemented"
|
477
|
+
end
|
478
|
+
|
479
|
+
|
480
|
+
|
481
|
+
|
482
|
+
protected
|
483
|
+
|
484
|
+
|
485
|
+
# Helpers
|
486
|
+
|
487
|
+
def regenerate_key_pair
|
488
|
+
if @private_key
|
489
|
+
privkey, pubkey = BTC::OpenSSL.regenerate_keypair(@private_key, public_key_compressed: @public_key_compressed)
|
490
|
+
if privkey && pubkey
|
491
|
+
@private_key = privkey
|
492
|
+
@public_key = pubkey
|
493
|
+
end
|
494
|
+
end
|
495
|
+
self
|
496
|
+
end
|
497
|
+
|
498
|
+
def self.length_for_pubkey(data)
|
499
|
+
return 0 if data.bytesize == 0
|
500
|
+
header = data.bytes[0];
|
501
|
+
if header == 2 || header == 3
|
502
|
+
return COMPRESSED_PUBKEY_LENGTH
|
503
|
+
end
|
504
|
+
if header == 4 || header == 6 || header == 7
|
505
|
+
return UNCOMPRESSED_PUBKEY_LENGTH
|
506
|
+
end
|
507
|
+
return 0
|
508
|
+
end
|
509
|
+
|
510
|
+
def self.valid_pubkey?(data)
|
511
|
+
raise ArgumentError, "Pubkey is missing" if !data
|
512
|
+
length = data.bytesize
|
513
|
+
return length > 0 && self.length_for_pubkey(data) == length
|
514
|
+
end
|
515
|
+
|
516
|
+
end
|
517
|
+
end
|