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,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
|
data/lib/bitcoin/key.rb
ADDED
@@ -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
|
+
|