elliptic-lite 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ require 'elliptic-lite/base'
2
+
3
+
4
+ ### for convenience auto-include
5
+ ## ECC namespace in top-level
6
+ ## use require 'elliptic-lite/base' for "modular" version
7
+ ## without aut-include
8
+
9
+ include ECC
@@ -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
+