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