bls12-381 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/.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
|