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.
@@ -0,0 +1,191 @@
1
+ # encoding: ascii-8bit
2
+
3
+ module Bitcoin
4
+
5
+ def self.hmac_sha512(key, data)
6
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA512'), key, data)
7
+ end
8
+
9
+ # Integers modulo the order of the curve(secp256k1)
10
+ CURVE_ORDER = 115792089237316195423570985008687907852837564279074904382605163141518161494337
11
+
12
+ # BIP32 Extended private key
13
+ class ExtKey
14
+
15
+ attr_accessor :depth
16
+ attr_accessor :number
17
+ attr_accessor :chain_code
18
+ attr_accessor :priv_key
19
+ attr_accessor :parent_fingerprint
20
+
21
+ # generate master key from seed.
22
+ def self.generate_master(seed)
23
+ key = ExtKey.new
24
+ key.depth = key.number = 0
25
+ key.parent_fingerprint = '00000000'
26
+ l = Bitcoin.hmac_sha512('Bitcoin seed', seed)
27
+ left = OpenSSL::BN.from_hex(l[0..31].bth).to_i
28
+ raise 'invalid key' if left >= CURVE_ORDER || left == 0
29
+ key.priv_key = Bitcoin::Key.new(l[0..31].bth)
30
+ key.chain_code = l[32..-1]
31
+ key
32
+ end
33
+
34
+ # get ExtPubkey from priv_key
35
+ def ext_pubkey
36
+ k = ExtPubkey.new
37
+ k.depth = depth
38
+ k.number = number
39
+ k.parent_fingerprint = parent_fingerprint
40
+ k.chain_code = chain_code
41
+ key = Bitcoin::Key.new(nil, priv_key.pub, compressed: true)
42
+ k.pub_key = key.key.public_key
43
+ k
44
+ end
45
+
46
+ # serialize extended private key
47
+ def to_payload
48
+ Bitcoin.network[:extended_privkey_version].htb << [depth].pack('C') << parent_fingerprint.htb << [number].pack('N') << chain_code << [0x00].pack('C') << priv_key.priv.htb
49
+ end
50
+
51
+ # Base58 encoded extended private key
52
+ def to_base58
53
+ h = to_payload.bth
54
+ hex = h + Bitcoin.checksum(h)
55
+ Bitcoin.encode_base58(hex)
56
+ end
57
+
58
+ # get private key(hex)
59
+ def priv
60
+ priv_key.priv
61
+ end
62
+
63
+ # get public key(hex)
64
+ def pub
65
+ priv_key.pub
66
+ end
67
+
68
+ # get address
69
+ def addr
70
+ priv_key.addr
71
+ end
72
+
73
+ # get key identifier
74
+ def identifier
75
+ Bitcoin.hash160(priv_key.pub)
76
+ end
77
+
78
+ # get fingerprint
79
+ def fingerprint
80
+ identifier.slice(0..7)
81
+ end
82
+
83
+ # derive new key
84
+ def derive(number)
85
+ new_key = ExtKey.new
86
+ new_key.depth = depth + 1
87
+ new_key.number = number
88
+ new_key.parent_fingerprint = fingerprint
89
+ if number > (2**31 -1)
90
+ data = [0x00].pack('C') << priv_key.priv.htb << [number].pack('N')
91
+ else
92
+ data = priv_key.pub.htb << [number].pack('N')
93
+ end
94
+ l = Bitcoin.hmac_sha512(chain_code, data)
95
+ left = OpenSSL::BN.from_hex(l[0..31].bth).to_i
96
+ raise 'invalid key' if left >= CURVE_ORDER
97
+ child_priv = OpenSSL::BN.new((left + OpenSSL::BN.from_hex(priv_key.priv).to_i) % CURVE_ORDER)
98
+ raise 'invalid key ' if child_priv.to_i >= CURVE_ORDER
99
+ new_key.priv_key = Bitcoin::Key.new(child_priv.to_hex.rjust(64, '0'))
100
+ new_key.chain_code = l[32..-1]
101
+ new_key
102
+ end
103
+
104
+ # import private key from Base58 private key address
105
+ def self.from_base58(address)
106
+ data = StringIO.new(Bitcoin.decode_base58(address).htb)
107
+ key = ExtKey.new
108
+ data.read(4).bth # version
109
+ key.depth = data.read(1).unpack('C').first
110
+ key.parent_fingerprint = data.read(4).bth
111
+ key.number = data.read(4).unpack('N').first
112
+ key.chain_code = data.read(32)
113
+ data.read(1) # 0x00
114
+ key.priv_key = Bitcoin::Key.new(data.read(32).bth)
115
+ key
116
+ end
117
+
118
+ end
119
+
120
+ # BIP-32 Extended public key
121
+ class ExtPubkey
122
+ attr_accessor :depth
123
+ attr_accessor :number
124
+ attr_accessor :chain_code
125
+ attr_accessor :pub_key
126
+ attr_accessor :parent_fingerprint
127
+
128
+ # serialize extended pubkey
129
+ def to_payload
130
+ Bitcoin.network[:extended_pubkey_version].htb << [depth].pack('C') << parent_fingerprint.htb << [number].pack('N') << chain_code << pub.htb
131
+ end
132
+
133
+ # get public key(hex)
134
+ def pub
135
+ pub_key.group.point_conversion_form = :compressed
136
+ pub_key.to_hex.rjust(66, '0')
137
+ end
138
+
139
+ # get address
140
+ def addr
141
+ Bitcoin.hash160_to_address(Bitcoin.hash160(pub))
142
+ end
143
+
144
+ # get key identifier
145
+ def identifier
146
+ Bitcoin.hash160(pub)
147
+ end
148
+
149
+ # get fingerprint
150
+ def fingerprint
151
+ identifier.slice(0..7)
152
+ end
153
+
154
+ # Base58 encoded extended pubkey
155
+ def to_base58
156
+ h = to_payload.bth
157
+ hex = h + Bitcoin.checksum(h)
158
+ Bitcoin.encode_base58(hex)
159
+ end
160
+
161
+ # derive child key
162
+ def derive(number)
163
+ new_key = ExtPubkey.new
164
+ new_key.depth = depth + 1
165
+ new_key.number = number
166
+ new_key.parent_fingerprint = fingerprint
167
+ raise 'hardened key is not support' if number > (2**31 -1)
168
+ data = pub.htb << [number].pack('N')
169
+ l = Bitcoin.hmac_sha512(chain_code, data)
170
+ left = OpenSSL::BN.from_hex(l[0..31].bth)
171
+ raise 'invalid key' if left.to_i >= CURVE_ORDER
172
+ new_key.pub_key = Bitcoin.bitcoin_elliptic_curve.group.generator.mul(left).ec_add(pub_key)
173
+ new_key.chain_code = l[32..-1]
174
+ new_key
175
+ end
176
+
177
+ # import private key from Base58 private key address
178
+ def self.from_base58(address)
179
+ data = StringIO.new(Bitcoin.decode_base58(address).htb)
180
+ key = ExtPubkey.new
181
+ data.read(4).bth # version
182
+ key.depth = data.read(1).unpack('C').first
183
+ key.parent_fingerprint = data.read(4).bth
184
+ key.number = data.read(4).unpack('N').first
185
+ key.chain_code = data.read(32)
186
+ key.pub_key = OpenSSL::PKey::EC::Point.from_hex(Bitcoin.bitcoin_elliptic_curve.group, data.read(33).bth)
187
+ key
188
+ end
189
+ end
190
+
191
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: ascii-8bit
2
+
3
+ require 'ffi'
4
+
5
+ # binding for src/.libs/bitcoinconsensus.so (https://github.com/bitcoin/bitcoin)
6
+ # tag: v0.11.0
7
+ # commit: d26f951802c762de04fb68e1a112d611929920ba
8
+
9
+ module Bitcoin
10
+ module BitcoinConsensus
11
+ extend FFI::Library
12
+
13
+ SCRIPT_VERIFY_NONE = 0
14
+ SCRIPT_VERIFY_P2SH = (1 << 0)
15
+ SCRIPT_VERIFY_STRICTENC = (1 << 1)
16
+ SCRIPT_VERIFY_DERSIG = (1 << 2)
17
+ SCRIPT_VERIFY_LOW_S = (1 << 3)
18
+ SCRIPT_VERIFY_NULLDUMMY = (1 << 4)
19
+ SCRIPT_VERIFY_SIGPUSHONLY = (1 << 5)
20
+ SCRIPT_VERIFY_MINIMALDATA = (1 << 6)
21
+ SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 7)
22
+ SCRIPT_VERIFY_CLEANSTACK = (1 << 8)
23
+
24
+ ERR_CODES = { 0 => :ok, 1 => :tx_index, 2 => :tx_size_mismatch, 3 => :tx_deserialize }
25
+
26
+ def self.ffi_load_functions(file)
27
+ class_eval <<-RUBY
28
+ ffi_lib [ %[#{file}] ]
29
+ attach_function :bitcoinconsensus_version, [], :uint
30
+
31
+ # int bitcoinconsensus_verify_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen,
32
+ # const unsigned char *txTo , unsigned int txToLen,
33
+ # unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err);
34
+ attach_function :bitcoinconsensus_verify_script, [:pointer, :uint, :pointer, :uint, :uint, :uint, :pointer], :int
35
+ RUBY
36
+ end
37
+
38
+ def self.lib_available?
39
+ @__lib_path ||= [ ENV['BITCOINCONSENSUS_LIB_PATH'], 'vendor/bitcoin/src/.libs/libbitcoinconsensus.so' ].find{|f| File.exists?(f.to_s) }
40
+ end
41
+
42
+ def self.init
43
+ return if @bitcoin_consensus
44
+ lib_path = lib_available?
45
+ ffi_load_functions(lib_path)
46
+ @bitcoin_consensus = true
47
+ end
48
+
49
+ # api version
50
+ def self.version
51
+ init
52
+ bitcoinconsensus_version
53
+ end
54
+
55
+ def self.verify_script(input_index, script_pubkey, tx_payload, script_flags)
56
+ init
57
+
58
+ scriptPubKey = FFI::MemoryPointer.new(:uchar, script_pubkey.bytesize).put_bytes(0, script_pubkey)
59
+ txTo = FFI::MemoryPointer.new(:uchar, tx_payload.bytesize).put_bytes(0, tx_payload)
60
+ error_ret = FFI::MemoryPointer.new(:uint)
61
+
62
+ ret = bitcoinconsensus_verify_script(scriptPubKey, scriptPubKey.size, txTo, txTo.size, input_index, script_flags, error_ret)
63
+
64
+ case ret
65
+ when 0
66
+ false
67
+ when 1
68
+ (ERR_CODES[error_ret.read_int] == :ok) ? true : false
69
+ else
70
+ raise "error invalid result"
71
+ end
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,388 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # autoload when you need to re-generate a public_key from only its private_key.
4
+ # ported from: https://github.com/sipa/bitcoin/blob/2d40fe4da9ea82af4b652b691a4185431d6e47a8/key.h
5
+
6
+ require 'ffi'
7
+
8
+ module Bitcoin
9
+ module OpenSSL_EC
10
+ extend FFI::Library
11
+ if FFI::Platform.windows?
12
+ ffi_lib 'libeay32', 'ssleay32'
13
+ else
14
+ ffi_lib [ 'libssl.so.1.0.0', 'ssl' ]
15
+ end
16
+
17
+ NID_secp256k1 = 714
18
+ POINT_CONVERSION_COMPRESSED = 2
19
+ POINT_CONVERSION_UNCOMPRESSED = 4
20
+
21
+ attach_function :SSL_library_init, [], :int
22
+ attach_function :ERR_load_crypto_strings, [], :void
23
+ attach_function :SSL_load_error_strings, [], :void
24
+ attach_function :RAND_poll, [], :int
25
+
26
+ attach_function :BN_CTX_free, [:pointer], :int
27
+ attach_function :BN_CTX_new, [], :pointer
28
+ attach_function :BN_add, [:pointer, :pointer, :pointer], :int
29
+ attach_function :BN_bin2bn, [:pointer, :int, :pointer], :pointer
30
+ attach_function :BN_bn2bin, [:pointer, :pointer], :int
31
+ attach_function :BN_cmp, [:pointer, :pointer], :int
32
+ attach_function :BN_copy, [:pointer, :pointer], :pointer
33
+ attach_function :BN_dup, [:pointer], :pointer
34
+ attach_function :BN_free, [:pointer], :int
35
+ attach_function :BN_mod_inverse, [:pointer, :pointer, :pointer, :pointer], :pointer
36
+ attach_function :BN_mod_mul, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
37
+ attach_function :BN_mod_sub, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
38
+ attach_function :BN_mul_word, [:pointer, :int], :int
39
+ attach_function :BN_new, [], :pointer
40
+ attach_function :BN_rshift, [:pointer, :pointer, :int], :int
41
+ attach_function :BN_rshift1, [:pointer, :pointer], :int
42
+ attach_function :BN_set_word, [:pointer, :int], :int
43
+ attach_function :BN_sub, [:pointer, :pointer, :pointer], :int
44
+ attach_function :EC_GROUP_get_curve_GFp, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
45
+ attach_function :EC_GROUP_get_degree, [:pointer], :int
46
+ attach_function :EC_GROUP_get_order, [:pointer, :pointer, :pointer], :int
47
+ attach_function :EC_KEY_free, [:pointer], :int
48
+ attach_function :EC_KEY_get0_group, [:pointer], :pointer
49
+ attach_function :EC_KEY_get0_private_key, [:pointer], :pointer
50
+ attach_function :EC_KEY_new_by_curve_name, [:int], :pointer
51
+ attach_function :EC_KEY_set_conv_form, [:pointer, :int], :void
52
+ attach_function :EC_KEY_set_private_key, [:pointer, :pointer], :int
53
+ attach_function :EC_KEY_set_public_key, [:pointer, :pointer], :int
54
+ attach_function :EC_POINT_free, [:pointer], :int
55
+ attach_function :EC_POINT_is_at_infinity, [:pointer, :pointer], :int
56
+ attach_function :EC_POINT_mul, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int
57
+ attach_function :EC_POINT_new, [:pointer], :pointer
58
+ attach_function :EC_POINT_set_compressed_coordinates_GFp, [:pointer, :pointer, :pointer, :int, :pointer], :int
59
+ attach_function :d2i_ECPrivateKey, [:pointer, :pointer, :long], :pointer
60
+ attach_function :i2d_ECPrivateKey, [:pointer, :pointer], :int
61
+ attach_function :i2o_ECPublicKey, [:pointer, :pointer], :uint
62
+ attach_function :EC_KEY_check_key, [:pointer], :uint
63
+ attach_function :ECDSA_do_sign, [:pointer, :uint, :pointer], :pointer
64
+ attach_function :BN_num_bits, [:pointer], :int
65
+ attach_function :ECDSA_SIG_free, [:pointer], :void
66
+ attach_function :EC_POINT_add, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
67
+ attach_function :EC_POINT_point2hex, [:pointer, :pointer, :int, :pointer], :string
68
+ attach_function :EC_POINT_hex2point, [:pointer, :string, :pointer, :pointer], :pointer
69
+ attach_function :ECDSA_SIG_new, [], :pointer
70
+ attach_function :d2i_ECDSA_SIG, [:pointer, :pointer, :long], :pointer
71
+ attach_function :i2d_ECDSA_SIG, [:pointer, :pointer], :int
72
+ attach_function :OPENSSL_free, :CRYPTO_free, [:pointer], :void
73
+
74
+ def self.BN_num_bytes(ptr); (BN_num_bits(ptr) + 7) / 8; end
75
+
76
+
77
+ # resolve public from private key, using ffi and libssl.so
78
+ # example:
79
+ # keypair = Bitcoin.generate_key; Bitcoin::OpenSSL_EC.regenerate_key(keypair.first) == keypair
80
+ def self.regenerate_key(private_key)
81
+ private_key = [private_key].pack("H*") if private_key.bytesize >= (32*2)
82
+ private_key_hex = private_key.unpack("H*")[0]
83
+
84
+ #private_key = FFI::MemoryPointer.new(:uint8, private_key.bytesize)
85
+ # .put_bytes(0, private_key, 0, private_key.bytesize)
86
+ private_key = FFI::MemoryPointer.from_string(private_key)
87
+
88
+ init_ffi_ssl
89
+ eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
90
+ #priv_key = BN_bin2bn(private_key, private_key.size, BN_new())
91
+ priv_key = BN_bin2bn(private_key, private_key.size-1, BN_new())
92
+
93
+ group, order, ctx = EC_KEY_get0_group(eckey), BN_new(), BN_CTX_new()
94
+ EC_GROUP_get_order(group, order, ctx)
95
+
96
+ pub_key = EC_POINT_new(group)
97
+ EC_POINT_mul(group, pub_key, priv_key, nil, nil, ctx)
98
+ EC_KEY_set_private_key(eckey, priv_key)
99
+ EC_KEY_set_public_key(eckey, pub_key)
100
+
101
+ BN_free(order)
102
+ BN_CTX_free(ctx)
103
+ EC_POINT_free(pub_key)
104
+ BN_free(priv_key)
105
+
106
+
107
+ length = i2d_ECPrivateKey(eckey, nil)
108
+ buf = FFI::MemoryPointer.new(:uint8, length)
109
+ ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf)
110
+ priv_hex = if i2d_ECPrivateKey(eckey, ptr) == length
111
+ size = buf.get_array_of_uint8(8, 1)[0]
112
+ buf.get_array_of_uint8(9, size).pack("C*").rjust(32, "\x00").unpack("H*")[0]
113
+ #der_to_private_key( ptr.read_pointer.read_string(length).unpack("H*")[0] )
114
+ end
115
+
116
+ if priv_hex != private_key_hex
117
+ raise "regenerated wrong private_key, raise here before generating a faulty public_key too!"
118
+ end
119
+
120
+
121
+ length = i2o_ECPublicKey(eckey, nil)
122
+ buf = FFI::MemoryPointer.new(:uint8, length)
123
+ ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf)
124
+ pub_hex = if i2o_ECPublicKey(eckey, ptr) == length
125
+ buf.read_string(length).unpack("H*")[0]
126
+ end
127
+
128
+ EC_KEY_free(eckey)
129
+
130
+ [ priv_hex, pub_hex ]
131
+ end
132
+
133
+ # extract private key from uncompressed DER format
134
+ def self.der_to_private_key(der_hex)
135
+ init_ffi_ssl
136
+ #k = EC_KEY_new_by_curve_name(NID_secp256k1)
137
+ #kp = FFI::MemoryPointer.new(:pointer).put_pointer(0, eckey)
138
+
139
+ buf = FFI::MemoryPointer.from_string([der_hex].pack("H*"))
140
+ ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf)
141
+
142
+ #ec_key = d2i_ECPrivateKey(kp, ptr, buf.size-1)
143
+ ec_key = d2i_ECPrivateKey(nil, ptr, buf.size-1)
144
+ return nil if ec_key.null?
145
+ bn = EC_KEY_get0_private_key(ec_key)
146
+ BN_bn2bin(bn, buf)
147
+ buf.read_string(32).unpack("H*")[0]
148
+ end
149
+
150
+ # Given the components of a signature and a selector value, recover and
151
+ # return the public key that generated the signature according to the
152
+ # algorithm in SEC1v2 section 4.1.6.
153
+ #
154
+ # rec_id is an index from 0 to 3 that indicates which of the 4 possible
155
+ # keys is the correct one. Because the key recovery operation yields
156
+ # multiple potential keys, the correct key must either be stored alongside
157
+ # the signature, or you must be willing to try each rec_id in turn until
158
+ # you find one that outputs the key you are expecting.
159
+ #
160
+ # If this method returns nil, it means recovery was not possible and rec_id
161
+ # should be iterated.
162
+ #
163
+ # Given the above two points, a correct usage of this method is inside a
164
+ # for loop from 0 to 3, and if the output is nil OR a key that is not the
165
+ # one you expect, you try again with the next rec_id.
166
+ #
167
+ # message_hash = hash of the signed message.
168
+ # signature = the R and S components of the signature, wrapped.
169
+ # rec_id = which possible key to recover.
170
+ # is_compressed = whether or not the original pubkey was compressed.
171
+ def self.recover_public_key_from_signature(message_hash, signature, rec_id, is_compressed)
172
+ return nil if rec_id < 0 or signature.bytesize != 65
173
+ init_ffi_ssl
174
+
175
+ signature = FFI::MemoryPointer.from_string(signature)
176
+ #signature_bn = BN_bin2bn(signature, 65, BN_new())
177
+ r = BN_bin2bn(signature[1], 32, BN_new())
178
+ s = BN_bin2bn(signature[33], 32, BN_new())
179
+
180
+ n, i = 0, rec_id / 2
181
+ eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
182
+
183
+ EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED) if is_compressed
184
+
185
+ group = EC_KEY_get0_group(eckey)
186
+ order = BN_new()
187
+ EC_GROUP_get_order(group, order, nil)
188
+ x = BN_dup(order)
189
+ BN_mul_word(x, i)
190
+ BN_add(x, x, r)
191
+
192
+ field = BN_new()
193
+ EC_GROUP_get_curve_GFp(group, field, nil, nil, nil)
194
+
195
+ if BN_cmp(x, field) >= 0
196
+ [r, s, order, x, field].each{|i| BN_free(i) }
197
+ EC_KEY_free(eckey)
198
+ return nil
199
+ end
200
+
201
+ big_r = EC_POINT_new(group)
202
+ EC_POINT_set_compressed_coordinates_GFp(group, big_r, x, rec_id % 2, nil)
203
+
204
+ big_q = EC_POINT_new(group)
205
+ n = EC_GROUP_get_degree(group)
206
+ e = BN_bin2bn(message_hash, message_hash.bytesize, BN_new())
207
+ BN_rshift(e, e, 8 - (n & 7)) if 8 * message_hash.bytesize > n
208
+
209
+ ctx = BN_CTX_new()
210
+ zero, rr, sor, eor = BN_new(), BN_new(), BN_new(), BN_new()
211
+ BN_set_word(zero, 0)
212
+ BN_mod_sub(e, zero, e, order, ctx)
213
+ BN_mod_inverse(rr, r, order, ctx)
214
+ BN_mod_mul(sor, s, rr, order, ctx)
215
+ BN_mod_mul(eor, e, rr, order, ctx)
216
+ EC_POINT_mul(group, big_q, eor, big_r, sor, ctx)
217
+ EC_KEY_set_public_key(eckey, big_q)
218
+ BN_CTX_free(ctx)
219
+
220
+ [r, s, order, x, field, e, zero, rr, sor, eor].each{|i| BN_free(i) }
221
+ [big_r, big_q].each{|i| EC_POINT_free(i) }
222
+
223
+ length = i2o_ECPublicKey(eckey, nil)
224
+ buf = FFI::MemoryPointer.new(:uint8, length)
225
+ ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf)
226
+ pub_hex = if i2o_ECPublicKey(eckey, ptr) == length
227
+ buf.read_string(length).unpack("H*")[0]
228
+ end
229
+
230
+ EC_KEY_free(eckey)
231
+
232
+ pub_hex
233
+ end
234
+
235
+ # Regenerate a DER-encoded signature such that the S-value complies with the BIP62
236
+ # specification.
237
+ #
238
+ def self.signature_to_low_s(signature)
239
+ init_ffi_ssl
240
+
241
+ buf = FFI::MemoryPointer.new(:uint8, 34)
242
+ temp = signature.unpack("C*")
243
+ length_r = temp[3]
244
+ length_s = temp[5+length_r]
245
+ sig = FFI::MemoryPointer.from_string(signature)
246
+
247
+ # Calculate the lower s value
248
+ s = BN_bin2bn(sig[6 + length_r], length_s, BN_new())
249
+ eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
250
+ group, order, halforder, ctx = EC_KEY_get0_group(eckey), BN_new(), BN_new(), BN_CTX_new()
251
+
252
+ EC_GROUP_get_order(group, order, ctx)
253
+ BN_rshift1(halforder, order)
254
+ if BN_cmp(s, halforder) > 0
255
+ BN_sub(s, order, s)
256
+ end
257
+
258
+ BN_free(halforder)
259
+ BN_free(order)
260
+ BN_CTX_free(ctx)
261
+
262
+ length_s = BN_bn2bin(s, buf)
263
+ # p buf.read_string(length_s).unpack("H*")
264
+
265
+ # Re-encode the signature in DER format
266
+ sig = [0x30, 0, 0x02, length_r]
267
+ sig.concat(temp.slice(4, length_r))
268
+ sig << 0x02
269
+ sig << length_s
270
+ sig.concat(buf.read_string(length_s).unpack("C*"))
271
+ sig[1] = sig.size - 2
272
+
273
+ BN_free(s)
274
+ EC_KEY_free(eckey)
275
+
276
+ sig.pack("C*")
277
+ end
278
+
279
+ def self.sign_compact(hash, private_key, public_key_hex = nil, pubkey_compressed = nil)
280
+ msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hash)
281
+
282
+ private_key = [private_key].pack("H*") if private_key.bytesize >= 64
283
+ private_key_hex = private_key.unpack("H*")[0]
284
+
285
+ public_key_hex = regenerate_key(private_key_hex).last unless public_key_hex
286
+ pubkey_compressed = (public_key_hex[0..1] == "04" ? false : true) unless pubkey_compressed
287
+
288
+ init_ffi_ssl
289
+ eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
290
+ priv_key = BN_bin2bn(private_key, private_key.bytesize, BN_new())
291
+
292
+ group, order, ctx = EC_KEY_get0_group(eckey), BN_new(), BN_CTX_new()
293
+ EC_GROUP_get_order(group, order, ctx)
294
+
295
+ pub_key = EC_POINT_new(group)
296
+ EC_POINT_mul(group, pub_key, priv_key, nil, nil, ctx)
297
+ EC_KEY_set_private_key(eckey, priv_key)
298
+ EC_KEY_set_public_key(eckey, pub_key)
299
+
300
+ signature = ECDSA_do_sign(msg32, msg32.size, eckey)
301
+
302
+ BN_free(order)
303
+ BN_CTX_free(ctx)
304
+ EC_POINT_free(pub_key)
305
+ BN_free(priv_key)
306
+ EC_KEY_free(eckey)
307
+
308
+ buf, rec_id, head = FFI::MemoryPointer.new(:uint8, 32), nil, nil
309
+ 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") }
310
+
311
+ if signature.get_array_of_pointer(0, 2).all?{|i| BN_num_bits(i) <= 256 }
312
+ 4.times{|i|
313
+ head = [ 27 + i + (pubkey_compressed ? 4 : 0) ].pack("C")
314
+ if public_key_hex == recover_public_key_from_signature(msg32.read_string(32), [head, r, s].join, i, pubkey_compressed)
315
+ rec_id = i; break
316
+ end
317
+ }
318
+ end
319
+
320
+ ECDSA_SIG_free(signature)
321
+
322
+ [ head, [r,s] ].join if rec_id
323
+ end
324
+
325
+ def self.recover_compact(hash, signature)
326
+ return false if signature.bytesize != 65
327
+ msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hash)
328
+
329
+ version = signature.unpack('C')[0]
330
+ return false if version < 27 or version > 34
331
+
332
+ compressed = (version >= 31) ? (version -= 4; true) : false
333
+ pubkey = recover_public_key_from_signature(msg32.read_string(32), signature, version-27, compressed)
334
+ end
335
+
336
+ # lifted from https://github.com/GemHQ/money-tree
337
+ def self.ec_add(point_0, point_1)
338
+ init_ffi_ssl
339
+
340
+ eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
341
+ group = EC_KEY_get0_group(eckey)
342
+
343
+ point_0_hex = point_0.to_bn.to_s(16)
344
+ point_0_pt = EC_POINT_hex2point(group, point_0_hex, nil, nil)
345
+ point_1_hex = point_1.to_bn.to_s(16)
346
+ point_1_pt = EC_POINT_hex2point(group, point_1_hex, nil, nil)
347
+
348
+ sum_point = EC_POINT_new(group)
349
+ success = EC_POINT_add(group, sum_point, point_0_pt, point_1_pt, nil)
350
+ hex = EC_POINT_point2hex(group, sum_point, POINT_CONVERSION_UNCOMPRESSED, nil)
351
+ EC_KEY_free(eckey)
352
+ EC_POINT_free(sum_point)
353
+ hex
354
+ end
355
+
356
+ # repack signature for OpenSSL 1.0.1k handling of DER signatures
357
+ # https://github.com/bitcoin/bitcoin/pull/5634/files
358
+ def self.repack_der_signature(signature)
359
+ init_ffi_ssl
360
+
361
+ return false if signature.empty?
362
+
363
+ # New versions of OpenSSL will reject non-canonical DER signatures. de/re-serialize first.
364
+ norm_der = FFI::MemoryPointer.new(:pointer)
365
+ sig_ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, FFI::MemoryPointer.from_string(signature))
366
+
367
+ norm_sig = d2i_ECDSA_SIG(nil, sig_ptr, signature.bytesize)
368
+
369
+ derlen = i2d_ECDSA_SIG(norm_sig, norm_der)
370
+ ECDSA_SIG_free(norm_sig)
371
+ return false if derlen <= 0
372
+
373
+ ret = norm_der.read_pointer.read_string(derlen)
374
+ OPENSSL_free(norm_der.read_pointer)
375
+
376
+ ret
377
+ end
378
+
379
+ def self.init_ffi_ssl
380
+ return if @ssl_loaded
381
+ SSL_library_init()
382
+ ERR_load_crypto_strings()
383
+ SSL_load_error_strings()
384
+ RAND_poll()
385
+ @ssl_loaded = true
386
+ end
387
+ end
388
+ end