bitcoin-cigs 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.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