bls12-381 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/.github/workflows/main.yml +18 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +77 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/bls12-381.gemspec +37 -0
- data/lib/bls.rb +100 -0
- data/lib/bls/curve.rb +36 -0
- data/lib/bls/field.rb +692 -0
- data/lib/bls/math.rb +144 -0
- data/lib/bls/pairing.rb +20 -0
- data/lib/bls/point.rb +509 -0
- data/lib/bls/version.rb +5 -0
- metadata +93 -0
data/lib/bls/math.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BLS
|
4
|
+
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def mod(a, b)
|
8
|
+
res = a % b
|
9
|
+
res >= 0 ? res : b + res
|
10
|
+
end
|
11
|
+
|
12
|
+
def pow_mod(a, power, m)
|
13
|
+
res = 1
|
14
|
+
while power.positive?
|
15
|
+
res = mod(res * a, m) unless (power & 1).zero?
|
16
|
+
power >>= 1
|
17
|
+
a = mod(a * a, m)
|
18
|
+
end
|
19
|
+
res
|
20
|
+
end
|
21
|
+
|
22
|
+
def bit_get(n, pos)
|
23
|
+
(n >> pos) & 1
|
24
|
+
end
|
25
|
+
|
26
|
+
# Convert byte to non-negative integer.
|
27
|
+
# @param [Array[Integer]] bytes byte array.
|
28
|
+
# @return [Array[Integer]] byte array.
|
29
|
+
def os2ip(bytes)
|
30
|
+
res = 0
|
31
|
+
bytes.each do |b|
|
32
|
+
res <<= 8
|
33
|
+
res += b
|
34
|
+
end
|
35
|
+
res
|
36
|
+
end
|
37
|
+
|
38
|
+
# Convert +value+ to byte array of +length+.
|
39
|
+
# @param [Integer] value
|
40
|
+
# @param [Integer] length
|
41
|
+
# @return [Array[Integer] byte array.
|
42
|
+
# @raise [BLS::Error]
|
43
|
+
def i2osp(value, length)
|
44
|
+
raise BLS::Error, "bad I2OSP call: value=#{value} length=#{length}" if value < 0 || value >= (1 << 8 * length)
|
45
|
+
|
46
|
+
res = Array.new(length, 0)
|
47
|
+
i = length - 1
|
48
|
+
while i >= 0
|
49
|
+
res[i] = value & 0xff
|
50
|
+
value >>= 8
|
51
|
+
i -= 1
|
52
|
+
end
|
53
|
+
res
|
54
|
+
end
|
55
|
+
|
56
|
+
# Calculate binary xor between +a+ and +b+.
|
57
|
+
# @param [String] a binary string.
|
58
|
+
# @param [String] b binary string.
|
59
|
+
# @return [String] xor binary string.
|
60
|
+
def bin_xor(a, b)
|
61
|
+
res = Array.new(a.bytesize)
|
62
|
+
b_bytes = b.bytes
|
63
|
+
a.bytes.each.with_index do |b, i|
|
64
|
+
res[i] = b ^ b_bytes[i]
|
65
|
+
end
|
66
|
+
res.pack('C*')
|
67
|
+
end
|
68
|
+
|
69
|
+
# Optimized SWU Map - FQ2 to G2': y^2 = x^3 + 240i * x + 1012 + 1012i
|
70
|
+
def map_to_curve_sswu_g2(t)
|
71
|
+
iso_3_a = Fq2.new([0, 240])
|
72
|
+
iso_3_b = Fq2.new([1012, 1012])
|
73
|
+
iso_3_z = Fq2.new([-2, -1])
|
74
|
+
t = Fq2.new(t) if t.is_a?(Array)
|
75
|
+
t2 = t**2
|
76
|
+
iso_3_z_t2 = iso_3_z * t2
|
77
|
+
ztzt = iso_3_z_t2 + iso_3_z_t2**2
|
78
|
+
denominator = (iso_3_a * ztzt).negate
|
79
|
+
numerator = iso_3_b * (ztzt + Fq2::ONE)
|
80
|
+
denominator = iso_3_z * iso_3_a if denominator.zero?
|
81
|
+
v = denominator**3
|
82
|
+
u = numerator**3 + iso_3_a * numerator * denominator**2 + iso_3_b * v
|
83
|
+
success, sqrt_candidate_or_gamma = BLS.sqrt_div_fq2(u, v)
|
84
|
+
y = success ? sqrt_candidate_or_gamma : nil
|
85
|
+
sqrt_candidate_x1 = sqrt_candidate_or_gamma * t**3
|
86
|
+
u = iso_3_z_t2**3 * u
|
87
|
+
success2 = false
|
88
|
+
Fq2::ETAS.each do |eta|
|
89
|
+
eta_sqrt_candidate = eta * sqrt_candidate_x1
|
90
|
+
temp = eta_sqrt_candidate**2 * v - u
|
91
|
+
if temp.zero? && !success && !success2
|
92
|
+
y = eta_sqrt_candidate
|
93
|
+
success2 = true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
raise BLS::PointError, 'Hash to Curve - Optimized SWU failure' if !success && !success2
|
97
|
+
|
98
|
+
numerator *= iso_3_z_t2 if success2
|
99
|
+
y = y.negate if BLS.sgn0(t) != BLS.sgn0(y)
|
100
|
+
y *= denominator
|
101
|
+
[numerator, y, denominator]
|
102
|
+
end
|
103
|
+
|
104
|
+
# 3-isogeny map from E' to E
|
105
|
+
# Converts from Jacobi (xyz) to Projective (xyz) coordinates.
|
106
|
+
def isogeny_map_g2(x, y, z)
|
107
|
+
mapped = Array.new(4, Fq2::ZERO)
|
108
|
+
z_powers = [z, z**2, z**3]
|
109
|
+
ISOGENY_COEFFICIENTS.each.with_index do |k, i|
|
110
|
+
mapped[i] = k[-1]
|
111
|
+
arr = k[0...-1].reverse
|
112
|
+
arr.each.with_index do |a, j|
|
113
|
+
mapped[i] = mapped[i] * x + z_powers[j] * a
|
114
|
+
end
|
115
|
+
end
|
116
|
+
mapped[2] *= y
|
117
|
+
mapped[3] *= z
|
118
|
+
z2 = mapped[1] * mapped[3]
|
119
|
+
x2 = mapped[0] * mapped[3]
|
120
|
+
y2 = mapped[1] * mapped[2]
|
121
|
+
[x2, y2, z2]
|
122
|
+
end
|
123
|
+
|
124
|
+
# Normalize private key.
|
125
|
+
# @param [String|Integer] private_key a private key with hex or number.
|
126
|
+
# @return [BLS::Fq] private key field.
|
127
|
+
# @raise [BLS::Error] Occur when the private key is zero.
|
128
|
+
def normalize_priv_key(private_key)
|
129
|
+
k = private_key.is_a?(String) ? private_key.to_i(16) : private_key
|
130
|
+
fq = Fq.new(k)
|
131
|
+
raise BLS::Error, 'Private key cannot be 0' if fq.zero?
|
132
|
+
|
133
|
+
fq
|
134
|
+
end
|
135
|
+
|
136
|
+
# Convert number to +byte_length+ bytes hex string.
|
137
|
+
# @param [Integer] num number tobe converted.
|
138
|
+
# @param [Integer] byte_length byte length.
|
139
|
+
# @return [String] hex value.
|
140
|
+
def num_to_hex(num, byte_length)
|
141
|
+
num.to_s(16).rjust(2 * byte_length, '0')
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
data/lib/bls/pairing.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module BLS
|
2
|
+
module_function
|
3
|
+
|
4
|
+
class PairingError < StandardError; end
|
5
|
+
|
6
|
+
# @param [BLS::PointG1] p
|
7
|
+
# @param [BLS::PointG2] q
|
8
|
+
# @param [Boolean] with_final_exp
|
9
|
+
# @return [BLS::Fq12]
|
10
|
+
# @return [BLS::PairingError] Occur when p.zero? or q.zero?
|
11
|
+
def pairing(p, q, with_final_exp: true)
|
12
|
+
raise PairingError, 'No pairings at point of Infinity' if p.zero? || q.zero?
|
13
|
+
|
14
|
+
p.validate!
|
15
|
+
q.validate!
|
16
|
+
looped = p.miller_loop(q)
|
17
|
+
with_final_exp ? looped.final_exponentiate : looped
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/lib/bls/point.rb
ADDED
@@ -0,0 +1,509 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bigdecimal'
|
4
|
+
|
5
|
+
module BLS
|
6
|
+
|
7
|
+
class PointError < StandardError; end
|
8
|
+
|
9
|
+
# Abstract Point class that consist of projective coordinates.
|
10
|
+
class ProjectivePoint
|
11
|
+
|
12
|
+
attr_reader :x, :y, :z
|
13
|
+
attr_accessor :m_precomputes
|
14
|
+
|
15
|
+
def initialize(x, y, z)
|
16
|
+
@x = x
|
17
|
+
@y = y
|
18
|
+
@z = z
|
19
|
+
@m_precomputes = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def zero?
|
23
|
+
z.zero?
|
24
|
+
end
|
25
|
+
|
26
|
+
def zero
|
27
|
+
one = x.class.const_get(:ONE)
|
28
|
+
new_point(one, one, x.class.const_get(:ZERO))
|
29
|
+
end
|
30
|
+
|
31
|
+
def new_point(x, y, z)
|
32
|
+
self.class.new(x, y, z)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Compare one point to another.
|
36
|
+
# @param [ProjectivePoint] other another point.
|
37
|
+
# @return [Boolean] whether same point or not.
|
38
|
+
def ==(other)
|
39
|
+
raise PointError, "ProjectivePoint#==: this is #{self.class}, but other is #{other.class}" unless self.class == other.class
|
40
|
+
|
41
|
+
(x * other.z) == (other.x * z) && (y * other.z) == (other.y * z)
|
42
|
+
end
|
43
|
+
|
44
|
+
def negate
|
45
|
+
new_point(x, y.negate, z)
|
46
|
+
end
|
47
|
+
|
48
|
+
# http://hyperelliptic.org/EFD/g1p/auto-shortw-projective.html#doubling-dbl-1998-cmo-2
|
49
|
+
def double
|
50
|
+
w = x * x * 3
|
51
|
+
s = y * z
|
52
|
+
ss = s * s
|
53
|
+
sss = ss * s
|
54
|
+
b = x * y * s
|
55
|
+
h = w * w - ( b * 8)
|
56
|
+
x3 = h * s * 2
|
57
|
+
y3 = w * (b * 4 - h) - (y * y * 8 * ss) # W * (4 * B - H) - 8 * y * y * S_squared
|
58
|
+
z3 = sss * 8
|
59
|
+
new_point(x3, y3, z3)
|
60
|
+
end
|
61
|
+
|
62
|
+
# http://hyperelliptic.org/EFD/g1p/auto-shortw-projective.html#addition-add-1998-cmo-2
|
63
|
+
def add(other)
|
64
|
+
raise PointError, "ProjectivePoint#add: this is #{self.class}, but other is #{other.class}" unless self.class == other.class
|
65
|
+
|
66
|
+
return other if zero?
|
67
|
+
return self if other.zero?
|
68
|
+
|
69
|
+
x1 = self.x
|
70
|
+
y1 = self.y
|
71
|
+
z1 = self.z
|
72
|
+
x2 = other.x
|
73
|
+
y2 = other.y
|
74
|
+
z2 = other.z
|
75
|
+
u1 = y2 * z1
|
76
|
+
u2 = y1 * z2
|
77
|
+
v1 = x2 * z1
|
78
|
+
v2 = x1 * z2
|
79
|
+
return double if v1 == v2 && u1 == u2
|
80
|
+
return zero if v1 == v2
|
81
|
+
|
82
|
+
u = u1 - u2
|
83
|
+
v = v1 - v2
|
84
|
+
vv = v * v
|
85
|
+
vvv = vv * v
|
86
|
+
v2vv = v2 * vv
|
87
|
+
w = z1 * z2
|
88
|
+
a = u * u * w - vvv - v2vv * 2
|
89
|
+
x3 = v * a
|
90
|
+
y3 = u * (v2vv - a) - vvv * u2
|
91
|
+
z3 = vvv * w
|
92
|
+
new_point(x3, y3, z3)
|
93
|
+
end
|
94
|
+
alias + add
|
95
|
+
|
96
|
+
def subtract(other)
|
97
|
+
raise PointError, "ProjectivePoint#subtract: this is #{self.class}, but other is #{other.class}" unless self.class == other.class
|
98
|
+
|
99
|
+
add(other.negate)
|
100
|
+
end
|
101
|
+
alias - subtract
|
102
|
+
|
103
|
+
def multiply_unsafe(scalar)
|
104
|
+
n = scalar.is_a?(Fq) ? scalar.value : scalar
|
105
|
+
raise PointError, 'Point#multiply: invalid scalar, expected positive integer' if n <= 0
|
106
|
+
|
107
|
+
p = zero
|
108
|
+
d = self
|
109
|
+
while n.positive?
|
110
|
+
p += d unless (n & 1).zero?
|
111
|
+
d = d.double
|
112
|
+
n >>= 1
|
113
|
+
end
|
114
|
+
p
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_affine(inv_z = z.invert)
|
118
|
+
[x * inv_z, y * inv_z]
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_affine_batch(points)
|
122
|
+
to_inv = gen_invert_batch(points.map(&:z))
|
123
|
+
points.map.with_index { |p, i| p.to_affine(to_inv[i]) }
|
124
|
+
end
|
125
|
+
|
126
|
+
def from_affine_tuple(xy)
|
127
|
+
new_point(xy[0], xy[1], x.class.const_get(:ONE))
|
128
|
+
end
|
129
|
+
|
130
|
+
def gen_invert_batch(nums)
|
131
|
+
len = nums.length
|
132
|
+
scratch = Array.new(len)
|
133
|
+
acc = x.class::ONE
|
134
|
+
len.times do |i|
|
135
|
+
next if nums[i].zero?
|
136
|
+
|
137
|
+
scratch[i] = acc
|
138
|
+
acc *= nums[i]
|
139
|
+
end
|
140
|
+
acc = acc.invert
|
141
|
+
len.times do |t|
|
142
|
+
i = len - t - 1
|
143
|
+
next if nums[i].zero?
|
144
|
+
|
145
|
+
tmp = acc * nums[i]
|
146
|
+
nums[i] = acc * scratch[i]
|
147
|
+
acc = tmp
|
148
|
+
end
|
149
|
+
nums
|
150
|
+
end
|
151
|
+
|
152
|
+
# Constant time multiplication. Uses wNAF.
|
153
|
+
def multiply(scalar)
|
154
|
+
n = scalar.is_a?(Fq) ? scalar.value : scalar
|
155
|
+
raise PointError, 'Invalid scalar, expected positive integer' if n <= 0
|
156
|
+
raise PointError, "Scalar has more bits than maxBits, shouldn't happen" if n.bit_length > max_bits
|
157
|
+
|
158
|
+
wNAF(n).first
|
159
|
+
end
|
160
|
+
alias * multiply
|
161
|
+
|
162
|
+
def precomputes_window(w)
|
163
|
+
windows = (BigDecimal(max_bits) / w).ceil
|
164
|
+
window_size = 2**(w - 1)
|
165
|
+
points = []
|
166
|
+
p = self
|
167
|
+
windows.times do
|
168
|
+
base = p
|
169
|
+
points << base
|
170
|
+
(1...window_size).each do
|
171
|
+
base += p
|
172
|
+
points << base
|
173
|
+
end
|
174
|
+
p = base.double
|
175
|
+
end
|
176
|
+
points
|
177
|
+
end
|
178
|
+
|
179
|
+
def max_bits
|
180
|
+
self.class.const_get(:MAX_BITS)
|
181
|
+
end
|
182
|
+
|
183
|
+
def normalize_z(points)
|
184
|
+
to_affine_batch(points).map{ |p| from_affine_tuple(p) }
|
185
|
+
end
|
186
|
+
|
187
|
+
def calc_multiply_precomputes(w)
|
188
|
+
raise PointError, 'This point already has precomputes.' if m_precomputes
|
189
|
+
|
190
|
+
self.m_precomputes = [w, normalize_z(precomputes_window(w))]
|
191
|
+
end
|
192
|
+
|
193
|
+
def clear_multiply_precomputes
|
194
|
+
self.m_precomputes = nil
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
def wNAF(n)
|
200
|
+
w, precomputes = m_precomputes || [1, precomputes_window(1)]
|
201
|
+
p = zero
|
202
|
+
f = zero
|
203
|
+
windows = (BigDecimal(max_bits) / w).ceil
|
204
|
+
window_size = 2**(w - 1)
|
205
|
+
mask = (2**w - 1)
|
206
|
+
max_number = 2**w
|
207
|
+
shift_by = w
|
208
|
+
windows.times do |window|
|
209
|
+
offset = window * window_size
|
210
|
+
wbits = n & mask
|
211
|
+
n >>= shift_by
|
212
|
+
if wbits > window_size
|
213
|
+
wbits -= max_number
|
214
|
+
n += 1
|
215
|
+
end
|
216
|
+
if wbits.zero?
|
217
|
+
f += (window % 2 ? precomputes[offset].negate : precomputes[offset])
|
218
|
+
else
|
219
|
+
cached = precomputes[offset + wbits.abs - 1]
|
220
|
+
p += (wbits.negative? ? cached.negate : cached)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
[p, f]
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
class PointG1 < ProjectivePoint
|
228
|
+
|
229
|
+
BASE = PointG1.new(Fq.new(Curve::G_X), Fq.new(Curve::G_Y), Fq::ONE)
|
230
|
+
ZERO = PointG1.new(Fq::ONE, Fq::ONE, Fq::ZERO)
|
231
|
+
MAX_BITS = Fq::MAX_BITS
|
232
|
+
|
233
|
+
# Parse PointG1 from form hex.
|
234
|
+
# @param [String] hex hex value of PointG1.
|
235
|
+
# @return [PointG1]
|
236
|
+
# @raise [BLS::PointError] Occurs when hex length does not match, or point does not on G1.
|
237
|
+
def self.from_hex(hex)
|
238
|
+
bytes = [hex].pack('H*')
|
239
|
+
point = case bytes.bytesize
|
240
|
+
when 48
|
241
|
+
compressed_value = hex.to_i(16)
|
242
|
+
b_flag = BLS.mod(compressed_value, POW_2_383) / POW_2_382
|
243
|
+
return ZERO if b_flag == 1
|
244
|
+
|
245
|
+
x = BLS.mod(compressed_value, POW_2_381)
|
246
|
+
full_y = BLS.mod(x**3 + Fq.new(Curve::B).value, Curve::P)
|
247
|
+
y = BLS.pow_mod(full_y, (Curve::P + 1) / 4, Curve::P)
|
248
|
+
raise PointError, 'The given point is not on G1: y**2 = x**3 + b.' unless (BLS.pow_mod(y, 2, Curve::P) - full_y).zero?
|
249
|
+
|
250
|
+
a_flag = BLS.mod(compressed_value, POW_2_382) / POW_2_381
|
251
|
+
y = Curve::P - y unless ((y * 2) / Curve::P) == a_flag
|
252
|
+
PointG1.new(Fq.new(x), Fq.new(y), Fq::ONE)
|
253
|
+
when 96
|
254
|
+
return ZERO unless (bytes[0].unpack1('H*').to_i(16) & (1 << 6)).zero?
|
255
|
+
|
256
|
+
x = bytes[0...PUBLIC_KEY_LENGTH].unpack1('H*').to_i(16)
|
257
|
+
y = bytes[PUBLIC_KEY_LENGTH..-1].unpack1('H*').to_i(16)
|
258
|
+
PointG1.new(Fq.new(x), Fq.new(y), Fq::ONE)
|
259
|
+
else
|
260
|
+
raise PointError, 'Invalid point G1, expected 48 or 96 bytes.'
|
261
|
+
end
|
262
|
+
point.validate!
|
263
|
+
point
|
264
|
+
end
|
265
|
+
|
266
|
+
def to_hex(compressed: false)
|
267
|
+
if compressed
|
268
|
+
if self == PointG1::ZERO
|
269
|
+
hex = POW_2_383 + POW_2_382
|
270
|
+
else
|
271
|
+
x, y = to_affine
|
272
|
+
flag = (y.value * 2) / Curve::P
|
273
|
+
hex = x.value + flag * POW_2_381 + POW_2_383
|
274
|
+
end
|
275
|
+
BLS.num_to_hex(hex, PUBLIC_KEY_LENGTH)
|
276
|
+
else
|
277
|
+
if self == PointG1::ZERO
|
278
|
+
(1 << 6).to_s(16) + '00' * (2 * PUBLIC_KEY_LENGTH - 1)
|
279
|
+
else
|
280
|
+
x, y = to_affine
|
281
|
+
BLS.num_to_hex(x.value, PUBLIC_KEY_LENGTH) + BLS.num_to_hex(y.value, PUBLIC_KEY_LENGTH)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Parse Point from private key.
|
287
|
+
# @param [String|Integer] private_key a private key with hex or number.
|
288
|
+
# @return [PointG1] G1Point corresponding to private keys.
|
289
|
+
# @raise [BLS::Error] Occur when the private key is zero.
|
290
|
+
def self.from_private_key(private_key)
|
291
|
+
BASE * BLS.normalize_priv_key(private_key)
|
292
|
+
end
|
293
|
+
|
294
|
+
# Validate this point whether on curve over Fq.
|
295
|
+
# @raise [PointError] Occur when this point not on curve over Fq.
|
296
|
+
def validate!
|
297
|
+
b = Fq.new(Curve::B)
|
298
|
+
return if zero?
|
299
|
+
|
300
|
+
left = y.pow(2) * z - x.pow(3)
|
301
|
+
right = b * z.pow(3)
|
302
|
+
raise PointError, 'Invalid point: not on curve over Fq' unless left == right
|
303
|
+
end
|
304
|
+
|
305
|
+
# Sparse multiplication against precomputed coefficients.
|
306
|
+
# @param [PointG2] p
|
307
|
+
def miller_loop(p)
|
308
|
+
BLS.miller_loop(p.pairing_precomputes, to_affine)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
class PointG2 < ProjectivePoint
|
313
|
+
|
314
|
+
attr_accessor :precomputes
|
315
|
+
|
316
|
+
MAX_BITS = Fq2::MAX_BITS
|
317
|
+
BASE = PointG2.new(Fq2.new(Curve::G2_X), Fq2.new(Curve::G2_Y), Fq2::ONE)
|
318
|
+
ZERO = PointG2.new(Fq2::ONE, Fq2::ONE, Fq2::ZERO)
|
319
|
+
|
320
|
+
# Parse PointG1 from form hex.
|
321
|
+
# @param [String] hex hex value of PointG2. Currently, only uncompressed formats(196 bytes) are supported.
|
322
|
+
# @return [BLS::PointG2] PointG2 object.
|
323
|
+
# @raise [BLS::PointError]
|
324
|
+
def self.from_hex(hex)
|
325
|
+
bytes = [hex].pack('H*')
|
326
|
+
point = case bytes.bytesize
|
327
|
+
when 96
|
328
|
+
raise PointError, 'Compressed format not supported yet.'
|
329
|
+
when 192
|
330
|
+
return ZERO unless (bytes[0].unpack1('H*').to_i(16) & (1 << 6)).zero?
|
331
|
+
|
332
|
+
x1 = bytes[0...PUBLIC_KEY_LENGTH].unpack1('H*').to_i(16)
|
333
|
+
x0 = bytes[PUBLIC_KEY_LENGTH...(2 * PUBLIC_KEY_LENGTH)].unpack1('H*').to_i(16)
|
334
|
+
y1 = bytes[(2 * PUBLIC_KEY_LENGTH)...(3 * PUBLIC_KEY_LENGTH)].unpack1('H*').to_i(16)
|
335
|
+
y0 = bytes[(3 * PUBLIC_KEY_LENGTH)..-1].unpack1('H*').to_i(16)
|
336
|
+
PointG2.new(Fq2.new([x0, x1]), Fq2.new([y0, y1]), Fq2::ONE)
|
337
|
+
else
|
338
|
+
raise PointError, 'Invalid uncompressed point G2, expected 192 bytes.'
|
339
|
+
end
|
340
|
+
point.validate!
|
341
|
+
point
|
342
|
+
end
|
343
|
+
|
344
|
+
# Convert hash to PointG2
|
345
|
+
# @param [String] message a hash with hex format.
|
346
|
+
# @return [BLS::PointG2] point.
|
347
|
+
# @raise [BLS::PointError]
|
348
|
+
def self.hash_to_curve(message)
|
349
|
+
raise PointError, 'expected hex string' unless message[/^[a-fA-F0-9]*$/]
|
350
|
+
|
351
|
+
u = BLS.hash_to_field(message, 2)
|
352
|
+
q0 = PointG2.new(*BLS.isogeny_map_g2(*BLS.map_to_curve_sswu_g2(u[0])))
|
353
|
+
q1 = PointG2.new(*BLS.isogeny_map_g2(*BLS.map_to_curve_sswu_g2(u[1])))
|
354
|
+
r = q0 + q1
|
355
|
+
BLS.clear_cofactor_g2(r)
|
356
|
+
end
|
357
|
+
|
358
|
+
def to_hex(compressed: false)
|
359
|
+
raise ArgumentError, 'Not supported' if compressed
|
360
|
+
|
361
|
+
if self == PointG2::ZERO
|
362
|
+
(1 << 6).to_s(16) + '00' * (4 * PUBLIC_KEY_LENGTH - 1)
|
363
|
+
else
|
364
|
+
validate!
|
365
|
+
x, y = to_affine.map(&:values)
|
366
|
+
BLS.num_to_hex(x[1], PUBLIC_KEY_LENGTH) +
|
367
|
+
BLS.num_to_hex(x[0], PUBLIC_KEY_LENGTH) +
|
368
|
+
BLS.num_to_hex(y[1], PUBLIC_KEY_LENGTH) +
|
369
|
+
BLS.num_to_hex(y[0], PUBLIC_KEY_LENGTH)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
# Convert to signature with hex format.
|
374
|
+
# @return [String] signature with hex format.
|
375
|
+
def to_signature
|
376
|
+
if self == PointG2::ZERO
|
377
|
+
sum = POW_2_383 + POW_2_382
|
378
|
+
return BLS.num_to_hex(sum, PUBLIC_KEY_LENGTH) + BLS.num_to_hex(0, PUBLIC_KEY_LENGTH)
|
379
|
+
end
|
380
|
+
validate!
|
381
|
+
x, y = to_affine.map(&:values)
|
382
|
+
tmp = y[1] > 0 ? y[1] * 2 : y[0] * 2
|
383
|
+
aflag1 = tmp / Curve::P
|
384
|
+
z1 = x[1] + aflag1 * POW_2_381 + POW_2_383
|
385
|
+
z2 = x[0]
|
386
|
+
BLS.num_to_hex(z1, PUBLIC_KEY_LENGTH) + BLS.num_to_hex(z2, PUBLIC_KEY_LENGTH)
|
387
|
+
end
|
388
|
+
|
389
|
+
def validate!
|
390
|
+
b = Fq2.new(Curve::B2)
|
391
|
+
return if zero?
|
392
|
+
|
393
|
+
left = y.pow(2) * z - x.pow(3)
|
394
|
+
right = b * z.pow(3)
|
395
|
+
raise PointError, 'Invalid point: not on curve over Fq2' unless left == right
|
396
|
+
end
|
397
|
+
|
398
|
+
def clear_pairing_precomputes
|
399
|
+
self.precomputes = nil
|
400
|
+
end
|
401
|
+
|
402
|
+
def pairing_precomputes
|
403
|
+
return precomputes if precomputes
|
404
|
+
|
405
|
+
self.precomputes = calc_pairing_precomputes(*to_affine)
|
406
|
+
precomputes
|
407
|
+
end
|
408
|
+
|
409
|
+
private
|
410
|
+
|
411
|
+
def calc_pairing_precomputes(x, y)
|
412
|
+
q_x, q_y, q_z = [x, y, Fq2::ONE]
|
413
|
+
r_x, r_y, r_z = [q_x, q_y, q_z]
|
414
|
+
ell_coeff = []
|
415
|
+
i = BLS_X_LEN - 2
|
416
|
+
while i >= 0
|
417
|
+
t0 = r_y.square
|
418
|
+
t1 = r_z.square
|
419
|
+
t2 = t1.multiply(3).multiply_by_b
|
420
|
+
t3 = t2 * 3
|
421
|
+
t4 = (r_y + r_z).square - t1 - t0
|
422
|
+
ell_coeff << [t2 - t0, r_x.square * 3, t4.negate]
|
423
|
+
r_x = (t0 - t3) * r_x * r_y / 2
|
424
|
+
r_y = ((t0 + t3) / 2).square - t2.square * 3
|
425
|
+
r_z = t0 * t4
|
426
|
+
unless BLS.bit_get(Curve::X, i).zero?
|
427
|
+
t0 = r_y - q_y * r_z
|
428
|
+
t1 = r_x - q_x * r_z
|
429
|
+
ell_coeff << [t0 * q_x - t1 * q_y, t0.negate, t1]
|
430
|
+
t2 = t1.square
|
431
|
+
t3 = t2 * t1
|
432
|
+
t4 = t2 * r_x
|
433
|
+
t5 = t3 - t4 * 2 + t0.square * r_z
|
434
|
+
r_x = t1 * t5
|
435
|
+
r_y = (t4 - t5) * t0 - t3 * r_y
|
436
|
+
r_z *= t3
|
437
|
+
end
|
438
|
+
i -= 1
|
439
|
+
end
|
440
|
+
ell_coeff
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
module_function
|
445
|
+
|
446
|
+
def clear_cofactor_g2(p)
|
447
|
+
t1 = p.multiply_unsafe(Curve::X).negate
|
448
|
+
t2 = p.from_affine_tuple(BLS.psi(*p.to_affine))
|
449
|
+
p2 = p.from_affine_tuple(BLS.psi2(*p.double.to_affine))
|
450
|
+
p2 - t2 + (t1 + t2).multiply_unsafe(Curve::X).negate - t1 - p
|
451
|
+
end
|
452
|
+
|
453
|
+
def norm_p1(point)
|
454
|
+
point.is_a?(PointG1) ? point : PointG1.from_hex(point)
|
455
|
+
end
|
456
|
+
|
457
|
+
def norm_p2(point)
|
458
|
+
point.is_a?(PointG2) ? point : PointG2.from_hex(point)
|
459
|
+
end
|
460
|
+
|
461
|
+
def norm_p2h(point)
|
462
|
+
point.is_a?(PointG2) ? point : PointG2.hash_to_curve(point)
|
463
|
+
end
|
464
|
+
|
465
|
+
# Convert hash to Field.
|
466
|
+
# @param [String] message hash value with hex format.
|
467
|
+
# @return [Array[Integer]] byte array.
|
468
|
+
def hash_to_field(message, degree, random_oracle: true)
|
469
|
+
count = random_oracle ? 2 : 1
|
470
|
+
l = 64
|
471
|
+
len_in_bytes = count * degree * l
|
472
|
+
pseudo_random_bytes = BLS.expand_message_xmd(message, len_in_bytes)
|
473
|
+
u = Array.new(count)
|
474
|
+
count.times do |i|
|
475
|
+
e = Array.new(degree)
|
476
|
+
degree.times do |j|
|
477
|
+
elm_offset = l * (j + i * degree)
|
478
|
+
tv = pseudo_random_bytes[elm_offset...(elm_offset + l)]
|
479
|
+
e[j] = BLS.mod(BLS.os2ip(tv), Curve::P)
|
480
|
+
end
|
481
|
+
u[i] = e
|
482
|
+
end
|
483
|
+
u
|
484
|
+
end
|
485
|
+
|
486
|
+
# @param [String] message hash value with hex format.
|
487
|
+
# @param [Integer] len_in_bytes length
|
488
|
+
# @return [Array[Integer]] byte array.
|
489
|
+
# @raise BLS::Error
|
490
|
+
def expand_message_xmd(message, len_in_bytes)
|
491
|
+
b_in_bytes = BigDecimal(SHA256_DIGEST_SIZE)
|
492
|
+
r_in_bytes = b_in_bytes * 2
|
493
|
+
ell = (BigDecimal(len_in_bytes) / b_in_bytes).ceil
|
494
|
+
raise BLS::Error, 'Invalid xmd length' if ell > 255
|
495
|
+
|
496
|
+
dst_prime = DST_LABEL.bytes + BLS.i2osp(DST_LABEL.bytesize, 1)
|
497
|
+
z_pad = BLS.i2osp(0, r_in_bytes)
|
498
|
+
l_i_b_str = BLS.i2osp(len_in_bytes, 2)
|
499
|
+
b = Array.new(ell)
|
500
|
+
payload = z_pad + [message].pack('H*').bytes + l_i_b_str + BLS.i2osp(0, 1) + dst_prime
|
501
|
+
b_0 = Digest::SHA256.digest(payload.pack('C*'))
|
502
|
+
b[0] = Digest::SHA256.digest((b_0.bytes + BLS.i2osp(1, 1) + dst_prime).pack('C*'))
|
503
|
+
(1..ell).each do |i|
|
504
|
+
args = BLS.bin_xor(b_0, b[i - 1]).bytes + BLS.i2osp(i + 1, 1) + dst_prime
|
505
|
+
b[i] = Digest::SHA256.digest(args.pack('C*'))
|
506
|
+
end
|
507
|
+
b.map(&:bytes).flatten[0...len_in_bytes]
|
508
|
+
end
|
509
|
+
end
|