moac 0.4.8

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