Class: BLS::PointG2

Inherits:
ProjectivePoint
  • Object
show all
Defined in:
lib/bls/point/g2.rb

Constant Summary collapse

DST_BASIC =

Ciphersuite IDs of draft-irtf-cfrg-bls-signature section 4.2.

'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_'
DST_POP =
'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_'
DST_POP_PROOF =
'BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_'
KEY_SIZE_COMPRESSED =
96
KEY_SIZE_UNCOMPRESSED =
192
MAX_BITS =
Fp2::MAX_BITS
BASE =
PointG2.new(Fp2.new(Curve::G2_X), Fp2.new(Curve::G2_Y), Fp2::ONE)
ZERO =
PointG2.new(Fp2::ONE, Fp2::ONE, Fp2::ZERO)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#precomputesObject

Returns the value of attribute precomputes.



4
5
6
# File 'lib/bls/point/g2.rb', line 4

def precomputes
  @precomputes
end

Class Method Details

.clear_cofactor(p) ⇒ Object



203
204
205
206
207
208
# File 'lib/bls/point/g2.rb', line 203

def self.clear_cofactor(p)
  t1 = p.multiply_unsafe(Curve::X).negate
  t2 = p.from_affine_tuple(BLS.psi(*p.to_affine))
  p2 = p.from_affine_tuple(BLS.psi2(*p.double.to_affine))
  p2 - t2 + (t1 + t2).multiply_unsafe(Curve::X).negate - t1 - p
end

.from_hex(hex) ⇒ BLS::PointG2

Parameters:

  • hex (String)

    hex value of PointG2. Currently, only uncompressed formats(196 bytes) are supported.

Returns:

Raises:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/bls/point/g2.rb', line 23

def self.from_hex(hex)
  validate_hex!(hex)
  bytes = [hex].pack('H*')
  unless [KEY_SIZE_COMPRESSED, KEY_SIZE_UNCOMPRESSED].include?(bytes.bytesize)
    raise PointError, 'Invalid point G2, expected 96/192 bytes.'
  end

  m_byte = bytes[0].unpack1('C')& 0xe0
  if [0x20, 0x60, 0xe0].include?(m_byte)
    raise PointError, "Invalid encoding flag: #{m_byte.to_s(16)}"
  end

  c_bit = m_byte & POINT_COMPRESSION_FLAG # compression flag
  i_bit = m_byte & POINT_INFINITY_FLAG # infinity flag
  s_bit = m_byte & POINT_Y_FLAG # y coordinate sign flag
  bytes[0] = [bytes[0].unpack1('C') & 0x1f].pack('C') # set flag to 0

  if i_bit == POINT_INFINITY_FLAG && bytes.unpack1('H*').to_i(16) > 0
    raise PointError, 'Invalid point, infinity point should be all 0.'
  end

  point = if bytes.bytesize == KEY_SIZE_COMPRESSED && c_bit == POINT_COMPRESSION_FLAG # compress format
            return ZERO if i_bit == POINT_INFINITY_FLAG
            x1 = bytes[0...PUBLIC_KEY_LENGTH].unpack1('H*').to_i(16)
            x0 = bytes[PUBLIC_KEY_LENGTH...(2 * PUBLIC_KEY_LENGTH)].unpack1('H*').to_i(16)
            x = Fp2.new([x0, x1])
            right = x ** 3 + Fp2.new(Curve::B2)
            y = right.sqrt
            raise PointError, 'Invalid compressed G2 point' unless y
            bit_y = if y.coeffs[1].value == 0
                      (y.coeffs[0].value * 2) / Curve::P
                    else
                      (y.coeffs[1].value * 2) / Curve::P == 1 ? 1 : 0
                    end
            # Flip to whichever root the encoding asked for. Written as a comparison, the
            # way G1 does it, rather than as `s_bit > 0 && bit_y > 0 ? y : y.negate`:
            # that form is only right because sqrt returns the root of sign 1, which it
            # does not promise, and it would negate a root of sign 0 that wanted sign 0.
            y = y.negate unless bit_y == (s_bit.zero? ? 0 : 1)
            PointG2.new(x, y, Fp2::ONE)
          elsif bytes.bytesize == KEY_SIZE_UNCOMPRESSED && c_bit != POINT_COMPRESSION_FLAG # uncompressed format
            return ZERO if i_bit == POINT_INFINITY_FLAG
            x1 = bytes[0...PUBLIC_KEY_LENGTH].unpack1('H*').to_i(16)
            x0 = bytes[PUBLIC_KEY_LENGTH...(2 * PUBLIC_KEY_LENGTH)].unpack1('H*').to_i(16)
            y1 = bytes[(2 * PUBLIC_KEY_LENGTH)...(3 * PUBLIC_KEY_LENGTH)].unpack1('H*').to_i(16)
            y0 = bytes[(3 * PUBLIC_KEY_LENGTH)..-1].unpack1('H*').to_i(16)
            PointG2.new(Fp2.new([x0, x1]), Fp2.new([y0, y1]), Fp2::ONE)
          else
            raise PointError, 'Invalid point G2, compression flag does not match the encoded length.'
          end
  point.validate!
  point.validate_group!
  point
end

.from_private_key(private_key) ⇒ PointG1

Parse Point from private key.

Parameters:

  • private_key (String|Integer)

    a private key with hex or number.

Returns:

  • (PointG1)

    G1Point corresponding to private keys.

Raises:

  • (BLS::Error)

    Occur when the private key is zero.



82
83
84
# File 'lib/bls/point/g2.rb', line 82

def self.from_private_key(private_key)
  BASE * BLS.normalize_priv_key(private_key)
end

.hash_to_curve(message, scheme: :basic, dst: nil) ⇒ BLS::PointG2

Convert hash to PointG2 ciphersuite this library does not name, such as the ones the RFC 9380 vectors use.

Parameters:

  • message (String)

    a hash with hex format.

  • scheme (Symbol) (defaults to: :basic)

    signature scheme whose domain separation tag is used, :basic or :pop.

  • dst (String) (defaults to: nil)

    a domain separation tag to use instead of the scheme's, for a

Returns:

Raises:



93
94
95
96
97
98
99
100
101
# File 'lib/bls/point/g2.rb', line 93

def self.hash_to_curve(message, scheme: :basic, dst: nil)
  validate_hex!(message)

  u = BLS::H2C::G2.hash_to_field(message, dst || self.dst(scheme))
  q0 = PointG2.new(*BLS::H2C::G2.isogeny_map(*BLS::H2C::G2.map_to_curve_sswu(u[0])))
  q1 = PointG2.new(*BLS::H2C::G2.isogeny_map(*BLS::H2C::G2.map_to_curve_sswu(u[1])))
  r = q0 + q1
  clear_cofactor(r)
end

Instance Method Details

#clear_pairing_precomputesObject



152
153
154
# File 'lib/bls/point/g2.rb', line 152

def clear_pairing_precomputes
  self.precomputes = nil
end

#pairing_precomputesArray

The line coefficients BLS.miller_loop needs, worked out once and kept on the point.

This memoises without a lock, so concurrent callers can each compute it. They compute the same coefficients and assign a finished array, so a reader sees one or the other and never a partial one; the cost is the repeated work. BLS.verify reaches this on PointG2::BASE, which is shared, whenever the public key is a G2 point.

Returns:

  • (Array)


163
164
165
166
167
168
# File 'lib/bls/point/g2.rb', line 163

def pairing_precomputes
  return precomputes if precomputes

  self.precomputes = calc_pairing_precomputes(*to_affine)
  precomputes
end

#to_hex(compressed: false) ⇒ String

Serialize pont as hex value.

Parameters:

  • compressed (Boolean) (defaults to: false)

    whether to compress the point.

Returns:

  • (String)

    hex value of point.



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/bls/point/g2.rb', line 106

def to_hex(compressed: false)
  if compressed
    if zero?
      x1 = POW_2_383 + POW_2_382
      x0= 0
    else
      x, y = to_affine
      flag = if y.coeffs[1].value == 0
               (y.coeffs[0].value * 2) / Curve::P
             else
               ((y.coeffs[1].value * 2) / Curve::P).zero? ? 0 : 1
             end
      x1 = x.coeffs[1].value + flag * POW_2_381 + POW_2_383
      x0 = x.coeffs[0].value
    end
    BLS.num_to_hex(x1, PUBLIC_KEY_LENGTH) + BLS.num_to_hex(x0, PUBLIC_KEY_LENGTH)
  else
    if self == PointG2::ZERO
      (1 << 6).to_s(16) + '00' * (4 * PUBLIC_KEY_LENGTH - 1)
    else
      validate!
      x, y = to_affine.map(&:values)
      BLS.num_to_hex(x[1], PUBLIC_KEY_LENGTH) +
        BLS.num_to_hex(x[0], PUBLIC_KEY_LENGTH) +
        BLS.num_to_hex(y[1], PUBLIC_KEY_LENGTH) +
        BLS.num_to_hex(y[0], PUBLIC_KEY_LENGTH)
    end
  end
end

#to_signatureString

Deprecated.

Use #to_hex instead.

Convert to signature with hex format.

Returns:

  • (String)

    signature with hex format.



139
140
141
# File 'lib/bls/point/g2.rb', line 139

def to_signature
  to_hex(compressed: true)
end

#validate!Object

Raises:



143
144
145
146
147
148
149
150
# File 'lib/bls/point/g2.rb', line 143

def validate!
  b = Fp2.new(Curve::B2)
  return if zero?

  left = y.pow(2) * z - x.pow(3)
  right = b * z.pow(3)
  raise PointError, 'Invalid point: not on curve over Fp2' unless left == right
end