monacoin-ruby 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/bitcoin.rb +933 -0
- data/lib/bitcoin/bloom_filter.rb +125 -0
- data/lib/bitcoin/builder.rb +494 -0
- data/lib/bitcoin/connection.rb +130 -0
- data/lib/bitcoin/contracthash.rb +76 -0
- data/lib/bitcoin/dogecoin.rb +97 -0
- data/lib/bitcoin/electrum/mnemonic.rb +162 -0
- data/lib/bitcoin/ext_key.rb +191 -0
- data/lib/bitcoin/ffi/bitcoinconsensus.rb +75 -0
- data/lib/bitcoin/ffi/openssl.rb +388 -0
- data/lib/bitcoin/ffi/secp256k1.rb +266 -0
- data/lib/bitcoin/key.rb +280 -0
- data/lib/bitcoin/litecoin.rb +83 -0
- data/lib/bitcoin/logger.rb +86 -0
- data/lib/bitcoin/protocol.rb +189 -0
- data/lib/bitcoin/protocol/address.rb +50 -0
- data/lib/bitcoin/protocol/alert.rb +46 -0
- data/lib/bitcoin/protocol/aux_pow.rb +123 -0
- data/lib/bitcoin/protocol/block.rb +285 -0
- data/lib/bitcoin/protocol/handler.rb +43 -0
- data/lib/bitcoin/protocol/parser.rb +194 -0
- data/lib/bitcoin/protocol/partial_merkle_tree.rb +61 -0
- data/lib/bitcoin/protocol/reject.rb +38 -0
- data/lib/bitcoin/protocol/script_witness.rb +31 -0
- data/lib/bitcoin/protocol/tx.rb +587 -0
- data/lib/bitcoin/protocol/txin.rb +142 -0
- data/lib/bitcoin/protocol/txout.rb +95 -0
- data/lib/bitcoin/protocol/version.rb +88 -0
- data/lib/bitcoin/script.rb +1656 -0
- data/lib/bitcoin/trezor/mnemonic.rb +130 -0
- data/lib/bitcoin/version.rb +3 -0
- metadata +32 -1
@@ -0,0 +1,266 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
# bindings for secp256k1 inside bitcoin (https://github.com/bitcoin/bitcoin/tree/v0.13.1/src/secp256k1)
|
4
|
+
# tag: v0.13.1
|
5
|
+
# commit: 03422e564b552c1d3c16ae854f8471f7cb39e25d
|
6
|
+
# bitcoin@master% git checkout v0.13.1
|
7
|
+
# bitcoin@tags/v0.13.1^0% cd src/secp256k1
|
8
|
+
# bitcoin@tags/v0.13.1^0 src/secp256k1% ./autogen.sh
|
9
|
+
# bitcoin@tags/v0.13.1^0 src/secp256k1% ./configure --enable-module-recovery
|
10
|
+
# bitcoin@tags/v0.13.1^0 src/secp256k1% make libsecp256k1.la
|
11
|
+
# bitcoin@tags/v0.13.1^0 src/secp256k1% nm -D .libs/libsecp256k1.so.0.0.0 | grep secp
|
12
|
+
# export SECP256K1_LIB_PATH=/path/to/bitcoin/src/secp256k1/.libs/libsecp256k1.so.0.0.0
|
13
|
+
|
14
|
+
require 'ffi'
|
15
|
+
|
16
|
+
module Bitcoin
|
17
|
+
module Secp256k1
|
18
|
+
extend FFI::Library
|
19
|
+
|
20
|
+
SECP256K1_FLAGS_TYPE_MASK = ((1 << 8) - 1)
|
21
|
+
SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0)
|
22
|
+
SECP256K1_FLAGS_TYPE_COMPRESSION = (1 << 1)
|
23
|
+
|
24
|
+
# The higher bits contain the actual data. Do not use directly.
|
25
|
+
SECP256K1_FLAGS_BIT_CONTEXT_VERIFY = (1 << 8)
|
26
|
+
SECP256K1_FLAGS_BIT_CONTEXT_SIGN = (1 << 9)
|
27
|
+
SECP256K1_FLAGS_BIT_COMPRESSION = (1 << 8)
|
28
|
+
|
29
|
+
# Flags to pass to secp256k1_context_create.
|
30
|
+
SECP256K1_CONTEXT_VERIFY = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)
|
31
|
+
SECP256K1_CONTEXT_SIGN = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
|
32
|
+
|
33
|
+
# Flag to pass to secp256k1_ec_pubkey_serialize and secp256k1_ec_privkey_export.
|
34
|
+
SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION)
|
35
|
+
SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION)
|
36
|
+
|
37
|
+
def self.ffi_load_functions(file)
|
38
|
+
class_eval <<-RUBY
|
39
|
+
ffi_lib [ %[#{file}] ]
|
40
|
+
|
41
|
+
##
|
42
|
+
# source: https://github.com/bitcoin/bitcoin/blob/v0.13.1/src/secp256k1/include/secp256k1.h
|
43
|
+
##
|
44
|
+
|
45
|
+
# secp256k1_context* secp256k1_context_create(unsigned int flags)
|
46
|
+
attach_function :secp256k1_context_create, [:uint], :pointer
|
47
|
+
|
48
|
+
# void secp256k1_context_destroy(secp256k1_context* ctx)
|
49
|
+
attach_function :secp256k1_context_destroy, [:pointer], :void
|
50
|
+
|
51
|
+
# int secp256k1_context_randomize(secp256k1_context* ctx, const unsigned char *seed32)
|
52
|
+
attach_function :secp256k1_context_randomize, [:pointer, :pointer], :int
|
53
|
+
|
54
|
+
# int secp256k1_ec_seckey_verify(const secp256k1_context* ctx, const unsigned char *seckey)
|
55
|
+
attach_function :secp256k1_ec_seckey_verify, [:pointer, :pointer], :int
|
56
|
+
|
57
|
+
# int secp256k1_ec_pubkey_create(const secp256k1_context* ctx, secp256k1_pubkey *pubkey, const unsigned char *seckey)
|
58
|
+
attach_function :secp256k1_ec_pubkey_create, [:pointer, :pointer, :pointer], :int
|
59
|
+
|
60
|
+
# int secp256k1_ec_pubkey_serialize(const secp256k1_context* ctx, unsigned char *output, size_t *outputlen, const secp256k1_pubkey* pubkey, unsigned int flags)
|
61
|
+
attach_function :secp256k1_ec_pubkey_serialize, [:pointer, :pointer, :pointer, :pointer, :uint], :int
|
62
|
+
|
63
|
+
# int secp256k1_ecdsa_sign_recoverable(const secp256k1_context* ctx, secp256k1_ecdsa_recoverable_signature *sig, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void *ndata)
|
64
|
+
attach_function :secp256k1_ecdsa_sign_recoverable, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int
|
65
|
+
|
66
|
+
# int secp256k1_ecdsa_recoverable_signature_serialize_compact(const secp256k1_context* ctx, unsigned char *output64, int *recid, const secp256k1_ecdsa_recoverable_signature* sig)
|
67
|
+
attach_function :secp256k1_ecdsa_recoverable_signature_serialize_compact, [:pointer, :pointer, :pointer, :pointer], :int
|
68
|
+
|
69
|
+
# int secp256k1_ecdsa_recoverable_signature_parse_compact(const secp256k1_context* ctx, secp256k1_ecdsa_recoverable_signature* sig, const unsigned char *input64, int recid)
|
70
|
+
attach_function :secp256k1_ecdsa_recoverable_signature_parse_compact, [:pointer, :pointer, :pointer, :int], :int
|
71
|
+
|
72
|
+
# int secp256k1_ecdsa_recover(const secp256k1_context* ctx, secp256k1_pubkey *pubkey, const secp256k1_ecdsa_recoverable_signature *sig, const unsigned char *msg32)
|
73
|
+
attach_function :secp256k1_ecdsa_recover, [:pointer, :pointer, :pointer, :pointer], :int
|
74
|
+
|
75
|
+
# int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature *sig, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void *ndata)
|
76
|
+
attach_function :secp256k1_ecdsa_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int
|
77
|
+
|
78
|
+
# int secp256k1_ecdsa_signature_serialize_der(const secp256k1_context* ctx, unsigned char *output, size_t *outputlen, const secp256k1_ecdsa_signature* sig)
|
79
|
+
attach_function :secp256k1_ecdsa_signature_serialize_der, [:pointer, :pointer, :pointer, :pointer], :int
|
80
|
+
|
81
|
+
# int secp256k1_ec_pubkey_parse(const secp256k1_context* ctx, secp256k1_pubkey* pubkey, const unsigned char *input, size_t inputlen)
|
82
|
+
attach_function :secp256k1_ec_pubkey_parse, [:pointer, :pointer, :pointer, :size_t], :int
|
83
|
+
|
84
|
+
# int secp256k1_ecdsa_signature_normalize(const secp256k1_context* ctx, secp256k1_ecdsa_signature *sigout, const secp256k1_ecdsa_signature *sigin)
|
85
|
+
attach_function :secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int
|
86
|
+
|
87
|
+
# int secp256k1_ecdsa_verify(const secp256k1_context* ctx, const secp256k1_ecdsa_signature *sig, const unsigned char *msg32, const secp256k1_pubkey *pubkey)
|
88
|
+
attach_function :secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int
|
89
|
+
|
90
|
+
# int secp256k1_ecdsa_signature_parse_der(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const unsigned char *input, size_t inputlen)
|
91
|
+
attach_function :secp256k1_ecdsa_signature_parse_der, [:pointer, :pointer, :pointer, :size_t], :int
|
92
|
+
|
93
|
+
# TODO: add or port
|
94
|
+
# # int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const unsigned char *input, size_t inputlen)
|
95
|
+
# attach_function :ecdsa_signature_parse_der_lax, [:pointer, :pointer, :pointer, :size_t], :int
|
96
|
+
RUBY
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.init
|
100
|
+
return if @loaded
|
101
|
+
lib_path = [ ENV['SECP256K1_LIB_PATH'], 'vendor/bitcoin/src/secp256k1/.libs/libsecp256k1.so' ].find{|f| File.exists?(f.to_s) }
|
102
|
+
ffi_load_functions(lib_path)
|
103
|
+
@loaded = true
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.with_context(flags=nil, seed=nil)
|
107
|
+
init
|
108
|
+
flags = flags || (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN)
|
109
|
+
context = secp256k1_context_create(flags)
|
110
|
+
|
111
|
+
ret, tries, max = 0, 0, 20
|
112
|
+
while ret != 1
|
113
|
+
raise "secp256k1_context_randomize failed." if tries >= max
|
114
|
+
tries += 1
|
115
|
+
ret = secp256k1_context_randomize(context, FFI::MemoryPointer.from_string(seed || SecureRandom.random_bytes(32)))
|
116
|
+
end
|
117
|
+
|
118
|
+
yield(context) if block_given?
|
119
|
+
ensure
|
120
|
+
secp256k1_context_destroy(context)
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.generate_key_pair(compressed=true)
|
124
|
+
with_context do |context|
|
125
|
+
|
126
|
+
ret, tries, max = 0, 0, 20
|
127
|
+
while ret != 1
|
128
|
+
raise "secp256k1_ec_seckey_verify in generate_key_pair failed." if tries >= max
|
129
|
+
tries += 1
|
130
|
+
|
131
|
+
seckey = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, SecureRandom.random_bytes(32))
|
132
|
+
ret = secp256k1_ec_seckey_verify(context, seckey)
|
133
|
+
end
|
134
|
+
|
135
|
+
internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
136
|
+
result = secp256k1_ec_pubkey_create(context, internal_pubkey, seckey)
|
137
|
+
raise "error creating pubkey" unless result
|
138
|
+
|
139
|
+
pubkey, pubkey_len = FFI::MemoryPointer.new(:uchar, 65), FFI::MemoryPointer.new(:uint64)
|
140
|
+
result = if compressed
|
141
|
+
pubkey_len.put_uint64(0, 33)
|
142
|
+
secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_COMPRESSED)
|
143
|
+
else
|
144
|
+
pubkey_len.put_uint64(0, 65)
|
145
|
+
secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_UNCOMPRESSED)
|
146
|
+
end
|
147
|
+
raise "error serialize pubkey" unless result || pubkey_len.read_uint64 > 0
|
148
|
+
|
149
|
+
[ seckey.read_string(32), pubkey.read_string(pubkey_len.read_uint64) ]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.generate_key(compressed=true)
|
154
|
+
priv, pub = generate_key_pair(compressed)
|
155
|
+
Bitcoin::Key.new(priv.unpack("H*")[0], pub.unpack("H*")[0])
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.sign(data, priv_key)
|
159
|
+
with_context do |context|
|
160
|
+
seckey = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key)
|
161
|
+
raise "priv_key invalid" unless secp256k1_ec_seckey_verify(context, seckey)
|
162
|
+
|
163
|
+
internal_signature = FFI::MemoryPointer.new(:uchar, 64)
|
164
|
+
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
|
165
|
+
|
166
|
+
ret, tries, max = 0, 0, 20
|
167
|
+
while ret != 1
|
168
|
+
raise "secp256k1_ecdsa_sign failed." if tries >= max
|
169
|
+
tries += 1
|
170
|
+
|
171
|
+
ret = secp256k1_ecdsa_sign(context, internal_signature, msg32, seckey, nil, nil)
|
172
|
+
end
|
173
|
+
|
174
|
+
signature, signature_len = FFI::MemoryPointer.new(:uchar, 72), FFI::MemoryPointer.new(:uint64).put_uint64(0, 72)
|
175
|
+
result = secp256k1_ecdsa_signature_serialize_der(context, signature, signature_len, internal_signature)
|
176
|
+
raise "secp256k1_ecdsa_signature_serialize_der failed" unless result
|
177
|
+
|
178
|
+
signature.read_string(signature_len.read_uint64)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.verify(data, sig, pub_key)
|
183
|
+
with_context do |context|
|
184
|
+
return false if data.bytesize == 0
|
185
|
+
|
186
|
+
pubkey = FFI::MemoryPointer.new(:uchar, pub_key.bytesize).put_bytes(0, pub_key)
|
187
|
+
internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
188
|
+
result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pubkey.size)
|
189
|
+
return false unless result
|
190
|
+
|
191
|
+
signature = FFI::MemoryPointer.new(:uchar, sig.bytesize).put_bytes(0, sig)
|
192
|
+
internal_signature = FFI::MemoryPointer.new(:uchar, 64)
|
193
|
+
result = secp256k1_ecdsa_signature_parse_der(context, internal_signature, signature, signature.size)
|
194
|
+
#result = ecdsa_signature_parse_der_lax(context, internal_signature, signature, signature.size)
|
195
|
+
return false unless result
|
196
|
+
|
197
|
+
# libsecp256k1's ECDSA verification requires lower-S signatures, which have not historically been enforced in Bitcoin, so normalize them first.
|
198
|
+
secp256k1_ecdsa_signature_normalize(context, internal_signature, internal_signature)
|
199
|
+
|
200
|
+
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
|
201
|
+
result = secp256k1_ecdsa_verify(context, internal_signature, msg32, internal_pubkey)
|
202
|
+
|
203
|
+
return result ? true : false
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.sign_compact(message, priv_key, compressed=true)
|
208
|
+
with_context do |context|
|
209
|
+
seckey = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key)
|
210
|
+
raise "priv_key invalid" unless secp256k1_ec_seckey_verify(context, seckey)
|
211
|
+
|
212
|
+
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, message)
|
213
|
+
internal_recoverable_signature = FFI::MemoryPointer.new(:uchar, 65)
|
214
|
+
rec_id = FFI::MemoryPointer.new(:int).put_int(0, -1)
|
215
|
+
|
216
|
+
ret, tries, max = 0, 0, 20
|
217
|
+
while ret != 1
|
218
|
+
raise "secp256k1_ecdsa_sign_recoverable failed." if tries >= max
|
219
|
+
tries += 1
|
220
|
+
|
221
|
+
ret = secp256k1_ecdsa_sign_recoverable(context, internal_recoverable_signature, msg32, seckey, nil, nil)
|
222
|
+
end
|
223
|
+
|
224
|
+
recoverable_signature = FFI::MemoryPointer.new(:uchar, 64)
|
225
|
+
result = secp256k1_ecdsa_recoverable_signature_serialize_compact(context, recoverable_signature, rec_id, internal_recoverable_signature)
|
226
|
+
raise "secp256k1_ecdsa_recoverable_signature_serialize_compact failed" unless result
|
227
|
+
raise "secp256k1_ecdsa_recoverable_signature_serialize_compact failed" unless rec_id.read_int != -1
|
228
|
+
|
229
|
+
header = [27 + rec_id.read_int + (compressed ? 4 : 0)].pack("C")
|
230
|
+
[ header, recoverable_signature.read_string(64) ].join
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def self.recover_compact(message, signature)
|
235
|
+
with_context do |context|
|
236
|
+
return nil if signature.bytesize != 65
|
237
|
+
|
238
|
+
version = signature.unpack('C')[0]
|
239
|
+
return nil if version < 27 || version > 34
|
240
|
+
|
241
|
+
compressed = version >= 31 ? true : false
|
242
|
+
flag = compressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED
|
243
|
+
version -= 4 if compressed
|
244
|
+
|
245
|
+
recid = version - 27
|
246
|
+
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, message)
|
247
|
+
recoverable_signature = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, signature[1..-1])
|
248
|
+
|
249
|
+
internal_recoverable_signature = FFI::MemoryPointer.new(:uchar, 65)
|
250
|
+
result = secp256k1_ecdsa_recoverable_signature_parse_compact(context, internal_recoverable_signature, recoverable_signature, recid)
|
251
|
+
return nil unless result
|
252
|
+
|
253
|
+
internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
254
|
+
result = secp256k1_ecdsa_recover(context, internal_pubkey, internal_recoverable_signature, msg32)
|
255
|
+
return nil unless result
|
256
|
+
|
257
|
+
pubkey, pubkey_len = FFI::MemoryPointer.new(:uchar, 65), FFI::MemoryPointer.new(:uint64).put_uint64(0, 65)
|
258
|
+
result = secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, flag)
|
259
|
+
raise "error serialize pubkey" unless result || pubkey_len.read_uint64 > 0
|
260
|
+
|
261
|
+
pubkey.read_string(pubkey_len.read_uint64)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
end
|
data/lib/bitcoin/key.rb
ADDED
@@ -0,0 +1,280 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
module Bitcoin
|
4
|
+
|
5
|
+
# Elliptic Curve key as used in bitcoin.
|
6
|
+
class Key
|
7
|
+
|
8
|
+
attr_reader :key
|
9
|
+
|
10
|
+
MIN_PRIV_KEY_MOD_ORDER = 0x01
|
11
|
+
# Order of secp256k1's generator minus 1.
|
12
|
+
MAX_PRIV_KEY_MOD_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140
|
13
|
+
|
14
|
+
# Generate a new keypair.
|
15
|
+
# Bitcoin::Key.generate
|
16
|
+
def self.generate(opts={compressed: true})
|
17
|
+
k = new(nil, nil, opts); k.generate; k
|
18
|
+
end
|
19
|
+
|
20
|
+
# Import private key from base58 fromat as described in
|
21
|
+
# https://en.bitcoin.it/wiki/Private_key#Base_58_Wallet_Import_format and
|
22
|
+
# https://en.bitcoin.it/wiki/Base58Check_encoding#Encoding_a_private_key.
|
23
|
+
# See also #to_base58
|
24
|
+
def self.from_base58(str)
|
25
|
+
hex = Bitcoin.decode_base58(str)
|
26
|
+
compressed = hex.size == 76
|
27
|
+
version, key, flag, checksum = hex.unpack("a2a64a#{compressed ? 2 : 0}a8")
|
28
|
+
raise "Invalid version" unless version == Bitcoin.network[:privkey_version]
|
29
|
+
raise "Invalid checksum" unless Bitcoin.checksum(version + key + flag) == checksum
|
30
|
+
key = new(key, nil, compressed)
|
31
|
+
end
|
32
|
+
|
33
|
+
def ==(other)
|
34
|
+
self.priv == other.priv
|
35
|
+
end
|
36
|
+
|
37
|
+
# Create a new key with given +privkey+ and +pubkey+.
|
38
|
+
# Bitcoin::Key.new
|
39
|
+
# Bitcoin::Key.new(privkey)
|
40
|
+
# Bitcoin::Key.new(nil, pubkey)
|
41
|
+
def initialize(privkey = nil, pubkey = nil, opts={compressed: true})
|
42
|
+
compressed = opts.is_a?(Hash) ? opts.fetch(:compressed, true) : opts
|
43
|
+
@key = Bitcoin.bitcoin_elliptic_curve
|
44
|
+
@pubkey_compressed = pubkey ? self.class.is_compressed_pubkey?(pubkey) : compressed
|
45
|
+
set_priv(privkey) if privkey
|
46
|
+
set_pub(pubkey, @pubkey_compressed) if pubkey
|
47
|
+
end
|
48
|
+
|
49
|
+
# Generate new priv/pub key.
|
50
|
+
def generate
|
51
|
+
@key.generate_key
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get the private key (in hex).
|
55
|
+
def priv
|
56
|
+
return nil unless @key.private_key
|
57
|
+
@key.private_key.to_hex.rjust(64, '0')
|
58
|
+
end
|
59
|
+
|
60
|
+
# Set the private key to +priv+ (in hex).
|
61
|
+
def priv= priv
|
62
|
+
set_priv(priv)
|
63
|
+
regenerate_pubkey
|
64
|
+
end
|
65
|
+
|
66
|
+
# Get the public key (in hex).
|
67
|
+
# In case the key was initialized with only
|
68
|
+
# a private key, the public key is regenerated.
|
69
|
+
def pub
|
70
|
+
regenerate_pubkey unless @key.public_key
|
71
|
+
return nil unless @key.public_key
|
72
|
+
@pubkey_compressed ? pub_compressed : pub_uncompressed
|
73
|
+
end
|
74
|
+
|
75
|
+
def pub_compressed
|
76
|
+
public_key = @key.public_key
|
77
|
+
public_key.group.point_conversion_form = :compressed
|
78
|
+
public_key.to_hex.rjust(66, '0')
|
79
|
+
end
|
80
|
+
|
81
|
+
def pub_uncompressed
|
82
|
+
public_key = @key.public_key
|
83
|
+
public_key.group.point_conversion_form = :uncompressed
|
84
|
+
public_key.to_hex.rjust(130, '0')
|
85
|
+
end
|
86
|
+
|
87
|
+
def compressed
|
88
|
+
@pubkey_compressed
|
89
|
+
end
|
90
|
+
|
91
|
+
# Set the public key (in hex).
|
92
|
+
def pub= pub
|
93
|
+
set_pub(pub)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Get the hash160 of the public key.
|
97
|
+
def hash160
|
98
|
+
Bitcoin.hash160(pub)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Get the address corresponding to the public key.
|
102
|
+
def addr
|
103
|
+
Bitcoin.hash160_to_address(hash160)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Sign +data+ with the key.
|
107
|
+
# key1 = Bitcoin::Key.generate
|
108
|
+
# sig = key1.sign("some data")
|
109
|
+
def sign(data)
|
110
|
+
Bitcoin.sign_data(key, data)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Verify signature +sig+ for +data+.
|
114
|
+
# key2 = Bitcoin::Key.new(nil, key1.pub)
|
115
|
+
# key2.verify("some data", sig)
|
116
|
+
def verify(data, sig)
|
117
|
+
regenerate_pubkey unless @key.public_key
|
118
|
+
sig = Bitcoin::OpenSSL_EC.repack_der_signature(sig)
|
119
|
+
if sig
|
120
|
+
@key.dsa_verify_asn1(data, sig)
|
121
|
+
else
|
122
|
+
false
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
def sign_message(message)
|
128
|
+
Bitcoin.sign_message(priv, pub, message)['signature']
|
129
|
+
end
|
130
|
+
|
131
|
+
def verify_message(signature, message)
|
132
|
+
Bitcoin.verify_message(addr, signature, message)
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.verify_message(address, signature, message)
|
136
|
+
Bitcoin.verify_message(address, signature, message)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Thanks to whoever wrote http://pastebin.com/bQtdDzHx
|
140
|
+
# for help with compact signatures
|
141
|
+
#
|
142
|
+
# Given +data+ and a compact signature (65 bytes, base64-encoded to
|
143
|
+
# a larger string), recover the public components of the key whose
|
144
|
+
# private counterpart validly signed +data+.
|
145
|
+
#
|
146
|
+
# If the signature validly signed +data+, create a new Key
|
147
|
+
# having the signing public key and address. Otherwise return nil.
|
148
|
+
#
|
149
|
+
# Be sure to check that the returned Key matches the one you were
|
150
|
+
# expecting! Otherwise you are merely checking that *someone* validly
|
151
|
+
# signed the data.
|
152
|
+
def self.recover_compact_signature_to_key(data, signature_base64)
|
153
|
+
signature = signature_base64.unpack("m0")[0]
|
154
|
+
return nil if signature.size != 65
|
155
|
+
|
156
|
+
version = signature.unpack('C')[0]
|
157
|
+
return nil if version < 27 or version > 34
|
158
|
+
|
159
|
+
compressed = (version >= 31) ? (version -= 4; true) : false
|
160
|
+
|
161
|
+
hash = Bitcoin.bitcoin_signed_message_hash(data)
|
162
|
+
pub_hex = Bitcoin::OpenSSL_EC.recover_public_key_from_signature(hash, signature, version-27, compressed)
|
163
|
+
return nil unless pub_hex
|
164
|
+
|
165
|
+
Key.new(nil, pub_hex)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Export private key to base58 format.
|
169
|
+
# See also Key.from_base58
|
170
|
+
def to_base58
|
171
|
+
data = Bitcoin.network[:privkey_version] + priv
|
172
|
+
data += "01" if @pubkey_compressed
|
173
|
+
hex = data + Bitcoin.checksum(data)
|
174
|
+
Bitcoin.int_to_base58( hex.to_i(16) )
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
# Export private key to bip38 (non-ec-multiply) format as described in
|
179
|
+
# https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki
|
180
|
+
# See also Key.from_bip38
|
181
|
+
def to_bip38(passphrase)
|
182
|
+
flagbyte = compressed ? "\xe0" : "\xc0"
|
183
|
+
addresshash = Digest::SHA256.digest( Digest::SHA256.digest( self.addr ) )[0...4]
|
184
|
+
|
185
|
+
require 'scrypt' unless defined?(::SCrypt::Engine)
|
186
|
+
buf = SCrypt::Engine.__sc_crypt(passphrase, addresshash, 16384, 8, 8, 64)
|
187
|
+
derivedhalf1, derivedhalf2 = buf[0...32], buf[32..-1]
|
188
|
+
|
189
|
+
aes = proc{|k,a,b|
|
190
|
+
cipher = OpenSSL::Cipher::AES.new(256, :ECB); cipher.encrypt; cipher.padding = 0; cipher.key = k
|
191
|
+
cipher.update [ (a.to_i(16) ^ b.unpack("H*")[0].to_i(16)).to_s(16).rjust(32, '0') ].pack("H*")
|
192
|
+
}
|
193
|
+
|
194
|
+
encryptedhalf1 = aes.call(derivedhalf2, self.priv[0...32], derivedhalf1[0...16])
|
195
|
+
encryptedhalf2 = aes.call(derivedhalf2, self.priv[32..-1], derivedhalf1[16..-1])
|
196
|
+
|
197
|
+
encrypted_privkey = "\x01\x42" + flagbyte + addresshash + encryptedhalf1 + encryptedhalf2
|
198
|
+
encrypted_privkey += Digest::SHA256.digest( Digest::SHA256.digest( encrypted_privkey ) )[0...4]
|
199
|
+
|
200
|
+
encrypted_privkey = Bitcoin.encode_base58( encrypted_privkey.unpack("H*")[0] )
|
201
|
+
end
|
202
|
+
|
203
|
+
# Import private key from bip38 (non-ec-multiply) fromat as described in
|
204
|
+
# https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki
|
205
|
+
# See also #to_bip38
|
206
|
+
def self.from_bip38(encrypted_privkey, passphrase)
|
207
|
+
version, flagbyte, addresshash, encryptedhalf1, encryptedhalf2, checksum =
|
208
|
+
[ Bitcoin.decode_base58(encrypted_privkey) ].pack("H*").unpack("a2aa4a16a16a4")
|
209
|
+
compressed = (flagbyte == "\xe0") ? true : false
|
210
|
+
|
211
|
+
raise "Invalid version" unless version == "\x01\x42"
|
212
|
+
raise "Invalid checksum" unless Digest::SHA256.digest(Digest::SHA256.digest(version + flagbyte + addresshash + encryptedhalf1 + encryptedhalf2))[0...4] == checksum
|
213
|
+
|
214
|
+
require 'scrypt' unless defined?(::SCrypt::Engine)
|
215
|
+
buf = SCrypt::Engine.__sc_crypt(passphrase, addresshash, 16384, 8, 8, 64)
|
216
|
+
derivedhalf1, derivedhalf2 = buf[0...32], buf[32..-1]
|
217
|
+
|
218
|
+
aes = proc{|k,a|
|
219
|
+
cipher = OpenSSL::Cipher::AES.new(256, :ECB); cipher.decrypt; cipher.padding = 0; cipher.key = k
|
220
|
+
cipher.update(a)
|
221
|
+
}
|
222
|
+
|
223
|
+
decryptedhalf2 = aes.call(derivedhalf2, encryptedhalf2)
|
224
|
+
decryptedhalf1 = aes.call(derivedhalf2, encryptedhalf1)
|
225
|
+
|
226
|
+
priv = decryptedhalf1 + decryptedhalf2
|
227
|
+
priv = (priv.unpack("H*")[0].to_i(16) ^ derivedhalf1.unpack("H*")[0].to_i(16)).to_s(16).rjust(64, '0')
|
228
|
+
key = Bitcoin::Key.new(priv, nil, compressed)
|
229
|
+
|
230
|
+
if Digest::SHA256.digest( Digest::SHA256.digest( key.addr ) )[0...4] != addresshash
|
231
|
+
raise "Invalid addresshash! Password is likely incorrect."
|
232
|
+
end
|
233
|
+
|
234
|
+
key
|
235
|
+
end
|
236
|
+
|
237
|
+
# Import private key from warp fromat as described in
|
238
|
+
# https://github.com/keybase/warpwallet
|
239
|
+
# https://keybase.io/warp/
|
240
|
+
def self.from_warp(passphrase, salt="", compressed=false)
|
241
|
+
require 'scrypt' unless defined?(::SCrypt::Engine)
|
242
|
+
s1 = SCrypt::Engine.scrypt(passphrase+"\x01", salt+"\x01", 2**18, 8, 1, 32)
|
243
|
+
s2 = OpenSSL::PKCS5.pbkdf2_hmac(passphrase+"\x02", salt+"\x02", 2**16, 32, OpenSSL::Digest::SHA256.new)
|
244
|
+
s3 = s1.bytes.zip(s2.bytes).map{|a,b| a ^ b }.pack("C*")
|
245
|
+
|
246
|
+
key = Bitcoin::Key.new(s3.unpack("H*")[0], nil, compressed)
|
247
|
+
# [key.addr, key.to_base58, [s1,s2,s3].map{|i| i.unpack("H*")[0] }, compressed]
|
248
|
+
key
|
249
|
+
end
|
250
|
+
|
251
|
+
|
252
|
+
protected
|
253
|
+
|
254
|
+
# Regenerate public key from the private key.
|
255
|
+
def regenerate_pubkey
|
256
|
+
return nil unless @key.private_key
|
257
|
+
set_pub(Bitcoin::OpenSSL_EC.regenerate_key(priv)[1], @pubkey_compressed)
|
258
|
+
end
|
259
|
+
|
260
|
+
# Set +priv+ as the new private key (converting from hex).
|
261
|
+
def set_priv(priv)
|
262
|
+
value = priv.to_i(16)
|
263
|
+
raise 'private key is not on curve' unless MIN_PRIV_KEY_MOD_ORDER <= value && value <= MAX_PRIV_KEY_MOD_ORDER
|
264
|
+
@key.private_key = OpenSSL::BN.from_hex(priv)
|
265
|
+
end
|
266
|
+
|
267
|
+
# Set +pub+ as the new public key (converting from hex).
|
268
|
+
def set_pub(pub, compressed = nil)
|
269
|
+
@pubkey_compressed = compressed == nil ? self.class.is_compressed_pubkey?(pub) : compressed
|
270
|
+
@key.public_key = OpenSSL::PKey::EC::Point.from_hex(@key.group, pub)
|
271
|
+
end
|
272
|
+
|
273
|
+
def self.is_compressed_pubkey?(pub)
|
274
|
+
["02","03"].include?(pub[0..1])
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
|