rb-pure25519 1.0.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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +32 -0
  3. data/lib/rb-pure25519.rb +803 -0
  4. metadata +45 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c86f97c8304808ea193c621388ac448c5feb7d3c
4
+ data.tar.gz: c07984a8e9c391b15467f3eba9de80881cd4ed5d
5
+ SHA512:
6
+ metadata.gz: 25ad334cedb874dd433a051bc4d6cfc2a33150686def8ca17d928cccaebb639afb14d44bae4f5e0e2fc51f1da6d90ff9fe065520fae313724c7db124e01bc87a
7
+ data.tar.gz: 391e53f6744fa9f1b29fbe4870bbf320325e42304a820d71092ae9e2a9ef0025f660a94526faf966c4cce5b6598336c0ed661d2621047d7e05a9f1f93a3d1b74
data/LICENSE ADDED
@@ -0,0 +1,32 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Daniel Pruessner
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 all
13
+ 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 THE
21
+ SOFTWARE.
22
+
23
+
24
+ I really cannot stress enough how, if you want to use this library, you should
25
+ do your own damn homework and make sure it is secure enough for your needs. I
26
+ took pains to make sure the tests work; however, it may not be immune to
27
+ side-channel attacks related to how Ruby implements the multiplications in
28
+ Bignum or other places. However, if your attacker can measure the power of the
29
+ CPU on a modern CPU that can run Ruby, then they probably have **far** easier
30
+ ways of reading memory across data busses to gain access to secure keys. Or
31
+ access to root accounts and they can just read the main memory. It's really
32
+ easy to find EC25519 keys in memory. — Daniel
@@ -0,0 +1,803 @@
1
+ #
2
+ # Finite field algebra
3
+ #
4
+
5
+ require 'prime'
6
+
7
+
8
+
9
+
10
+ class Integer
11
+ def ferm_is_prime?
12
+ if self.bit_length < 10
13
+ return Prime.first(200).member? self
14
+ end
15
+ Rb25519::FField.rosetta_mod_exp(2, self-1, self) == 1
16
+ end
17
+
18
+ def ferm_ndiv(v)
19
+ a = self / v
20
+ b = self - (a * v)
21
+ [a,b]
22
+ end
23
+
24
+ def rb25519_clamp
25
+ v = self & 248
26
+ v &= (127 << (31*8))
27
+ v |= ( 64 << (31*8))
28
+ v
29
+ end
30
+
31
+ def to_binary_string
32
+ v = self
33
+ ary = []
34
+ while v > 0
35
+ ary << (v & 0xFF)
36
+ v >>= 8
37
+ end
38
+ ary.pack('c*')
39
+ end
40
+
41
+ end
42
+
43
+ class String
44
+ #
45
+ # Convert to a binary fixed size; LSB first
46
+ def to_binary
47
+ v = 0
48
+ self.reverse.each_byte do |byte|
49
+ v = (v << 8) | byte
50
+ end
51
+ v
52
+ end
53
+ def rb25519_clamp
54
+ bytes = self.each_byte.to_a
55
+ bytes[0] &= 248;
56
+ bytes[31] &= 127;
57
+ bytes[31] |= 64;
58
+
59
+ return bytes.pack('c*')
60
+ end
61
+ end
62
+
63
+ class Array
64
+ def to_xz
65
+ [ self[0], self[0].f[1] ]
66
+ end
67
+ def to_xy
68
+ self[0] / self[1]
69
+ end
70
+ end
71
+
72
+
73
+
74
+
75
+ class ECInfinity
76
+ def self.to_xy
77
+ self
78
+ end
79
+ def inspect
80
+ "#<ECInfinity>"
81
+ end
82
+ def to_s; inspect; end
83
+ end
84
+
85
+
86
+
87
+ module Rb25519
88
+ class FField
89
+ class FFieldValue
90
+ attr_accessor :val
91
+
92
+ def ==(v)
93
+ @val == v.to_i
94
+ end
95
+ def f
96
+ @field
97
+ end
98
+
99
+ def to_i
100
+ @val
101
+ end
102
+
103
+ def to_f
104
+ @val.to_f
105
+ end
106
+
107
+ def initialize(f, v)
108
+ @field = f
109
+ @val = v.to_i
110
+
111
+ if @val >= @field.p || @val < 0
112
+ @val = @val % @field.p
113
+ end
114
+ end
115
+
116
+ def +(v)
117
+ FFieldValue.new(@field, @field.add(self, v))
118
+ end
119
+
120
+ def -(v)
121
+ FFieldValue.new(@field, @field.sub(self, v))
122
+ end
123
+
124
+ def *(v)
125
+ FFieldValue.new(@field, @field.mul(self, v))
126
+ end
127
+
128
+ def inv
129
+ FFieldValue.new(@field, @field.inv(self))
130
+ end
131
+
132
+ def /(v)
133
+ FFieldValue.new(@field, @field.div(self, v))
134
+ end
135
+
136
+ def **(v)
137
+ FFieldValue.new(@field, @field.exp(self, v))
138
+ end
139
+
140
+ def sqrt
141
+ @field.sqrt(self).map{|e| FFieldValue.new(@field, e) }
142
+ end
143
+
144
+ def -@()
145
+ self * -1
146
+ end
147
+
148
+ def inspect
149
+ "#<FFieldValue_#{@field.p}: #{@val} >"
150
+ end
151
+
152
+ def to_s
153
+ inspect
154
+ end
155
+
156
+ end # FFieldValue
157
+
158
+
159
+
160
+
161
+ #
162
+ # Class methods
163
+ #
164
+ def self.rosetta_mod_exp(b, exp, mod)
165
+ exp < 0 and raise ArgumentError, "negative exponent"
166
+ prod = 1
167
+ base = b % mod
168
+ until exp.zero?
169
+ exp.odd? and prod = (prod * base) % mod
170
+ exp >>= 1
171
+ base = (base * base) % mod
172
+ end
173
+ prod
174
+ end
175
+
176
+ def self.eea(i,j)
177
+ s,t,u,v = 1,0,0,1
178
+ while (j != 0)
179
+ q, r = i / j, i % j
180
+ unew, vnew = s , t
181
+
182
+ s = u - (q * s)
183
+ t = v - (q * t)
184
+
185
+ i, j = j, r
186
+ u, v = unew, vnew
187
+
188
+
189
+ end
190
+ d, m, n = i, u, v
191
+
192
+ return [d, m, n]
193
+ end
194
+
195
+
196
+
197
+
198
+ attr_accessor :p
199
+
200
+ def initialize(size)
201
+ raise RuntimeError.new("Field must be prime") unless size.ferm_is_prime?
202
+ @p = size
203
+ end
204
+
205
+
206
+ def [](v)
207
+ FFieldValue.new(self, v)
208
+ end
209
+
210
+ def add(a,b)
211
+ nv = a.to_i + b.to_i
212
+ nv -= @p if nv >= @p
213
+ nv
214
+ end
215
+
216
+ def sub(a,b)
217
+ nv = a.to_i - b.to_i
218
+ nv += @p if nv < 0
219
+ nv
220
+ end
221
+
222
+ def mul(a,b)
223
+ # Naive implementation of multiply
224
+ (a.to_i * b.to_i) % @p
225
+ end
226
+
227
+ def inv(v)
228
+ #puts "Inversion"
229
+ return self.class.eea(@p, v.to_i)[1]
230
+ end
231
+
232
+
233
+
234
+ ##
235
+ # rv = (a / b)
236
+ #
237
+ def div(a,b)
238
+ a.to_i * inv(b.to_i)
239
+ end
240
+
241
+
242
+ ##
243
+ # rv = b^e
244
+ #
245
+ def exp(b,e)
246
+ self.class.rosetta_mod_exp(b.to_i, e.to_i, @p)
247
+ end
248
+
249
+ def sqrt(n)
250
+ n = n.to_i
251
+ return nil if exp(n, (@p-1)/2) != 1
252
+
253
+ if (@p % 4) == 3
254
+ r = exp(n, (p+1) / 4)
255
+ return [ r, -r % @p ]
256
+ end
257
+
258
+
259
+
260
+ ##
261
+ ## Implement Tonelli-Shanks (from https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm )
262
+ ##
263
+
264
+ # Factor out Q and S
265
+ #
266
+ #
267
+ p1 = @p-1
268
+
269
+ q,s = nil,nil
270
+
271
+ p1.bit_length.times.each do |_s|
272
+
273
+ _q, _res = p1.ferm_ndiv(1 << _s)
274
+ ## puts [_s, _q, _res].inspect
275
+
276
+ if _res == 0 and _q.odd?
277
+ q,s = _q, _s
278
+ end
279
+ end
280
+
281
+ ## puts "q,s: #{[q,s].inspect}"
282
+
283
+ # Find `z` such that Legendre ( z | p ) == -1
284
+ z = nil
285
+ (1..@p).each{|_z| (z = _z; break) if self.exp(_z, (@p-1)/2) > 1 }
286
+ ## puts "Found z: #{z}"
287
+
288
+ c = exp(z, q)
289
+ ## puts "Calculated c: #{c}"
290
+
291
+ r = nil
292
+ _r = exp(n, (q+1) / 2 )
293
+ t = exp(n, q)
294
+ m = s
295
+
296
+
297
+ @p.times do
298
+
299
+ if (t == 1)
300
+ r = _r
301
+ ## puts "R is #{r}"
302
+ break
303
+ end
304
+
305
+ # Modify t and R
306
+ #
307
+
308
+ # find i such that 0 < i < M, such that t**2**i == 1 (mod p)
309
+ i = nil
310
+ i = (1..(m-1)).find{|_i| exp(t, (1<<_i)) == 1 }
311
+ ## puts "Found i: #{i}"
312
+
313
+ b = exp(c, (1 << (m - i - 1)))
314
+ _r = mul(_r, b)
315
+ t = mul(t, exp(b, 2) )
316
+ c = exp(b, 2)
317
+
318
+ m = i
319
+
320
+ ## puts({:b => b, :r => _r, :t => t, :c => c}.inspect)
321
+
322
+ end
323
+
324
+
325
+ [r, @p - r] if r
326
+ end
327
+
328
+
329
+
330
+ class EC
331
+ attr_reader :field
332
+ def initialize(field, coeffs=nil)
333
+ @coeffs = coeffs
334
+ @field = field
335
+ end
336
+
337
+ def on_curve(x,y)
338
+ raise NotImplementedError.new
339
+ end
340
+
341
+ def naive_points
342
+ points = [ECInfinity]
343
+ @field.p.times do |x|
344
+ @field.p.times do |y|
345
+ points << [x,y] if on_curve(x,y)
346
+ end
347
+ end
348
+ points
349
+ end
350
+
351
+ def point_add(point_a, point_b)
352
+ xa = point_a[0].kind_of?(FFieldValue) ? point_a[0] : @field[point_a[0]]
353
+ xb = point_b[0].kind_of?(FFieldValue) ? point_b[0] : @field[point_b[0]]
354
+
355
+ ya = point_a[1].kind_of?(FFieldValue) ? point_a[1] : @field[point_a[1]]
356
+ yb = point_b[1].kind_of?(FFieldValue) ? point_b[1] : @field[point_b[1]]
357
+
358
+ if xa == xb and ya == yb
359
+ return double_point(point_a)
360
+ end
361
+ #puts "point_add: #{point_a.inspect} + #{point_b.inspect}"
362
+
363
+ # All the following operations are in F_p (eg, "mod p")
364
+
365
+ s = (yb - ya) / (xb - xa)
366
+ #puts "Slope: #{s}"
367
+
368
+ xc = s**2 - xa - xb
369
+ yc = (ya * -1) + (xa - xc) * s
370
+
371
+ [xc, yc]
372
+ end
373
+
374
+ def scale_naive(k, point_a)
375
+ point = point_a
376
+
377
+ (k-1).times do
378
+ point = point_add(point, point_a)
379
+ end
380
+
381
+ point
382
+ end
383
+
384
+ def scale_double_add(k, point_a)
385
+ t = point_a
386
+
387
+ bits = k.bit_length
388
+
389
+ (bits-1).times.to_a.reverse.each do |bit|
390
+ t = point_add( t, t )
391
+ if (k >> bit) & 0x1 == 1
392
+ t = point_add(t, point_a)
393
+ end
394
+ end
395
+ t
396
+ end
397
+
398
+ end
399
+
400
+
401
+ ##
402
+ #
403
+ # Toy Montgomery curve to test
404
+ #
405
+ # Montgomery curves have the form:
406
+ #
407
+ # B * y^2 = x^3 + A * x^2 + x
408
+ #
409
+ #
410
+ #
411
+ # Test cases:
412
+ #
413
+ # E(@a = 3, @b = 1) over p=47
414
+ #
415
+ # H = [5,8] on curve.
416
+ #
417
+ #
418
+ class MontgomeryEC < EC
419
+ attr_accessor :a, :b
420
+
421
+ def initialize(field, a: nil, b:nil)
422
+ super(field)
423
+
424
+ @a = @field[ a || 3]
425
+ @b = @field[ b || 1]
426
+
427
+ @a24 = (@a + 2) / @field[4]
428
+ end
429
+
430
+
431
+ def on_curve(x,y)
432
+ x = @field[x] unless x.kind_of? FFieldValue
433
+ y = @field[y] unless y.kind_of? FFieldValue
434
+
435
+ (@b * y**2) == (x**3) + x**2 * @a + x
436
+ end
437
+
438
+
439
+ ##
440
+ #
441
+ # Add points in affine coordinates
442
+ #
443
+ def point_add(point_a, point_b)
444
+
445
+ return point_b if point_a == ECInfinity
446
+ return point_a if point_b == ECInfinity
447
+
448
+ xa = point_a[0].kind_of?(FFieldValue) ? point_a[0] : @field[point_a[0]]
449
+ xb = point_b[0].kind_of?(FFieldValue) ? point_b[0] : @field[point_b[0]]
450
+
451
+ ya = point_a[1].kind_of?(FFieldValue) ? point_a[1] : @field[point_a[1]]
452
+ yb = point_b[1].kind_of?(FFieldValue) ? point_b[1] : @field[point_b[1]]
453
+
454
+ #puts "MontgomeryEC#point-add: #{[xa, ya].inspect} + #{[xb, yb].inspect}"
455
+
456
+ if xa == xb and ya == -yb
457
+ return ECInfinity
458
+ end
459
+
460
+ if xa == xb and ya == yb
461
+ return double_point(point_a)
462
+ end
463
+
464
+ # All the following operations are in F_p (eg, "mod p")
465
+
466
+ l = ( yb - ya) / (xb - xa)
467
+ m = ya - l * xa
468
+
469
+ xc = @b * l**2 - @a - xa - xb
470
+ yc = (xa * 2 + xb + @a) * (yb - ya) / (xb - xa) - ( @b * (yb - ya) ** 3 ) / (xb - xa)**3 - ya
471
+ [xc, yc]
472
+ end
473
+
474
+
475
+ ##
476
+ #
477
+ # Doubles a point in affine coordinates
478
+ #
479
+ def double_point(point_a)
480
+ #puts "Double point: #{point_a.inspect}"
481
+
482
+ return point_a if point_a == ECInfinity
483
+
484
+ xa = point_a[0].kind_of?(FFieldValue) ? point_a[0] : @field[point_a[0]]
485
+ ya = point_a[1].kind_of?(FFieldValue) ? point_a[1] : @field[point_a[1]]
486
+
487
+
488
+ bb_inv = (@b * 2 * ya).inv
489
+
490
+ c1 = (xa**2 * 3 + @a * xa * 2 + 1 )
491
+
492
+
493
+ xc = @b * c1**2 * bb_inv**2 - @a - xa - xa
494
+ yc = (xa * 2 + xa + @a) * c1 / (@b * ya * 2) - @b * c1**3 * bb_inv**3 - ya
495
+
496
+
497
+ #x3 = b* (3*x12+2*a*x1+1) **2 / (2*b*y1)**2 -a-x1-x1
498
+
499
+ #y3 = (2*x1+x1+a) *(3*x12+2*a*x1+1)/(2*b*y1)-b*(3*x12+2*a*x1+1)3/(2*b*y1)3-y1
500
+
501
+ [xc, yc]
502
+ end
503
+
504
+ end
505
+
506
+
507
+
508
+ class EC25519 < MontgomeryEC
509
+ attr_accessor :gen
510
+
511
+ def initialize
512
+ super(FField.new(2**255 - 19), a: 486662, b: 1 )
513
+ end
514
+ end
515
+
516
+ end
517
+
518
+
519
+
520
+
521
+
522
+ ##
523
+
524
+ # Extend the Montgomery EC with projective (XZ) coordinate functions
525
+ #
526
+ class FField::MontgomeryEC
527
+
528
+ def xz_from_xy(point)
529
+ return [ @field[point[0].to_i], @field[1] ]
530
+ end
531
+
532
+
533
+ ##
534
+ # Convert XZ to XY coordinates.
535
+ #
536
+ # point must be Array<FastFrac<FFieldValue>> to work in the EC field.
537
+ #
538
+ #
539
+ # Montgomery curves have the form:
540
+ #
541
+ # B * y^2 = x^3 + A * x^2 + x
542
+ #
543
+ def xz_to_xy(point)
544
+
545
+ x = point[0] / point[1]
546
+
547
+ y_sq = (x**3 + x**2 * @a + x) / @b
548
+
549
+ return y_sq.sqrt.map{|e| [x, e]}
550
+ end
551
+
552
+
553
+ ##
554
+ #
555
+ # Implementing "mdbl-1987-m" described in DJB's EFD database:
556
+ # http://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html
557
+ #
558
+ def xz_double(pa)
559
+ x1 = pa[0]
560
+ z1 = pa[1]
561
+
562
+ c = (x1 - z1)**2
563
+ d = (x1 * 4 * z1)
564
+
565
+ x3 = (x1 + z1) ** 2 * c
566
+ z3 = d*( c + @a24 * d )
567
+ [x3, z3]
568
+ end
569
+
570
+ ###
571
+ ### XZ scaling:
572
+ #
573
+ # Check out: Montgomery Scalar Multiplication for Genus 2 Curves
574
+ # p. 3 (Prop 1) + explanation of the ladder in Efficient Elliptic Curve Point Multiplication
575
+ #
576
+
577
+ ##
578
+ #
579
+ # Implement the XZ coordinates from
580
+ # "Speeding the Pollard and elliptic curve methods of factorization" p.261
581
+ #
582
+ # Actually, best description is at:
583
+ #
584
+ # Cryptographic Algorithms on Reconfigurable Hardware p.301
585
+ #
586
+ # Actually-- I'm not sure where this one came from. Figuring out XZ
587
+ # projective point adding was a real pain in the ass!
588
+ #
589
+ #
590
+ # Test points for scaling/point addition and testing the Montgomery ladder.
591
+ #
592
+ #(5 : 8 : 1)
593
+ # --
594
+ # (2, (14 : 44 : 1))
595
+ # (3, (41 : 36 : 1))
596
+ # (4, (34 : 6 : 1))
597
+ # (5, (23 : 37 : 1))
598
+ # (6, (17 : 4 : 1))
599
+ # (7, (43 : 36 : 1))
600
+ # (8, (8 : 17 : 1))
601
+ # (9, (40 : 28 : 1))
602
+ # (10, (7 : 11 : 1))
603
+ # (11, (46 : 1 : 1))
604
+ # (12, (27 : 29 : 1))
605
+ # (13, (20 : 14 : 1))
606
+ # (14, (6 : 1 : 1))
607
+ # (15, (35 : 14 : 1))
608
+ # (16, (36 : 14 : 1))
609
+ # (17, (45 : 7 : 1))
610
+ # (18, (18 : 17 : 1))
611
+ # (19, (39 : 1 : 1))
612
+ # (20, (37 : 29 : 1))
613
+ # (41, (41 : 11 : 1))
614
+ # (42, (14 : 3 : 1))
615
+ # (43, (5 : 39 : 1))
616
+ # (44, (0 : 1 : 0))
617
+ # (45, (5 : 8 : 1))
618
+ #
619
+ def xz_simple_add(pa, pb, x)
620
+
621
+ return pb if (pa == ECInfinity)
622
+ return pa if (pb == ECInfinity)
623
+
624
+ x1 = pa[0]
625
+ z1 = pa[1]
626
+ x2 = pb[0]
627
+ z2 = pb[1]
628
+
629
+ x3 = ( (x1 - z1)*(x2 + z2) + (x1 + z1)*(x2 - z2) )**2
630
+ z3 = x * ( (x1 - z1)*(x2 + z2) - (x1 + z1)*(x2 - z2) )**2
631
+
632
+ [x3, z3]
633
+ end
634
+
635
+
636
+ def scale_proj(k, p)
637
+ # puts "Scaling #{k} times: #{p.inspect}"
638
+
639
+ pa = ECInfinity
640
+ pb = p.to_xz
641
+
642
+ x = p[0]
643
+
644
+
645
+ bits = k.bit_length
646
+ # puts "Bits: #{bits}"
647
+
648
+ (1..bits).each do |j|
649
+ # puts "Aff[a:x] = #{[pa, pa.to_xy ]}"
650
+ # puts "Aff[b:x] = #{[pb, pb.to_xy ]}"
651
+
652
+ if (k >> (bits - j) ) & 1 == 0
653
+
654
+ # puts "[[ bit: 0 ]]; pb = pa + pb; pa = 2*pa"
655
+
656
+ pb = xz_simple_add( pa, pb, x )
657
+ pa = xz_double( pa )
658
+ else
659
+
660
+ # puts "[[ bit: 1 ]]; pb = 2*pb; pa = pa + pb"
661
+
662
+ pa = xz_simple_add( pa, pb, x )
663
+ pb = xz_double(pb)
664
+ end
665
+
666
+ # puts
667
+
668
+ end
669
+
670
+ # puts "--end--"
671
+ # puts "Aff[a:x] = #{pa[0] / pa[1]}"
672
+ # puts "Aff[b:x] = #{pb[0] / pb[1]}"
673
+
674
+ return ECInfinity if pa[1] == 0
675
+ pa
676
+ end
677
+
678
+
679
+ ##
680
+ #
681
+ # List of scaled points of [5,8] on toy curve to test laddering and other
682
+ # REPL-style exploration/testing to get this working right.
683
+ #
684
+ def pts
685
+ [
686
+ [ @field[ 0], @field[ 0] ], # 0
687
+ [ @field[ 5], @field[ 8] ], # 1
688
+ [ @field[14], @field[44] ], # 2
689
+ [ @field[41], @field[36] ], # 3
690
+ [ @field[34], @field[ 6] ], # 4
691
+ [ @field[23], @field[37] ], # 5
692
+ [ @field[17], @field[ 4] ], # 6
693
+ [ @field[43], @field[36] ], # 7
694
+ [ @field[ 8], @field[17] ], # 8
695
+ [ @field[40], @field[28] ], # 9
696
+ ]
697
+ end
698
+
699
+ end
700
+
701
+
702
+
703
+
704
+
705
+
706
+
707
+ # Module Methods
708
+
709
+ CURVE = FField::EC25519.new
710
+ BASE_XZ = [ CURVE.field[9], CURVE.field[1] ]
711
+
712
+
713
+
714
+ def self.string_to_number(val)
715
+ v = 0
716
+ val.reverse.each_byte do |byte|
717
+ v = (v << 8) | byte
718
+ end
719
+ v
720
+ end
721
+
722
+ def self.number_to_string(v)
723
+ ary = []
724
+ while v > 0
725
+ ary << (v & 0xFF)
726
+ v >>= 8
727
+ end
728
+ ary.pack('c*')
729
+ end
730
+
731
+ def self.clamp_string(str)
732
+ bytes = str.each_byte.to_a
733
+ bytes[0] &= 248;
734
+ bytes[31] &= 127;
735
+ bytes[31] |= 64;
736
+
737
+ return bytes.pack('c*')
738
+ end
739
+
740
+ def self.random_secret_str
741
+ rv = SecureRandom.random_bytes(32)
742
+ rv = clamp_string(rv)
743
+ rv
744
+ end
745
+
746
+ def self.random_secret_num
747
+ string_to_number(random_secret_str)
748
+ end
749
+
750
+
751
+ #
752
+ #
753
+ def self.public_key_num(secret)
754
+ if String === secret
755
+ secret = string_to_number(secret)
756
+ end
757
+
758
+ xz = CURVE.scale_proj( secret, BASE_XZ )
759
+ (xz[0] / xz[1]).to_i
760
+ end
761
+
762
+
763
+ def self.public_key_str(secret)
764
+ number_to_string( public_key_num(secret) )
765
+ end
766
+
767
+ ##
768
+ # Secret is a 'k' in Q = k*P
769
+ #
770
+ # We want to calculate:
771
+ #
772
+ # P_shared_secret = (skey + other_skey) * P_base
773
+ #
774
+ # We get there because:
775
+ #
776
+ # P_other_pkey = (other_skey) * P_base
777
+ #
778
+ #
779
+ # So continuing to scale P_other_pkey by `skey` will get us to
780
+ # P_shared_secret. The other party is also doing this calculation; the
781
+ # Abelian group property means this operation is commutative.
782
+ #
783
+ # Note that the Points are all X points in XZ projective space.
784
+ #
785
+ #
786
+ def self.shared_secret_num(pkey, skey)
787
+ if String === pkey
788
+ pkey = string_to_number(pkey)
789
+ end
790
+ if String === skey
791
+ skey = string_to_number(skey)
792
+ end
793
+
794
+ shared_xz = CURVE.scale_proj( skey, [ CURVE.field[pkey], CURVE.field[1] ] )
795
+
796
+ (shared_xz[0] / shared_xz[1]).to_i # Final projective -> affine inversion
797
+ end
798
+
799
+ def self.shared_secret_str(pkey, skey)
800
+ number_to_string( shared_secret_num(pkey, skey) )
801
+ end
802
+
803
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rb-pure25519
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Pruessner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-05 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A pure-Ruby EC25519 library for ECDHE
14
+ email: daniel.pruessner@ieee.org
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - LICENSE
20
+ - lib/rb-pure25519.rb
21
+ homepage: http://rubygems.org/gems/rb-pure25519
22
+ licenses:
23
+ - MIT
24
+ metadata: {}
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubyforge_project:
41
+ rubygems_version: 2.4.5
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: Ruby Pure EC25519
45
+ test_files: []