bls12-381 0.3.1 → 0.4.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/README.md +95 -7
- data/lib/bls/error.rb +18 -0
- data/lib/bls/field.rb +23 -2
- data/lib/bls/h2c.rb +7 -4
- data/lib/bls/math.rb +24 -4
- data/lib/bls/pairing.rb +15 -2
- data/lib/bls/point/g1.rb +46 -18
- data/lib/bls/point/g2.rb +28 -5
- data/lib/bls/point.rb +88 -11
- data/lib/bls/version.rb +1 -1
- data/lib/bls.rb +94 -24
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1398ff4737e7cc35aee61178bb064235e4f5765ec18a69511681c9389c1384a5
|
|
4
|
+
data.tar.gz: f6f386f4681e0cb3a3c642a0d5fa2e061fc9c96b4034c019cdf0e510020e059a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7589e44db5a83ae84615cfbad287d7c36c3eb223186e3b0552755ef36ceff516b8fcf40a1f11fec534344d48db49f4277be8fdbb82eb16e5f972854334732818
|
|
7
|
+
data.tar.gz: b0d5fb49ba941dec4fe7e3eb892cc08036e10505bf6cc6e01f429cf3874219092aaffb530bd54e96ba48c7ce01aa59cd4774871c81d05b72282e76f3a78757be
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@ This library is a Ruby BLS12-381 implementation based on the JavaScript implemen
|
|
|
4
4
|
In addition to that, it is possible to switch between public key and signature group (G1 and G2).
|
|
5
5
|
|
|
6
6
|
Note: This library has passed the same tests as noble-bls12-381, but has not been audited to prove its safety.
|
|
7
|
-
Please be careful when using this.
|
|
7
|
+
Please be careful when using this, and read [Side channels](#side-channels) before signing with it.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
@@ -49,18 +49,12 @@ signature.to_signature
|
|
|
49
49
|
is_correct = BLS.verify(signature, message, public_key)
|
|
50
50
|
=> true
|
|
51
51
|
|
|
52
|
-
# Sign 1 msg with 3 keys
|
|
53
52
|
private_keys = [
|
|
54
53
|
'18f020b98eb798752a50ed0563b079c125b0db5dd0b1060d1c1b47d4a193e1e4',
|
|
55
54
|
'ed69a8c50cf8c9836be3b67c7eeff416612d45ba39a5c099d48fa668bf558c9c',
|
|
56
55
|
'16ae669f3be7a2121e17d0c68c05a8f3d6bef21ec0f2315f1d7aec12484e4cf5'
|
|
57
56
|
]
|
|
58
57
|
public_keys = private_keys.map { |p| BLS.get_public_key(p) }
|
|
59
|
-
signatures2 = private_keys.map { |p| BLS.sign(message, p) }
|
|
60
|
-
agg_public_keys2 = BLS.aggregate_public_keys(public_keys)
|
|
61
|
-
agg_signatures2 = BLS.aggregate_signatures(signatures2)
|
|
62
|
-
is_correct2 = BLS.verify(agg_signatures2, message, agg_public_keys2)
|
|
63
|
-
=> true
|
|
64
58
|
|
|
65
59
|
# Sign 3 msgs with 3 keys
|
|
66
60
|
messages = %w[d2 0d98 05caf3]
|
|
@@ -68,8 +62,102 @@ signatures3 = private_keys.map.with_index { |p, i| BLS.sign(messages[i], p)}
|
|
|
68
62
|
agg_signatures3 = BLS.aggregate_signatures(signatures3)
|
|
69
63
|
is_correct3 = BLS.verify_batch(agg_signatures3, messages, public_keys)
|
|
70
64
|
=> true
|
|
65
|
+
|
|
66
|
+
# Sign 1 msg with 3 keys.
|
|
67
|
+
# This needs the proof of possession scheme: see the warning below.
|
|
68
|
+
proofs = private_keys.map { |p| BLS.pop_prove(p) }
|
|
69
|
+
public_keys.zip(proofs).each { |pubkey, proof| raise 'invalid key' unless BLS.pop_verify(pubkey, proof) }
|
|
70
|
+
|
|
71
|
+
signatures2 = private_keys.map { |p| BLS.sign(message, p, scheme: :pop) }
|
|
72
|
+
agg_signatures2 = BLS.aggregate_signatures(signatures2)
|
|
73
|
+
is_correct2 = BLS.fast_aggregate_verify(agg_signatures2, message, public_keys)
|
|
74
|
+
=> true
|
|
71
75
|
```
|
|
72
76
|
|
|
77
|
+
## Signature schemes
|
|
78
|
+
|
|
79
|
+
This library implements two of the schemes of
|
|
80
|
+
[draft-irtf-cfrg-bls-signature](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05),
|
|
81
|
+
selected with the `scheme:` keyword of `BLS.sign` / `BLS.verify` / `BLS.verify_batch`.
|
|
82
|
+
They use different domain separation tags, so a signature made under one never verifies
|
|
83
|
+
under the other. The default is `:basic`, which is what earlier versions of this gem used.
|
|
84
|
+
|
|
85
|
+
| scheme | aggregation |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| `:basic` (default) | `BLS.verify_batch`, **only when every message is distinct** |
|
|
88
|
+
| `:pop` | the above with no such restriction, plus `BLS.fast_aggregate_verify` for many signatures over a single message |
|
|
89
|
+
|
|
90
|
+
`BLS.verify_batch` returns `false` for a `:basic` aggregate that covers the same message
|
|
91
|
+
twice, however well formed the signature is. Pass `scheme: :pop` to verify those.
|
|
92
|
+
|
|
93
|
+
**Aggregating public keys is unsafe without proofs of possession.** Given a public key
|
|
94
|
+
`pk_victim`, an attacker can register `pk_attacker = g * x - pk_victim` for an `x` they
|
|
95
|
+
choose. The two keys aggregate to `g * x`, so the attacker alone can produce a signature
|
|
96
|
+
that verifies against the aggregate, making it look like the victim signed. This is the
|
|
97
|
+
rogue key attack, and it is why `:basic` supports no single-message aggregation at all.
|
|
98
|
+
|
|
99
|
+
`BLS.pop_prove` closes the hole by signing a key with itself: producing a proof requires
|
|
100
|
+
the private key, which the attacker does not have for `pk_attacker`. Call `BLS.pop_verify`
|
|
101
|
+
on every public key **before** passing it to `BLS.aggregate_public_keys` or
|
|
102
|
+
`BLS.fast_aggregate_verify` — neither of them can do it for you, since they never see the
|
|
103
|
+
proofs.
|
|
104
|
+
|
|
105
|
+
## Errors
|
|
106
|
+
|
|
107
|
+
Everything this library raises about the data it was handed descends from `BLS::Error`, so
|
|
108
|
+
one rescue covers a key that will not parse, a point outside the subgroup, a private key that
|
|
109
|
+
is not hex, and a pairing that is not defined:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
begin
|
|
113
|
+
public_key = BLS::PointG1.from_hex(untrusted)
|
|
114
|
+
rescue BLS::Error => e # BLS::PointError here, and BLS::PairingError elsewhere
|
|
115
|
+
...
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Passing the wrong type or the wrong number of coefficients is a mistake in the calling code
|
|
120
|
+
rather than a fact about a key, and raises `ArgumentError` as it would anywhere else in Ruby.
|
|
121
|
+
|
|
122
|
+
Verification never raises over the data it is given. `BLS.verify`, `BLS.verify_batch`,
|
|
123
|
+
`BLS.fast_aggregate_verify` and `BLS.pop_verify` return `false` for anything they will not
|
|
124
|
+
accept, including the point at infinity and an empty set of signers, so untrusted input cannot
|
|
125
|
+
turn into an exception. Deserializing that input with `from_hex` beforehand still raises, which
|
|
126
|
+
is where a malformed encoding is caught.
|
|
127
|
+
|
|
128
|
+
## Threads
|
|
129
|
+
|
|
130
|
+
Signing and verifying from several threads gives the right answers, but two caches are
|
|
131
|
+
written without a lock and neither is worth sharing a point over.
|
|
132
|
+
|
|
133
|
+
`calc_multiply_precomputes` builds its table over long enough that MRI switches threads part
|
|
134
|
+
way through, so callers racing on one point all pass the "already has precomputes" check and
|
|
135
|
+
each build their own. They build the same table, so the result stays correct; what you lose is
|
|
136
|
+
the time and the memory of doing it several times over, and a guard that reads as protection
|
|
137
|
+
while providing none. Build the table once, before the point goes anywhere else.
|
|
138
|
+
|
|
139
|
+
`BLS.verify` memoises pairing coefficients onto the G2 point it is given, and onto
|
|
140
|
+
`BLS::PointG2::BASE` when the public key is a G2 point. Racing callers recompute rather than
|
|
141
|
+
corrupt, for the same reason: what is assigned is always a finished array.
|
|
142
|
+
|
|
143
|
+
## Side channels
|
|
144
|
+
|
|
145
|
+
**Nothing here is constant time, and signing is not safe against an attacker who can
|
|
146
|
+
measure it.** `BLS.sign` and `BLS.get_public_key` multiply a curve point by the private
|
|
147
|
+
key, and that multiplication leaks:
|
|
148
|
+
|
|
149
|
+
* the scalar picks which precomputed point each window adds, so it decides which memory
|
|
150
|
+
gets touched, which is visible through the cache;
|
|
151
|
+
* point addition returns early when an operand is the identity or the two are equal;
|
|
152
|
+
* underneath it all, Ruby's bignum arithmetic and the `%` in `BLS::Fp` take time that
|
|
153
|
+
depends on their operands.
|
|
154
|
+
|
|
155
|
+
A pure Ruby implementation cannot fix this: it has no control over how the interpreter
|
|
156
|
+
lays out or times its integers. Treat this gem as suitable where the private key operation
|
|
157
|
+
cannot be observed — verification of untrusted input is fine, since it touches no secret —
|
|
158
|
+
and use a binding to a constant time library such as [blst](https://github.com/supranational/blst)
|
|
159
|
+
where an attacker can watch the signer.
|
|
160
|
+
|
|
73
161
|
## License
|
|
74
162
|
|
|
75
163
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/lib/bls/error.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BLS
|
|
4
|
+
|
|
5
|
+
# Base class for everything this library raises about the data it was given, so that
|
|
6
|
+
# `rescue BLS::Error` catches all of it.
|
|
7
|
+
#
|
|
8
|
+
# A wrong type or arity is a mistake in the calling code rather than a fact about a key or a
|
|
9
|
+
# point, and still raises ArgumentError, as it would anywhere else in Ruby.
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
|
|
12
|
+
# A point that could not be parsed, is not on the curve, or is not in the prime-order
|
|
13
|
+
# subgroup.
|
|
14
|
+
class PointError < Error; end
|
|
15
|
+
|
|
16
|
+
# A pairing that was asked for where it is not defined.
|
|
17
|
+
class PairingError < Error; end
|
|
18
|
+
end
|
data/lib/bls/field.rb
CHANGED
|
@@ -13,7 +13,15 @@ module BLS
|
|
|
13
13
|
self.class.new(-value)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
# Multiplicative inverse.
|
|
17
|
+
# Zero has none, and the extended Euclid below returns 0 for it rather than failing, which
|
|
18
|
+
# is a wrong answer that propagates: ProjectivePoint#to_affine inverts z, so the point at
|
|
19
|
+
# infinity would come back as the affine coordinates (0, 0) instead of being refused.
|
|
20
|
+
# @return [BLS::Field] the inverse.
|
|
21
|
+
# @raise [BLS::Error] Occur when this element is zero.
|
|
16
22
|
def invert
|
|
23
|
+
raise BLS::Error, 'Zero has no multiplicative inverse.' if zero?
|
|
24
|
+
|
|
17
25
|
x0 = 1
|
|
18
26
|
x1 = 0
|
|
19
27
|
y0 = 0
|
|
@@ -248,7 +256,15 @@ module BLS
|
|
|
248
256
|
Fp2.new([a * b, c * c1])
|
|
249
257
|
end
|
|
250
258
|
|
|
259
|
+
# Square root, or nil when this element is not a square.
|
|
260
|
+
# Zero is answered up front. It is a square, its root being itself, and the division below
|
|
261
|
+
# would otherwise be asked for the inverse of zero. That used to yield zero and land here
|
|
262
|
+
# as a nil, which reads as "not a square" and is wrong for the one element that has
|
|
263
|
+
# exactly one root.
|
|
264
|
+
# @return [BLS::Fp2, nil]
|
|
251
265
|
def sqrt
|
|
266
|
+
return self if zero?
|
|
267
|
+
|
|
252
268
|
candidate = pow((Fp2::ORDER + 8) / 16)
|
|
253
269
|
check = candidate.square / self
|
|
254
270
|
r = ROOTS_OF_UNITY
|
|
@@ -700,12 +716,17 @@ module BLS
|
|
|
700
716
|
|
|
701
717
|
|
|
702
718
|
|
|
719
|
+
# sgn0 for GF(p^2), from RFC 9380 section 4.1.
|
|
720
|
+
# Combined with | and & rather than || and &&: sign_0 is 0 or 1, and 0 is truthy in Ruby,
|
|
721
|
+
# so a disjunction would always short circuit on it and x_1 would never be reached.
|
|
722
|
+
# @param [BLS::Fp2] x
|
|
723
|
+
# @return [Integer] 0 or 1.
|
|
703
724
|
def sgn0(x)
|
|
704
725
|
x0, x1 = x.values
|
|
705
726
|
sign_0 = x0 % 2
|
|
706
|
-
zero_0 = x0
|
|
727
|
+
zero_0 = x0.zero? ? 1 : 0
|
|
707
728
|
sign_1 = x1 % 2
|
|
708
|
-
sign_0
|
|
729
|
+
sign_0 | (zero_0 & sign_1)
|
|
709
730
|
end
|
|
710
731
|
|
|
711
732
|
def sqrt_div_fp2(u, v)
|
data/lib/bls/h2c.rb
CHANGED
|
@@ -7,15 +7,17 @@ module BLS
|
|
|
7
7
|
|
|
8
8
|
# @param [String] message hash value with hex format.
|
|
9
9
|
# @param [Integer] len_in_bytes length
|
|
10
|
+
# @param [String] dst Domain separation tag. Mixing tags across schemes breaks the
|
|
11
|
+
# domain separation the ciphersuites rely on, so it must always be passed explicitly.
|
|
10
12
|
# @return [Array[Integer]] byte array.
|
|
11
13
|
# @raise BLS::Error
|
|
12
|
-
def expand_message_xmd(message, len_in_bytes)
|
|
14
|
+
def expand_message_xmd(message, len_in_bytes, dst)
|
|
13
15
|
b_in_bytes = BigDecimal(SHA256_DIGEST_SIZE)
|
|
14
16
|
r_in_bytes = b_in_bytes * 2
|
|
15
17
|
ell = (BigDecimal(len_in_bytes) / b_in_bytes).ceil
|
|
16
18
|
raise BLS::Error, 'Invalid xmd length' if ell > 255
|
|
17
19
|
|
|
18
|
-
dst_prime =
|
|
20
|
+
dst_prime = dst.bytes + BLS.i2osp(dst.bytesize, 1)
|
|
19
21
|
z_pad = BLS.i2osp(0, r_in_bytes)
|
|
20
22
|
l_i_b_str = BLS.i2osp(len_in_bytes, 2)
|
|
21
23
|
b = Array.new(ell)
|
|
@@ -34,12 +36,13 @@ module BLS
|
|
|
34
36
|
|
|
35
37
|
# Convert hash to Field.
|
|
36
38
|
# @param [String] message hash value with hex format.
|
|
39
|
+
# @param [String] dst Domain separation tag of the ciphersuite in use.
|
|
37
40
|
# @return [Array[Integer]] byte array.
|
|
38
|
-
def hash_to_field(message, random_oracle: true)
|
|
41
|
+
def hash_to_field(message, dst = PointG2::DST_BASIC, random_oracle: true)
|
|
39
42
|
degree = 2
|
|
40
43
|
count = random_oracle ? 2 : 1
|
|
41
44
|
len_in_bytes = count * degree * LENGTH
|
|
42
|
-
pseudo_random_bytes = BLS::H2C.expand_message_xmd(message, len_in_bytes)
|
|
45
|
+
pseudo_random_bytes = BLS::H2C.expand_message_xmd(message, len_in_bytes, dst)
|
|
43
46
|
u = Array.new(count)
|
|
44
47
|
count.times do |i|
|
|
45
48
|
e = Array.new(degree)
|
data/lib/bls/math.rb
CHANGED
|
@@ -66,13 +66,33 @@ module BLS
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
# Normalize private key.
|
|
69
|
+
# A key at or above the group order is reduced rather than rejected, matching the reference
|
|
70
|
+
# implementation and the test vectors. Reducing raw entropy that way is slightly biased, so
|
|
71
|
+
# prefer drawing the key from 0 < k < Fr::ORDER to begin with.
|
|
69
72
|
# @param [String|Integer] private_key a private key with hex or number.
|
|
70
73
|
# @return [BLS::Fr] Normalized private key.
|
|
71
|
-
# @raise [BLS::Error] Occur when the private key is
|
|
74
|
+
# @raise [BLS::Error] Occur when the private key is not a positive hex string or integer,
|
|
75
|
+
# or when it reduces to zero.
|
|
72
76
|
def normalize_priv_key(private_key)
|
|
73
|
-
k =
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
k = case private_key
|
|
78
|
+
when String
|
|
79
|
+
# String#to_i stops at the first character it cannot read and returns what it has,
|
|
80
|
+
# so a mistyped or truncated key would otherwise turn into a different, much
|
|
81
|
+
# smaller one without any sign that it happened.
|
|
82
|
+
raise BLS::Error, 'Private key must be a hex string.' unless private_key.match?(/\A[0-9a-fA-F]+\z/)
|
|
83
|
+
|
|
84
|
+
private_key.to_i(16)
|
|
85
|
+
when Integer
|
|
86
|
+
private_key
|
|
87
|
+
else
|
|
88
|
+
raise BLS::Error, 'Private key must be Integer or String.'
|
|
89
|
+
end
|
|
90
|
+
raise BLS::Error, 'Private key must be positive.' unless k.positive?
|
|
91
|
+
|
|
92
|
+
key = Fr.new(k)
|
|
93
|
+
raise BLS::Error, "Private key must not be a multiple of #{Fr::ORDER}." if key.zero?
|
|
94
|
+
|
|
95
|
+
key
|
|
76
96
|
end
|
|
77
97
|
|
|
78
98
|
# Convert number to +byte_length+ bytes hex string.
|
data/lib/bls/pairing.rb
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
module BLS
|
|
2
2
|
module_function
|
|
3
3
|
|
|
4
|
-
class PairingError < StandardError; end
|
|
5
|
-
|
|
6
4
|
# @param [BLS::PointG1] p
|
|
7
5
|
# @param [BLS::PointG2] q
|
|
8
6
|
# @param [Boolean] with_final_exp
|
|
@@ -20,4 +18,19 @@ module BLS
|
|
|
20
18
|
with_final_exp ? looped.final_exponentiate : looped
|
|
21
19
|
end
|
|
22
20
|
|
|
21
|
+
# Pairing without the final exponentiation, using e(O, Q) = e(P, O) = 1 where {pairing}
|
|
22
|
+
# raises instead. Verification multiplies several of these together, so a degenerate key
|
|
23
|
+
# or signature has to fall out as a failed check rather than as an exception.
|
|
24
|
+
# @param [BLS::PointG1] p
|
|
25
|
+
# @param [BLS::PointG2] q
|
|
26
|
+
# @return [BLS::Fp12]
|
|
27
|
+
# @raise [ArgumentError]
|
|
28
|
+
def partial_pairing(p, q)
|
|
29
|
+
raise ArgumentError, 'p should be BLS::PointG1 object' unless p.is_a?(BLS::PointG1)
|
|
30
|
+
raise ArgumentError, 'q should be BLS::PointG2 object' unless q.is_a?(BLS::PointG2)
|
|
31
|
+
return Fp12::ONE if p.zero? || q.zero?
|
|
32
|
+
|
|
33
|
+
pairing(p, q, with_final_exp: false)
|
|
34
|
+
end
|
|
35
|
+
|
|
23
36
|
end
|
data/lib/bls/point/g1.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
module BLS
|
|
2
2
|
class PointG1 < BLS::ProjectivePoint
|
|
3
3
|
|
|
4
|
+
# Ciphersuite IDs of draft-irtf-cfrg-bls-signature section 4.3.
|
|
4
5
|
DST_BASIC = 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_'
|
|
6
|
+
DST_POP = 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_POP_'
|
|
7
|
+
DST_POP_PROOF = 'BLS_POP_BLS12381G1_XMD:SHA-256_SSWU_RO_POP_'
|
|
5
8
|
|
|
6
9
|
KEY_SIZE_COMPRESSED = 48
|
|
7
10
|
KEY_SIZE_UNCOMPRESSED = 96
|
|
@@ -13,33 +16,55 @@ module BLS
|
|
|
13
16
|
# Parse PointG1 from form hex.
|
|
14
17
|
# @param [String] hex hex value of PointG1.
|
|
15
18
|
# @return [PointG1]
|
|
16
|
-
# @raise [BLS::PointError] Occurs when hex length does not match, or point
|
|
19
|
+
# @raise [BLS::PointError] Occurs when hex length does not match, or point is not on G1.
|
|
17
20
|
def self.from_hex(hex)
|
|
21
|
+
validate_hex!(hex)
|
|
18
22
|
bytes = [hex].pack('H*')
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
unless [KEY_SIZE_COMPRESSED, KEY_SIZE_UNCOMPRESSED].include?(bytes.bytesize)
|
|
24
|
+
raise PointError, 'Invalid point G1, expected 48 or 96 bytes.'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
m_byte = bytes[0].unpack1('C') & 0xe0
|
|
28
|
+
if [0x20, 0x60, 0xe0].include?(m_byte)
|
|
29
|
+
raise PointError, "Invalid encoding flag: #{m_byte.to_s(16)}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
c_bit = m_byte & POINT_COMPRESSION_FLAG # compression flag
|
|
33
|
+
i_bit = m_byte & POINT_INFINITY_FLAG # infinity flag
|
|
34
|
+
s_bit = m_byte & POINT_Y_FLAG # y coordinate sign flag
|
|
35
|
+
bytes[0] = [bytes[0].unpack1('C') & 0x1f].pack('C') # set flag to 0
|
|
36
|
+
|
|
37
|
+
if i_bit == POINT_INFINITY_FLAG && bytes.unpack1('H*').to_i(16) > 0
|
|
38
|
+
raise PointError, 'Invalid point, infinity point should be all 0.'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
point = if bytes.bytesize == KEY_SIZE_COMPRESSED && c_bit == POINT_COMPRESSION_FLAG # compressed format
|
|
42
|
+
return ZERO if i_bit == POINT_INFINITY_FLAG
|
|
43
|
+
|
|
44
|
+
x = bytes.unpack1('H*').to_i(16)
|
|
45
|
+
raise PointError, 'Invalid point G1, x must be less than the field order.' unless x < Curve::P
|
|
46
|
+
|
|
47
|
+
full_y = BLS.mod(x**3 + Curve::B, Curve::P)
|
|
27
48
|
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
|
|
49
|
+
raise PointError, 'The given point is not on G1: y**2 = x**3 + b.' unless BLS.pow_mod(y, 2, Curve::P) == full_y
|
|
29
50
|
|
|
30
|
-
|
|
31
|
-
y = Curve::P - y unless ((y * 2) / Curve::P) == a_flag
|
|
51
|
+
y = Curve::P - y unless ((y * 2) / Curve::P) == (s_bit.zero? ? 0 : 1)
|
|
32
52
|
PointG1.new(Fp.new(x), Fp.new(y), Fp::ONE)
|
|
33
|
-
|
|
34
|
-
return ZERO
|
|
53
|
+
elsif bytes.bytesize == KEY_SIZE_UNCOMPRESSED && c_bit != POINT_COMPRESSION_FLAG # uncompressed format
|
|
54
|
+
return ZERO if i_bit == POINT_INFINITY_FLAG
|
|
35
55
|
|
|
36
56
|
x = bytes[0...PUBLIC_KEY_LENGTH].unpack1('H*').to_i(16)
|
|
37
57
|
y = bytes[PUBLIC_KEY_LENGTH..-1].unpack1('H*').to_i(16)
|
|
58
|
+
unless x < Curve::P && y < Curve::P
|
|
59
|
+
raise PointError, 'Invalid point G1, coordinates must be less than the field order.'
|
|
60
|
+
end
|
|
61
|
+
|
|
38
62
|
PointG1.new(Fp.new(x), Fp.new(y), Fp::ONE)
|
|
39
63
|
else
|
|
40
|
-
raise PointError, 'Invalid point G1,
|
|
64
|
+
raise PointError, 'Invalid point G1, compression flag does not match the encoded length.'
|
|
41
65
|
end
|
|
42
66
|
point.validate!
|
|
67
|
+
point.validate_group!
|
|
43
68
|
point
|
|
44
69
|
end
|
|
45
70
|
|
|
@@ -73,12 +98,15 @@ module BLS
|
|
|
73
98
|
|
|
74
99
|
# Convert hash to PointG1
|
|
75
100
|
# @param [String] message a hash with hex format.
|
|
101
|
+
# @param [Symbol] scheme signature scheme whose domain separation tag is used, :basic or :pop.
|
|
102
|
+
# @param [String] dst a domain separation tag to use instead of the scheme's, for a
|
|
103
|
+
# ciphersuite this library does not name, such as the ones the RFC 9380 vectors use.
|
|
76
104
|
# @return [BLS::PointG1] point.
|
|
77
105
|
# @raise [BLS::PointError]
|
|
78
|
-
def self.hash_to_curve(message)
|
|
79
|
-
|
|
106
|
+
def self.hash_to_curve(message, scheme: :basic, dst: nil)
|
|
107
|
+
validate_hex!(message)
|
|
80
108
|
|
|
81
|
-
h2c = ::H2C.get(::H2C::Suite::BLS12381G1_XMDSHA256_SSWU_RO_,
|
|
109
|
+
h2c = ::H2C.get(::H2C::Suite::BLS12381G1_XMDSHA256_SSWU_RO_, dst || self.dst(scheme))
|
|
82
110
|
p = h2c.digest([message].pack('H*'))
|
|
83
111
|
|
|
84
112
|
PointG1.new(Fp.new(p.x), Fp.new(p.y), Fp::ONE)
|
data/lib/bls/point/g2.rb
CHANGED
|
@@ -3,7 +3,10 @@ module BLS
|
|
|
3
3
|
|
|
4
4
|
attr_accessor :precomputes
|
|
5
5
|
|
|
6
|
+
# Ciphersuite IDs of draft-irtf-cfrg-bls-signature section 4.2.
|
|
6
7
|
DST_BASIC = 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_'
|
|
8
|
+
DST_POP = 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_'
|
|
9
|
+
DST_POP_PROOF = 'BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_'
|
|
7
10
|
|
|
8
11
|
KEY_SIZE_COMPRESSED = 96
|
|
9
12
|
KEY_SIZE_UNCOMPRESSED = 192
|
|
@@ -18,7 +21,12 @@ module BLS
|
|
|
18
21
|
# @return [BLS::PointG2] PointG2.
|
|
19
22
|
# @raise [BLS::PointError]
|
|
20
23
|
def self.from_hex(hex)
|
|
24
|
+
validate_hex!(hex)
|
|
21
25
|
bytes = [hex].pack('H*')
|
|
26
|
+
unless [KEY_SIZE_COMPRESSED, KEY_SIZE_UNCOMPRESSED].include?(bytes.bytesize)
|
|
27
|
+
raise PointError, 'Invalid point G2, expected 96/192 bytes.'
|
|
28
|
+
end
|
|
29
|
+
|
|
22
30
|
m_byte = bytes[0].unpack1('C')& 0xe0
|
|
23
31
|
if [0x20, 0x60, 0xe0].include?(m_byte)
|
|
24
32
|
raise PointError, "Invalid encoding flag: #{m_byte.to_s(16)}"
|
|
@@ -46,7 +54,11 @@ module BLS
|
|
|
46
54
|
else
|
|
47
55
|
(y.coeffs[1].value * 2) / Curve::P == 1 ? 1 : 0
|
|
48
56
|
end
|
|
49
|
-
|
|
57
|
+
# Flip to whichever root the encoding asked for. Written as a comparison, the
|
|
58
|
+
# way G1 does it, rather than as `s_bit > 0 && bit_y > 0 ? y : y.negate`:
|
|
59
|
+
# that form is only right because sqrt returns the root of sign 1, which it
|
|
60
|
+
# does not promise, and it would negate a root of sign 0 that wanted sign 0.
|
|
61
|
+
y = y.negate unless bit_y == (s_bit.zero? ? 0 : 1)
|
|
50
62
|
PointG2.new(x, y, Fp2::ONE)
|
|
51
63
|
elsif bytes.bytesize == KEY_SIZE_UNCOMPRESSED && c_bit != POINT_COMPRESSION_FLAG # uncompressed format
|
|
52
64
|
return ZERO if i_bit == POINT_INFINITY_FLAG
|
|
@@ -56,9 +68,10 @@ module BLS
|
|
|
56
68
|
y0 = bytes[(3 * PUBLIC_KEY_LENGTH)..-1].unpack1('H*').to_i(16)
|
|
57
69
|
PointG2.new(Fp2.new([x0, x1]), Fp2.new([y0, y1]), Fp2::ONE)
|
|
58
70
|
else
|
|
59
|
-
raise PointError, 'Invalid point G2,
|
|
71
|
+
raise PointError, 'Invalid point G2, compression flag does not match the encoded length.'
|
|
60
72
|
end
|
|
61
73
|
point.validate!
|
|
74
|
+
point.validate_group!
|
|
62
75
|
point
|
|
63
76
|
end
|
|
64
77
|
|
|
@@ -72,12 +85,15 @@ module BLS
|
|
|
72
85
|
|
|
73
86
|
# Convert hash to PointG2
|
|
74
87
|
# @param [String] message a hash with hex format.
|
|
88
|
+
# @param [Symbol] scheme signature scheme whose domain separation tag is used, :basic or :pop.
|
|
89
|
+
# @param [String] dst a domain separation tag to use instead of the scheme's, for a
|
|
90
|
+
# ciphersuite this library does not name, such as the ones the RFC 9380 vectors use.
|
|
75
91
|
# @return [BLS::PointG2] point.
|
|
76
92
|
# @raise [BLS::PointError]
|
|
77
|
-
def self.hash_to_curve(message)
|
|
78
|
-
|
|
93
|
+
def self.hash_to_curve(message, scheme: :basic, dst: nil)
|
|
94
|
+
validate_hex!(message)
|
|
79
95
|
|
|
80
|
-
u = BLS::H2C::G2.hash_to_field(message)
|
|
96
|
+
u = BLS::H2C::G2.hash_to_field(message, dst || self.dst(scheme))
|
|
81
97
|
q0 = PointG2.new(*BLS::H2C::G2.isogeny_map(*BLS::H2C::G2.map_to_curve_sswu(u[0])))
|
|
82
98
|
q1 = PointG2.new(*BLS::H2C::G2.isogeny_map(*BLS::H2C::G2.map_to_curve_sswu(u[1])))
|
|
83
99
|
r = q0 + q1
|
|
@@ -137,6 +153,13 @@ module BLS
|
|
|
137
153
|
self.precomputes = nil
|
|
138
154
|
end
|
|
139
155
|
|
|
156
|
+
# The line coefficients {BLS.miller_loop} needs, worked out once and kept on the point.
|
|
157
|
+
#
|
|
158
|
+
# This memoises without a lock, so concurrent callers can each compute it. They compute
|
|
159
|
+
# the same coefficients and assign a finished array, so a reader sees one or the other and
|
|
160
|
+
# never a partial one; the cost is the repeated work. BLS.verify reaches this on
|
|
161
|
+
# PointG2::BASE, which is shared, whenever the public key is a G2 point.
|
|
162
|
+
# @return [Array]
|
|
140
163
|
def pairing_precomputes
|
|
141
164
|
return precomputes if precomputes
|
|
142
165
|
|
data/lib/bls/point.rb
CHANGED
|
@@ -13,8 +13,6 @@ module BLS
|
|
|
13
13
|
autoload :PointG1, "bls/point/g1"
|
|
14
14
|
autoload :PointG2, "bls/point/g2"
|
|
15
15
|
|
|
16
|
-
class PointError < StandardError; end
|
|
17
|
-
|
|
18
16
|
# Abstract Point class that consist of projective coordinates.
|
|
19
17
|
class ProjectivePoint
|
|
20
18
|
|
|
@@ -28,6 +26,40 @@ module BLS
|
|
|
28
26
|
@m_precomputes = nil
|
|
29
27
|
end
|
|
30
28
|
|
|
29
|
+
# Domain separation tag this point class uses for +scheme+.
|
|
30
|
+
# @param [Symbol] scheme :basic, :pop, or :pop_proof(the tag proofs of possession
|
|
31
|
+
# are signed under, kept separate from :pop so a proof cannot pass as a signature).
|
|
32
|
+
# @return [String] domain separation tag.
|
|
33
|
+
# @raise [BLS::Error] Occur when the scheme is unknown.
|
|
34
|
+
def self.dst(scheme)
|
|
35
|
+
case scheme
|
|
36
|
+
when :basic then const_get(:DST_BASIC)
|
|
37
|
+
when :pop then const_get(:DST_POP)
|
|
38
|
+
when :pop_proof then const_get(:DST_POP_PROOF)
|
|
39
|
+
else raise BLS::Error, "Unknown scheme: #{scheme.inspect}. Must be :basic or :pop."
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Check that +hex+ is a whole number of bytes written in hex, for {from_hex} and
|
|
44
|
+
# {hash_to_curve}, which both unpack their argument with pack('H*').
|
|
45
|
+
#
|
|
46
|
+
# pack maps a character to a nibble by its low bits, so it reads '3', '#', 'J', 'Z', 'j'
|
|
47
|
+
# and 'z' all as 3 and never complains, and it pads an odd number of digits out to a whole
|
|
48
|
+
# byte. Left alone, that gives every point and every message a large family of spellings
|
|
49
|
+
# that all arrive at the same bytes. Applications tend to carry keys around as hex and
|
|
50
|
+
# compare, deduplicate and index them that way, so the aliases matter as much here as the
|
|
51
|
+
# non-canonical encodings do a layer down.
|
|
52
|
+
#
|
|
53
|
+
# \A..\z rather than ^..$ because those match at line boundaries in Ruby, which would let
|
|
54
|
+
# a newline carry a non-hex tail past the check.
|
|
55
|
+
# @param [String] hex a byte string in hex format.
|
|
56
|
+
# @raise [PointError] Occur when it is not an even length string of hex digits.
|
|
57
|
+
def self.validate_hex!(hex)
|
|
58
|
+
return if hex.is_a?(String) && hex.match?(/\A(?:[0-9a-fA-F]{2})*\z/)
|
|
59
|
+
|
|
60
|
+
raise PointError, 'expected hex string'
|
|
61
|
+
end
|
|
62
|
+
|
|
31
63
|
def zero?
|
|
32
64
|
z.zero?
|
|
33
65
|
end
|
|
@@ -109,6 +141,10 @@ module BLS
|
|
|
109
141
|
end
|
|
110
142
|
alias - subtract
|
|
111
143
|
|
|
144
|
+
# Scalar multiplication by plain double-and-add, which skips the addition entirely on a
|
|
145
|
+
# zero bit and so takes a number of operations that tracks the scalar's Hamming weight.
|
|
146
|
+
# Only for scalars an observer already knows, such as the curve parameters. {multiply}
|
|
147
|
+
# hides that particular pattern, but is not constant time either.
|
|
112
148
|
def multiply_unsafe(scalar)
|
|
113
149
|
n = scalar.is_a?(Field) ? scalar.value : scalar
|
|
114
150
|
raise PointError, 'Point#multiply: invalid scalar, expected positive integer' if n <= 0
|
|
@@ -123,11 +159,30 @@ module BLS
|
|
|
123
159
|
p
|
|
124
160
|
end
|
|
125
161
|
|
|
162
|
+
# Check whether this point belongs to the prime-order subgroup (G1 or G2).
|
|
163
|
+
# Being on the curve is not sufficient: E(Fp) and E'(Fp2) both contain points
|
|
164
|
+
# outside the order-r subgroup, and accepting them makes keys and signatures malleable.
|
|
165
|
+
# Since r**2 does not divide the group order, [r]P == O holds only for P in the subgroup.
|
|
166
|
+
# @return [Boolean] true if this point is in the prime-order subgroup.
|
|
167
|
+
def in_group?
|
|
168
|
+
zero? || multiply_unsafe(Curve::R).zero?
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Validate that this point belongs to the prime-order subgroup.
|
|
172
|
+
# @raise [PointError] Occur when this point is not in the prime-order subgroup.
|
|
173
|
+
def validate_group!
|
|
174
|
+
raise PointError, 'Invalid point: not in prime-order subgroup' unless in_group?
|
|
175
|
+
end
|
|
176
|
+
|
|
126
177
|
def to_affine(inv_z = z.invert)
|
|
127
178
|
[x * inv_z, y * inv_z]
|
|
128
179
|
end
|
|
129
180
|
|
|
181
|
+
# @raise [PointError] Occur when any of +points+ is the point at infinity, which has no
|
|
182
|
+
# affine representation.
|
|
130
183
|
def to_affine_batch(points)
|
|
184
|
+
raise PointError, 'The point at infinity has no affine representation.' if points.any?(&:zero?)
|
|
185
|
+
|
|
131
186
|
to_inv = gen_invert_batch(points.map(&:z))
|
|
132
187
|
points.map.with_index { |p, i| p.to_affine(to_inv[i]) }
|
|
133
188
|
end
|
|
@@ -136,21 +191,23 @@ module BLS
|
|
|
136
191
|
new_point(xy[0], xy[1], x.class.const_get(:ONE))
|
|
137
192
|
end
|
|
138
193
|
|
|
194
|
+
# Inverts a whole array for the price of one inversion plus a few multiplications each.
|
|
195
|
+
# Zero is refused rather than skipped: leaving it in place would hand the caller a zero
|
|
196
|
+
# where it asked for an inverse, and Field#invert refuses it for the same reason.
|
|
197
|
+
# @raise [BLS::Error] Occur when any element is zero.
|
|
139
198
|
def gen_invert_batch(nums)
|
|
199
|
+
raise BLS::Error, 'Zero has no multiplicative inverse.' if nums.any?(&:zero?)
|
|
200
|
+
|
|
140
201
|
len = nums.length
|
|
141
202
|
scratch = Array.new(len)
|
|
142
203
|
acc = x.class::ONE
|
|
143
204
|
len.times do |i|
|
|
144
|
-
next if nums[i].zero?
|
|
145
|
-
|
|
146
205
|
scratch[i] = acc
|
|
147
206
|
acc *= nums[i]
|
|
148
207
|
end
|
|
149
208
|
acc = acc.invert
|
|
150
209
|
len.times do |t|
|
|
151
210
|
i = len - t - 1
|
|
152
|
-
next if nums[i].zero?
|
|
153
|
-
|
|
154
211
|
tmp = acc * nums[i]
|
|
155
212
|
nums[i] = acc * scratch[i]
|
|
156
213
|
acc = tmp
|
|
@@ -158,7 +215,15 @@ module BLS
|
|
|
158
215
|
nums
|
|
159
216
|
end
|
|
160
217
|
|
|
161
|
-
#
|
|
218
|
+
# Scalar multiplication using wNAF.
|
|
219
|
+
#
|
|
220
|
+
# This is NOT constant time, despite what {multiply_unsafe} implies by contrast. wNAF
|
|
221
|
+
# gives every window an addition, but the point it adds is read at an index derived from
|
|
222
|
+
# the scalar, and the additions themselves branch on whether an operand is the identity
|
|
223
|
+
# or the two are equal. Underneath, Ruby's bignum arithmetic and the modulo in Fp both run
|
|
224
|
+
# in time that depends on their operands. Anything the scalar decides is therefore visible
|
|
225
|
+
# to something watching timing or cache behaviour, which matters here because #sign and
|
|
226
|
+
# .from_private_key reach this with the private key. See the README.
|
|
162
227
|
def multiply(scalar)
|
|
163
228
|
n = scalar.is_a?(Field) ? scalar.value : scalar
|
|
164
229
|
raise PointError, 'Invalid scalar, expected positive integer' if n <= 0
|
|
@@ -193,6 +258,18 @@ module BLS
|
|
|
193
258
|
to_affine_batch(points).map{ |p| from_affine_tuple(p) }
|
|
194
259
|
end
|
|
195
260
|
|
|
261
|
+
# Build the window table that {multiply} then uses, trading memory for speed on a point
|
|
262
|
+
# that will be multiplied repeatedly, such as a generator.
|
|
263
|
+
#
|
|
264
|
+
# Call this once, before the point is shared between threads. The check below is not a
|
|
265
|
+
# lock: the table takes long enough to build that MRI will switch threads part way
|
|
266
|
+
# through, so several callers can pass the check together and each build their own, each
|
|
267
|
+
# paying the time and the memory. What they build is identical, so whichever assignment
|
|
268
|
+
# lands last is still correct and no half-built table is ever visible; the cost is the
|
|
269
|
+
# duplicated work, and a guard that reads as protection while providing none.
|
|
270
|
+
#
|
|
271
|
+
# @param [Integer] w window width.
|
|
272
|
+
# @raise [PointError] Occur when this point already has precomputes.
|
|
196
273
|
def calc_multiply_precomputes(w)
|
|
197
274
|
raise PointError, 'This point already has precomputes.' if m_precomputes
|
|
198
275
|
|
|
@@ -243,12 +320,12 @@ module BLS
|
|
|
243
320
|
point.is_a?(PointG2) ? point : PointG2.from_hex(point)
|
|
244
321
|
end
|
|
245
322
|
|
|
246
|
-
def norm_p1h(point)
|
|
247
|
-
point.is_a?(PointG1) ? point : PointG1.hash_to_curve(point)
|
|
323
|
+
def norm_p1h(point, scheme: :basic)
|
|
324
|
+
point.is_a?(PointG1) ? point : PointG1.hash_to_curve(point, scheme: scheme)
|
|
248
325
|
end
|
|
249
326
|
|
|
250
|
-
def norm_p2h(point)
|
|
251
|
-
point.is_a?(PointG2) ? point : PointG2.hash_to_curve(point)
|
|
327
|
+
def norm_p2h(point, scheme: :basic)
|
|
328
|
+
point.is_a?(PointG2) ? point : PointG2.hash_to_curve(point, scheme: scheme)
|
|
252
329
|
end
|
|
253
330
|
|
|
254
331
|
end
|
data/lib/bls/version.rb
CHANGED
data/lib/bls.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'digest'
|
|
4
4
|
require 'bls/version'
|
|
5
|
+
require 'bls/error' # defines the exception classes the files below raise
|
|
5
6
|
require 'bls/math'
|
|
6
7
|
require 'bls/curve'
|
|
7
8
|
require 'bls/field'
|
|
@@ -11,8 +12,6 @@ require 'bls/pairing'
|
|
|
11
12
|
|
|
12
13
|
module BLS
|
|
13
14
|
|
|
14
|
-
class Error < StandardError; end
|
|
15
|
-
|
|
16
15
|
POW_2_381 = 2**381
|
|
17
16
|
POW_2_382 = POW_2_381 * 2
|
|
18
17
|
POW_2_383 = POW_2_382 * 2
|
|
@@ -28,13 +27,15 @@ module BLS
|
|
|
28
27
|
# @param [Symbol] sig_type Signature type, :g1 or :g2.
|
|
29
28
|
# If :g1 is specified, the signature is a point on G1 and the public key is a point on G2.
|
|
30
29
|
# If :g2 is specified, the signature is a point on G2 and the public key is a point on G1.
|
|
30
|
+
# @param [Symbol] scheme Signature scheme, :basic or :pop. Signatures made under one scheme
|
|
31
|
+
# do not verify under the other, so signer and verifier must agree on it.
|
|
31
32
|
# @return [PointG2] The signature point.
|
|
32
|
-
def sign(message, private_key, sig_type: :g2)
|
|
33
|
+
def sign(message, private_key, sig_type: :g2, scheme: :basic)
|
|
33
34
|
msg_point = case sig_type
|
|
34
35
|
when :g1
|
|
35
|
-
BLS.norm_p1h(message)
|
|
36
|
+
BLS.norm_p1h(message, scheme: scheme)
|
|
36
37
|
when :g2
|
|
37
|
-
BLS.norm_p2h(message)
|
|
38
|
+
BLS.norm_p2h(message, scheme: scheme)
|
|
38
39
|
else
|
|
39
40
|
raise Error, 'sig_type must be :g1 or :g2.'
|
|
40
41
|
end
|
|
@@ -62,30 +63,42 @@ module BLS
|
|
|
62
63
|
# @param [BLS::PointG1|BLS::PointG2] signature
|
|
63
64
|
# @param [String] message Message digest(hash value with hex format) to be verified.
|
|
64
65
|
# @param [BLS::PointG2|BLS::PointG1] public_key Public key with hex format or PointG1.
|
|
66
|
+
# @param [Symbol] scheme Signature scheme the signature was made under, :basic or :pop.
|
|
65
67
|
# @return [Boolean] verification result.
|
|
66
|
-
def verify(signature, message, public_key)
|
|
68
|
+
def verify(signature, message, public_key, scheme: :basic)
|
|
67
69
|
unless signature.is_a?(PointG1) && public_key.is_a?(PointG2) ||
|
|
68
70
|
signature.is_a?(PointG2) && public_key.is_a?(PointG1)
|
|
69
71
|
raise BLS::Error, 'Invalid signature or public key. If the public key is PointG1, the signature must be an element of Point::G2 or vice versa.'
|
|
70
72
|
end
|
|
73
|
+
# KeyValidate of draft-irtf-cfrg-bls-signature section 2.5. The identity is not a public
|
|
74
|
+
# key anybody holds, and pairing it away would leave the identity signature verifying
|
|
75
|
+
# against every message.
|
|
76
|
+
return false if public_key.zero?
|
|
77
|
+
|
|
71
78
|
g = public_key.is_a?(PointG1) ? PointG1::BASE : PointG2::BASE
|
|
72
79
|
ephm = if public_key.is_a?(PointG1)
|
|
73
|
-
hm = BLS.norm_p2h(message)
|
|
74
|
-
BLS.
|
|
80
|
+
hm = BLS.norm_p2h(message, scheme: scheme)
|
|
81
|
+
BLS.partial_pairing(public_key.negate, hm)
|
|
75
82
|
else
|
|
76
|
-
hm = BLS.norm_p1h(message)
|
|
77
|
-
BLS.
|
|
83
|
+
hm = BLS.norm_p1h(message, scheme: scheme)
|
|
84
|
+
BLS.partial_pairing(hm, public_key.negate)
|
|
78
85
|
end
|
|
79
86
|
egs = if public_key.is_a?(PointG1)
|
|
80
|
-
BLS.
|
|
87
|
+
BLS.partial_pairing(g, signature)
|
|
81
88
|
else
|
|
82
|
-
BLS.
|
|
89
|
+
BLS.partial_pairing(signature, g)
|
|
83
90
|
end
|
|
84
91
|
exp = (egs * ephm).final_exponentiate
|
|
85
92
|
exp == Fp12::ONE
|
|
86
93
|
end
|
|
87
94
|
|
|
88
95
|
# Aggregate multiple public keys.
|
|
96
|
+
#
|
|
97
|
+
# WARNING: the aggregate is only meaningful once every input key has been checked with
|
|
98
|
+
# {pop_verify}. Without that check an attacker who publishes pk_a = g * x - pk_victim
|
|
99
|
+
# can produce, on their own, a signature that verifies against the aggregate, making it
|
|
100
|
+
# look like the victim signed. See {fast_aggregate_verify}.
|
|
101
|
+
#
|
|
89
102
|
# @param [Array[BLS::PointG1]|Array[BLS::PointG2]] public_keys the list of public keys.
|
|
90
103
|
# @return [BLS::PointG1|BLS::PointG2] aggregated public key.
|
|
91
104
|
def aggregate_public_keys(public_keys)
|
|
@@ -120,13 +133,20 @@ module BLS
|
|
|
120
133
|
end
|
|
121
134
|
|
|
122
135
|
# Verify aggregated signature.
|
|
136
|
+
#
|
|
137
|
+
# Under the :basic scheme the messages must all be distinct: repeating one is reported as
|
|
138
|
+
# an invalid signature, since that scheme has nothing but message distinctness to stop a
|
|
139
|
+
# rogue key from signing on another key's behalf. Use :pop, whose proofs of possession rule
|
|
140
|
+
# that out, to verify several signatures over the same message.
|
|
141
|
+
#
|
|
123
142
|
# @param [BLS::PointG2|BLS::PointG1] signature aggregated signature(BLS::PointG2 or BLS::PointG1).
|
|
124
143
|
# @param [Array[String]] messages the list of message.
|
|
125
144
|
# @param [Array[BLS::PointG1]|Array[BLS::PointG2]] public_keys the list of public keys(BLS::PointG1 or BLS::PointG2).
|
|
145
|
+
# @param [Symbol] scheme Signature scheme the signatures were made under, :basic or :pop.
|
|
126
146
|
# @return [Boolean] verification result.
|
|
127
|
-
def verify_batch(signature, messages, public_keys)
|
|
128
|
-
raise BLS::Error, 'Expected non-empty array.' if messages.empty?
|
|
147
|
+
def verify_batch(signature, messages, public_keys, scheme: :basic)
|
|
129
148
|
raise BLS::Error, 'Public keys count should equal msg count.' unless messages.size == public_keys.size
|
|
149
|
+
return false if messages.empty? # nothing was signed, so nothing is verified
|
|
130
150
|
|
|
131
151
|
sig_g2_flag = signature.is_a?(PointG2)
|
|
132
152
|
public_keys.each do |public_key|
|
|
@@ -134,20 +154,70 @@ module BLS
|
|
|
134
154
|
raise BLS::Error, "Public key must be #{sig_g2_flag ? 'PointG1' : 'PointG2'}"
|
|
135
155
|
end
|
|
136
156
|
end
|
|
157
|
+
return false if public_keys.any?(&:zero?) # KeyValidate, as in #verify
|
|
158
|
+
|
|
159
|
+
n_message = messages.map { |m| sig_g2_flag ? BLS.norm_p2h(m, scheme: scheme) : BLS.norm_p1h(m, scheme: scheme)}
|
|
137
160
|
|
|
138
|
-
|
|
139
|
-
|
|
161
|
+
# Keys that signed the same message are summed so that message is paired exactly once:
|
|
162
|
+
# e(P1, Q) * e(P2, Q) == e(P1 + P2, Q). Grouping on the serialized point rather than on
|
|
163
|
+
# the message keeps this independent of how equal messages happened to be spelled.
|
|
140
164
|
zero = sig_g2_flag ? PointG1::ZERO : PointG2::ZERO
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
165
|
+
grouped = {}
|
|
166
|
+
n_message.each_with_index do |message, i|
|
|
167
|
+
group = (grouped[message.to_hex(compressed: true)] ||= [message, zero])
|
|
168
|
+
group[1] += public_keys[i]
|
|
169
|
+
end
|
|
170
|
+
return false if scheme == :basic && grouped.size < n_message.size
|
|
171
|
+
|
|
172
|
+
paired = grouped.each_value.map do |message, group_pubkey|
|
|
173
|
+
sig_g2_flag ? BLS.partial_pairing(group_pubkey, message) :
|
|
174
|
+
BLS.partial_pairing(message, group_pubkey)
|
|
147
175
|
end
|
|
148
|
-
paired << (sig_g2_flag ? BLS.
|
|
149
|
-
BLS.
|
|
176
|
+
paired << (sig_g2_flag ? BLS.partial_pairing(PointG1::BASE.negate, signature) :
|
|
177
|
+
BLS.partial_pairing(signature, PointG2::BASE.negate))
|
|
150
178
|
product = paired.inject(Fp12::ONE) { |a, b| a * b }
|
|
151
179
|
product.final_exponentiate == Fp12::ONE
|
|
152
180
|
end
|
|
181
|
+
|
|
182
|
+
# Generate a proof of possession for +private_key+: a signature, under that key, over the
|
|
183
|
+
# public key it belongs to. Producing one requires knowing the private key, which is what
|
|
184
|
+
# makes aggregation safe against rogue key attacks.
|
|
185
|
+
# @param [Integer|String] private_key The private key. Integer or String(hex).
|
|
186
|
+
# @param [Symbol] key_type Public key type, :g1 or :g2. The proof lives in the other group.
|
|
187
|
+
# @return [BLS::PointG2|BLS::PointG1] proof of possession.
|
|
188
|
+
def pop_prove(private_key, key_type: :g1)
|
|
189
|
+
public_key = get_public_key(private_key, key_type: key_type)
|
|
190
|
+
msg = public_key.to_hex(compressed: true)
|
|
191
|
+
msg_point = public_key.is_a?(PointG1) ? BLS.norm_p2h(msg, scheme: :pop_proof) :
|
|
192
|
+
BLS.norm_p1h(msg, scheme: :pop_proof)
|
|
193
|
+
msg_point * BLS.normalize_priv_key(private_key)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Verify a proof of possession produced by {pop_prove}.
|
|
197
|
+
# @param [BLS::PointG1|BLS::PointG2] public_key the public key being proven.
|
|
198
|
+
# @param [BLS::PointG2|BLS::PointG1] proof proof of possession.
|
|
199
|
+
# @return [Boolean] verification result.
|
|
200
|
+
def pop_verify(public_key, proof)
|
|
201
|
+
verify(proof, public_key.to_hex(compressed: true), public_key, scheme: :pop_proof)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Verify an aggregated signature over a single message signed by every key in +public_keys+.
|
|
205
|
+
#
|
|
206
|
+
# WARNING: every public key must have passed {pop_verify} first. This check cannot be done
|
|
207
|
+
# here because it needs each key's proof, and skipping it reopens the rogue key attack that
|
|
208
|
+
# the pop scheme exists to prevent.
|
|
209
|
+
#
|
|
210
|
+
# @param [BLS::PointG2|BLS::PointG1] signature aggregated signature.
|
|
211
|
+
# @param [String] message Message digest(hash value with hex format) to be verified.
|
|
212
|
+
# @param [Array[BLS::PointG1]|Array[BLS::PointG2]] public_keys the list of public keys.
|
|
213
|
+
# @return [Boolean] verification result.
|
|
214
|
+
def fast_aggregate_verify(signature, message, public_keys)
|
|
215
|
+
return false if public_keys.empty? # nothing signed, so nothing is verified
|
|
216
|
+
# KeyValidate every key, not just the aggregate. The identity adds nothing to the sum, so
|
|
217
|
+
# a set containing it would otherwise verify on the strength of the others while counting
|
|
218
|
+
# a key that nobody holds, and could hold, as one of the signers.
|
|
219
|
+
return false if public_keys.any?(&:zero?)
|
|
220
|
+
|
|
221
|
+
verify(signature, message, aggregate_public_keys(public_keys), scheme: :pop)
|
|
222
|
+
end
|
|
153
223
|
end
|
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.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shigeyuki Azuchi
|
|
@@ -93,6 +93,7 @@ files:
|
|
|
93
93
|
- bls12-381.gemspec
|
|
94
94
|
- lib/bls.rb
|
|
95
95
|
- lib/bls/curve.rb
|
|
96
|
+
- lib/bls/error.rb
|
|
96
97
|
- lib/bls/field.rb
|
|
97
98
|
- lib/bls/h2c.rb
|
|
98
99
|
- lib/bls/math.rb
|