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 +46 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +26 -0
- data/LICENSE.txt +21 -0
- data/README.md +33 -0
- data/Rakefile +7 -0
- data/bin/jasvet.py +615 -0
- data/bitcoin-cigs.gemspec +23 -0
- data/lib/bitcoin-cigs.rb +1 -0
- data/lib/bitcoin_cigs.rb +113 -0
- data/lib/bitcoin_cigs/base_58.rb +45 -0
- data/lib/bitcoin_cigs/curve_fp.rb +15 -0
- data/lib/bitcoin_cigs/error.rb +4 -0
- data/lib/bitcoin_cigs/math_helper.rb +46 -0
- data/lib/bitcoin_cigs/point.rb +91 -0
- data/lib/bitcoin_cigs/public_key.rb +35 -0
- data/lib/bitcoin_cigs/version.rb +3 -0
- data/spec/bitcoin_cigs/base_58_spec.rb +28 -0
- data/spec/bitcoin_cigs_spec.rb +61 -0
- data/spec/spec_helper.rb +6 -0
- metadata +100 -0
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
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
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
|
data/lib/bitcoin-cigs.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bitcoin_cigs'
|
data/lib/bitcoin_cigs.rb
ADDED
@@ -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,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,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
|
data/spec/spec_helper.rb
ADDED
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
|