bls12-381 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/bls/h2c.rb ADDED
@@ -0,0 +1,114 @@
1
+ module BLS
2
+ module H2C
3
+
4
+ LENGTH = 64
5
+
6
+ module_function
7
+
8
+ # @param [String] message hash value with hex format.
9
+ # @param [Integer] len_in_bytes length
10
+ # @return [Array[Integer]] byte array.
11
+ # @raise BLS::Error
12
+ def expand_message_xmd(message, len_in_bytes)
13
+ b_in_bytes = BigDecimal(SHA256_DIGEST_SIZE)
14
+ r_in_bytes = b_in_bytes * 2
15
+ ell = (BigDecimal(len_in_bytes) / b_in_bytes).ceil
16
+ raise BLS::Error, 'Invalid xmd length' if ell > 255
17
+
18
+ dst_prime = PointG2::DST_BASIC.bytes + BLS.i2osp(PointG2::DST_BASIC.bytesize, 1)
19
+ z_pad = BLS.i2osp(0, r_in_bytes)
20
+ l_i_b_str = BLS.i2osp(len_in_bytes, 2)
21
+ b = Array.new(ell)
22
+ payload = z_pad + [message].pack('H*').bytes + l_i_b_str + BLS.i2osp(0, 1) + dst_prime
23
+ b_0 = Digest::SHA256.digest(payload.pack('C*'))
24
+ b[0] = Digest::SHA256.digest((b_0.bytes + BLS.i2osp(1, 1) + dst_prime).pack('C*'))
25
+ (1..ell).each do |i|
26
+ args = BLS.bin_xor(b_0, b[i - 1]).bytes + BLS.i2osp(i + 1, 1) + dst_prime
27
+ b[i] = Digest::SHA256.digest(args.pack('C*'))
28
+ end
29
+ b.map(&:bytes).flatten[0...len_in_bytes]
30
+ end
31
+
32
+ module G2
33
+ module_function
34
+
35
+ # Convert hash to Field.
36
+ # @param [String] message hash value with hex format.
37
+ # @return [Array[Integer]] byte array.
38
+ def hash_to_field(message, random_oracle: true)
39
+ degree = 2
40
+ count = random_oracle ? 2 : 1
41
+ len_in_bytes = count * degree * LENGTH
42
+ pseudo_random_bytes = BLS::H2C.expand_message_xmd(message, len_in_bytes)
43
+ u = Array.new(count)
44
+ count.times do |i|
45
+ e = Array.new(degree)
46
+ degree.times do |j|
47
+ elm_offset = LENGTH * (j + i * degree)
48
+ tv = pseudo_random_bytes[elm_offset...(elm_offset + LENGTH)]
49
+ e[j] = BLS.mod(BLS.os2ip(tv), Curve::P)
50
+ end
51
+ u[i] = e
52
+ end
53
+ u
54
+ end
55
+
56
+ # Optimized SWU Map - Fp2 to G2': y^2 = x^3 + 240i * x + 1012 + 1012i
57
+ def map_to_curve_sswu(t)
58
+ iso_3_a = Fp2.new([0, 240])
59
+ iso_3_b = Fp2.new([1012, 1012])
60
+ iso_3_z = Fp2.new([-2, -1])
61
+ t = Fp2.new(t) if t.is_a?(Array)
62
+ t2 = t**2
63
+ iso_3_z_t2 = iso_3_z * t2
64
+ ztzt = iso_3_z_t2 + iso_3_z_t2**2
65
+ denominator = (iso_3_a * ztzt).negate
66
+ numerator = iso_3_b * (ztzt + Fp2::ONE)
67
+ denominator = iso_3_z * iso_3_a if denominator.zero?
68
+ v = denominator**3
69
+ u = numerator**3 + iso_3_a * numerator * denominator**2 + iso_3_b * v
70
+ success, sqrt_candidate_or_gamma = BLS.sqrt_div_fp2(u, v)
71
+ y = success ? sqrt_candidate_or_gamma : nil
72
+ sqrt_candidate_x1 = sqrt_candidate_or_gamma * t**3
73
+ u = iso_3_z_t2**3 * u
74
+ success2 = false
75
+ Fp2::ETAS.each do |eta|
76
+ eta_sqrt_candidate = eta * sqrt_candidate_x1
77
+ temp = eta_sqrt_candidate**2 * v - u
78
+ if temp.zero? && !success && !success2
79
+ y = eta_sqrt_candidate
80
+ success2 = true
81
+ end
82
+ end
83
+ raise BLS::PointError, 'Hash to Curve - Optimized SWU failure' if !success && !success2
84
+
85
+ numerator *= iso_3_z_t2 if success2
86
+ y = y.negate if BLS.sgn0(t) != BLS.sgn0(y)
87
+ y *= denominator
88
+ [numerator, y, denominator]
89
+ end
90
+
91
+ # 3-isogeny map from E' to E
92
+ # Converts from Jacobi (xyz) to Projective (xyz) coordinates.
93
+ def isogeny_map(x, y, z)
94
+ mapped = Array.new(4, Fp2::ZERO)
95
+ z_powers = [z, z**2, z**3]
96
+ ISOGENY_COEFFICIENTS.each.with_index do |k, i|
97
+ mapped[i] = k[-1]
98
+ arr = k[0...-1].reverse
99
+ arr.each.with_index do |a, j|
100
+ mapped[i] = mapped[i] * x + z_powers[j] * a
101
+ end
102
+ end
103
+ mapped[2] *= y
104
+ mapped[3] *= z
105
+ z2 = mapped[1] * mapped[3]
106
+ x2 = mapped[0] * mapped[3]
107
+ y2 = mapped[1] * mapped[2]
108
+ [x2, y2, z2]
109
+ end
110
+
111
+ end
112
+
113
+ end
114
+ end
data/lib/bls/math.rb CHANGED
@@ -25,7 +25,7 @@ module BLS
25
25
 
26
26
  # Convert byte to non-negative integer.
27
27
  # @param [Array[Integer]] bytes byte array.
28
- # @return [Array[Integer]] byte array.
28
+ # @return [Integer] Integer.
29
29
  def os2ip(bytes)
30
30
  res = 0
31
31
  bytes.each do |b|
@@ -66,71 +66,14 @@ module BLS
66
66
  res.pack('C*')
67
67
  end
68
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
69
  # Normalize private key.
125
70
  # @param [String|Integer] private_key a private key with hex or number.
126
- # @return [BLS::Fq] private key field.
71
+ # @return [BLS::Fr] Normalized private key.
127
72
  # @raise [BLS::Error] Occur when the private key is zero.
128
73
  def normalize_priv_key(private_key)
129
74
  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
75
+ raise BLS::Error, "Private key must be 0 < private_key < #{Fr::ORDER}" if k <= 0
76
+ Fr.new(k)
134
77
  end
135
78
 
136
79
  # Convert number to +byte_length+ bytes hex string.
data/lib/bls/pairing.rb CHANGED
@@ -6,7 +6,7 @@ module BLS
6
6
  # @param [BLS::PointG1] p
7
7
  # @param [BLS::PointG2] q
8
8
  # @param [Boolean] with_final_exp
9
- # @return [BLS::Fq12]
9
+ # @return [BLS::Fp12]
10
10
  # @raise [BLS::PairingError] Occur when p.zero? or q.zero?
11
11
  # @raise [ArgumentError]
12
12
  def pairing(p, q, with_final_exp: true)
@@ -0,0 +1,105 @@
1
+ module BLS
2
+ class PointG1 < BLS::ProjectivePoint
3
+
4
+ DST_BASIC = 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_'
5
+
6
+ KEY_SIZE_COMPRESSED = 48
7
+ KEY_SIZE_UNCOMPRESSED = 96
8
+
9
+ BASE = PointG1.new(Fp.new(Curve::G_X), Fp.new(Curve::G_Y), Fp::ONE)
10
+ ZERO = PointG1.new(Fp::ONE, Fp::ONE, Fp::ZERO)
11
+ MAX_BITS = Fp::MAX_BITS
12
+
13
+ # Parse PointG1 from form hex.
14
+ # @param [String] hex hex value of PointG1.
15
+ # @return [PointG1]
16
+ # @raise [BLS::PointError] Occurs when hex length does not match, or point does not on G1.
17
+ def self.from_hex(hex)
18
+ bytes = [hex].pack('H*')
19
+ point = case bytes.bytesize
20
+ when KEY_SIZE_COMPRESSED
21
+ compressed_value = hex.to_i(16)
22
+ b_flag = BLS.mod(compressed_value, POW_2_383) / POW_2_382
23
+ return ZERO if b_flag == 1
24
+
25
+ x = BLS.mod(compressed_value, POW_2_381)
26
+ full_y = BLS.mod(x**3 + Fp.new(Curve::B).value, Curve::P)
27
+ y = BLS.pow_mod(full_y, (Curve::P + 1) / 4, Curve::P)
28
+ 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?
29
+
30
+ a_flag = BLS.mod(compressed_value, POW_2_382) / POW_2_381
31
+ y = Curve::P - y unless ((y * 2) / Curve::P) == a_flag
32
+ PointG1.new(Fp.new(x), Fp.new(y), Fp::ONE)
33
+ when KEY_SIZE_UNCOMPRESSED
34
+ return ZERO unless (bytes[0].unpack1('H*').to_i(16) & (1 << 6)).zero?
35
+
36
+ x = bytes[0...PUBLIC_KEY_LENGTH].unpack1('H*').to_i(16)
37
+ y = bytes[PUBLIC_KEY_LENGTH..-1].unpack1('H*').to_i(16)
38
+ PointG1.new(Fp.new(x), Fp.new(y), Fp::ONE)
39
+ else
40
+ raise PointError, 'Invalid point G1, expected 48 or 96 bytes.'
41
+ end
42
+ point.validate!
43
+ point
44
+ end
45
+
46
+ def to_hex(compressed: false)
47
+ if compressed
48
+ if self == PointG1::ZERO
49
+ hex = POW_2_383 + POW_2_382
50
+ else
51
+ x, y = to_affine
52
+ flag = (y.value * 2) / Curve::P
53
+ hex = x.value + flag * POW_2_381 + POW_2_383
54
+ end
55
+ BLS.num_to_hex(hex, PUBLIC_KEY_LENGTH)
56
+ else
57
+ if self == PointG1::ZERO
58
+ (1 << 6).to_s(16) + '00' * (2 * PUBLIC_KEY_LENGTH - 1)
59
+ else
60
+ x, y = to_affine
61
+ BLS.num_to_hex(x.value, PUBLIC_KEY_LENGTH) + BLS.num_to_hex(y.value, PUBLIC_KEY_LENGTH)
62
+ end
63
+ end
64
+ end
65
+
66
+ # Parse Point from private key.
67
+ # @param [String|Integer] private_key a private key with hex or number.
68
+ # @return [PointG1] G1Point corresponding to private keys.
69
+ # @raise [BLS::Error] Occur when the private key is zero.
70
+ def self.from_private_key(private_key)
71
+ BASE * BLS.normalize_priv_key(private_key)
72
+ end
73
+
74
+ # Convert hash to PointG1
75
+ # @param [String] message a hash with hex format.
76
+ # @return [BLS::PointG1] point.
77
+ # @raise [BLS::PointError]
78
+ def self.hash_to_curve(message)
79
+ raise PointError, 'expected hex string' unless message[/^[a-fA-F0-9]*$/]
80
+
81
+ h2c = ::H2C.get(::H2C::Suite::BLS12381G1_XMDSHA256_SWU_RO_, PointG1::DST_BASIC)
82
+ p = h2c.digest([message].pack('H*'))
83
+
84
+ PointG1.new(Fp.new(p.x), Fp.new(p.y), Fp::ONE)
85
+ end
86
+
87
+ # Validate this point whether on curve over Fp.
88
+ # @raise [PointError] Occur when this point not on curve over Fp.
89
+ def validate!
90
+ b = Fp.new(Curve::B)
91
+ return if zero?
92
+
93
+ left = y.pow(2) * z - x.pow(3)
94
+ right = b * z.pow(3)
95
+ raise PointError, 'Invalid point: not on curve over Fp' unless left == right
96
+ end
97
+
98
+ # Sparse multiplication against precomputed coefficients.
99
+ # @param [PointG2] p
100
+ def miller_loop(p)
101
+ BLS.miller_loop(p.pairing_precomputes, to_affine)
102
+ end
103
+ end
104
+
105
+ end
@@ -0,0 +1,188 @@
1
+ module BLS
2
+ class PointG2 < BLS::ProjectivePoint
3
+
4
+ attr_accessor :precomputes
5
+
6
+ DST_BASIC = 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_'
7
+
8
+ KEY_SIZE_COMPRESSED = 96
9
+ KEY_SIZE_UNCOMPRESSED = 192
10
+
11
+ MAX_BITS = Fp2::MAX_BITS
12
+ BASE = PointG2.new(Fp2.new(Curve::G2_X), Fp2.new(Curve::G2_Y), Fp2::ONE)
13
+ ZERO = PointG2.new(Fp2::ONE, Fp2::ONE, Fp2::ZERO)
14
+
15
+ # Parse PointG2 from form hex.
16
+ # https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-07#section-appendix.c
17
+ # @param [String] hex hex value of PointG2. Currently, only uncompressed formats(196 bytes) are supported.
18
+ # @return [BLS::PointG2] PointG2.
19
+ # @raise [BLS::PointError]
20
+ def self.from_hex(hex)
21
+ bytes = [hex].pack('H*')
22
+ m_byte = bytes[0].unpack1('C')& 0xe0
23
+ if [0x20, 0x60, 0xe0].include?(m_byte)
24
+ raise PointError, "Invalid encoding flag: #{m_byte.to_s(16)}"
25
+ end
26
+
27
+ c_bit = m_byte & POINT_COMPRESSION_FLAG # compression flag
28
+ i_bit = m_byte & POINT_INFINITY_FLAG # infinity flag
29
+ s_bit = m_byte & POINT_Y_FLAG # y coordinate sign flag
30
+ bytes[0] = [bytes[0].unpack1('C') & 0x1f].pack('C') # set flag to 0
31
+
32
+ if i_bit == POINT_INFINITY_FLAG && bytes.unpack1('H*').to_i(16) > 0
33
+ raise PointError, 'Invalid point, infinity point should be all 0.'
34
+ end
35
+
36
+ point = if bytes.bytesize == KEY_SIZE_COMPRESSED && c_bit == POINT_COMPRESSION_FLAG # compress format
37
+ return ZERO if i_bit == POINT_INFINITY_FLAG
38
+ x1 = bytes[0...PUBLIC_KEY_LENGTH].unpack1('H*').to_i(16)
39
+ x0 = bytes[PUBLIC_KEY_LENGTH...(2 * PUBLIC_KEY_LENGTH)].unpack1('H*').to_i(16)
40
+ x = Fp2.new([x0, x1])
41
+ right = x ** 3 + Fp2.new(Curve::B2)
42
+ y = right.sqrt
43
+ raise PointError, 'Invalid compressed G2 point' unless y
44
+ bit_y = if y.coeffs[1].value == 0
45
+ (y.coeffs[0].value * 2) / Curve::P
46
+ else
47
+ (y.coeffs[1].value * 2) / Curve::P == 1 ? 1 : 0
48
+ end
49
+ y = s_bit > 0 && bit_y > 0 ? y : y.negate
50
+ PointG2.new(x, y, Fp2::ONE)
51
+ elsif bytes.bytesize == KEY_SIZE_UNCOMPRESSED && c_bit != POINT_COMPRESSION_FLAG # uncompressed format
52
+ return ZERO if i_bit == POINT_INFINITY_FLAG
53
+ x1 = bytes[0...PUBLIC_KEY_LENGTH].unpack1('H*').to_i(16)
54
+ x0 = bytes[PUBLIC_KEY_LENGTH...(2 * PUBLIC_KEY_LENGTH)].unpack1('H*').to_i(16)
55
+ y1 = bytes[(2 * PUBLIC_KEY_LENGTH)...(3 * PUBLIC_KEY_LENGTH)].unpack1('H*').to_i(16)
56
+ y0 = bytes[(3 * PUBLIC_KEY_LENGTH)..-1].unpack1('H*').to_i(16)
57
+ PointG2.new(Fp2.new([x0, x1]), Fp2.new([y0, y1]), Fp2::ONE)
58
+ else
59
+ raise PointError, 'Invalid point G2, expected 96/192 bytes.'
60
+ end
61
+ point.validate!
62
+ point
63
+ end
64
+
65
+ # Parse Point from private key.
66
+ # @param [String|Integer] private_key a private key with hex or number.
67
+ # @return [PointG1] G1Point corresponding to private keys.
68
+ # @raise [BLS::Error] Occur when the private key is zero.
69
+ def self.from_private_key(private_key)
70
+ BASE * BLS.normalize_priv_key(private_key)
71
+ end
72
+
73
+ # Convert hash to PointG2
74
+ # @param [String] message a hash with hex format.
75
+ # @return [BLS::PointG2] point.
76
+ # @raise [BLS::PointError]
77
+ def self.hash_to_curve(message)
78
+ raise PointError, 'expected hex string' unless message[/^[a-fA-F0-9]*$/]
79
+
80
+ u = BLS::H2C::G2.hash_to_field(message)
81
+ q0 = PointG2.new(*BLS::H2C::G2.isogeny_map(*BLS::H2C::G2.map_to_curve_sswu(u[0])))
82
+ q1 = PointG2.new(*BLS::H2C::G2.isogeny_map(*BLS::H2C::G2.map_to_curve_sswu(u[1])))
83
+ r = q0 + q1
84
+ clear_cofactor(r)
85
+ end
86
+
87
+ # Serialize pont as hex value.
88
+ # @param [Boolean] compressed whether to compress the point.
89
+ # @return [String] hex value of point.
90
+ def to_hex(compressed: false)
91
+ if compressed
92
+ if zero?
93
+ x1 = POW_2_383 + POW_2_382
94
+ x0= 0
95
+ else
96
+ x, y = to_affine
97
+ flag = if y.coeffs[1].value == 0
98
+ (y.coeffs[0].value * 2) / Curve::P
99
+ else
100
+ ((y.coeffs[1].value * 2) / Curve::P).zero? ? 0 : 1
101
+ end
102
+ x1 = x.coeffs[1].value + flag * POW_2_381 + POW_2_383
103
+ x0 = x.coeffs[0].value
104
+ end
105
+ BLS.num_to_hex(x1, PUBLIC_KEY_LENGTH) + BLS.num_to_hex(x0, PUBLIC_KEY_LENGTH)
106
+ else
107
+ if self == PointG2::ZERO
108
+ (1 << 6).to_s(16) + '00' * (4 * PUBLIC_KEY_LENGTH - 1)
109
+ else
110
+ validate!
111
+ x, y = to_affine.map(&:values)
112
+ BLS.num_to_hex(x[1], PUBLIC_KEY_LENGTH) +
113
+ BLS.num_to_hex(x[0], PUBLIC_KEY_LENGTH) +
114
+ BLS.num_to_hex(y[1], PUBLIC_KEY_LENGTH) +
115
+ BLS.num_to_hex(y[0], PUBLIC_KEY_LENGTH)
116
+ end
117
+ end
118
+ end
119
+
120
+ # Convert to signature with hex format.
121
+ # @return [String] signature with hex format.
122
+ # @deprecated Use {#to_hex} instead.
123
+ def to_signature
124
+ to_hex(compressed: true)
125
+ end
126
+
127
+ def validate!
128
+ b = Fp2.new(Curve::B2)
129
+ return if zero?
130
+
131
+ left = y.pow(2) * z - x.pow(3)
132
+ right = b * z.pow(3)
133
+ raise PointError, 'Invalid point: not on curve over Fp2' unless left == right
134
+ end
135
+
136
+ def clear_pairing_precomputes
137
+ self.precomputes = nil
138
+ end
139
+
140
+ def pairing_precomputes
141
+ return precomputes if precomputes
142
+
143
+ self.precomputes = calc_pairing_precomputes(*to_affine)
144
+ precomputes
145
+ end
146
+
147
+ private
148
+
149
+ def calc_pairing_precomputes(x, y)
150
+ q_x, q_y, q_z = [x, y, Fp2::ONE]
151
+ r_x, r_y, r_z = [q_x, q_y, q_z]
152
+ ell_coeff = []
153
+ i = BLS_X_LEN - 2
154
+ while i >= 0
155
+ t0 = r_y.square
156
+ t1 = r_z.square
157
+ t2 = t1.multiply(3).multiply_by_b
158
+ t3 = t2 * 3
159
+ t4 = (r_y + r_z).square - t1 - t0
160
+ ell_coeff << [t2 - t0, r_x.square * 3, t4.negate]
161
+ r_x = (t0 - t3) * r_x * r_y / 2
162
+ r_y = ((t0 + t3) / 2).square - t2.square * 3
163
+ r_z = t0 * t4
164
+ unless BLS.bit_get(Curve::X, i).zero?
165
+ t0 = r_y - q_y * r_z
166
+ t1 = r_x - q_x * r_z
167
+ ell_coeff << [t0 * q_x - t1 * q_y, t0.negate, t1]
168
+ t2 = t1.square
169
+ t3 = t2 * t1
170
+ t4 = t2 * r_x
171
+ t5 = t3 - t4 * 2 + t0.square * r_z
172
+ r_x = t1 * t5
173
+ r_y = (t4 - t5) * t0 - t3 * r_y
174
+ r_z *= t3
175
+ end
176
+ i -= 1
177
+ end
178
+ ell_coeff
179
+ end
180
+ def self.clear_cofactor(p)
181
+ t1 = p.multiply_unsafe(Curve::X).negate
182
+ t2 = p.from_affine_tuple(BLS.psi(*p.to_affine))
183
+ p2 = p.from_affine_tuple(BLS.psi2(*p.double.to_affine))
184
+ p2 - t2 + (t1 + t2).multiply_unsafe(Curve::X).negate - t1 - p
185
+ end
186
+ end
187
+
188
+ end