elliptic 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/Manifest.txt +10 -0
- data/README.md +293 -0
- data/Rakefile +29 -0
- data/i/secp256k1.png +0 -0
- data/lib/elliptic.rb +186 -0
- data/lib/elliptic/version.rb +23 -0
- data/test/helper.rb +11 -0
- data/test/test_sign.rb +87 -0
- data/test/test_version.rb +19 -0
- metadata +93 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ef1dcaf22384ecedc56973cf3e2a75bb5da817bb6d5dcaa65f5559e48d4731d4
|
4
|
+
data.tar.gz: a4b7cf7b207cddae72ddd7a5a2cd848fe41e824d56284f46ffb65e3332f96d50
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f78242b719f4871949419099adde1cb3f209d11f176f761aefba7d7828a597ff30f1e4b75fae8ed1048612a226f9dabc4758f21be01466c486851ee483f00a84
|
7
|
+
data.tar.gz: e4561b2707ec6c5dce8e5e09d7dbc516dc5434dbbce7e03e773f6ef8ff0aff7610c9998da0e9c27b9baec976b71e97dccc853c8e9c548a5904476a5297822475
|
data/CHANGELOG.md
ADDED
data/Manifest.txt
ADDED
data/README.md
ADDED
@@ -0,0 +1,293 @@
|
|
1
|
+
# elliptic - elliptic curve digital signature algorithm (ECDSA) cryptography with OpenSSL made easy (incl. secp256k1 curve)
|
2
|
+
|
3
|
+
|
4
|
+
* home :: [github.com/rubycoco/blockchain](https://github.com/rubycoco/blockchain)
|
5
|
+
* bugs :: [github.com/rubycoco/blockchain/issues](https://github.com/rubycoco/blockchain/issues)
|
6
|
+
* gem :: [rubygems.org/gems/elliptic](https://rubygems.org/gems/elliptic)
|
7
|
+
* rdoc :: [rubydoc.info/gems/elliptic](http://rubydoc.info/gems/elliptic)
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
|
14
|
+
|
15
|
+
### Intro
|
16
|
+
|
17
|
+
Did you know? All you need to open up a new account on a blockchain
|
18
|
+
is an (unsigned) 256-bit / 32-byte integer number.
|
19
|
+
Yes, that's it. No questions asked.
|
20
|
+
The private key is the secret "magic" that unlocks your own bank.
|
21
|
+
|
22
|
+
|
23
|
+
Q: What's the maximum value for a 256-bit / 32-byte integer number
|
24
|
+
(hint 2^256-1)?
|
25
|
+
|
26
|
+
Maximum value of 2^256-1 =
|
27
|
+
|
28
|
+
``` ruby
|
29
|
+
2**256-1
|
30
|
+
#=> 115792089237316195423570985008687907853269984665640564039457584007913129639935
|
31
|
+
(2**256-1).to_s.length
|
32
|
+
#=> 78
|
33
|
+
```
|
34
|
+
|
35
|
+
Yes, that's 78 (!) decimal digits.
|
36
|
+
|
37
|
+
|
38
|
+
Let's (re)try the maximum value for a 256-bit (32-byte) integer number
|
39
|
+
in hexadecimal (base 16) and binary (base 2) format?
|
40
|
+
|
41
|
+
``` ruby
|
42
|
+
(2**256-1).to_s(16)
|
43
|
+
#=> "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
44
|
+
(2**256-1).to_s(16).length
|
45
|
+
#=> 64
|
46
|
+
|
47
|
+
(2**256-1).to_s(2)
|
48
|
+
#=> "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
|
49
|
+
(2**256-1).to_s(2).length
|
50
|
+
#=> 256
|
51
|
+
```
|
52
|
+
|
53
|
+
Surprise - a 256-bit number has 256 binary digits (0 and 1s).
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
BEWARE - Blockchain Bandits!
|
58
|
+
If you use a low integer number e.g. 1, 2, etc.
|
59
|
+
your account is guaranteed to get robbed by blockchain bandits in
|
60
|
+
seconds.
|
61
|
+
|
62
|
+
(See [A "Blockchain Bandit" Is Guessing Private Keys and Scoring Millions](https://www.wired.com/story/blockchain-bandit-ethereum-weak-private-keys/)
|
63
|
+
by Andy Greenberg, Wired Magazine, April 2019)
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
### Private Key
|
69
|
+
|
70
|
+
An ECDSA (Elliptic Curve Digital Signature Algorithm) private key is a random number between 1 and the order of the elliptic curve group.
|
71
|
+
|
72
|
+
|
73
|
+
``` ruby
|
74
|
+
require 'elliptic'
|
75
|
+
|
76
|
+
# note: Algo will auto-generate (random) private key if no private key passed in
|
77
|
+
algo = EC::Algo.new # by default uses Secp256k1 curve (used in Bitcoin and Ethereum)
|
78
|
+
|
79
|
+
algo.private_key
|
80
|
+
#=> 72190737707147846840353520312904745954595478835413056312168022784020322830309
|
81
|
+
```
|
82
|
+
|
83
|
+
|
84
|
+
### (Auto-)Calculate the Public Key - Enter Elliptic Curve (EC) Cryptography
|
85
|
+
|
86
|
+
The public key are two numbers (that is, a point with the coordinates x and y) computed by multiplying
|
87
|
+
the generator point (`G`) of the curve with the private key.
|
88
|
+
This is equivalent to adding the generator to itself `private_key` times.
|
89
|
+
Magic?
|
90
|
+
Let's try:
|
91
|
+
|
92
|
+
|
93
|
+
``` ruby
|
94
|
+
# This private key is just an example. It should be much more secure!
|
95
|
+
privatekey = 1234
|
96
|
+
|
97
|
+
# Elliptic curve (EC) machinery - pass in our own key
|
98
|
+
algo = EC::Algo.new( privatekey ) # by default uses Secp256k1 curve (used in Bitcoin and Ethereum)
|
99
|
+
point = algo.public_key ## the "magic" one-way K=k*G curve multiplication (K=public key,k=private key, G=generator point)
|
100
|
+
|
101
|
+
point.x
|
102
|
+
#=> 102884003323827292915668239759940053105992008087520207150474896054185180420338
|
103
|
+
point.y
|
104
|
+
#=> 49384988101491619794462775601349526588349137780292274540231125201115197157452
|
105
|
+
|
106
|
+
point.x.to_s(16)
|
107
|
+
#=> "e37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f2"
|
108
|
+
point.y.to_s(16)
|
109
|
+
#=> "6d2ee9a82d4158f164ae653e9c6fa7f982ed8c94347fc05c2d068ff1d38b304c"
|
110
|
+
```
|
111
|
+
|
112
|
+
|
113
|
+
### Sign & Verify Transactions
|
114
|
+
|
115
|
+
Sign a transaction with an (elliptic curve) private key:
|
116
|
+
|
117
|
+
``` ruby
|
118
|
+
# Step 1 - Calculate the Transaction (tx) Hash
|
119
|
+
tx = 'from: Alice to: Bob cryptos: 43_000_000_000'
|
120
|
+
txhash = Digest::SHA256.digest( tx )
|
121
|
+
|
122
|
+
# Step 2 - Get the Signer's Private key
|
123
|
+
privatekey = 1234 # This private key is just an example. It should be much more secure!
|
124
|
+
|
125
|
+
# Sign!
|
126
|
+
signature = EC.sign( txhash, privatekey )
|
127
|
+
|
128
|
+
signature.r
|
129
|
+
#=> 80563021554295584320113598933963644829902821722081604563031030942154621916407
|
130
|
+
signature.s
|
131
|
+
#=> 58316177618967642068351252425530175807242657664855230973164972803783751708604
|
132
|
+
|
133
|
+
signature.r.to_s(16)
|
134
|
+
#=> "3306a2f81ad2b2f62ebe0faec129545bc772babe1ca5e70f6e56556b406464c0"
|
135
|
+
signature.s.to_s(16)
|
136
|
+
#=> "4fe202bb0835758f514cd4a0787986f8f6bf303df629dc98c5b1a438a426f49a"
|
137
|
+
```
|
138
|
+
|
139
|
+
|
140
|
+
Verify a signed transaction with an (elliptic curve) public key:
|
141
|
+
|
142
|
+
``` ruby
|
143
|
+
# Step 1 - Calculate the Transaction (tx) Hash
|
144
|
+
tx = 'from: Alice to: Bob cryptos: 43_000_000_000'
|
145
|
+
txhash = Digest::SHA256.digest( tx )
|
146
|
+
|
147
|
+
# Step 2 - Get the Signer's Public Key
|
148
|
+
pubkey = EC::Point.new(
|
149
|
+
102884003323827292915668239759940053105992008087520207150474896054185180420338,
|
150
|
+
49384988101491619794462775601349526588349137780292274540231125201115197157452
|
151
|
+
)
|
152
|
+
|
153
|
+
# Step 3 - Get the Transaction's Signature
|
154
|
+
signature = EC::Signature.new(
|
155
|
+
80563021554295584320113598933963644829902821722081604563031030942154621916407,
|
156
|
+
58316177618967642068351252425530175807242657664855230973164972803783751708604
|
157
|
+
)
|
158
|
+
|
159
|
+
# Don't Trust - Verify
|
160
|
+
EC.valid_signature?( txhash, pubkey, signature )
|
161
|
+
#=> true
|
162
|
+
|
163
|
+
|
164
|
+
# or using hexadecimal numbers
|
165
|
+
|
166
|
+
pubkey = EC::Point.new(
|
167
|
+
0xe37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f2,
|
168
|
+
0x6d2ee9a82d4158f164ae653e9c6fa7f982ed8c94347fc05c2d068ff1d38b304c
|
169
|
+
)
|
170
|
+
|
171
|
+
signature = EC::Signature.new(
|
172
|
+
0x3306a2f81ad2b2f62ebe0faec129545bc772babe1ca5e70f6e56556b406464c0,
|
173
|
+
0x4fe202bb0835758f514cd4a0787986f8f6bf303df629dc98c5b1a438a426f49a
|
174
|
+
)
|
175
|
+
|
176
|
+
EC.valid_signature?( txhash, pubkey, signature )
|
177
|
+
#=> true
|
178
|
+
```
|
179
|
+
|
180
|
+
|
181
|
+
To sum up:
|
182
|
+
|
183
|
+
- The (raw) private key is a 256-bit unsigned integer number
|
184
|
+
- The (raw) public key is a point (x,y), that is, two 256-bit unsigned integer numbers - derived (calculated) from the private key
|
185
|
+
- A (raw) signature is composed of (r,s), that is, two 256-bit unsigned integer numbers
|
186
|
+
|
187
|
+
That's all the magic.
|
188
|
+
|
189
|
+
|
190
|
+
|
191
|
+
|
192
|
+
## Public Key Formats
|
193
|
+
|
194
|
+
To get the all-in-one-string
|
195
|
+
public key from a point with the coordinates x and y
|
196
|
+
use the
|
197
|
+
Standards for Efficient Cryptography (SEC) 1) uncompressed format
|
198
|
+
or the 2) compressed format:
|
199
|
+
|
200
|
+
|
201
|
+
``` ruby
|
202
|
+
# 1) Uncompressed format (with prefix 04)
|
203
|
+
# Convert to 64 hexstring characters (32 bytes) in length
|
204
|
+
|
205
|
+
prefix = '04'
|
206
|
+
pubkey = prefix + "%064x" % point.x + "%064x" % point.y
|
207
|
+
#=> "04e37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f26d2ee9a82d4158f164ae653e9c6fa7f982ed8c94347fc05c2d068ff1d38b304c"
|
208
|
+
|
209
|
+
# 2) Compressed format (with prefix - 02 = even / 03 = odd)
|
210
|
+
# Instead of using both x and y coordinates,
|
211
|
+
# just use the x-coordinate and whether y is even/odd
|
212
|
+
|
213
|
+
prefix = point.y % 2 == 0 ? '02' : '03'
|
214
|
+
pubkey = prefix + "%064x" % point.x
|
215
|
+
#=> "02e37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f2"
|
216
|
+
```
|
217
|
+
|
218
|
+
or use the builtin helpers:
|
219
|
+
|
220
|
+
``` ruby
|
221
|
+
# 1) Uncompressed format (with prefix 04)
|
222
|
+
# Convert to 64 hexstring characters (32 bytes) in length
|
223
|
+
|
224
|
+
point.to_s # or point.to_s( :uncompressed )
|
225
|
+
#=> "04e37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f26d2ee9a82d4158f164ae653e9c6fa7f982ed8c94347fc05c2d068ff1d38b304c"
|
226
|
+
|
227
|
+
# 2) Compressed format (with prefix - 02 = even / 03 = odd)
|
228
|
+
# Instead of using both x and y coordinates,
|
229
|
+
# just use the x-coordinate and whether y is even/odd
|
230
|
+
|
231
|
+
point.to_s( :compressed )
|
232
|
+
#=> "02e37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f2"
|
233
|
+
```
|
234
|
+
|
235
|
+
|
236
|
+
That's it.
|
237
|
+
|
238
|
+
|
239
|
+
|
240
|
+
|
241
|
+
## Aside - Elliptic What?
|
242
|
+
|
243
|
+
> Elliptic-curve cryptography (ECC) is
|
244
|
+
> an approach to public-key cryptography based
|
245
|
+
> on the algebraic structure of elliptic curves over finite fields.
|
246
|
+
>
|
247
|
+
> (Source: [Elliptic-curve cryptography @ Wikipedia](https://en.wikipedia.org/wiki/Elliptic-curve_cryptography))
|
248
|
+
|
249
|
+
|
250
|
+
What's an Elliptic Curve?
|
251
|
+
|
252
|
+

|
253
|
+
|
254
|
+
> This is a graph of secp256k1's elliptic curve `y² = x³ + 7`
|
255
|
+
> over the real numbers.
|
256
|
+
> Note that because secp256k1 is actually defined over the field Zₚ,
|
257
|
+
> its graph will in reality look like random scattered points,
|
258
|
+
> not anything like this.
|
259
|
+
>
|
260
|
+
> (Source: [Secp256k1 @ Bitcoin Wiki](https://en.bitcoin.it/wiki/Secp256k1))
|
261
|
+
|
262
|
+
|
263
|
+
|
264
|
+
|
265
|
+
**Bitcon Public Service Announcement:**
|
266
|
+
|
267
|
+
> If we all buy Bitcoin from one another at ever higher
|
268
|
+
> prices we'll all be rich beyond our wildest dreams.
|
269
|
+
>
|
270
|
+
> -- Trolly McTrollface
|
271
|
+
|
272
|
+
**[BEWARE: Yes, Bitcoin Is a Ponzi - Learn How the Investment Fraud Works »](https://github.com/openblockchains/bitcoin-ponzi)**
|
273
|
+
|
274
|
+
|
275
|
+
|
276
|
+
|
277
|
+
## Install
|
278
|
+
|
279
|
+
Just install the gem:
|
280
|
+
|
281
|
+
$ gem install elliptic
|
282
|
+
|
283
|
+
|
284
|
+
## License
|
285
|
+
|
286
|
+
The scripts are dedicated to the public domain.
|
287
|
+
Use it as you please with no restrictions whatsoever.
|
288
|
+
|
289
|
+
|
290
|
+
## Questions? Comments?
|
291
|
+
|
292
|
+
Send them along to the [wwwmake forum](http://groups.google.com/group/wwwmake).
|
293
|
+
Thanks!
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'hoe'
|
2
|
+
require './lib/elliptic/version.rb'
|
3
|
+
|
4
|
+
Hoe.spec 'elliptic' do
|
5
|
+
|
6
|
+
self.version = EC::VERSION
|
7
|
+
|
8
|
+
self.summary = "elliptic - elliptic curve digital signature algorithm (ECDSA) cryptography with OpenSSL made easy (incl. secp256k1 curve)"
|
9
|
+
self.description = summary
|
10
|
+
|
11
|
+
self.urls = { home: 'https://github.com/rubycoco/blockchain' }
|
12
|
+
|
13
|
+
self.author = 'Gerald Bauer'
|
14
|
+
self.email = 'wwwmake@googlegroups.com'
|
15
|
+
|
16
|
+
# switch extension to .markdown for gihub formatting
|
17
|
+
self.readme_file = 'README.md'
|
18
|
+
self.history_file = 'CHANGELOG.md'
|
19
|
+
|
20
|
+
self.extra_deps = [
|
21
|
+
]
|
22
|
+
|
23
|
+
self.licenses = ['Public Domain']
|
24
|
+
|
25
|
+
self.spec_extras = {
|
26
|
+
required_ruby_version: '>= 2.3'
|
27
|
+
}
|
28
|
+
|
29
|
+
end
|
data/i/secp256k1.png
ADDED
Binary file
|
data/lib/elliptic.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'digest'
|
3
|
+
require 'base64'
|
4
|
+
require 'openssl'
|
5
|
+
|
6
|
+
## our own code
|
7
|
+
require 'elliptic/version' # note: let version always go first
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
module EC
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
class Signature
|
16
|
+
|
17
|
+
def self.decode_der( der )
|
18
|
+
asn1 = OpenSSL::ASN1.decode(der )
|
19
|
+
r = asn1.value[0].value.to_i
|
20
|
+
s = asn1.value[1].value.to_i
|
21
|
+
new(r, s)
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :r, :s
|
25
|
+
def initialize(r, s)
|
26
|
+
@r, @s = r, s
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_der
|
30
|
+
asn1 = OpenSSL::ASN1::Sequence.new [
|
31
|
+
OpenSSL::ASN1::Integer.new( @r ),
|
32
|
+
OpenSSL::ASN1::Integer.new( @s ),
|
33
|
+
]
|
34
|
+
asn1.to_der
|
35
|
+
end
|
36
|
+
end ## class Signature
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
## "cached" / available groups for now include:
|
42
|
+
GROUP = {
|
43
|
+
## todo/check: is there a more direct way to get a group object?
|
44
|
+
'secp256k1' => OpenSSL::PKey::EC.new( 'secp256k1' ).group
|
45
|
+
}
|
46
|
+
|
47
|
+
|
48
|
+
class Point
|
49
|
+
|
50
|
+
def initialize( *args, group: nil )
|
51
|
+
## case 1) assume OpenSSL::PKey::EC::Point
|
52
|
+
if args.size == 1 && args[0].is_a?( OpenSSL::PKey::EC::Point )
|
53
|
+
@pt = args[0]
|
54
|
+
|
55
|
+
## todo/check: is there a "better" way to get the x/y numbers?
|
56
|
+
hex = @pt.to_octet_string( :uncompressed ).unpack( 'H*' )[0]
|
57
|
+
|
58
|
+
## todo/fix: check for infinity / 0 !!!!
|
59
|
+
@x = hex[2,64].to_i(16) ## skip leading 0x04 marker
|
60
|
+
@y = hex[2+64,64].to_i(16)
|
61
|
+
else ## assume x,y with group
|
62
|
+
## rebuild openssl point from octet
|
63
|
+
|
64
|
+
@x = args[0]
|
65
|
+
@y = args[1]
|
66
|
+
|
67
|
+
## encoded_point is the octet string representation of the point.
|
68
|
+
## This must be either a String or an OpenSSL::BN
|
69
|
+
prefix = '04'
|
70
|
+
hex = prefix + ("%064x" % @x) + ("%064x" % @y)
|
71
|
+
bin = [hex].pack( 'H*' )
|
72
|
+
|
73
|
+
ec_group = GROUP[ group || 'secp256k1' ]
|
74
|
+
@pt = OpenSSL::PKey::EC::Point.new( ec_group, bin )
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def x() @x; end
|
79
|
+
def y() @y; end
|
80
|
+
def group() @pt.group; end
|
81
|
+
|
82
|
+
## formats - :compressed | :uncompressed
|
83
|
+
def to_bin( format=:uncompressed ) ## todo/check add alias .b too - why? why not?
|
84
|
+
@pt.to_octet_string( format )
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_s( format=:uncompressed )
|
88
|
+
to_bin( format ).unpack( 'H*' )[0]
|
89
|
+
end
|
90
|
+
|
91
|
+
## return OpenSSL::PKey::EC::Point - find a better name? e.g. to_raw/native or such - why? why not?
|
92
|
+
def to_point() @pt; end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
class Algo
|
99
|
+
def initialize( input=nil, group: nil )
|
100
|
+
|
101
|
+
## case 1) "restore" public key (only) from point for verify
|
102
|
+
if input.is_a?( OpenSSL::PKey::EC::Point ) || ## assume public key only (restore pkey object for verify?)
|
103
|
+
input.is_a?( Point )
|
104
|
+
|
105
|
+
point = input.is_a?( Point ) ? input.to_point : input
|
106
|
+
|
107
|
+
## note: (auto)get group from point
|
108
|
+
@pkey = OpenSSL::PKey::EC.new( point.group )
|
109
|
+
@pkey.public_key = point
|
110
|
+
else ## case 2) build a private/public key pair
|
111
|
+
|
112
|
+
ec_group = GROUP[ group || 'secp256k1' ]
|
113
|
+
@pkey = OpenSSL::PKey::EC.new( ec_group )
|
114
|
+
|
115
|
+
if input.nil? ## auto-generate new key
|
116
|
+
@pkey.generate_key
|
117
|
+
else
|
118
|
+
num = if input.is_a?( String )
|
119
|
+
input.to_i( 16 )
|
120
|
+
else ## assume (big) integer
|
121
|
+
input
|
122
|
+
end
|
123
|
+
@pkey.private_key = OpenSSL::BN.new( num )
|
124
|
+
## auto-calculate public key too
|
125
|
+
@pkey.public_key = @pkey.group.generator.mul( @pkey.private_key )
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
def group() @pkey.group; end
|
132
|
+
|
133
|
+
def public?() @pkey.public?; end
|
134
|
+
def private?() @pkey.private?; end
|
135
|
+
|
136
|
+
|
137
|
+
## todo/check/fix: fix case with no private_key if passed in point/public key for verify!!!
|
138
|
+
def private_key() @pkey.private_key.to_i; end
|
139
|
+
alias_method :priv_key, :private_key ## add signing_key alias too?
|
140
|
+
|
141
|
+
def public_key() @public_key ||= Point.new( @pkey.public_key ); end
|
142
|
+
|
143
|
+
|
144
|
+
def sign( message )
|
145
|
+
signature_der = @pkey.dsa_sign_asn1( message )
|
146
|
+
Signature.decode_der( signature_der )
|
147
|
+
end
|
148
|
+
|
149
|
+
def verify?( message, signature )
|
150
|
+
signature_der = signature.to_der
|
151
|
+
@pkey.dsa_verify_asn1( message, signature_der )
|
152
|
+
end
|
153
|
+
alias_method :valid_signature?, :verify?
|
154
|
+
|
155
|
+
|
156
|
+
def to_text() @pkey.to_text; end
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
|
161
|
+
def self.sign( message, priv_key )
|
162
|
+
algo = Algo.new( priv_key )
|
163
|
+
algo.sign( message )
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.verify?( message, pub_key, signature )
|
167
|
+
algo = Algo.new( pub_key )
|
168
|
+
algo.verify?( message, signature )
|
169
|
+
end
|
170
|
+
|
171
|
+
class << self
|
172
|
+
alias_method :valid_signature?, :verify?
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
|
178
|
+
def self.builtin_curves
|
179
|
+
OpenSSL::PKey::EC.builtin_curves
|
180
|
+
end
|
181
|
+
end ## module EC
|
182
|
+
|
183
|
+
|
184
|
+
|
185
|
+
|
186
|
+
puts EC.banner ## say hello
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
module EC
|
5
|
+
|
6
|
+
MAJOR = 0
|
7
|
+
MINOR = 1
|
8
|
+
PATCH = 0
|
9
|
+
VERSION = [MAJOR,MINOR,PATCH].join('.')
|
10
|
+
|
11
|
+
def self.version
|
12
|
+
VERSION
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.banner
|
16
|
+
"elliptic/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}] in (#{root})"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.root
|
20
|
+
File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) )
|
21
|
+
end
|
22
|
+
|
23
|
+
end # module EC
|
data/test/helper.rb
ADDED
data/test/test_sign.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
###
|
2
|
+
# to run use
|
3
|
+
# ruby -I ./lib -I ./test test/test_sign.rb
|
4
|
+
|
5
|
+
|
6
|
+
require 'helper'
|
7
|
+
|
8
|
+
|
9
|
+
class TestSign < MiniTest::Test
|
10
|
+
|
11
|
+
|
12
|
+
def test_keys
|
13
|
+
algo = EC::Algo.new( 1234 )
|
14
|
+
assert_equal 1234, algo.private_key
|
15
|
+
assert_equal "secp256k1", algo.group.curve_name
|
16
|
+
|
17
|
+
public_key = algo.public_key
|
18
|
+
assert_equal 102884003323827292915668239759940053105992008087520207150474896054185180420338,
|
19
|
+
public_key.x
|
20
|
+
|
21
|
+
assert_equal 49384988101491619794462775601349526588349137780292274540231125201115197157452,
|
22
|
+
public_key.y
|
23
|
+
|
24
|
+
assert_equal "e37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f2",
|
25
|
+
public_key.x.to_s(16)
|
26
|
+
assert_equal "6d2ee9a82d4158f164ae653e9c6fa7f982ed8c94347fc05c2d068ff1d38b304c",
|
27
|
+
public_key.y.to_s(16)
|
28
|
+
|
29
|
+
assert_equal "02e37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f2",
|
30
|
+
public_key.to_s( :compressed )
|
31
|
+
|
32
|
+
assert_equal "04e37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f26d2ee9a82d4158f164ae653e9c6fa7f982ed8c94347fc05c2d068ff1d38b304c",
|
33
|
+
public_key.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_sign
|
37
|
+
algo = EC::Algo.new( 1234 )
|
38
|
+
priv_key = algo.private_key
|
39
|
+
|
40
|
+
message = Digest::SHA256.digest( "hello" )
|
41
|
+
|
42
|
+
## note: sign uses a secure random generated temp key
|
43
|
+
## every signature is different!!!
|
44
|
+
signature = EC.sign( message, priv_key )
|
45
|
+
pp signature.to_der
|
46
|
+
|
47
|
+
signature = EC.sign( message, priv_key )
|
48
|
+
pp signature.to_der
|
49
|
+
|
50
|
+
signature = EC.sign( message, priv_key )
|
51
|
+
pp signature.to_der
|
52
|
+
|
53
|
+
pp algo.sign( message )
|
54
|
+
|
55
|
+
|
56
|
+
### verify
|
57
|
+
public_key = algo.public_key
|
58
|
+
|
59
|
+
assert EC.verify?( message, public_key, signature )
|
60
|
+
assert EC.valid_signature?( message, public_key, signature )
|
61
|
+
|
62
|
+
assert algo.verify?( message, signature )
|
63
|
+
assert algo.valid_signature?( message, signature )
|
64
|
+
|
65
|
+
## public key from point (x,y)
|
66
|
+
point = EC::Point.new(
|
67
|
+
102884003323827292915668239759940053105992008087520207150474896054185180420338,
|
68
|
+
49384988101491619794462775601349526588349137780292274540231125201115197157452)
|
69
|
+
assert EC.verify?( message, point, signature )
|
70
|
+
|
71
|
+
## signature from r,s values
|
72
|
+
signature = EC::Signature.new(
|
73
|
+
17435452009115387225781053203681496505351828576563920844344513414624978140095,
|
74
|
+
57357162317582879484266879619863223941116872722080991745355058707843528515381)
|
75
|
+
assert EC.verify?( message, point, signature )
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
## tamper with message
|
80
|
+
message = Digest::SHA256.digest( "hellox" )
|
81
|
+
assert_equal false, EC.verify?( message, public_key, signature )
|
82
|
+
assert_equal false, algo.verify?( message, signature )
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
end # class TestSign
|
@@ -0,0 +1,19 @@
|
|
1
|
+
###
|
2
|
+
# to run use
|
3
|
+
# ruby -I ./lib -I ./test test/test_version.rb
|
4
|
+
|
5
|
+
|
6
|
+
require 'helper'
|
7
|
+
|
8
|
+
|
9
|
+
class TestVersion < MiniTest::Test
|
10
|
+
|
11
|
+
def test_version
|
12
|
+
pp EC.version
|
13
|
+
pp EC.banner
|
14
|
+
pp EC.root
|
15
|
+
|
16
|
+
assert true ## (for now) everything ok if we get here
|
17
|
+
end
|
18
|
+
|
19
|
+
end # class TestVersion
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: elliptic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gerald Bauer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-01-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rdoc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '7'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '7'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: hoe
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.22'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3.22'
|
47
|
+
description: elliptic - elliptic curve digital signature algorithm (ECDSA) cryptography
|
48
|
+
with OpenSSL made easy (incl. secp256k1 curve)
|
49
|
+
email: wwwmake@googlegroups.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files:
|
53
|
+
- CHANGELOG.md
|
54
|
+
- Manifest.txt
|
55
|
+
- README.md
|
56
|
+
files:
|
57
|
+
- CHANGELOG.md
|
58
|
+
- Manifest.txt
|
59
|
+
- README.md
|
60
|
+
- Rakefile
|
61
|
+
- i/secp256k1.png
|
62
|
+
- lib/elliptic.rb
|
63
|
+
- lib/elliptic/version.rb
|
64
|
+
- test/helper.rb
|
65
|
+
- test/test_sign.rb
|
66
|
+
- test/test_version.rb
|
67
|
+
homepage: https://github.com/rubycoco/blockchain
|
68
|
+
licenses:
|
69
|
+
- Public Domain
|
70
|
+
metadata: {}
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options:
|
73
|
+
- "--main"
|
74
|
+
- README.md
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '2.3'
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubygems_version: 3.1.4
|
89
|
+
signing_key:
|
90
|
+
specification_version: 4
|
91
|
+
summary: elliptic - elliptic curve digital signature algorithm (ECDSA) cryptography
|
92
|
+
with OpenSSL made easy (incl. secp256k1 curve)
|
93
|
+
test_files: []
|