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.
@@ -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
+