rb-pure25519 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []