bls12-381 0.4.0 → 0.5.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 +4 -4
- data/CHANGELOG.md +57 -0
- data/README.md +11 -9
- data/bls12-381.gemspec +2 -3
- data/lib/bls/field.rb +9 -0
- data/lib/bls/h2c.rb +17 -4
- data/lib/bls/pairing.rb +5 -0
- data/lib/bls/point/g1.rb +21 -2
- data/lib/bls/point/g2.rb +23 -2
- data/lib/bls/point.rb +66 -32
- data/lib/bls/version.rb +1 -1
- metadata +6 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24158dde28a5367b3fd19c5d77dfeb70ba7429c43f501f1e15345dc5792cfcd2
|
|
4
|
+
data.tar.gz: ee9dd866975f45538e1bd4cfa044fd88741cd0e01176fc6dea075111eab88cfe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1751863707f75ea0d6bcc0fe8d4e1e1da82fb9657256f4a272efb129288dfec205be049dd6dde478f96488dcbf083d9131ba50779f3467f364a2b6c8659e1f4a
|
|
7
|
+
data.tar.gz: f523b7e1d92e900b44aa395359a9fa2a98e701a381e5b217d934cd50fa9f194908f5789ee197047007e3b80f41cf2a67fcbbb224779219292b4c8ab557b4bfd7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2026-11-02
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `PointG2.from_hex` now rejects a coordinate at or above the field order. A coordinate written as `c + P` used to decode to the same point as `c`, giving one point several encodings.
|
|
8
|
+
- `expand_message_xmd`, and so `hash_to_curve` with a custom `dst:`, handles a domain separation tag longer than 255 bytes by hashing it as RFC 9380 section 5.3.3 prescribes, instead of failing inside I2OSP.
|
|
9
|
+
- Raising a field element to a negative power raises `ArgumentError`; `Fp2`, `Fp6` and `Fp12` used to answer with one.
|
|
10
|
+
- `expand_message_xmd` computes exactly the blocks RFC 9380 asks for. It used to compute one more, which cost a SHA-256 per call and failed outright at the largest permitted length.
|
|
11
|
+
- `==` on `Fp2`, `Fp6` and `Fp12` returns false for anything that is not the same kind of element instead of raising `NoMethodError`.
|
|
12
|
+
- `==` on a point returns false for a point of the other group, nil or anything else that is not a point of the same group, instead of raising `BLS::PointError`.
|
|
13
|
+
- `BLS.pairing` refuses a point outside the prime-order subgroup with `BLS::PointError`; it used to check only that the point was on the curve.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Scalar multiplication sizes its wNAF window table from the 255 bits of the group order rather than the base field, reduces the scalar modulo the order first, and skips zero windows instead of adding a throwaway point. `multiply` on G2 runs about three times faster and precompute tables shrink to a third.
|
|
18
|
+
- Subgroup membership (`in_group?`, and so `from_hex`) uses the endomorphism tests of Scott's "A note on group membership tests for G1, G2 and GT on BLS pairing-friendly curves" in place of a multiplication by the group order.
|
|
19
|
+
- `BLS::PointG1::MAX_BITS` and `BLS::PointG2::MAX_BITS` are gone; `BLS::ProjectivePoint::SCALAR_BITS` holds the width that matters.
|
|
20
|
+
- `BLS.norm_p1` and `BLS.norm_p2`, which nothing used, are gone.
|
|
21
|
+
- The `bigdecimal` dependency is gone.
|
|
22
|
+
- Requires Ruby 3.3 or later, matching what is tested.
|
|
23
|
+
- Depend on h2c 0.4.x, which no longer defines a top-level `BLS` module of its own.
|
|
24
|
+
- `hash_to_curve` refuses an empty or non-string `dst:` with `BLS::Error` for both groups, as RFC 9380 section 3.1 requires, and anything the h2c gem raises for G1 arrives as `BLS::PointError` rather than `H2C::Error`.
|
|
25
|
+
|
|
26
|
+
## [0.4.0] - 2026-08-10
|
|
27
|
+
|
|
28
|
+
- Check that every parsed point lies in the prime-order subgroup.
|
|
29
|
+
- Add the proof of possession scheme: `scheme:` on `sign`, `verify` and `verify_batch`, plus `pop_prove`, `pop_verify` and `fast_aggregate_verify`.
|
|
30
|
+
- `verify_batch` handles repeated messages correctly and refuses them under `:basic`.
|
|
31
|
+
- Verification returns false for the point at infinity instead of raising.
|
|
32
|
+
- Validate G1 encodings as strictly as G2: infinity padding, flag and length consistency, reduced coordinates.
|
|
33
|
+
- Validate hex input for points, messages and private keys instead of silently truncating or aliasing it.
|
|
34
|
+
- `Field#invert` raises on zero instead of returning zero.
|
|
35
|
+
- Fix `sgn0` to consult the second coordinate when the first is zero.
|
|
36
|
+
- All exceptions descend from `BLS::Error`, with `BLS::PointError` and `BLS::PairingError` beneath it.
|
|
37
|
+
- Add the bls12-381-tests and RFC 9380 test vectors. Document that nothing here is constant time.
|
|
38
|
+
|
|
39
|
+
## [0.3.1] - 2026-04-24
|
|
40
|
+
|
|
41
|
+
- Ruby 3.4 compatibility: depend on `bigdecimal` explicitly, follow the h2c gem's SSWU rename.
|
|
42
|
+
|
|
43
|
+
## [0.3.0] - 2023-07-21
|
|
44
|
+
|
|
45
|
+
- Support the compressed PointG2 format.
|
|
46
|
+
- Fix the private key range check.
|
|
47
|
+
|
|
48
|
+
## [0.2.2] - 2023-01-26
|
|
49
|
+
|
|
50
|
+
- Add `BLS::Field#==` and `BLS::Field#to_s`.
|
|
51
|
+
|
|
52
|
+
## [0.2.1] - 2023-01-16
|
|
53
|
+
|
|
54
|
+
- Add argument validation and error message cleanup.
|
|
55
|
+
|
|
56
|
+
## [0.2.0] - 2022-09-07
|
|
57
|
+
|
|
58
|
+
- Add `BLS::PointG2.from_private_key`.
|
|
59
|
+
|
|
3
60
|
## [0.1.0] - 2021-03-29
|
|
4
61
|
|
|
5
62
|
- Initial release
|
data/README.md
CHANGED
|
@@ -27,8 +27,8 @@ Or install it yourself as:
|
|
|
27
27
|
```ruby
|
|
28
28
|
require 'bls'
|
|
29
29
|
|
|
30
|
-
# Generate private key.
|
|
31
|
-
private_key = SecureRandom.random_number(BLS::Curve::R - 1)
|
|
30
|
+
# Generate private key: an integer in 1..(R - 1). Zero is not a key, so shift the range up.
|
|
31
|
+
private_key = 1 + SecureRandom.random_number(BLS::Curve::R - 1)
|
|
32
32
|
|
|
33
33
|
# Or you can use hex string.
|
|
34
34
|
private_key = '67d53f170b908cabb9eb326c3c337762d59289a8fec79f7bc9254b584b73265c'
|
|
@@ -44,7 +44,7 @@ message = '64726e3da8'
|
|
|
44
44
|
signature = BLS.sign(message, private_key)
|
|
45
45
|
# signature is BLS::PointG2 object. You can get signature with hex format using #to_hex method.
|
|
46
46
|
# If you want to use BLS::PointG1 signature, use BLS.sign(message, p, sig_type: :g1)
|
|
47
|
-
signature.
|
|
47
|
+
signature.to_hex(compressed: true)
|
|
48
48
|
|
|
49
49
|
is_correct = BLS.verify(signature, message, public_key)
|
|
50
50
|
=> true
|
|
@@ -119,11 +119,13 @@ end
|
|
|
119
119
|
Passing the wrong type or the wrong number of coefficients is a mistake in the calling code
|
|
120
120
|
rather than a fact about a key, and raises `ArgumentError` as it would anywhere else in Ruby.
|
|
121
121
|
|
|
122
|
-
Verification
|
|
123
|
-
`BLS.fast_aggregate_verify` and `BLS.pop_verify` return `false` for
|
|
124
|
-
accept, including the point at infinity and an empty set of signers, so
|
|
125
|
-
turn into an exception. Deserializing
|
|
126
|
-
is where a malformed encoding is caught.
|
|
122
|
+
Verification does not raise over the keys and signatures it is given. `BLS.verify`,
|
|
123
|
+
`BLS.verify_batch`, `BLS.fast_aggregate_verify` and `BLS.pop_verify` return `false` for any
|
|
124
|
+
point they will not accept, including the point at infinity and an empty set of signers, so an
|
|
125
|
+
untrusted key or signature cannot turn into an exception. Deserializing it with `from_hex`
|
|
126
|
+
beforehand still raises, which is where a malformed encoding is caught. The message is
|
|
127
|
+
different: it is the hex of a hash the caller computed, and one that is not hex, or has an odd
|
|
128
|
+
number of digits, raises `BLS::PointError` from signing and verifying alike.
|
|
127
129
|
|
|
128
130
|
## Threads
|
|
129
131
|
|
|
@@ -164,4 +166,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
164
166
|
|
|
165
167
|
## Code of Conduct
|
|
166
168
|
|
|
167
|
-
Everyone interacting in the
|
|
169
|
+
Everyone interacting in the BLS12-381 project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/azuchi/bls12-381/blob/main/CODE_OF_CONDUCT.md).
|
data/bls12-381.gemspec
CHANGED
|
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.description = 'BLS12-381 implementation for Ruby.'
|
|
13
13
|
spec.homepage = 'https://github.com/azuchi/bls12-381'
|
|
14
14
|
spec.license = 'MIT'
|
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new('>=
|
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 3.3.0')
|
|
16
16
|
|
|
17
17
|
spec.metadata['homepage_uri'] = spec.homepage
|
|
18
18
|
spec.metadata['source_code_uri'] = spec.homepage
|
|
@@ -27,8 +27,7 @@ Gem::Specification.new do |spec|
|
|
|
27
27
|
spec.require_paths = ['lib']
|
|
28
28
|
|
|
29
29
|
# Uncomment to register a new dependency of your gem
|
|
30
|
-
spec.add_dependency "h2c", "
|
|
31
|
-
spec.add_dependency "bigdecimal"
|
|
30
|
+
spec.add_dependency "h2c", "~> 0.4.0"
|
|
32
31
|
|
|
33
32
|
spec.add_development_dependency 'bundler'
|
|
34
33
|
spec.add_development_dependency 'rake', '>= 12.3.3'
|
data/lib/bls/field.rb
CHANGED
|
@@ -45,7 +45,10 @@ module BLS
|
|
|
45
45
|
self.class.new(value**2)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
# @raise [ArgumentError] Occur when +n+ is negative. Divide by the positive power instead.
|
|
48
49
|
def pow(n)
|
|
50
|
+
raise ArgumentError, 'Exponent must not be negative.' if n.negative?
|
|
51
|
+
|
|
49
52
|
v = value.pow(n, self.class.const_get(:ORDER))
|
|
50
53
|
self.class.new(v)
|
|
51
54
|
end
|
|
@@ -130,6 +133,8 @@ module BLS
|
|
|
130
133
|
module FQP
|
|
131
134
|
|
|
132
135
|
def ==(other)
|
|
136
|
+
return false unless other.is_a?(self.class)
|
|
137
|
+
|
|
133
138
|
coeffs == other.coeffs
|
|
134
139
|
end
|
|
135
140
|
|
|
@@ -157,7 +162,11 @@ module BLS
|
|
|
157
162
|
self.class.new(coeffs.map(&:negate))
|
|
158
163
|
end
|
|
159
164
|
|
|
165
|
+
# @raise [ArgumentError] Occur when +n+ is negative, which the loop below would otherwise
|
|
166
|
+
# answer with ONE as though it had been zero.
|
|
160
167
|
def pow(n)
|
|
168
|
+
raise ArgumentError, 'Exponent must not be negative.' if n.negative?
|
|
169
|
+
|
|
161
170
|
one = self.class.const_get(:ONE)
|
|
162
171
|
return one if n.zero?
|
|
163
172
|
return self if n == 1
|
data/lib/bls/h2c.rb
CHANGED
|
@@ -3,18 +3,28 @@ module BLS
|
|
|
3
3
|
|
|
4
4
|
LENGTH = 64
|
|
5
5
|
|
|
6
|
+
# Longest tag expand_message_xmd can carry as is; its length has to fit in one byte.
|
|
7
|
+
MAX_DST_LENGTH = 255
|
|
8
|
+
# Prefix under which a longer tag is hashed down to size, from RFC 9380 section 5.3.3.
|
|
9
|
+
OVERSIZE_DST_PREFIX = 'H2C-OVERSIZE-DST-'
|
|
10
|
+
|
|
6
11
|
module_function
|
|
7
12
|
|
|
8
13
|
# @param [String] message hash value with hex format.
|
|
9
14
|
# @param [Integer] len_in_bytes length
|
|
10
15
|
# @param [String] dst Domain separation tag. Mixing tags across schemes breaks the
|
|
11
16
|
# domain separation the ciphersuites rely on, so it must always be passed explicitly.
|
|
17
|
+
# A tag longer than {MAX_DST_LENGTH} bytes is replaced by its hash, as RFC 9380 section
|
|
18
|
+
# 5.3.3 prescribes, so any length works and the result matches other implementations.
|
|
12
19
|
# @return [Array[Integer]] byte array.
|
|
13
20
|
# @raise BLS::Error
|
|
14
21
|
def expand_message_xmd(message, len_in_bytes, dst)
|
|
15
|
-
|
|
22
|
+
raise BLS::Error, 'Domain separation tag must not be empty.' if dst.empty? # RFC 9380 section 3.1
|
|
23
|
+
|
|
24
|
+
dst = Digest::SHA256.digest(OVERSIZE_DST_PREFIX + dst) if dst.bytesize > MAX_DST_LENGTH
|
|
25
|
+
b_in_bytes = SHA256_DIGEST_SIZE
|
|
16
26
|
r_in_bytes = b_in_bytes * 2
|
|
17
|
-
ell = (
|
|
27
|
+
ell = (len_in_bytes + b_in_bytes - 1) / b_in_bytes
|
|
18
28
|
raise BLS::Error, 'Invalid xmd length' if ell > 255
|
|
19
29
|
|
|
20
30
|
dst_prime = dst.bytes + BLS.i2osp(dst.bytesize, 1)
|
|
@@ -24,7 +34,10 @@ module BLS
|
|
|
24
34
|
payload = z_pad + [message].pack('H*').bytes + l_i_b_str + BLS.i2osp(0, 1) + dst_prime
|
|
25
35
|
b_0 = Digest::SHA256.digest(payload.pack('C*'))
|
|
26
36
|
b[0] = Digest::SHA256.digest((b_0.bytes + BLS.i2osp(1, 1) + dst_prime).pack('C*'))
|
|
27
|
-
(1..ell
|
|
37
|
+
# b[i] holds the RFC's b_(i + 1), so b_2 .. b_ell is ell - 1 more blocks. Running the
|
|
38
|
+
# loop to ell made one block too many: harmless after the truncation below, except that
|
|
39
|
+
# at ell == 255 its counter byte overflowed and the call failed at the RFC's own limit.
|
|
40
|
+
(1...ell).each do |i|
|
|
28
41
|
args = BLS.bin_xor(b_0, b[i - 1]).bytes + BLS.i2osp(i + 1, 1) + dst_prime
|
|
29
42
|
b[i] = Digest::SHA256.digest(args.pack('C*'))
|
|
30
43
|
end
|
|
@@ -92,7 +105,7 @@ module BLS
|
|
|
92
105
|
end
|
|
93
106
|
|
|
94
107
|
# 3-isogeny map from E' to E
|
|
95
|
-
#
|
|
108
|
+
# Takes and returns projective (x, y, z) coordinates.
|
|
96
109
|
def isogeny_map(x, y, z)
|
|
97
110
|
mapped = Array.new(4, Fp2::ZERO)
|
|
98
111
|
z_powers = [z, z**2, z**3]
|
data/lib/bls/pairing.rb
CHANGED
|
@@ -6,6 +6,9 @@ module BLS
|
|
|
6
6
|
# @param [Boolean] with_final_exp
|
|
7
7
|
# @return [BLS::Fp12]
|
|
8
8
|
# @raise [BLS::PairingError] Occur when p.zero? or q.zero?
|
|
9
|
+
# @raise [BLS::PointError] Occur when either point is off its curve or outside its
|
|
10
|
+
# prime-order subgroup. from_hex has already checked what it returns; this is for points
|
|
11
|
+
# built by hand, for which the pairing would otherwise be computed without complaint.
|
|
9
12
|
# @raise [ArgumentError]
|
|
10
13
|
def pairing(p, q, with_final_exp: true)
|
|
11
14
|
raise ArgumentError, 'p should be BLS::PointG1 object' unless p.is_a?(BLS::PointG1)
|
|
@@ -14,6 +17,8 @@ module BLS
|
|
|
14
17
|
|
|
15
18
|
p.validate!
|
|
16
19
|
q.validate!
|
|
20
|
+
p.validate_group!
|
|
21
|
+
q.validate_group!
|
|
17
22
|
looped = p.miller_loop(q)
|
|
18
23
|
with_final_exp ? looped.final_exponentiate : looped
|
|
19
24
|
end
|
data/lib/bls/point/g1.rb
CHANGED
|
@@ -11,7 +11,6 @@ module BLS
|
|
|
11
11
|
|
|
12
12
|
BASE = PointG1.new(Fp.new(Curve::G_X), Fp.new(Curve::G_Y), Fp::ONE)
|
|
13
13
|
ZERO = PointG1.new(Fp::ONE, Fp::ONE, Fp::ZERO)
|
|
14
|
-
MAX_BITS = Fp::MAX_BITS
|
|
15
14
|
|
|
16
15
|
# Parse PointG1 from form hex.
|
|
17
16
|
# @param [String] hex hex value of PointG1.
|
|
@@ -106,10 +105,30 @@ module BLS
|
|
|
106
105
|
def self.hash_to_curve(message, scheme: :basic, dst: nil)
|
|
107
106
|
validate_hex!(message)
|
|
108
107
|
|
|
109
|
-
h2c = ::H2C.get(::H2C::Suite::BLS12381G1_XMDSHA256_SSWU_RO_, dst
|
|
108
|
+
h2c = ::H2C.get(::H2C::Suite::BLS12381G1_XMDSHA256_SSWU_RO_, resolve_dst(dst, scheme))
|
|
110
109
|
p = h2c.digest([message].pack('H*'))
|
|
111
110
|
|
|
112
111
|
PointG1.new(Fp.new(p.x), Fp.new(p.y), Fp::ONE)
|
|
112
|
+
rescue ::H2C::Error => e
|
|
113
|
+
# Whatever the h2c gem objects to, callers rescue it the way they rescue the rest.
|
|
114
|
+
raise PointError, e.message
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# A primitive cube root of unity in Fp. (x, y) -> (BETA * x, y) is an endomorphism of E(Fp)
|
|
118
|
+
# that acts on G1 as multiplication by -z**2, z being the curve parameter. Of the two
|
|
119
|
+
# primitive roots, this is the one for which that holds; the other gives the inverse map.
|
|
120
|
+
ENDO_BETA = Fp.new(PSI2_C1).square
|
|
121
|
+
|
|
122
|
+
# Check whether this point belongs to G1, by the test of section 4 of
|
|
123
|
+
# https://eprint.iacr.org/2021/1130: a point of E(Fp) satisfies phi(P) == [-z**2]P exactly
|
|
124
|
+
# when it lies in G1. Two multiplications by the 64-bit z, with its six set bits, instead
|
|
125
|
+
# of one by the 255-bit r.
|
|
126
|
+
# @return [Boolean] true if this point is in G1.
|
|
127
|
+
def in_group?
|
|
128
|
+
return true if zero?
|
|
129
|
+
|
|
130
|
+
endo = PointG1.new(x * ENDO_BETA, y, z)
|
|
131
|
+
endo == multiply_unsafe(Curve::X).multiply_unsafe(Curve::X).negate
|
|
113
132
|
end
|
|
114
133
|
|
|
115
134
|
# Validate this point whether on curve over Fp.
|
data/lib/bls/point/g2.rb
CHANGED
|
@@ -11,7 +11,6 @@ module BLS
|
|
|
11
11
|
KEY_SIZE_COMPRESSED = 96
|
|
12
12
|
KEY_SIZE_UNCOMPRESSED = 192
|
|
13
13
|
|
|
14
|
-
MAX_BITS = Fp2::MAX_BITS
|
|
15
14
|
BASE = PointG2.new(Fp2.new(Curve::G2_X), Fp2.new(Curve::G2_Y), Fp2::ONE)
|
|
16
15
|
ZERO = PointG2.new(Fp2::ONE, Fp2::ONE, Fp2::ZERO)
|
|
17
16
|
|
|
@@ -45,6 +44,11 @@ module BLS
|
|
|
45
44
|
return ZERO if i_bit == POINT_INFINITY_FLAG
|
|
46
45
|
x1 = bytes[0...PUBLIC_KEY_LENGTH].unpack1('H*').to_i(16)
|
|
47
46
|
x0 = bytes[PUBLIC_KEY_LENGTH...(2 * PUBLIC_KEY_LENGTH)].unpack1('H*').to_i(16)
|
|
47
|
+
# Fp.new reduces modulo P, so without this check x + P would decode as the same
|
|
48
|
+
# point under a second encoding. G1 refuses these too.
|
|
49
|
+
unless x0 < Curve::P && x1 < Curve::P
|
|
50
|
+
raise PointError, 'Invalid point G2, coordinates must be less than the field order.'
|
|
51
|
+
end
|
|
48
52
|
x = Fp2.new([x0, x1])
|
|
49
53
|
right = x ** 3 + Fp2.new(Curve::B2)
|
|
50
54
|
y = right.sqrt
|
|
@@ -66,6 +70,9 @@ module BLS
|
|
|
66
70
|
x0 = bytes[PUBLIC_KEY_LENGTH...(2 * PUBLIC_KEY_LENGTH)].unpack1('H*').to_i(16)
|
|
67
71
|
y1 = bytes[(2 * PUBLIC_KEY_LENGTH)...(3 * PUBLIC_KEY_LENGTH)].unpack1('H*').to_i(16)
|
|
68
72
|
y0 = bytes[(3 * PUBLIC_KEY_LENGTH)..-1].unpack1('H*').to_i(16)
|
|
73
|
+
unless [x0, x1, y0, y1].all? { |c| c < Curve::P }
|
|
74
|
+
raise PointError, 'Invalid point G2, coordinates must be less than the field order.'
|
|
75
|
+
end
|
|
69
76
|
PointG2.new(Fp2.new([x0, x1]), Fp2.new([y0, y1]), Fp2::ONE)
|
|
70
77
|
else
|
|
71
78
|
raise PointError, 'Invalid point G2, compression flag does not match the encoded length.'
|
|
@@ -93,7 +100,7 @@ module BLS
|
|
|
93
100
|
def self.hash_to_curve(message, scheme: :basic, dst: nil)
|
|
94
101
|
validate_hex!(message)
|
|
95
102
|
|
|
96
|
-
u = BLS::H2C::G2.hash_to_field(message, dst
|
|
103
|
+
u = BLS::H2C::G2.hash_to_field(message, resolve_dst(dst, scheme))
|
|
97
104
|
q0 = PointG2.new(*BLS::H2C::G2.isogeny_map(*BLS::H2C::G2.map_to_curve_sswu(u[0])))
|
|
98
105
|
q1 = PointG2.new(*BLS::H2C::G2.isogeny_map(*BLS::H2C::G2.map_to_curve_sswu(u[1])))
|
|
99
106
|
r = q0 + q1
|
|
@@ -140,6 +147,20 @@ module BLS
|
|
|
140
147
|
to_hex(compressed: true)
|
|
141
148
|
end
|
|
142
149
|
|
|
150
|
+
# Check whether this point belongs to G2, by the test of section 4 of
|
|
151
|
+
# https://eprint.iacr.org/2021/1130: psi, the untwist-Frobenius-twist endomorphism that
|
|
152
|
+
# {clear_cofactor} also uses, acts on G2 as multiplication by the curve parameter z, and a
|
|
153
|
+
# point of E'(Fp2) satisfies psi(P) == [z]P exactly when it lies in G2. z is negative for
|
|
154
|
+
# BLS12-381 and Curve::X holds its magnitude, hence the negate. One multiplication by a
|
|
155
|
+
# 64-bit scalar with six set bits instead of one by the 255-bit r.
|
|
156
|
+
# @return [Boolean] true if this point is in G2.
|
|
157
|
+
def in_group?
|
|
158
|
+
return true if zero?
|
|
159
|
+
|
|
160
|
+
psi = from_affine_tuple(BLS.psi(*to_affine))
|
|
161
|
+
psi == multiply_unsafe(Curve::X).negate
|
|
162
|
+
end
|
|
163
|
+
|
|
143
164
|
def validate!
|
|
144
165
|
b = Fp2.new(Curve::B2)
|
|
145
166
|
return if zero?
|
data/lib/bls/point.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'bigdecimal'
|
|
4
3
|
require 'h2c'
|
|
5
4
|
|
|
6
5
|
module BLS
|
|
@@ -16,6 +15,11 @@ module BLS
|
|
|
16
15
|
# Abstract Point class that consist of projective coordinates.
|
|
17
16
|
class ProjectivePoint
|
|
18
17
|
|
|
18
|
+
# Bits in the group order r. Every point this library hands out lies in the subgroup of
|
|
19
|
+
# that order, where [n]P == [n mod r]P, so this, not the size of the base field, is what
|
|
20
|
+
# a scalar multiplication has to cover.
|
|
21
|
+
SCALAR_BITS = Curve::R.bit_length
|
|
22
|
+
|
|
19
23
|
attr_reader :x, :y, :z
|
|
20
24
|
attr_accessor :m_precomputes
|
|
21
25
|
|
|
@@ -40,6 +44,22 @@ module BLS
|
|
|
40
44
|
end
|
|
41
45
|
end
|
|
42
46
|
|
|
47
|
+
# The tag {hash_to_curve} will use: the +dst+ given, or the one +scheme+ names.
|
|
48
|
+
# An empty tag is refused here, under BLS::Error, for both groups alike. RFC 9380 section
|
|
49
|
+
# 3.1 forbids it, and left to the layers below it would either go through unnoticed (G2)
|
|
50
|
+
# or surface as the h2c gem's own exception (G1), outside this library's hierarchy.
|
|
51
|
+
# @param [String, nil] dst explicit domain separation tag, if any.
|
|
52
|
+
# @param [Symbol] scheme signature scheme to take the tag from otherwise.
|
|
53
|
+
# @return [String] domain separation tag.
|
|
54
|
+
# @raise [BLS::Error] Occur when the tag is empty or not a string, or the scheme unknown.
|
|
55
|
+
def self.resolve_dst(dst, scheme)
|
|
56
|
+
return self.dst(scheme) if dst.nil?
|
|
57
|
+
raise BLS::Error, 'Domain separation tag must be a string.' unless dst.is_a?(String)
|
|
58
|
+
raise BLS::Error, 'Domain separation tag must not be empty.' if dst.empty?
|
|
59
|
+
|
|
60
|
+
dst
|
|
61
|
+
end
|
|
62
|
+
|
|
43
63
|
# Check that +hex+ is a whole number of bytes written in hex, for {from_hex} and
|
|
44
64
|
# {hash_to_curve}, which both unpack their argument with pack('H*').
|
|
45
65
|
#
|
|
@@ -73,11 +93,13 @@ module BLS
|
|
|
73
93
|
self.class.new(x, y, z)
|
|
74
94
|
end
|
|
75
95
|
|
|
76
|
-
# Compare one point to another.
|
|
96
|
+
# Compare one point to another. A G1 point never equals a G2 point, nil, or anything
|
|
97
|
+
# else that is not a point of the same group, and says so with false rather than an
|
|
98
|
+
# exception, since Array#include?, uniq and the like call this on whatever they hold.
|
|
77
99
|
# @param [ProjectivePoint] other another point.
|
|
78
100
|
# @return [Boolean] whether same point or not.
|
|
79
101
|
def ==(other)
|
|
80
|
-
|
|
102
|
+
return false unless other.is_a?(self.class)
|
|
81
103
|
|
|
82
104
|
(x * other.z) == (other.x * z) && (y * other.z) == (other.y * z)
|
|
83
105
|
end
|
|
@@ -163,6 +185,9 @@ module BLS
|
|
|
163
185
|
# Being on the curve is not sufficient: E(Fp) and E'(Fp2) both contain points
|
|
164
186
|
# outside the order-r subgroup, and accepting them makes keys and signatures malleable.
|
|
165
187
|
# Since r**2 does not divide the group order, [r]P == O holds only for P in the subgroup.
|
|
188
|
+
# That is the definition and the reference the specs compare against; PointG1 and PointG2
|
|
189
|
+
# override it with an endomorphism check that reaches the same answer in a fraction of
|
|
190
|
+
# the time.
|
|
166
191
|
# @return [Boolean] true if this point is in the prime-order subgroup.
|
|
167
192
|
def in_group?
|
|
168
193
|
zero? || multiply_unsafe(Curve::R).zero?
|
|
@@ -217,24 +242,35 @@ module BLS
|
|
|
217
242
|
|
|
218
243
|
# Scalar multiplication using wNAF.
|
|
219
244
|
#
|
|
220
|
-
# This is NOT constant time, despite what {multiply_unsafe} implies by contrast.
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
# or the two are equal. Underneath, Ruby's
|
|
224
|
-
# in time that depends on their operands.
|
|
225
|
-
#
|
|
226
|
-
# .from_private_key reach this with the
|
|
245
|
+
# This is NOT constant time, despite what {multiply_unsafe} implies by contrast. The
|
|
246
|
+
# number of additions follows the number of non-zero windows in the scalar, the point
|
|
247
|
+
# each one adds is read at an index derived from the scalar, and the additions themselves
|
|
248
|
+
# branch on whether an operand is the identity or the two are equal. Underneath, Ruby's
|
|
249
|
+
# bignum arithmetic and the modulo in Fp both run in time that depends on their operands.
|
|
250
|
+
# Anything the scalar decides is therefore visible to something watching timing or cache
|
|
251
|
+
# behaviour, which matters here because #sign and .from_private_key reach this with the
|
|
252
|
+
# private key. See the README.
|
|
253
|
+
#
|
|
254
|
+
# noble, which this follows, adds a throwaway point on every zero window so that each
|
|
255
|
+
# window costs one addition whatever its digit. That buys nothing here, where the
|
|
256
|
+
# arithmetic underneath is not constant time to begin with, so the zero windows are
|
|
257
|
+
# skipped and the work saved instead.
|
|
227
258
|
def multiply(scalar)
|
|
228
259
|
n = scalar.is_a?(Field) ? scalar.value : scalar
|
|
229
260
|
raise PointError, 'Invalid scalar, expected positive integer' if n <= 0
|
|
230
|
-
raise PointError, "Scalar has more bits than maxBits, shouldn't happen" if n.bit_length > max_bits
|
|
231
261
|
|
|
232
|
-
wNAF
|
|
262
|
+
# Reduced modulo r so that wNAF has {SCALAR_BITS} bits to walk, whatever was passed in.
|
|
263
|
+
# The table used to be sized from the base field instead, 381 bits for G1 and 762 for
|
|
264
|
+
# G2, and every window past the scalar's top bit was a wasted addition.
|
|
265
|
+
n %= Curve::R
|
|
266
|
+
return zero if n.zero?
|
|
267
|
+
|
|
268
|
+
wNAF(n)
|
|
233
269
|
end
|
|
234
270
|
alias * multiply
|
|
235
271
|
|
|
236
272
|
def precomputes_window(w)
|
|
237
|
-
windows = (
|
|
273
|
+
windows = window_count(w)
|
|
238
274
|
window_size = 2**(w - 1)
|
|
239
275
|
points = []
|
|
240
276
|
p = self
|
|
@@ -250,8 +286,17 @@ module BLS
|
|
|
250
286
|
points
|
|
251
287
|
end
|
|
252
288
|
|
|
253
|
-
|
|
254
|
-
|
|
289
|
+
# Number of width-+w+ windows that {wNAF} walks and {precomputes_window} must cover.
|
|
290
|
+
#
|
|
291
|
+
# One bit more than the scalar has. A window whose digit comes out above 2**(w - 1) is
|
|
292
|
+
# recoded as a negative digit and carries 1 into the next window, and when the top
|
|
293
|
+
# window of the scalar does that, the carry needs a window of its own or it is dropped
|
|
294
|
+
# on the floor. With r's top five bits being 11100, a width of 5 hits this for about
|
|
295
|
+
# four scalars in ten.
|
|
296
|
+
# @param [Integer] w window width.
|
|
297
|
+
# @return [Integer] window count.
|
|
298
|
+
def window_count(w)
|
|
299
|
+
(SCALAR_BITS + w) / w
|
|
255
300
|
end
|
|
256
301
|
|
|
257
302
|
def normalize_z(points)
|
|
@@ -285,8 +330,7 @@ module BLS
|
|
|
285
330
|
def wNAF(n)
|
|
286
331
|
w, precomputes = m_precomputes || [1, precomputes_window(1)]
|
|
287
332
|
p = zero
|
|
288
|
-
|
|
289
|
-
windows = (BigDecimal(max_bits) / w).ceil
|
|
333
|
+
windows = window_count(w)
|
|
290
334
|
window_size = 2**(w - 1)
|
|
291
335
|
mask = (2**w - 1)
|
|
292
336
|
max_number = 2**w
|
|
@@ -299,27 +343,17 @@ module BLS
|
|
|
299
343
|
wbits -= max_number
|
|
300
344
|
n += 1
|
|
301
345
|
end
|
|
302
|
-
if wbits.zero?
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
p += (wbits.negative? ? cached.negate : cached)
|
|
307
|
-
end
|
|
346
|
+
next if wbits.zero?
|
|
347
|
+
|
|
348
|
+
cached = precomputes[offset + wbits.abs - 1]
|
|
349
|
+
p += (wbits.negative? ? cached.negate : cached)
|
|
308
350
|
end
|
|
309
|
-
|
|
351
|
+
p
|
|
310
352
|
end
|
|
311
353
|
end
|
|
312
354
|
|
|
313
355
|
module_function
|
|
314
356
|
|
|
315
|
-
def norm_p1(point)
|
|
316
|
-
point.is_a?(PointG1) ? point : PointG1.from_hex(point)
|
|
317
|
-
end
|
|
318
|
-
|
|
319
|
-
def norm_p2(point)
|
|
320
|
-
point.is_a?(PointG2) ? point : PointG2.from_hex(point)
|
|
321
|
-
end
|
|
322
|
-
|
|
323
357
|
def norm_p1h(point, scheme: :basic)
|
|
324
358
|
point.is_a?(PointG1) ? point : PointG1.hash_to_curve(point, scheme: scheme)
|
|
325
359
|
end
|
data/lib/bls/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bls12-381
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shigeyuki Azuchi
|
|
@@ -13,36 +13,16 @@ dependencies:
|
|
|
13
13
|
name: h2c
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.2.1
|
|
19
|
-
- - "<"
|
|
16
|
+
- - "~>"
|
|
20
17
|
- !ruby/object:Gem::Version
|
|
21
|
-
version:
|
|
18
|
+
version: 0.4.0
|
|
22
19
|
type: :runtime
|
|
23
20
|
prerelease: false
|
|
24
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
25
22
|
requirements:
|
|
26
|
-
- - "
|
|
27
|
-
- !ruby/object:Gem::Version
|
|
28
|
-
version: 0.2.1
|
|
29
|
-
- - "<"
|
|
30
|
-
- !ruby/object:Gem::Version
|
|
31
|
-
version: '0.3'
|
|
32
|
-
- !ruby/object:Gem::Dependency
|
|
33
|
-
name: bigdecimal
|
|
34
|
-
requirement: !ruby/object:Gem::Requirement
|
|
35
|
-
requirements:
|
|
36
|
-
- - ">="
|
|
23
|
+
- - "~>"
|
|
37
24
|
- !ruby/object:Gem::Version
|
|
38
|
-
version:
|
|
39
|
-
type: :runtime
|
|
40
|
-
prerelease: false
|
|
41
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
-
requirements:
|
|
43
|
-
- - ">="
|
|
44
|
-
- !ruby/object:Gem::Version
|
|
45
|
-
version: '0'
|
|
25
|
+
version: 0.4.0
|
|
46
26
|
- !ruby/object:Gem::Dependency
|
|
47
27
|
name: bundler
|
|
48
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -115,7 +95,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
115
95
|
requirements:
|
|
116
96
|
- - ">="
|
|
117
97
|
- !ruby/object:Gem::Version
|
|
118
|
-
version:
|
|
98
|
+
version: 3.3.0
|
|
119
99
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
120
100
|
requirements:
|
|
121
101
|
- - ">="
|