elliptic-lite 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 +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
|
+
|