monacoin-ruby 0.1.2 → 0.1.3

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.
@@ -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
@@ -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
+