bitcoin-cigs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,46 @@
1
+ # rcov generated
2
+ coverage
3
+
4
+ # rdoc generated
5
+ rdoc
6
+
7
+ # yard generated
8
+ doc
9
+ .yardoc
10
+
11
+ # bundler
12
+ .bundle
13
+ *.gem
14
+
15
+ # jeweler generated
16
+ pkg
17
+
18
+ # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
19
+ #
20
+ # * Create a file at ~/.gitignore
21
+ # * Include files you want ignored
22
+ # * Run: git config --global core.excludesfile ~/.gitignore
23
+ #
24
+ # After doing this, these files will be ignored in all your git projects,
25
+ # saving you from having to 'pollute' every project you touch with them
26
+ #
27
+ # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
28
+ #
29
+ # For MacOS:
30
+
31
+ .DS_Store
32
+
33
+ # For TextMate
34
+ *.tmproj
35
+ tmtags
36
+ .tmtags
37
+
38
+ # For emacs:
39
+ *~
40
+ \#*
41
+ .\#*
42
+
43
+ # For vim:
44
+ *.swp
45
+
46
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
data/Gemfile.lock ADDED
@@ -0,0 +1,26 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ bitcoin-cigs (0.0.1)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.2.4)
10
+ rake (10.0.4)
11
+ rspec (2.13.0)
12
+ rspec-core (~> 2.13.0)
13
+ rspec-expectations (~> 2.13.0)
14
+ rspec-mocks (~> 2.13.0)
15
+ rspec-core (2.13.1)
16
+ rspec-expectations (2.13.0)
17
+ diff-lcs (>= 1.1.3, < 2.0)
18
+ rspec-mocks (2.13.1)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ bitcoin-cigs!
25
+ rake (~> 10.0.4)
26
+ rspec (~> 2.13.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Michael Pearce
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Bitcoin Cigs - Smokin' Hot Bitcoin Signatures
2
+
3
+ ## Installation
4
+
5
+ gem install bitcoin-cigs
6
+
7
+ ## Examples
8
+
9
+ Verify a message signature:
10
+ ```ruby
11
+ require 'rubygems'
12
+ require 'bitcoin-cigs'
13
+
14
+ address = "13C5HZKutjMDeuc7f5mPj6XGpJCZu7xKh2"
15
+ signature = "H55JIuwEi4YXzINOzx2oU6VsfBcTOScpFtp10pP/M4EWV336ClH65SObwPXnRf/fMXDu8hs8nweB42CtpWgngeM="
16
+ message = "aaa"
17
+
18
+ if BitcoinCigs.verify_message(address, signature, message)
19
+ puts "It looks like you own address #{address}!"
20
+ end
21
+ ```
22
+
23
+ # Credits
24
+
25
+ Thanks to jackjack for pointing me to Armory's implementation of message signatures:
26
+ https://github.com/jackjack-jj/jasvet/blob/master/jasvet.py
27
+
28
+ [Bitcoin Cigs](https://github.com/michaelgpearce/bitcoin-cigs) is maintained by [Michael Pearce](https://github.com/michaelgpearce).
29
+
30
+ # Copyright
31
+
32
+ Copyright (c) 2013 Michael Pearce. See LICENSE.txt for further details.
33
+
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc 'Run Specs'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
data/bin/jasvet.py ADDED
@@ -0,0 +1,615 @@
1
+ #!/usr/bin/env python
2
+
3
+ # jackjack's signing/verifying tool
4
+ # verifies base64 signatures from Bitcoin
5
+ # signs message in three formats:
6
+ # - Bitcoin base64 (compatible with Bitcoin)
7
+ # - ASCII armored, Clearsign
8
+ # - ASCII armored, Base64
9
+ #
10
+ # Licence: Public domain or CC0
11
+
12
+ import time
13
+ import hashlib
14
+ import random
15
+ import base64
16
+
17
+ FTVerbose=False
18
+
19
+
20
+
21
+ def randomk(): #better make it stronger
22
+ rk=0
23
+ for i in range(8):
24
+ rk=long(random.random()*0xffffffff)<<(32*i)
25
+ return rk
26
+
27
+ # Common constants/functions for Bitcoin
28
+
29
+ def hash_160_to_bc_address(h160, addrtype=0):
30
+ vh160 = chr(addrtype) + h160
31
+ h = Hash(vh160)
32
+ addr = vh160 + h[0:4]
33
+ print "b58:::", base64.b64encode(addr), b58encode(addr)
34
+ return b58encode(addr)
35
+
36
+ def bc_address_to_hash_160(addr):
37
+ bytes = b58decode(addr, 25)
38
+ return bytes[1:21]
39
+
40
+ def Hash(data):
41
+ print "HASH", hashlib.sha256(hashlib.sha256(data).digest()).digest()
42
+ return hashlib.sha256(hashlib.sha256(data).digest()).digest()
43
+
44
+ def sha256(data):
45
+ return hashlib.sha256(data).digest()
46
+
47
+ def sha1(data):
48
+ return hashlib.sha1(data).digest()
49
+
50
+ __b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
51
+ __b58base = len(__b58chars)
52
+
53
+ def b58encode(v):
54
+ long_value = 0L
55
+ for (i, c) in enumerate(v[::-1]):
56
+ long_value += (256**i) * ord(c)
57
+
58
+ result = ''
59
+ while long_value >= __b58base:
60
+ div, mod = divmod(long_value, __b58base)
61
+ result = __b58chars[mod] + result
62
+ long_value = div
63
+ result = __b58chars[long_value] + result
64
+
65
+ nPad = 0
66
+ for c in v:
67
+ if c == '\0': nPad += 1
68
+ else: break
69
+
70
+ return (__b58chars[0]*nPad) + result
71
+
72
+ def b58decode(v, length):
73
+ long_value = 0L
74
+ for (i, c) in enumerate(v[::-1]):
75
+ long_value += __b58chars.find(c) * (__b58base**i)
76
+
77
+ result = ''
78
+ while long_value >= 256:
79
+ div, mod = divmod(long_value, 256)
80
+ result = chr(mod) + result
81
+ long_value = div
82
+ result = chr(long_value) + result
83
+
84
+ nPad = 0
85
+ for c in v:
86
+ if c == __b58chars[0]: nPad += 1
87
+ else: break
88
+
89
+ result = chr(0)*nPad + result
90
+ if length is not None and len(result) != length:
91
+ return None
92
+
93
+ return result
94
+
95
+
96
+ def regenerate_key(sec):
97
+ b = ASecretToSecret(sec)
98
+ if not b:
99
+ return False
100
+ b = b[0:32]
101
+ secret = int('0x' + b.encode('hex'), 16)
102
+ return EC_KEY(secret)
103
+
104
+ def GetPubKey(pkey, compressed=False):
105
+ return i2o_ECPublicKey(pkey, compressed)
106
+
107
+ def GetPrivKey(pkey, compressed=False):
108
+ return i2d_ECPrivateKey(pkey, compressed)
109
+
110
+ def GetSecret(pkey):
111
+ return ('%064x' % pkey.secret).decode('hex')
112
+
113
+
114
+ def i2d_ECPrivateKey(pkey, compressed=False):#, crypted=True):
115
+ part3='a081a53081a2020101302c06072a8648ce3d0101022100' # for uncompressed keys
116
+ if compressed:
117
+ if True:#not crypted: ## Bitcoin accepts both part3's for crypted wallets...
118
+ part3='a08185308182020101302c06072a8648ce3d0101022100' # for compressed keys
119
+ key = '3081d30201010420' + \
120
+ '%064x' % pkey.secret + \
121
+ part3 + \
122
+ '%064x' % _p + \
123
+ '3006040100040107042102' + \
124
+ '%064x' % _Gx + \
125
+ '022100' + \
126
+ '%064x' % _r + \
127
+ '020101a124032200'
128
+ else:
129
+ key = '308201130201010420' + \
130
+ '%064x' % pkey.secret + \
131
+ part3 + \
132
+ '%064x' % _p + \
133
+ '3006040100040107044104' + \
134
+ '%064x' % _Gx + \
135
+ '%064x' % _Gy + \
136
+ '022100' + \
137
+ '%064x' % _r + \
138
+ '020101a144034200'
139
+
140
+ return key.decode('hex') + i2o_ECPublicKey(pkey, compressed)
141
+
142
+ def i2o_ECPublicKey(pkey, compressed=False):
143
+ if compressed:
144
+ if pkey.pubkey.point.y() & 1:
145
+ key = '03' + '%064x' % pkey.pubkey.point.x()
146
+ else:
147
+ key = '02' + '%064x' % pkey.pubkey.point.x()
148
+ else:
149
+ key = '04' + \
150
+ '%064x' % pkey.pubkey.point.x() + \
151
+ '%064x' % pkey.pubkey.point.y()
152
+
153
+ return key.decode('hex')
154
+
155
+ def hash_160(public_key):
156
+ md = hashlib.new('ripemd160')
157
+ md.update(hashlib.sha256(public_key).digest())
158
+ return md.digest()
159
+
160
+ def public_key_to_bc_address(public_key, v=0):
161
+ h160 = hash_160(public_key)
162
+ return hash_160_to_bc_address(h160, v)
163
+
164
+ def inverse_mod( a, m ):
165
+ if a < 0 or m <= a: a = a % m
166
+ c, d = a, m
167
+ uc, vc, ud, vd = 1, 0, 0, 1
168
+ while c != 0:
169
+ q, c, d = divmod( d, c ) + ( c, )
170
+ uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc
171
+ assert d == 1
172
+ if ud > 0: return ud
173
+ else: return ud + m
174
+
175
+ class CurveFp( object ):
176
+ def __init__( self, p, a, b ):
177
+ self.__p = p
178
+ self.__a = a
179
+ self.__b = b
180
+
181
+ def p( self ):
182
+ return self.__p
183
+
184
+ def a( self ):
185
+ return self.__a
186
+
187
+ def b( self ):
188
+ return self.__b
189
+
190
+ def contains_point( self, x, y ):
191
+ return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0
192
+
193
+ class Point( object ):
194
+ def __init__( self, curve, x, y, order = None ):
195
+ print "JERE", curve, x, y, order
196
+ self.__curve = curve
197
+ self.__x = x
198
+ self.__y = y
199
+ self.__order = order
200
+ if self.__curve: assert self.__curve.contains_point( x, y )
201
+ if order: assert self * order == INFINITY
202
+
203
+ def __add__( self, other ):
204
+ if other == INFINITY: return self
205
+ if self == INFINITY: return other
206
+ assert self.__curve == other.__curve
207
+ if self.__x == other.__x:
208
+ if ( self.__y + other.__y ) % self.__curve.p() == 0:
209
+ return INFINITY
210
+ else:
211
+ return self.double()
212
+
213
+ p = self.__curve.p()
214
+ l = ( ( other.__y - self.__y ) * \
215
+ inverse_mod( other.__x - self.__x, p ) ) % p
216
+ x3 = ( l * l - self.__x - other.__x ) % p
217
+ y3 = ( l * ( self.__x - x3 ) - self.__y ) % p
218
+ return Point( self.__curve, x3, y3 )
219
+
220
+ def __mul__( self, other ):
221
+ def leftmost_bit( x ):
222
+ assert x > 0
223
+ result = 1L
224
+ while result <= x: result = 2 * result
225
+ return result / 2
226
+
227
+ print "MULT ARG ", other
228
+ e = other
229
+ if self.__order: e = e % self.__order
230
+ print "EEEE ", e, self.__order
231
+ if e == 0: return INFINITY
232
+ if self == INFINITY: return INFINITY
233
+ assert e > 0
234
+ e3 = 3 * e
235
+ negative_self = Point( self.__curve, self.__x, -self.__y, self.__order )
236
+ i = leftmost_bit( e3 ) / 2
237
+ result = self
238
+ while i > 1:
239
+ result = result.double()
240
+ if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self
241
+ if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self
242
+ i = i / 2
243
+ return result
244
+
245
+ def __rmul__( self, other ):
246
+ return self * other
247
+
248
+ def __str__( self ):
249
+ if self == INFINITY: return "infinity"
250
+ return "(%d,%d)" % ( self.__x, self.__y )
251
+
252
+ def double( self ):
253
+ if self == INFINITY:
254
+ return INFINITY
255
+
256
+ p = self.__curve.p()
257
+ a = self.__curve.a()
258
+ l = ( ( 3 * self.__x * self.__x + a ) * \
259
+ inverse_mod( 2 * self.__y, p ) ) % p
260
+ x3 = ( l * l - 2 * self.__x ) % p
261
+ y3 = ( l * ( self.__x - x3 ) - self.__y ) % p
262
+ return Point( self.__curve, x3, y3 )
263
+
264
+ def x( self ):
265
+ return self.__x
266
+
267
+ def y( self ):
268
+ return self.__y
269
+
270
+ def curve( self ):
271
+ return self.__curve
272
+
273
+ def order( self ):
274
+ return self.__order
275
+
276
+ INFINITY = Point( None, None, None )
277
+
278
+ def str_to_long(b):
279
+ res = 0
280
+ pos = 1
281
+ for a in reversed(b):
282
+ res += ord(a) * pos
283
+ pos *= 256
284
+ return res
285
+
286
+ class Public_key( object ):
287
+ def __init__( self, generator, point, c ):
288
+ self.curve = generator.curve()
289
+ self.generator = generator
290
+ self.point = point
291
+ self.compressed = c
292
+ n = generator.order()
293
+ if not n:
294
+ raise RuntimeError, "Generator point must have order."
295
+ if not n * point == INFINITY:
296
+ raise RuntimeError, "Generator point order is bad."
297
+ if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y():
298
+ raise RuntimeError, "Generator point has x or y out of range."
299
+
300
+ def verify( self, hash, signature ):
301
+ if isinstance(hash, str):
302
+ hash=str_to_long(hash)
303
+ G = self.generator
304
+ n = G.order()
305
+ r = signature.r
306
+ s = signature.s
307
+ if r < 1 or r > n-1: return False
308
+ if s < 1 or s > n-1: return False
309
+ c = inverse_mod( s, n )
310
+ u1 = ( hash * c ) % n
311
+ u2 = ( r * c ) % n
312
+ xy = u1 * G + u2 * self.point
313
+ v = xy.x() % n
314
+ return v == r
315
+
316
+ def ser(self):
317
+ if self.compressed:
318
+ if self.point.y() & 1:
319
+ key = '03' + '%064x' % self.point.x()
320
+ else:
321
+ key = '02' + '%064x' % self.point.x()
322
+ else:
323
+ key = '04' + \
324
+ '%064x' % self.point.x() + \
325
+ '%064x' % self.point.y()
326
+
327
+ return key.decode('hex')
328
+
329
+
330
+ class Signature( object ):
331
+ def __init__( self, r, s ):
332
+ self.r = r
333
+ self.s = s
334
+
335
+ def ser(self):
336
+ return ("%064x%064x"%(self.r,self.s)).decode('hex')
337
+
338
+ class Private_key( object ):
339
+ def __init__( self, public_key, secret_multiplier ):
340
+ self.public_key = public_key
341
+ self.secret_multiplier = secret_multiplier
342
+
343
+ # def der( self ):
344
+ # hex_der_key = '06052b8104000a30740201010420' + \
345
+ # '%064x' % self.secret_multiplier + \
346
+ # 'a00706052b8104000aa14403420004' + \
347
+ # '%064x' % self.public_key.point.x() + \
348
+ # '%064x' % self.public_key.point.y()
349
+ # return hex_der_key.decode('hex')
350
+
351
+ def sign( self, hash, random_k ):
352
+ if isinstance(hash, str):
353
+ hash=str_to_long(hash)
354
+ G = self.public_key.generator
355
+ n = G.order()
356
+ k = random_k % n
357
+ p1 = k * G
358
+ r = p1.x()
359
+ if r == 0: raise RuntimeError, "amazingly unlucky random number r"
360
+ s = ( inverse_mod( k, n ) * \
361
+ ( hash + ( self.secret_multiplier * r ) % n ) ) % n
362
+ if s == 0: raise RuntimeError, "amazingly unlucky random number s"
363
+ return Signature( r, s )
364
+
365
+ class EC_KEY(object):
366
+ def __init__( self, secret, c=False):
367
+ curve = CurveFp( _p, _a, _b )
368
+ generator = Point( curve, _Gx, _Gy, _r )
369
+ self.pubkey = Public_key( generator, generator * secret, c )
370
+ self.privkey = Private_key( self.pubkey, secret )
371
+ self.secret = secret
372
+
373
+ def format_msg_to_sign(msg):
374
+ return "\x18Bitcoin Signed Message:\n"+chr(len(msg))+msg #todo: check 18
375
+
376
+ def sqrt_mod(a, p):
377
+ return pow(a, (p+1)/4, p)
378
+
379
+ _p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL
380
+ _r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L
381
+ _b = 0x0000000000000000000000000000000000000000000000000000000000000007L
382
+ _a = 0x0000000000000000000000000000000000000000000000000000000000000000L
383
+ _Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L
384
+ _Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L
385
+
386
+ curve_secp256k1 = CurveFp (_p, _a, _b)
387
+ generator_secp256k1 = g = Point (curve_secp256k1, _Gx, _Gy, _r)
388
+ randrange = random.SystemRandom().randrange
389
+
390
+ # Signing/verifying
391
+
392
+ def verify_message_Bitcoin(address, signature, message, pureECDSASigning=False):
393
+ print "DECODE"
394
+ print address
395
+ print base64.b64encode(b58decode(address, None))
396
+ networkversion=str_to_long(b58decode(address, None)) >> (8*24)
397
+ print "COOL"
398
+ print str_to_long(b58decode(address, None))
399
+ print "NETWORK VERSION"
400
+ print networkversion
401
+ msg=message
402
+ print msg
403
+ if not pureECDSASigning:
404
+ print "SHIT"
405
+ print message
406
+ print format_msg_to_sign(message)
407
+ msg=Hash(format_msg_to_sign(message))
408
+ print msg
409
+
410
+ compressed=False
411
+ curve = curve_secp256k1
412
+ G = generator_secp256k1
413
+ _a,_b,_p=curve.a(),curve.b(),curve.p()
414
+
415
+ order = G.order()
416
+ print "G ORDER ", order
417
+ sig = base64.b64decode(signature)
418
+ if len(sig) != 65:
419
+ raise Exception("vmB","Bad signature")
420
+ print "SIG ", sig
421
+
422
+ hb = ord(sig[0])
423
+ print "hb", hb
424
+ r,s = map(str_to_long,[sig[1:33],sig[33:65]])
425
+ print "r, s", r, s
426
+
427
+ if hb < 27 or hb >= 35:
428
+ raise Exception("vmB","Bad first byte")
429
+ if hb >= 31:
430
+ compressed = True
431
+ hb -= 4
432
+
433
+ recid = hb - 27
434
+ x = (r + (recid/2) * order) % _p
435
+ y2 = ( pow(x,3,_p) + _a*x + _b ) % _p
436
+ print "y2, p", y2, _p
437
+ yomy = sqrt_mod(y2, _p)
438
+ print "yomy", yomy
439
+ if (yomy - recid) % 2 == 0:
440
+ y=yomy
441
+ else:
442
+ y=_p - yomy
443
+ print "y", y
444
+
445
+ R = Point(curve, x, y, order)
446
+ print "R", R
447
+ e = str_to_long(msg)
448
+ minus_e = -e % order
449
+ print "e, -e: ", e, minus_e
450
+ inv_r = inverse_mod(r,order)
451
+ print "inv_r", inv_r
452
+ print "s", s
453
+
454
+ Q = inv_r * ( R*s + G*minus_e )
455
+ print "Q", Q
456
+
457
+ public_key = Public_key(G, Q, compressed)
458
+ print "SER", public_key.ser()
459
+ addr = public_key_to_bc_address(public_key.ser(), networkversion)
460
+ if address != addr:
461
+ raise Exception("vmB","Bad address. Signing: %s, received: %s"%(addr,address))
462
+
463
+
464
+ def sign_message(secret, message, pureECDSASigning=False):
465
+ if len(secret) == 32:
466
+ pkey = EC_KEY(str_to_long(secret))
467
+ compressed = False
468
+ elif len(secret) == 33:
469
+ pkey = EC_KEY(str_to_long(secret[:-1]))
470
+ secret=secret[:-1]
471
+ compressed = True
472
+ else:
473
+ raise Exception("sm","Bad private key size")
474
+
475
+ msg=message
476
+ if not pureECDSASigning:
477
+ msg=Hash(format_msg_to_sign(message))
478
+
479
+ eckey = EC_KEY(str_to_long(secret), compressed)
480
+ private_key = eckey.privkey
481
+ public_key = eckey.pubkey
482
+ addr = public_key_to_bc_address(GetPubKey(eckey,eckey.pubkey.compressed))
483
+
484
+ sig = private_key.sign(msg, randomk())
485
+ if not public_key.verify(msg, sig):
486
+ raise Exception("sm","Problem signing message")
487
+ return [sig,addr,compressed,public_key]
488
+
489
+
490
+ def sign_message_Bitcoin(secret, msg, pureECDSASigning=False):
491
+ sig,addr,compressed,public_key=sign_message(secret, msg, pureECDSASigning)
492
+
493
+ for i in range(4):
494
+ hb=27+i
495
+ if compressed:
496
+ hb+=4
497
+ sign=base64.b64encode(chr(hb)+sig.ser())
498
+ try:
499
+ verify_message_Bitcoin(addr, sign, msg, pureECDSASigning)
500
+ return {'address':addr, 'b64-signature':sign, 'signature':chr(hb)+sig.ser(), 'message':msg}
501
+ except Exception as e:
502
+ # print e.args
503
+ pass
504
+
505
+ raise Exception("smB","Unable to construct recoverable key")
506
+
507
+ def FormatText(t, sigctx, verbose=False): #sigctx: False=what is displayed, True=what is signed
508
+ r=''
509
+ te=t.split('\n')
510
+ for l in te:
511
+ while len(l) and l[len(l)-1] in [' ', '\t', chr(9)]:
512
+ l=l[:-1]
513
+ if not len(l) or l[len(l)-1]!='\r':
514
+ l+='\r'
515
+ if not sigctx:
516
+ if len(l) and l[0]=='-':
517
+ l='- '+l[1:]
518
+ r+=l+'\n'
519
+ r=r[:-2]
520
+
521
+ global FTVerbose
522
+ if FTVerbose:
523
+ print ' -- Sent: '+t.encode('hex')
524
+ if sigctx:
525
+ print ' -- Signed: '+r.encode('hex')
526
+ else:
527
+ print ' -- Displayed: '+r.encode('hex')
528
+
529
+ return r
530
+
531
+
532
+ def crc24(m):
533
+ INIT = 0xB704CE
534
+ POLY = 0x1864CFB
535
+ crc = INIT
536
+ r = ''
537
+ for o in m:
538
+ o=ord(o)
539
+ crc ^= (o << 16)
540
+ for i in xrange(8):
541
+ crc <<= 1
542
+ if crc & 0x1000000:
543
+ crc ^= POLY
544
+ for i in range(3):
545
+ r += chr( ( crc & (0xff<<(8*i))) >> (8*i) )
546
+ return r
547
+
548
+ def chunks(t, n):
549
+ return [t[i:i+n] for i in range(0, len(t), n)]
550
+
551
+ def ASCIIArmory(block, name):
552
+ r='-----BEGIN '+name+'-----\r\n'
553
+ r+='\r\n'.join(chunks(base64.b64encode(block), 64))+'\r\n='
554
+ r+=base64.b64encode(crc24(block))+'\r\n'
555
+ r+='-----END '+name+'-----'
556
+ return r
557
+
558
+
559
+ #==============================================
560
+
561
+ def verifySignature(addr, b64sig, msg):
562
+ return verify_message_Bitcoin(addr, b64sig, FormatText(msg, True))
563
+
564
+ def ASv0(privkey, msg):
565
+ return sign_message_Bitcoin(privkey, FormatText(msg, True))
566
+
567
+ def ASv1CS(privkey, msg):
568
+ sig=ASv0(privkey, msg)
569
+ r='-----BEGIN SIGNED BITCOIN MESSAGE-----\r\n\r\n'
570
+ r+=FormatText(msg, False)+'\r\n'
571
+ r+=ASCIIArmory(sig['signature'], 'BITCOIN SIGNATURE')
572
+ return r
573
+
574
+ def ASv1B64(privkey, msg):
575
+ sig=ASv0(privkey, msg)
576
+ return ASCIIArmory(sig['signature']+sig['message'], 'BITCOIN SIGNED MESSAGE')
577
+
578
+
579
+
580
+ #==============================================
581
+
582
+ #
583
+ # Some tests with ugly output
584
+ # You can delete the print commands in FormatText() after testing
585
+ #
586
+
587
+ pvk1='\x01'*32
588
+ text0='Hello world!'
589
+ text1='Hello world!\n'
590
+ text2='Hello world!\n\t'
591
+ text3='Hello world!\n-jackjack'
592
+ text4='Hello world!\n-jackjack '
593
+ text5='Hello world!'
594
+
595
+ FTVerbose=True
596
+ # sv0=ASv0(pvk1, text1)
597
+ # print "STUFF TO SIGN"
598
+ # print sv0
599
+ # print "KK"
600
+ # print verifySignature(sv0['address'], sv0['b64-signature'], sv0['message'])
601
+ # print
602
+ # print ASv1B64(pvk1, text1)
603
+ # print
604
+ # print ASv1CS(pvk1, text1)
605
+ # print
606
+ # print ASv1CS(pvk1, text2)
607
+ # print
608
+ # print ASv1CS(pvk1, text3)
609
+ # print
610
+ # print ASv1CS(pvk1, text4)
611
+ # print
612
+ # print ASv1CS(pvk1, text5)
613
+ print verifySignature('13C5HZKutjMDeuc7f5mPj6XGpJCZu7xKh2', 'H55JIuwEi4YXzINOzx2oU6VsfBcTOScpFtp10pP/M4EWV336ClH65SObwPXnRf/fMXDu8hs8nweB42CtpWgngeM=', 'abc')
614
+
615
+ #print verifySignature('13C5HZKutjMDeuc7f5mPj6XGpJCZu7xKh2', 'H55JIuwEi4YXzINOzx2oU6VsfBcTOScpFtp10pP/M4EWV336ClH65SObwPXnRf/fMXDu8hs8nweB42CtpWgngeM=', 'aaa')
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require "bitcoin_cigs/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "bitcoin-cigs"
8
+ s.version = BitcoinCigs::VERSION
9
+ s.authors = ["Michael Pearce"]
10
+ s.email = ["michaelgpearce@yahoo.com"]
11
+ s.homepage = "http://github.com/michaelgpearce/bitcoin-cigs"
12
+ s.summary = "Create and Verify Bitcoin Signatures"
13
+ s.description = "Create and Verify Bitcoin Signatures."
14
+
15
+ s.rubyforge_project = "bitcoin-cigs"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency 'rspec', '~> 2.13.0'
22
+ s.add_development_dependency 'rake', '~> 10.0.4'
23
+ end
@@ -0,0 +1 @@
1
+ require 'bitcoin_cigs'
@@ -0,0 +1,113 @@
1
+ require 'base64'
2
+
3
+ require 'bitcoin_cigs/error'
4
+ require 'bitcoin_cigs/math_helper'
5
+ require 'bitcoin_cigs/base_58'
6
+ require 'bitcoin_cigs/curve_fp'
7
+ require 'bitcoin_cigs/point'
8
+ require 'bitcoin_cigs/public_key'
9
+
10
+ module BitcoinCigs
11
+ P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
12
+ R = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
13
+ B = 0x0000000000000000000000000000000000000000000000000000000000000007
14
+ A = 0x0000000000000000000000000000000000000000000000000000000000000000
15
+ Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
16
+ Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
17
+
18
+ CURVE_SECP256K1 = ::BitcoinCigs::CurveFp.new(P, A, B)
19
+ GENERATOR_SECP256K1 = ::BitcoinCigs::Point.new(CURVE_SECP256K1, Gx, Gy, R)
20
+
21
+ class << self
22
+ include ::BitcoinCigs::MathHelper
23
+
24
+ def verify_message(address, signature, message)
25
+ begin
26
+ verify_message!(address, signature, message)
27
+ true
28
+ rescue ::BitcoinCigs::Error
29
+ false
30
+ end
31
+ end
32
+
33
+ def verify_message!(address, signature, message)
34
+ network_version = str_to_num(::BitcoinCigs::Base58.decode(address)) >> (8 * 24)
35
+
36
+ message = calculate_hash(format_message_to_sign(message))
37
+
38
+ curve = CURVE_SECP256K1
39
+ g = GENERATOR_SECP256K1
40
+ a, b, p = curve.a, curve.b, curve.p
41
+
42
+ order = g.order
43
+
44
+ sig = Base64.decode64(signature)
45
+ raise ::BitcoinCigs::Error.new("Bad signature") if sig.size != 65
46
+
47
+ hb = sig[0].ord
48
+ r, s = [sig[1...33], sig[33...65]].collect { |s| str_to_num(s) }
49
+
50
+
51
+ raise ::BitcoinCigs::Error.new("Bad first byte") if hb < 27 || hb >= 35
52
+
53
+ compressed = false
54
+ if hb >= 31
55
+ compressed = true
56
+ hb -= 4
57
+ end
58
+
59
+ recid = hb - 27
60
+ x = (r + (recid / 2) * order) % p
61
+ y2 = ((x ** 3 % p) + a * x + b) % p
62
+ yomy = sqrt_mod(y2, p)
63
+ if (yomy - recid) % 2 == 0
64
+ y = yomy
65
+ else
66
+ y = p - yomy
67
+ end
68
+
69
+ r_point = ::BitcoinCigs::Point.new(curve, x, y, order)
70
+ e = str_to_num(message)
71
+ minus_e = -e % order
72
+
73
+ inv_r = inverse_mod(r, order)
74
+ q = (r_point * s + g * minus_e) * inv_r
75
+
76
+
77
+ public_key = ::BitcoinCigs::PublicKey.new(g, q, compressed)
78
+ addr = public_key_to_bc_address(public_key.ser(), network_version)
79
+ raise ::BitcoinCigs::Error.new("Bad address. Signing: #{addr}, Received: #{address}") if address != addr
80
+
81
+ nil
82
+ end
83
+
84
+ private
85
+
86
+ def calculate_hash(d)
87
+ sha256(sha256(d))
88
+ end
89
+
90
+ def format_message_to_sign(message)
91
+ "\x18Bitcoin Signed Message:\n#{message.length.chr}#{message}"
92
+ end
93
+
94
+ def public_key_to_bc_address(public_key, network_version)
95
+ h160 = hash_160(public_key)
96
+
97
+ hash_160_to_bc_address(h160, network_version)
98
+ end
99
+
100
+ def hash_160_to_bc_address(h160, address_type)
101
+ vh160 = address_type.chr + h160
102
+ h = calculate_hash(vh160)
103
+ addr = vh160 + h[0...4]
104
+
105
+ ::BitcoinCigs::Base58.encode(addr)
106
+ end
107
+
108
+ def hash_160(public_key)
109
+ ripemd160(sha256(public_key))
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,45 @@
1
+ module BitcoinCigs
2
+ class Base58
3
+ CHARS = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
4
+ CHAR_SIZE = CHARS.size
5
+
6
+ def self.decode(s)
7
+ value = s.chars.reverse_each.each_with_index.inject(0) { |acc, (ch, i)| acc + (CHARS.index(ch) || -1) * (CHAR_SIZE ** i) }
8
+
9
+ result = []
10
+ while value >= 256
11
+ div = value / 256
12
+ mod = value % 256
13
+ result.unshift(mod.chr)
14
+ value = div
15
+ end
16
+ result.unshift(value.chr)
17
+
18
+ pad_size = s.chars.inject(0) { |acc, ch| acc + (CHARS[0] == ch ? 1 : 0) }
19
+
20
+ result.unshift(0.chr * pad_size)
21
+
22
+ result.join
23
+ end
24
+
25
+ def self.encode(s)
26
+ value = 0
27
+
28
+ value = s.chars.reverse_each.each_with_index.inject(0) { |acc, (ch, i)| acc + (256 ** i) * ch.ord }
29
+
30
+ result = []
31
+ while value >= CHAR_SIZE
32
+ div, mod = value / CHAR_SIZE, value % CHAR_SIZE
33
+ result.unshift(CHARS[mod])
34
+ value = div
35
+ end
36
+ result.unshift(CHARS[value])
37
+
38
+ pad_size = s.chars.inject(0) { |acc, ch| acc + (ch == "\0" ? 1 : 0) }
39
+
40
+ result.unshift(CHARS[0] * pad_size)
41
+
42
+ result.join
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,15 @@
1
+ module BitcoinCigs
2
+ class CurveFp
3
+ attr_accessor :p, :a, :b
4
+
5
+ def initialize(p, a, b)
6
+ self.p = p
7
+ self.a = a
8
+ self.b = b
9
+ end
10
+
11
+ def contains_point(x, y)
12
+ return (y * y - (x * x * x + a * x + b)) % p == 0
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ module BitcoinCigs
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,46 @@
1
+ require 'openssl'
2
+ require 'digest/sha2'
3
+
4
+ module BitcoinCigs
5
+ module MathHelper
6
+ def inverse_mod(a, m)
7
+ a = a % m if a < 0 || m <= a
8
+
9
+ c, d = a, m
10
+
11
+ uc, vc, ud, vd = 1, 0, 0, 1
12
+
13
+ while c != 0
14
+ q, c, d = d / c, d % c, c
15
+ uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc
16
+ end
17
+ raise ::BitcoinCigs::Error.new unless d == 1
18
+
19
+ ud > 0 ? ud : ud + m
20
+ end
21
+
22
+ def leftmost_bit(x)
23
+ raise ::BitcoinCigs::Error.new unless x > 0
24
+
25
+ result = 1
26
+ result *= 2 while result <= x
27
+ result / 2
28
+ end
29
+
30
+ def ripemd160(d)
31
+ (OpenSSL::Digest::RIPEMD160.new << d).digest
32
+ end
33
+
34
+ def sha256(d)
35
+ (Digest::SHA2.new << d).digest
36
+ end
37
+
38
+ def sqrt_mod(a, p)
39
+ a.to_bn.mod_exp((p + 1) / 4, p).to_i
40
+ end
41
+
42
+ def str_to_num(s)
43
+ s.chars.reverse_each.with_index.inject(0) { |acc, (ch, i)| acc + ch.ord * (256 ** i) }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,91 @@
1
+ module BitcoinCigs
2
+ class Point
3
+ include MathHelper
4
+
5
+ attr_accessor :curve, :x, :y, :order
6
+
7
+ def self.infinity
8
+ ::BitcoinCigs::Point.new(nil, nil, nil)
9
+ end
10
+
11
+ def initialize(curve, x, y, order = nil)
12
+ self.curve = curve
13
+ self.x = x
14
+ self.y = y
15
+ self.order = order
16
+
17
+ return if infinite?
18
+
19
+ raise ::BitcoinCigs::Error.new if curve && !curve.contains_point(x, y)
20
+ raise ::BitcoinCigs::Error.new if order && !(self * order).infinite?
21
+ end
22
+
23
+ def infinite?
24
+ curve.nil? && x.nil? && y.nil? && order.nil?
25
+ end
26
+
27
+ def +(other)
28
+ return self if other.infinite?
29
+ return other if infinite?
30
+
31
+ raise ::BitcoinCigs::Error.new if curve != other.curve
32
+
33
+ if x == other.x
34
+ return (y + other.y) % curve.p == 0 ? ::BitcoinCigs::Point.infinity : double
35
+ end
36
+
37
+ p = curve.p
38
+ l = ( ( other.y - y ) * inverse_mod( other.x - x, p ) ) % p
39
+ x3 = ( l * l - x - other.x ) % p
40
+ y3 = ( l * ( x - x3 ) - y ) % p
41
+
42
+ Point.new(curve, x3, y3)
43
+ end
44
+
45
+ def *(other)
46
+ e = other
47
+
48
+ e = e % order if order
49
+
50
+ return ::BitcoinCigs::Point.infinity if e == 0
51
+ return ::BitcoinCigs::Point.infinity if infinite?
52
+
53
+ raise ::BitcoinCigs::Error.new unless e > 0
54
+
55
+ e3 = 3 * e
56
+ negative_self = ::BitcoinCigs::Point.new(curve, x, -y, order)
57
+ i = leftmost_bit(e3) / 2
58
+ result = self
59
+
60
+ while i > 1
61
+ result = result.double
62
+ result += self if (e3 & i) != 0 && (e & i) == 0
63
+ result += negative_self if (e3 & i) == 0 && (e & i) != 0
64
+ i = i / 2
65
+ end
66
+
67
+ result
68
+ end
69
+
70
+ def ==(other)
71
+ curve == other.curve && x == other.x && y == other.y && order == other.order
72
+ end
73
+
74
+ def to_s
75
+ infinite? ? "infinity" : "(#{x},#{y})"
76
+ end
77
+
78
+ def double
79
+ return ::BitcoinCigs::Point.infinity if infinite?
80
+
81
+ p = curve.p
82
+ a = curve.a
83
+ l = ( ( 3 * x * x + a ) * \
84
+ inverse_mod( 2 * y, p ) ) % p
85
+ x3 = ( l * l - 2 * x ) % p
86
+ y3 = ( l * ( x - x3 ) - y ) % p
87
+
88
+ ::BitcoinCigs::Point.new(curve, x3, y3)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,35 @@
1
+ module BitcoinCigs
2
+ class PublicKey
3
+ attr_accessor :curve, :generator, :point, :compressed
4
+
5
+ def initialize(generator, point, compressed)
6
+ self.curve = generator.curve
7
+ self.generator = generator
8
+ self.point = point
9
+ self.compressed = compressed
10
+
11
+ n = generator.order
12
+
13
+ raise ::BitcoinCigs::Error.new("Generator point must have order.") if n.nil?
14
+ raise ::BitcoinCigs::Error.new("Generator point order is bad.") unless (point * n).infinite?
15
+ if point.x < 0 || n <= point.x || point.y < 0 || n <= point.y
16
+ raise ::BitcoinCigs::Error, "Generator point has x or y out of range."
17
+ end
18
+ end
19
+
20
+ def ser
21
+ if compressed
22
+ if point.y & 1 > 0
23
+ key = '03%064x' % point.x
24
+ else
25
+ key = '02%064x' % point.x
26
+ end
27
+ else
28
+ key = '04%064x%064x' % [point.x, point.y]
29
+ end
30
+
31
+ [key].pack('H*')
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,3 @@
1
+ module BitcoinCigs
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+ require 'base64'
3
+
4
+ describe BitcoinCigs::Base58 do
5
+ describe "decode" do
6
+ let(:base_58_encoded_data) { "13C5HZKutjMDeuc7f5mPj6XGpJCZu7xKh2" }
7
+
8
+ subject { BitcoinCigs::Base58.decode(base_58_encoded_data) }
9
+
10
+ context "with valid data" do
11
+ it "decodes properly" do
12
+ expect(Base64.encode64(subject).strip).to eq("ABgIZ4vqJRDJAqbMPNKKwHNFFgMWIJM1kQ==")
13
+ end
14
+ end
15
+ end
16
+
17
+ describe "encode" do
18
+ let(:data) { Base64.decode64("ABgIZ4vqJRDJAqbMPNKKwHNFFgMWIJM1kQ==") }
19
+
20
+ subject { BitcoinCigs::Base58.encode(data) }
21
+
22
+ context "with valid data" do
23
+ it "encodes properly" do
24
+ expect(subject).to eq("13C5HZKutjMDeuc7f5mPj6XGpJCZu7xKh2")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe BitcoinCigs do
4
+ let(:address) { "13C5HZKutjMDeuc7f5mPj6XGpJCZu7xKh2"}
5
+ let(:signature) { "H55JIuwEi4YXzINOzx2oU6VsfBcTOScpFtp10pP/M4EWV336ClH65SObwPXnRf/fMXDu8hs8nweB42CtpWgngeM=" }
6
+ let(:message) { "aaa" }
7
+
8
+ describe "verify_message!" do
9
+ subject { BitcoinCigs.verify_message!(address, signature, message) }
10
+
11
+ context "with valid data" do
12
+ it "verifies valid message" do
13
+ expect(subject).to be_nil
14
+ end
15
+ end
16
+
17
+ context "with invalid address" do
18
+ let(:address) { "invalid" }
19
+
20
+ it "raises ::BitcoinCigs::Error" do
21
+ expect { subject }.to raise_error(::BitcoinCigs::Error, "Bad address. Signing: 13C5HZKutjMDeuc7f5mPj6XGpJCZu7xKh2, Received: invalid")
22
+ end
23
+ end
24
+
25
+ context "with invalid signature" do
26
+ let(:signature) { "invalid" }
27
+
28
+ it "raises ::BitcoinCigs::Error" do
29
+ expect { subject }.to raise_error(::BitcoinCigs::Error, "Bad signature")
30
+ end
31
+ end
32
+
33
+ context "with invalid message" do
34
+ let(:message) { "invalid" }
35
+
36
+ it "raises ::BitcoinCigs::Error" do
37
+ # TODO: wrong message, also occurs in python version
38
+ expect { subject }.to raise_error(::BitcoinCigs::Error, "Bad address. Signing: 1887raouuBL3BJHMxgsGBZAWGqTjBEJP2p, Received: 13C5HZKutjMDeuc7f5mPj6XGpJCZu7xKh2")
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "verify_message!" do
44
+ subject { BitcoinCigs.verify_message(address, signature, message) }
45
+
46
+ context "with valid data" do
47
+ it "verifies valid message" do
48
+ expect(subject).to be_true
49
+ end
50
+ end
51
+
52
+ context "with invalid data" do
53
+ let(:address) { "invalid" }
54
+
55
+ it "raises ::BitcoinCigs::Error" do
56
+ expect(subject).to be_false
57
+ end
58
+ end
59
+ end
60
+
61
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+ require 'bitcoin-cigs'
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bitcoin-cigs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael Pearce
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.13.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 2.13.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 10.0.4
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 10.0.4
46
+ description: Create and Verify Bitcoin Signatures.
47
+ email:
48
+ - michaelgpearce@yahoo.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - Gemfile.lock
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - bin/jasvet.py
60
+ - bitcoin-cigs.gemspec
61
+ - lib/bitcoin-cigs.rb
62
+ - lib/bitcoin_cigs.rb
63
+ - lib/bitcoin_cigs/base_58.rb
64
+ - lib/bitcoin_cigs/curve_fp.rb
65
+ - lib/bitcoin_cigs/error.rb
66
+ - lib/bitcoin_cigs/math_helper.rb
67
+ - lib/bitcoin_cigs/point.rb
68
+ - lib/bitcoin_cigs/public_key.rb
69
+ - lib/bitcoin_cigs/version.rb
70
+ - spec/bitcoin_cigs/base_58_spec.rb
71
+ - spec/bitcoin_cigs_spec.rb
72
+ - spec/spec_helper.rb
73
+ homepage: http://github.com/michaelgpearce/bitcoin-cigs
74
+ licenses: []
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project: bitcoin-cigs
93
+ rubygems_version: 1.8.25
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: Create and Verify Bitcoin Signatures
97
+ test_files:
98
+ - spec/bitcoin_cigs/base_58_spec.rb
99
+ - spec/bitcoin_cigs_spec.rb
100
+ - spec/spec_helper.rb