elliptic-lite 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/Manifest.txt +16 -0
- data/README.md +399 -0
- data/Rakefile +29 -0
- data/lib/elliptic-lite.rb +9 -0
- data/lib/elliptic-lite/base.rb +76 -0
- data/lib/elliptic-lite/field.rb +151 -0
- data/lib/elliptic-lite/point.rb +179 -0
- data/lib/elliptic-lite/secp256k1.rb +31 -0
- data/lib/elliptic-lite/signature.rb +62 -0
- data/lib/elliptic-lite/version.rb +20 -0
- data/lib/elliptic/lite.rb +2 -0
- data/test/helper.rb +12 -0
- data/test/test_field.rb +142 -0
- data/test/test_point.rb +83 -0
- data/test/test_signature.rb +61 -0
- metadata +103 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
### stdlibs
|
2
|
+
require 'pp'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'digest'
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
## our own code
|
9
|
+
require 'elliptic-lite/version'
|
10
|
+
|
11
|
+
|
12
|
+
module ECC
|
13
|
+
class IntegerOp ## change to IntegerFieldOp or such - why? why not?
|
14
|
+
def self.add( a, b ) a + b; end
|
15
|
+
def self.sub( a, b ) a - b; end
|
16
|
+
def self.mul( a, b ) a * b; end
|
17
|
+
def self.pow( a, exponent ) a.pow( exponent ); end
|
18
|
+
def self.div( a, b ) a / b; end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
class Curve
|
23
|
+
attr_reader :a, :b, :f
|
24
|
+
|
25
|
+
def initialize( a:, b:, f: IntegerOp ) ## field (operations type) class
|
26
|
+
@a = a
|
27
|
+
@b = b
|
28
|
+
@f = f
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def field?( other ) ## matching field (operations type) class?
|
33
|
+
@f == other.f
|
34
|
+
end
|
35
|
+
|
36
|
+
def ==(other)
|
37
|
+
if other.is_a?( Curve ) && field?( other )
|
38
|
+
self.a == other.a && self.b == other.b
|
39
|
+
else
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end # class Curve
|
44
|
+
end # module ECC
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
require 'elliptic-lite/field'
|
49
|
+
require 'elliptic-lite/point'
|
50
|
+
|
51
|
+
|
52
|
+
module ECC
|
53
|
+
class Group
|
54
|
+
attr_reader :g, :n
|
55
|
+
## add generator alias for g - why? why not?
|
56
|
+
## add order alias for n - why? why not?
|
57
|
+
def initialize( g:, n: )
|
58
|
+
@g, @n = g, n
|
59
|
+
|
60
|
+
## note: generator point (scalar multiplied by n - group order results in infinity point)
|
61
|
+
## pp @n*@g #=> Point(:infinity)
|
62
|
+
end
|
63
|
+
|
64
|
+
## note: get point class from generator point
|
65
|
+
def point( *args ) @g.class.new( *args ); end
|
66
|
+
end # class Group
|
67
|
+
end # module ECC
|
68
|
+
|
69
|
+
|
70
|
+
require 'elliptic-lite/secp256k1'
|
71
|
+
require 'elliptic-lite/signature'
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
puts ECCLite.banner ## say hello
|
@@ -0,0 +1,151 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module ECC
|
4
|
+
|
5
|
+
class FiniteField
|
6
|
+
|
7
|
+
###################
|
8
|
+
# meta-programming "macro" - build class (on the fly)
|
9
|
+
#
|
10
|
+
# todo/check: rename max to modulus or prime or ?? - why? why not?
|
11
|
+
# todo/check: memoize generated classes ( do NOT regenerate duplicates) - why? why not?
|
12
|
+
def self.build_class( prime )
|
13
|
+
klass = Class.new( Element )
|
14
|
+
|
15
|
+
klass.class_eval( <<RUBY )
|
16
|
+
def self.prime
|
17
|
+
#{prime}
|
18
|
+
end
|
19
|
+
RUBY
|
20
|
+
|
21
|
+
klass
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
alias_method :old_new, :new # note: store "old" orginal version of new
|
26
|
+
alias_method :new, :build_class # replace original version with build_class
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
class Element ## FiniteFiledElement base class
|
32
|
+
###
|
33
|
+
# base functionality
|
34
|
+
attr_reader :num
|
35
|
+
|
36
|
+
|
37
|
+
def self.include?( num )
|
38
|
+
num >=0 && num < prime
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.add( a, b ) ## note: assumes integer as arguments values
|
42
|
+
( a + b ) % prime
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.sub( a, b )
|
46
|
+
( a - b ) % prime
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.mul( a, b )
|
50
|
+
( a * b ) % prime
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.pow( a, exponent )
|
54
|
+
n = exponent % ( prime - 1 ) # note: make possible negative exponent ALWAYS positive
|
55
|
+
a.pow( n, prime ) % prime
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.div( a, b )
|
59
|
+
# use Fermat's little theorem:
|
60
|
+
# self.num ** (prime-1) % prime == 1
|
61
|
+
# this means:
|
62
|
+
# 1/num == num.pow( prime-2, prime )
|
63
|
+
( a * b.pow( prime-2, prime )) % prime
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
def self.[]( num )
|
69
|
+
new( num )
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
def initialize( num )
|
75
|
+
raise ArgumentError, "number #{num} not in finite field range 0 to #{self.class.prime}" unless self.class.include?( num )
|
76
|
+
|
77
|
+
@num = num
|
78
|
+
self.freeze ## make "immutable"
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def prime() self.class.prime; end ## convenience helper
|
83
|
+
|
84
|
+
def inspect
|
85
|
+
"#{self.class.name}(#{@num})"
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
def prime?( other ) ## check for matching prime
|
91
|
+
self.class.prime == other.class.prime
|
92
|
+
end
|
93
|
+
|
94
|
+
def require_prime!( other )
|
95
|
+
raise ArgumentError, "cannot operate on different finite fields; expected #{self.class.prime} got #{other.class.prime}" unless prime?( other )
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
def ==(other)
|
100
|
+
if other.is_a?( Element ) && prime?( other )
|
101
|
+
@num == other.num
|
102
|
+
else
|
103
|
+
false
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def add( other )
|
108
|
+
require_prime!( other )
|
109
|
+
|
110
|
+
num = self.class.add( @num, other.num )
|
111
|
+
self.class.new( num )
|
112
|
+
end
|
113
|
+
|
114
|
+
def sub( other )
|
115
|
+
require_prime!( other )
|
116
|
+
|
117
|
+
num = self.class.sub( @num, other.num )
|
118
|
+
self.class.new( num )
|
119
|
+
end
|
120
|
+
|
121
|
+
def mul( other )
|
122
|
+
require_prime!( other )
|
123
|
+
|
124
|
+
num = self.class.mul( @num, other.num )
|
125
|
+
self.class.new( num )
|
126
|
+
end
|
127
|
+
|
128
|
+
def pow( exponent )
|
129
|
+
num = self.class.pow( @num, exponent )
|
130
|
+
self.class.new( num )
|
131
|
+
end
|
132
|
+
|
133
|
+
def div( other )
|
134
|
+
require_prime!( other )
|
135
|
+
|
136
|
+
num = self.class.div( @num, other.num )
|
137
|
+
self.class.new( num )
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
alias_method :+, :add
|
142
|
+
alias_method :-, :sub
|
143
|
+
alias_method :*, :mul
|
144
|
+
alias_method :/, :div
|
145
|
+
alias_method :**, :pow
|
146
|
+
end # (nested) class Element
|
147
|
+
|
148
|
+
|
149
|
+
end # class FiniteField
|
150
|
+
end # module ECC
|
151
|
+
|
@@ -0,0 +1,179 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module ECC
|
4
|
+
class Point
|
5
|
+
|
6
|
+
## convenience helpers - field operations
|
7
|
+
def self.add( *nums ) ## note: for convenience allow additions of more than two numbers
|
8
|
+
sum = nums.shift ## "pop" first item from array
|
9
|
+
nums.reduce( sum ) {|sum, num| curve.f.add( sum, num ) }
|
10
|
+
end
|
11
|
+
def self.sub( *nums )
|
12
|
+
sum = nums.shift ## "pop" first item from array
|
13
|
+
nums.reduce( sum ) {|sum, num| curve.f.sub( sum, num ) }
|
14
|
+
end
|
15
|
+
def self.mul( a, b ) curve.f.mul( a, b ); end
|
16
|
+
def self.pow( a, exponent ) curve.f.pow( a, exponent ); end
|
17
|
+
def self.div( a, b ) curve.f.div( a, b ); end
|
18
|
+
|
19
|
+
def self.on_curve?( x, y )
|
20
|
+
## y**2 == x**3 + curve.a*x + curve.b
|
21
|
+
pow( y, 2 ) == add( pow( x, 3),
|
22
|
+
mul( curve.a, x ),
|
23
|
+
curve.b )
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def self.[]( *args )
|
28
|
+
new( *args )
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.infinity ## convenience helper / shortcut for infinity - in use anywhere? keep? why? why not?
|
32
|
+
new( :infinity )
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
## convenience helpers
|
38
|
+
def curve() self.class.curve; end
|
39
|
+
## convenience helpers - field operations
|
40
|
+
def _add( *nums ) self.class.add( *nums ); end
|
41
|
+
def _sub( *nums ) self.class.sub( *nums ); end
|
42
|
+
def _mul( a, b ) self.class.mul( a, b ); end
|
43
|
+
def _pow( a, exponent ) self.class.pow( a, exponent ); end
|
44
|
+
def _div( a, b ) self.class.div( a, b ); end
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
attr_reader :x, :y
|
49
|
+
|
50
|
+
def initialize( *args )
|
51
|
+
## allow :infinity or :inf for infinity for now - add more? why? why not?
|
52
|
+
if args.size == 1 && [:inf, :infinity].include?( args[0] )
|
53
|
+
@x = nil
|
54
|
+
@y = nil
|
55
|
+
elsif args.size == 2 &&
|
56
|
+
args[0].is_a?( Integer ) &&
|
57
|
+
args[1].is_a?( Integer )
|
58
|
+
@x, @y = args
|
59
|
+
raise ArgumentError, "point (#{@x}/#{@y}) is NOT on the curve" unless self.class.on_curve?( @x, @y )
|
60
|
+
else
|
61
|
+
raise ArgumentError, "expected two integer numbers or :inf/:infinity; got #{args.inspect}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def infinity?
|
67
|
+
@x.nil? && @y.nil?
|
68
|
+
end
|
69
|
+
|
70
|
+
def inspect
|
71
|
+
if infinity?
|
72
|
+
"#{self.class.name}(:infinity)"
|
73
|
+
else
|
74
|
+
"#{self.class.name}(#{@x},#{@y})"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def curve?( other ) ## check for matching curver
|
80
|
+
self.curve == other.curve
|
81
|
+
end
|
82
|
+
|
83
|
+
def require_curve!( other )
|
84
|
+
raise ArgumentError, "cannot operate on different curves; expected #{self.class.curve} got #{other.class.curve}" unless curve?( other )
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
def ==( other )
|
89
|
+
if other.is_a?( Point ) && curve?( other )
|
90
|
+
@x == other.x && @y == other.y
|
91
|
+
else
|
92
|
+
false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
def coerce(other)
|
98
|
+
args = [self, other]
|
99
|
+
## puts " coerce(#{other} <#{other.class.name}>):"
|
100
|
+
## pp args
|
101
|
+
args
|
102
|
+
end
|
103
|
+
|
104
|
+
def mul( coefficient ) ## note: for rmul-style to work needs coerce method to swap s*p to p*s!!
|
105
|
+
raise ArgumentError, "integer expected for mul; got #{other.class.name}" unless coefficient.is_a?( Integer )
|
106
|
+
## todo/check/fix: check for negative integer e.g. -1 etc.
|
107
|
+
|
108
|
+
|
109
|
+
## puts "mul( #{coefficient} <#{coefficient.class.name}>)"
|
110
|
+
=begin
|
111
|
+
result = self.class.new( nil, nil )
|
112
|
+
coefficient.times do
|
113
|
+
result += self
|
114
|
+
end
|
115
|
+
result
|
116
|
+
=end
|
117
|
+
|
118
|
+
coef = coefficient
|
119
|
+
current = self
|
120
|
+
result = self.class.new( :infinity )
|
121
|
+
while coef > 0
|
122
|
+
result += current if coef.odd? ## if (coef & 0b1) > 0
|
123
|
+
current = current.double ## double the point
|
124
|
+
coef >>= 1 ## bit shift to the right
|
125
|
+
end
|
126
|
+
result
|
127
|
+
end
|
128
|
+
alias_method :*, :mul
|
129
|
+
|
130
|
+
|
131
|
+
def add( other )
|
132
|
+
require_curve!( other )
|
133
|
+
|
134
|
+
return other if infinity? ## self is infinity/infinity ?
|
135
|
+
return self if other.infinity? ## other is infinity/infinity ?
|
136
|
+
|
137
|
+
if @x == other.x && @y != other.y
|
138
|
+
return self.class.new( :infinity )
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
if @x != other.x ## e.g. add point operation
|
143
|
+
# s = (other.y - @y) / (other.x - @x)
|
144
|
+
# x3 = s**2 - @x - other.x
|
145
|
+
# y3 = s * (@x - x3) - @y
|
146
|
+
s = _div( _sub(other.y, @y),
|
147
|
+
_sub(other.x, @x))
|
148
|
+
x3 = _sub( _pow(s,2), @x, other.x )
|
149
|
+
y3 = _sub( _mul( s, _sub(@x, x3)), @y )
|
150
|
+
|
151
|
+
return self.class.new( x3, y3 )
|
152
|
+
end
|
153
|
+
|
154
|
+
if @x == other.x && @y == other.y ## e.g. double point operation
|
155
|
+
return double
|
156
|
+
end
|
157
|
+
|
158
|
+
raise "failed to add point #{inspect} to #{other.inspect} - no point addition rules matched; sorry"
|
159
|
+
end
|
160
|
+
alias_method :+, :add
|
161
|
+
|
162
|
+
|
163
|
+
def double # double point operation
|
164
|
+
if @y == _mul( 0, @x )
|
165
|
+
self.class.new( :infinity )
|
166
|
+
else
|
167
|
+
# s = (3 * @x**2 + curve.a) / (2 * @y)
|
168
|
+
# x3 = s**2 - 2 * @x
|
169
|
+
# y3 = s * (@x - x3) - @y
|
170
|
+
s = _div( _add( _mul( 3,_pow(@x, 2)), curve.a),
|
171
|
+
_mul(2, @y))
|
172
|
+
x3 = _sub( _pow(s, 2), _mul( 2, @x))
|
173
|
+
y3 = _sub( _mul( s, _sub(@x, x3)), @y)
|
174
|
+
|
175
|
+
self.class.new( x3, y3 )
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end # class Point
|
179
|
+
end # module ECC
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
module ECC
|
3
|
+
|
4
|
+
class S256Field < FiniteField::Element
|
5
|
+
P = 2**256 - 2**32 - 977
|
6
|
+
def self.prime() P; end
|
7
|
+
end
|
8
|
+
|
9
|
+
class S256Point < Point
|
10
|
+
def self.curve() @curve ||= Curve.new( a: 0, b: 7, f: S256Field ); end
|
11
|
+
|
12
|
+
def inspect ## change to fixed 64 char hexstring for x/y
|
13
|
+
if infinity?
|
14
|
+
"#{self.class.name}(:infinity)"
|
15
|
+
else
|
16
|
+
"#{self.class.name}(#{'0x%064x' % @x},#{'0x%064x' % @y})"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end # class S256Point
|
20
|
+
|
21
|
+
|
22
|
+
# use Secp256k1 - why? why not?
|
23
|
+
# or use GROUP_SECP256K1 or SECP256K1_GROUP (different from "plain" CURVE_SECP256K1) - why? why not?
|
24
|
+
SECP256K1 = Group.new(
|
25
|
+
g: S256Point.new( 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
|
26
|
+
0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 ),
|
27
|
+
n: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
|
28
|
+
)
|
29
|
+
end # module ECC
|
30
|
+
|
31
|
+
|