moac 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
data/lib/moac/key.rb ADDED
@@ -0,0 +1,71 @@
1
+ module Moac
2
+ class Key
3
+ autoload :Decrypter, 'moac/key/decrypter'
4
+ autoload :Encrypter, 'moac/key/encrypter'
5
+
6
+ attr_reader :private_key, :public_key
7
+
8
+ def self.encrypt(key, password)
9
+ key = new(priv: key) unless key.is_a?(Key)
10
+
11
+ Encrypter.perform key.private_hex, password
12
+ end
13
+
14
+ def self.decrypt(data, password)
15
+ priv = Decrypter.perform data, password
16
+ new priv: priv
17
+ end
18
+
19
+
20
+ def initialize(priv: nil)
21
+ @private_key = MoneyTree::PrivateKey.new key: priv
22
+ @public_key = MoneyTree::PublicKey.new private_key, compressed: false
23
+ end
24
+
25
+ def private_hex
26
+ private_key.to_hex
27
+ end
28
+
29
+ def public_bytes
30
+ public_key.to_bytes
31
+ end
32
+
33
+ def public_hex
34
+ public_key.to_hex
35
+ end
36
+
37
+ def address
38
+ Utils.public_key_to_address public_hex
39
+ end
40
+ alias_method :to_address, :address
41
+
42
+ def sign(message)
43
+ sign_hash message_hash(message)
44
+ end
45
+
46
+ def sign_hash(hash)
47
+ loop do
48
+ signature = OpenSsl.sign_compact hash, private_hex, public_hex
49
+ return signature if valid_s? signature
50
+ end
51
+ end
52
+
53
+ def verify_signature(message, signature)
54
+ hash = message_hash(message)
55
+ public_hex == OpenSsl.recover_compact(hash, signature)
56
+ end
57
+
58
+
59
+ private
60
+
61
+ def message_hash(message)
62
+ Utils.keccak256 message
63
+ end
64
+
65
+ def valid_s?(signature)
66
+ s_value = Utils.v_r_s_for(signature).last
67
+ s_value <= Secp256k1::N/2 && s_value != 0
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,113 @@
1
+ require 'json'
2
+ require 'scrypt'
3
+
4
+ class Moac::Key::Decrypter
5
+ include Moac::Utils
6
+
7
+ def self.perform(data, password)
8
+ new(data, password).perform
9
+ end
10
+
11
+ def initialize(data, password)
12
+ @data = JSON.parse(data)
13
+ @password = password
14
+ end
15
+
16
+ def perform
17
+ derive_key password
18
+ check_macs
19
+ bin_to_hex decrypted_data
20
+ end
21
+
22
+
23
+ private
24
+
25
+ attr_reader :data, :key, :password
26
+
27
+ def derive_key(password)
28
+ case kdf
29
+ when 'pbkdf2'
30
+ @key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)
31
+ when 'scrypt'
32
+ # OpenSSL 1.1 inclues OpenSSL::KDF.scrypt, but it is not available usually, otherwise we could do: OpenSSL::KDF.scrypt(password, salt: salt, N: n, r: r, p: p, length: key_length)
33
+ @key = SCrypt::Engine.scrypt(password, salt, n, r, p, key_length)
34
+ else
35
+ raise "Unsupported key derivation function: #{kdf}!"
36
+ end
37
+ end
38
+
39
+ def check_macs
40
+ mac1 = keccak256(key[(key_length/2), key_length] + ciphertext)
41
+ mac2 = hex_to_bin crypto_data['mac']
42
+
43
+ if mac1 != mac2
44
+ raise "Message Authentications Codes do not match!"
45
+ end
46
+ end
47
+
48
+ def decrypted_data
49
+ @decrypted_data ||= cipher.update(ciphertext) + cipher.final
50
+ end
51
+
52
+ def crypto_data
53
+ @crypto_data ||= data['crypto'] || data['Crypto']
54
+ end
55
+
56
+ def ciphertext
57
+ hex_to_bin crypto_data['ciphertext']
58
+ end
59
+
60
+ def cipher_name
61
+ "aes-128-ctr"
62
+ end
63
+
64
+ def cipher
65
+ @cipher ||= OpenSSL::Cipher.new(cipher_name).tap do |cipher|
66
+ cipher.decrypt
67
+ cipher.key = key[0, (key_length/2)]
68
+ cipher.iv = iv
69
+ end
70
+ end
71
+
72
+ def iv
73
+ hex_to_bin crypto_data['cipherparams']['iv']
74
+ end
75
+
76
+ def salt
77
+ hex_to_bin crypto_data['kdfparams']['salt']
78
+ end
79
+
80
+ def iterations
81
+ crypto_data['kdfparams']['c'].to_i
82
+ end
83
+
84
+ def kdf
85
+ crypto_data['kdf']
86
+ end
87
+
88
+ def key_length
89
+ crypto_data['kdfparams']['dklen'].to_i
90
+ end
91
+
92
+ def n
93
+ crypto_data['kdfparams']['n'].to_i
94
+ end
95
+
96
+ def r
97
+ crypto_data['kdfparams']['r'].to_i
98
+ end
99
+
100
+ def p
101
+ crypto_data['kdfparams']['p'].to_i
102
+ end
103
+
104
+ def digest
105
+ OpenSSL::Digest.new digest_name
106
+ end
107
+
108
+ def digest_name
109
+ "sha256"
110
+ end
111
+
112
+
113
+ end
@@ -0,0 +1,128 @@
1
+ require 'json'
2
+ require 'securerandom'
3
+
4
+ class Moac::Key::Encrypter
5
+ include Moac::Utils
6
+
7
+ def self.perform(key, password, options = {})
8
+ new(key, options).perform(password)
9
+ end
10
+
11
+ def initialize(key, options = {})
12
+ @key = key
13
+ @options = options
14
+ end
15
+
16
+ def perform(password)
17
+ derive_key password
18
+ encrypt
19
+
20
+ data.to_json
21
+ end
22
+
23
+ def data
24
+ {
25
+ crypto: {
26
+ cipher: cipher_name,
27
+ cipherparams: {
28
+ iv: bin_to_hex(iv),
29
+ },
30
+ ciphertext: bin_to_hex(encrypted_key),
31
+ kdf: "pbkdf2",
32
+ kdfparams: {
33
+ c: iterations,
34
+ dklen: 32,
35
+ prf: prf,
36
+ salt: bin_to_hex(salt),
37
+ },
38
+ mac: bin_to_hex(mac),
39
+ },
40
+ id: id,
41
+ version: 3,
42
+ }.tap do |data|
43
+ data[:address] = address unless options[:skip_address]
44
+ end
45
+ end
46
+
47
+ def id
48
+ @id ||= options[:id] || SecureRandom.uuid
49
+ end
50
+
51
+
52
+ private
53
+
54
+ attr_reader :derived_key, :encrypted_key, :key, :options
55
+
56
+ def cipher
57
+ @cipher ||= OpenSSL::Cipher.new(cipher_name).tap do |cipher|
58
+ cipher.encrypt
59
+ cipher.iv = iv
60
+ cipher.key = derived_key[0, (key_length/2)]
61
+ end
62
+ end
63
+
64
+ def digest
65
+ @digest ||= OpenSSL::Digest.new digest_name
66
+ end
67
+
68
+ def derive_key(password)
69
+ @derived_key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)
70
+ end
71
+
72
+ def encrypt
73
+ @encrypted_key = cipher.update(hex_to_bin key) + cipher.final
74
+ end
75
+
76
+ def mac
77
+ keccak256(derived_key[(key_length/2), key_length] + encrypted_key)
78
+ end
79
+
80
+ def cipher_name
81
+ "aes-128-ctr"
82
+ end
83
+
84
+ def digest_name
85
+ "sha256"
86
+ end
87
+
88
+ def prf
89
+ "hmac-#{digest_name}"
90
+ end
91
+
92
+ def key_length
93
+ 32
94
+ end
95
+
96
+ def salt_length
97
+ 32
98
+ end
99
+
100
+ def iv_length
101
+ 16
102
+ end
103
+
104
+ def iterations
105
+ options[:iterations] || 262_144
106
+ end
107
+
108
+ def salt
109
+ @salt ||= if options[:salt]
110
+ hex_to_bin options[:salt]
111
+ else
112
+ SecureRandom.random_bytes(salt_length)
113
+ end
114
+ end
115
+
116
+ def iv
117
+ @iv ||= if options[:iv]
118
+ hex_to_bin options[:iv]
119
+ else
120
+ SecureRandom.random_bytes(iv_length)
121
+ end
122
+ end
123
+
124
+ def address
125
+ Moac::Key.new(priv: key).address
126
+ end
127
+
128
+ end
@@ -0,0 +1,197 @@
1
+ # originally lifted from https://github.com/lian/bitcoin-ruby
2
+ # thanks to everyone there for figuring this out
3
+
4
+ module Moac
5
+ class OpenSsl
6
+ extend FFI::Library
7
+
8
+ if FFI::Platform.windows?
9
+ ffi_lib 'libeay32', 'ssleay32'
10
+ else
11
+ ffi_lib ['libssl.so.1.0.0', 'libssl.so.10', 'ssl']
12
+ end
13
+
14
+ NID_secp256k1 = 714
15
+ POINT_CONVERSION_COMPRESSED = 2
16
+ POINT_CONVERSION_UNCOMPRESSED = 4
17
+
18
+ attach_function :SSL_library_init, [], :int
19
+ attach_function :ERR_load_crypto_strings, [], :void
20
+ attach_function :SSL_load_error_strings, [], :void
21
+ attach_function :RAND_poll, [], :int
22
+
23
+ attach_function :BN_CTX_free, [:pointer], :int
24
+ attach_function :BN_CTX_new, [], :pointer
25
+ attach_function :BN_add, [:pointer, :pointer, :pointer], :int
26
+ attach_function :BN_bin2bn, [:pointer, :int, :pointer], :pointer
27
+ attach_function :BN_bn2bin, [:pointer, :pointer], :int
28
+ attach_function :BN_cmp, [:pointer, :pointer], :int
29
+ attach_function :BN_dup, [:pointer], :pointer
30
+ attach_function :BN_free, [:pointer], :int
31
+ attach_function :BN_mod_inverse, [:pointer, :pointer, :pointer, :pointer], :pointer
32
+ attach_function :BN_mod_mul, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
33
+ attach_function :BN_mod_sub, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
34
+ attach_function :BN_mul_word, [:pointer, :int], :int
35
+ attach_function :BN_new, [], :pointer
36
+ attach_function :BN_num_bits, [:pointer], :int
37
+ attach_function :BN_rshift, [:pointer, :pointer, :int], :int
38
+ attach_function :BN_set_word, [:pointer, :int], :int
39
+ attach_function :ECDSA_SIG_free, [:pointer], :void
40
+ attach_function :ECDSA_do_sign, [:pointer, :uint, :pointer], :pointer
41
+ attach_function :EC_GROUP_get_curve_GFp, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
42
+ attach_function :EC_GROUP_get_degree, [:pointer], :int
43
+ attach_function :EC_GROUP_get_order, [:pointer, :pointer, :pointer], :int
44
+ attach_function :EC_KEY_free, [:pointer], :int
45
+ attach_function :EC_KEY_get0_group, [:pointer], :pointer
46
+ attach_function :EC_KEY_new_by_curve_name, [:int], :pointer
47
+ attach_function :EC_KEY_set_conv_form, [:pointer, :int], :void
48
+ attach_function :EC_KEY_set_private_key, [:pointer, :pointer], :int
49
+ attach_function :EC_KEY_set_public_key, [:pointer, :pointer], :int
50
+ attach_function :EC_POINT_free, [:pointer], :int
51
+ attach_function :EC_POINT_mul, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int
52
+ attach_function :EC_POINT_new, [:pointer], :pointer
53
+ attach_function :EC_POINT_set_compressed_coordinates_GFp, [:pointer, :pointer, :pointer, :int, :pointer], :int
54
+ attach_function :i2o_ECPublicKey, [:pointer, :pointer], :uint
55
+
56
+ class << self
57
+ def BN_num_bytes(ptr)
58
+ (BN_num_bits(ptr) + 7) / 8
59
+ end
60
+
61
+ def sign_compact(hash, private_key, public_key_hex)
62
+ private_key = [private_key].pack("H*") if private_key.bytesize >= 64
63
+ pubkey_compressed = false
64
+
65
+ init_ffi_ssl
66
+ eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
67
+ priv_key = BN_bin2bn(private_key, private_key.bytesize, BN_new())
68
+
69
+ group, order, ctx = EC_KEY_get0_group(eckey), BN_new(), BN_CTX_new()
70
+ EC_GROUP_get_order(group, order, ctx)
71
+
72
+ pub_key = EC_POINT_new(group)
73
+ EC_POINT_mul(group, pub_key, priv_key, nil, nil, ctx)
74
+ EC_KEY_set_private_key(eckey, priv_key)
75
+ EC_KEY_set_public_key(eckey, pub_key)
76
+
77
+ signature = ECDSA_do_sign(hash, hash.bytesize, eckey)
78
+
79
+ BN_free(order)
80
+ BN_CTX_free(ctx)
81
+ EC_POINT_free(pub_key)
82
+ BN_free(priv_key)
83
+ EC_KEY_free(eckey)
84
+
85
+ buf, rec_id, head = FFI::MemoryPointer.new(:uint8, 32), nil, nil
86
+ r, s = signature.get_array_of_pointer(0, 2).map{|i| BN_bn2bin(i, buf); buf.read_string(BN_num_bytes(i)).rjust(32, "\x00") }
87
+
88
+ if signature.get_array_of_pointer(0, 2).all?{|i| BN_num_bits(i) <= 256 }
89
+ 4.times{|i|
90
+ head = [ Moac.v_base + i ].pack("C")
91
+ if public_key_hex == recover_public_key_from_signature(hash, [head, r, s].join, i, pubkey_compressed)
92
+ rec_id = i; break
93
+ end
94
+ }
95
+ end
96
+
97
+ ECDSA_SIG_free(signature)
98
+
99
+ [ head, [r,s] ].join if rec_id
100
+ end
101
+
102
+ def recover_public_key_from_signature(message_hash, signature, rec_id, is_compressed)
103
+ return nil if rec_id < 0 or signature.bytesize != 65
104
+ init_ffi_ssl
105
+
106
+ signature = FFI::MemoryPointer.from_string(signature)
107
+ r = BN_bin2bn(signature[1], 32, BN_new())
108
+ s = BN_bin2bn(signature[33], 32, BN_new())
109
+
110
+ _n, i = 0, rec_id / 2
111
+ eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
112
+
113
+ EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED) if is_compressed
114
+
115
+ group = EC_KEY_get0_group(eckey)
116
+ order = BN_new()
117
+ EC_GROUP_get_order(group, order, nil)
118
+ x = BN_dup(order)
119
+ BN_mul_word(x, i)
120
+ BN_add(x, x, r)
121
+
122
+ field = BN_new()
123
+ EC_GROUP_get_curve_GFp(group, field, nil, nil, nil)
124
+
125
+ if BN_cmp(x, field) >= 0
126
+ bn_free_each r, s, order, x, field
127
+ EC_KEY_free(eckey)
128
+ return nil
129
+ end
130
+
131
+ big_r = EC_POINT_new(group)
132
+ EC_POINT_set_compressed_coordinates_GFp(group, big_r, x, rec_id % 2, nil)
133
+
134
+ big_q = EC_POINT_new(group)
135
+ n = EC_GROUP_get_degree(group)
136
+ e = BN_bin2bn(message_hash, message_hash.bytesize, BN_new())
137
+ BN_rshift(e, e, 8 - (n & 7)) if 8 * message_hash.bytesize > n
138
+
139
+ ctx = BN_CTX_new()
140
+ zero, rr, sor, eor = BN_new(), BN_new(), BN_new(), BN_new()
141
+ BN_set_word(zero, 0)
142
+ BN_mod_sub(e, zero, e, order, ctx)
143
+ BN_mod_inverse(rr, r, order, ctx)
144
+ BN_mod_mul(sor, s, rr, order, ctx)
145
+ BN_mod_mul(eor, e, rr, order, ctx)
146
+ EC_POINT_mul(group, big_q, eor, big_r, sor, ctx)
147
+ EC_KEY_set_public_key(eckey, big_q)
148
+ BN_CTX_free(ctx)
149
+
150
+ bn_free_each r, s, order, x, field, e, zero, rr, sor, eor
151
+ [big_r, big_q].each{|j| EC_POINT_free(j) }
152
+
153
+ recover_public_hex eckey
154
+ end
155
+
156
+ def recover_compact(hash, signature)
157
+ return false if signature.bytesize != 65
158
+
159
+ version = signature.unpack('C')[0]
160
+ v_base = Moac.replayable_v?(version) ? Moac.replayable_chain_id : Moac.v_base
161
+ return false if version < v_base
162
+
163
+ recover_public_key_from_signature(hash, signature, (version - v_base), false)
164
+ end
165
+
166
+ def init_ffi_ssl
167
+ return if @ssl_loaded
168
+ SSL_library_init()
169
+ ERR_load_crypto_strings()
170
+ SSL_load_error_strings()
171
+ RAND_poll()
172
+ @ssl_loaded = true
173
+ end
174
+
175
+
176
+ private
177
+
178
+ def bn_free_each(*list)
179
+ list.each{|j| BN_free(j) }
180
+ end
181
+
182
+ def recover_public_hex(eckey)
183
+ length = i2o_ECPublicKey(eckey, nil)
184
+ buf = FFI::MemoryPointer.new(:uint8, length)
185
+ ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf)
186
+ pub_hex = if i2o_ECPublicKey(eckey, ptr) == length
187
+ buf.read_string(length).unpack("H*")[0]
188
+ end
189
+
190
+ EC_KEY_free(eckey)
191
+
192
+ pub_hex
193
+ end
194
+ end
195
+
196
+ end
197
+ end