Module: BLS
- Defined in:
- lib/bls.rb,
lib/bls/h2c.rb,
lib/bls/math.rb,
lib/bls/curve.rb,
lib/bls/error.rb,
lib/bls/field.rb,
lib/bls/point.rb,
lib/bls/pairing.rb,
lib/bls/version.rb,
lib/bls/point/g1.rb,
lib/bls/point/g2.rb
Defined Under Namespace
Modules: Curve, FQP, Field, H2C Classes: Error, Fp, Fp12, Fp2, Fp6, Fr, PairingError, PointError, PointG1, PointG2, ProjectivePoint
Constant Summary collapse
- POW_2_381 =
2**381
- POW_2_382 =
POW_2_381 * 2
- POW_2_383 =
POW_2_382 * 2
- PUBLIC_KEY_LENGTH =
48- SHA256_DIGEST_SIZE =
32- UT_ROOT =
BLS::Fp6.new([BLS::Fp2::ZERO, BLS::Fp2::ONE, BLS::Fp2::ZERO])
- WSQ =
BLS::Fp12.new([UT_ROOT, BLS::Fp6::ZERO])
- WSQ_INV =
WSQ.invert
- WCU =
BLS::Fp12.new([BLS::Fp6::ZERO, UT_ROOT])
- WCU_INV =
WCU.invert
- PSI2_C1 =
1 / F2(2)^((p - 1) / 3) in GF(p^2)
0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaac- BLS_X_LEN =
Curve::X.bit_length
- P_MINUS_9_DIV_16 =
(Curve::P**2 - 9) / 16
- XNUM =
[ Fp2.new([ 0x5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97d6, 0x5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97d6]), Fp2.new([ 0x0, 0x11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71a]), Fp2.new([ 0x11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71e, 0x8ab05f8bdd54cde190937e76bc3e447cc27c3d6fbd7063fcd104635a790520c0a395554e5c6aaaa9354ffffffffe38d]), Fp2.new([ 0x171d6541fa38ccfaed6dea691f5fb614cb14b4e7f4e810aa22d6108f142b85757098e38d0f671c7188e2aaaaaaaa5ed1, 0x0]) ].freeze
- XDEN =
[ Fp2.new([ 0x0, 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa63]), Fp2.new([ 0xc, 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa9f]), Fp2::ONE, Fp2::ZERO ].freeze
- YNUM =
[ Fp2.new([ 0x1530477c7ab4113b59a4c18b076d11930f7da5d4a07f649bf54439d87d27e500fc8c25ebf8c92f6812cfc71c71c6d706, 0x1530477c7ab4113b59a4c18b076d11930f7da5d4a07f649bf54439d87d27e500fc8c25ebf8c92f6812cfc71c71c6d706]), Fp2.new([ 0x0, 0x5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97be]), Fp2.new([ 0x11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71c, 0x8ab05f8bdd54cde190937e76bc3e447cc27c3d6fbd7063fcd104635a790520c0a395554e5c6aaaa9354ffffffffe38f]), Fp2.new([ 0x124c9ad43b6cf79bfbf7043de3811ad0761b0f37a1e26286b0e977c69aa274524e79097a56dc4bd9e1b371c71c718b10, 0x0]) ].freeze
- YDEN =
[ Fp2.new([ 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa8fb, 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa8fb]), Fp2.new([ 0x0, 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa9d3]), Fp2.new([ 0x12, 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa99]), Fp2.new([0x1, 0x0]) ].freeze
- ISOGENY_COEFFICIENTS =
[XNUM, XDEN, YNUM, YDEN]
- POINT_COMPRESSION_FLAG =
Point serialization flags
0x80- POINT_INFINITY_FLAG =
0x40- POINT_Y_FLAG =
0x20- VERSION =
'0.4.0'
Class Method Summary collapse
-
.aggregate_public_keys(public_keys) ⇒ BLS::PointG1|BLS::PointG2
Aggregate multiple public keys.
-
.aggregate_signatures(signatures) ⇒ BLS::PointG2|BLS::PointG1
Aggregate multiple signatures.
-
.bin_xor(a, b) ⇒ String
Calculate binary xor between
aandb. - .bit_get(n, pos) ⇒ Object
-
.fast_aggregate_verify(signature, message, public_keys) ⇒ Boolean
Verify an aggregated signature over a single message signed by every key in
public_keys. -
.get_public_key(private_key, key_type: :g1) ⇒ BLS::PointG1|BLS::PointG2
Generate public key from
private_key. -
.i2osp(value, length) ⇒ Array[Integer] byte array.
Convert
valueto byte array oflength. - .miller_loop(ell, g1) ⇒ Object
- .mod(a, b) ⇒ Object
- .norm_p1(point) ⇒ Object
- .norm_p1h(point, scheme: :basic) ⇒ Object
- .norm_p2(point) ⇒ Object
- .norm_p2h(point, scheme: :basic) ⇒ Object
-
.normalize_priv_key(private_key) ⇒ BLS::Fr
Normalize private key.
-
.num_to_hex(num, byte_length) ⇒ String
Convert number to
byte_lengthbytes hex string. -
.os2ip(bytes) ⇒ Integer
Convert byte to non-negative integer.
- .pairing(p, q, with_final_exp: true) ⇒ BLS::Fp12
-
.partial_pairing(p, q) ⇒ BLS::Fp12
Pairing without the final exponentiation, using e(O, Q) = e(P, O) = 1 where BLS.pairing raises instead.
-
.pop_prove(private_key, key_type: :g1) ⇒ BLS::PointG2|BLS::PointG1
Generate a proof of possession for
private_key: a signature, under that key, over the public key it belongs to. -
.pop_verify(public_key, proof) ⇒ Boolean
Verify a proof of possession produced by BLS.pop_prove.
- .pow_mod(a, power, m) ⇒ Object
- .psi(x, y) ⇒ Object
- .psi2(x, y) ⇒ Object
-
.sgn0(x) ⇒ Integer
sgn0 for GF(p^2), from RFC 9380 section 4.1.
-
.sign(message, private_key, sig_type: :g2, scheme: :basic) ⇒ PointG2
Generate BLS signature: s = pk x H(m) If :g1 is specified, the signature is a point on G1 and the public key is a point on G2.
- .sqrt_div_fp2(u, v) ⇒ Object
-
.verify(signature, message, public_key, scheme: :basic) ⇒ Boolean
Verify BLS signature.
-
.verify_batch(signature, messages, public_keys, scheme: :basic) ⇒ Boolean
Verify aggregated signature.
Class Method Details
.aggregate_public_keys(public_keys) ⇒ BLS::PointG1|BLS::PointG2
Aggregate multiple public keys.
WARNING: the aggregate is only meaningful once every input key has been checked with pop_verify. Without that check an attacker who publishes pk_a = g * x - pk_victim can produce, on their own, a signature that verifies against the aggregate, making it look like the victim signed. See fast_aggregate_verify.
104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/bls.rb', line 104 def aggregate_public_keys(public_keys) raise BLS::Error, 'Expected non-empty array.' if public_keys.empty? g1_flag = public_keys.first.is_a?(PointG1) sum = g1_flag ? PointG1::ZERO : PointG2::ZERO public_keys.each do |pubkey| if g1_flag && !pubkey.is_a?(PointG1) || !g1_flag && !pubkey.is_a?(PointG2) raise BLS::Error, 'Point G1 and G2 are mixed.' end sum += pubkey end sum end |
.aggregate_signatures(signatures) ⇒ BLS::PointG2|BLS::PointG1
Aggregate multiple signatures. e(G, S) = e(G, sum(n)Si) = mul(n)(e(G, Si))
121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/bls.rb', line 121 def aggregate_signatures(signatures) raise BLS::Error, 'Expected non-empty array.' if signatures.empty? g2_flag = signatures.first.is_a?(PointG2) sum = g2_flag ? PointG2::ZERO : PointG1::ZERO signatures.each do |signature| if g2_flag && !signature.is_a?(PointG2) || !g2_flag && !signature.is_a?(PointG1) raise BLS::Error, 'Signature G1 and G2 are mixed.' end sum += signature end sum end |
.bin_xor(a, b) ⇒ String
Calculate binary xor between a and b.
59 60 61 62 63 64 65 66 |
# File 'lib/bls/math.rb', line 59 def bin_xor(a, b) res = Array.new(a.bytesize) b_bytes = b.bytes a.bytes.each.with_index do |b, i| res[i] = b ^ b_bytes[i] end res.pack('C*') end |
.bit_get(n, pos) ⇒ Object
21 22 23 |
# File 'lib/bls/math.rb', line 21 def bit_get(n, pos) (n >> pos) & 1 end |
.fast_aggregate_verify(signature, message, public_keys) ⇒ Boolean
Verify an aggregated signature over a single message signed by every key in public_keys.
WARNING: every public key must have passed pop_verify first. This check cannot be done here because it needs each key's proof, and skipping it reopens the rogue key attack that the pop scheme exists to prevent.
214 215 216 217 218 219 220 221 222 |
# File 'lib/bls.rb', line 214 def fast_aggregate_verify(signature, , public_keys) return false if public_keys.empty? # nothing signed, so nothing is verified # KeyValidate every key, not just the aggregate. The identity adds nothing to the sum, so # a set containing it would otherwise verify on the strength of the others while counting # a key that nobody holds, and could hold, as one of the signers. return false if public_keys.any?(&:zero?) verify(signature, , aggregate_public_keys(public_keys), scheme: :pop) end |
.get_public_key(private_key, key_type: :g1) ⇒ BLS::PointG1|BLS::PointG2
Generate public key from private_key.
49 50 51 52 53 54 55 56 57 58 |
# File 'lib/bls.rb', line 49 def get_public_key(private_key, key_type: :g1) case key_type when :g1 PointG1.from_private_key(private_key) when :g2 PointG2.from_private_key(private_key) else raise Error, 'key_type must be :g1 or :g2.' end end |
.i2osp(value, length) ⇒ Array[Integer] byte array.
Convert value to byte array of length.
42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/bls/math.rb', line 42 def i2osp(value, length) raise BLS::Error, "bad I2OSP call: value=#{value} length=#{length}" if value < 0 || value >= (1 << 8 * length) res = Array.new(length, 0) i = length - 1 while i >= 0 res[i] = value & 0xff value >>= 8 i -= 1 end res end |
.miller_loop(ell, g1) ⇒ Object
699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 |
# File 'lib/bls/field.rb', line 699 def miller_loop(ell, g1) f12 = Fp12::ONE p_x, p_y = g1 i = BLS_X_LEN - 2 j = 0 while i >= 0 f12 = f12.multiply_by_014(ell[j][0], ell[j][1] * p_x.value, ell[j][2] * p_y.value) unless bit_get(Curve::X, i).zero? j += 1 f12 = f12.multiply_by_014(ell[j][0], ell[j][1] * p_x.value, ell[j][2] * p_y.value) end f12 = f12.square unless i.zero? i -= 1 j += 1 end f12.conjugate end |
.mod(a, b) ⇒ Object
7 8 9 |
# File 'lib/bls/math.rb', line 7 def mod(a, b) a % b end |
.norm_p1(point) ⇒ Object
315 316 317 |
# File 'lib/bls/point.rb', line 315 def norm_p1(point) point.is_a?(PointG1) ? point : PointG1.from_hex(point) end |
.norm_p1h(point, scheme: :basic) ⇒ Object
323 324 325 |
# File 'lib/bls/point.rb', line 323 def norm_p1h(point, scheme: :basic) point.is_a?(PointG1) ? point : PointG1.hash_to_curve(point, scheme: scheme) end |
.norm_p2(point) ⇒ Object
319 320 321 |
# File 'lib/bls/point.rb', line 319 def norm_p2(point) point.is_a?(PointG2) ? point : PointG2.from_hex(point) end |
.norm_p2h(point, scheme: :basic) ⇒ Object
327 328 329 |
# File 'lib/bls/point.rb', line 327 def norm_p2h(point, scheme: :basic) point.is_a?(PointG2) ? point : PointG2.hash_to_curve(point, scheme: scheme) end |
.normalize_priv_key(private_key) ⇒ BLS::Fr
Normalize private key. A key at or above the group order is reduced rather than rejected, matching the reference implementation and the test vectors. Reducing raw entropy that way is slightly biased, so prefer drawing the key from 0 < k < Fr::ORDER to begin with. or when it reduces to zero.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/bls/math.rb', line 76 def normalize_priv_key(private_key) k = case private_key when String # String#to_i stops at the first character it cannot read and returns what it has, # so a mistyped or truncated key would otherwise turn into a different, much # smaller one without any sign that it happened. raise BLS::Error, 'Private key must be a hex string.' unless private_key.match?(/\A[0-9a-fA-F]+\z/) private_key.to_i(16) when Integer private_key else raise BLS::Error, 'Private key must be Integer or String.' end raise BLS::Error, 'Private key must be positive.' unless k.positive? key = Fr.new(k) raise BLS::Error, "Private key must not be a multiple of #{Fr::ORDER}." if key.zero? key end |
.num_to_hex(num, byte_length) ⇒ String
Convert number to byte_length bytes hex string.
102 103 104 |
# File 'lib/bls/math.rb', line 102 def num_to_hex(num, byte_length) num.to_s(16).rjust(2 * byte_length, '0') end |
.os2ip(bytes) ⇒ Integer
Convert byte to non-negative integer.
28 29 30 31 32 33 34 35 |
# File 'lib/bls/math.rb', line 28 def os2ip(bytes) res = 0 bytes.each do |b| res <<= 8 res += b end res end |
.pairing(p, q, with_final_exp: true) ⇒ BLS::Fp12
10 11 12 13 14 15 16 17 18 19 |
# File 'lib/bls/pairing.rb', line 10 def pairing(p, q, with_final_exp: true) raise ArgumentError, 'p should be BLS::PointG1 object' unless p.is_a?(BLS::PointG1) raise ArgumentError, 'q should be BLS::PointG2 object' unless q.is_a?(BLS::PointG2) raise PairingError, 'No pairings at point of Infinity' if p.zero? || q.zero? p.validate! q.validate! looped = p.miller_loop(q) with_final_exp ? looped.final_exponentiate : looped end |
.partial_pairing(p, q) ⇒ BLS::Fp12
Pairing without the final exponentiation, using e(O, Q) = e(P, O) = 1 where pairing raises instead. Verification multiplies several of these together, so a degenerate key or signature has to fall out as a failed check rather than as an exception.
28 29 30 31 32 33 34 |
# File 'lib/bls/pairing.rb', line 28 def partial_pairing(p, q) raise ArgumentError, 'p should be BLS::PointG1 object' unless p.is_a?(BLS::PointG1) raise ArgumentError, 'q should be BLS::PointG2 object' unless q.is_a?(BLS::PointG2) return Fp12::ONE if p.zero? || q.zero? pairing(p, q, with_final_exp: false) end |
.pop_prove(private_key, key_type: :g1) ⇒ BLS::PointG2|BLS::PointG1
Generate a proof of possession for private_key: a signature, under that key, over the
public key it belongs to. Producing one requires knowing the private key, which is what
makes aggregation safe against rogue key attacks.
188 189 190 191 192 193 194 |
# File 'lib/bls.rb', line 188 def pop_prove(private_key, key_type: :g1) public_key = get_public_key(private_key, key_type: key_type) msg = public_key.to_hex(compressed: true) msg_point = public_key.is_a?(PointG1) ? BLS.norm_p2h(msg, scheme: :pop_proof) : BLS.norm_p1h(msg, scheme: :pop_proof) msg_point * BLS.normalize_priv_key(private_key) end |
.pop_verify(public_key, proof) ⇒ Boolean
Verify a proof of possession produced by pop_prove.
200 201 202 |
# File 'lib/bls.rb', line 200 def pop_verify(public_key, proof) verify(proof, public_key.to_hex(compressed: true), public_key, scheme: :pop_proof) end |
.pow_mod(a, power, m) ⇒ Object
11 12 13 14 15 16 17 18 19 |
# File 'lib/bls/math.rb', line 11 def pow_mod(a, power, m) res = 1 while power.positive? res = mod(res * a, m) unless (power & 1).zero? power >>= 1 a = mod(a * a, m) end res end |
.psi(x, y) ⇒ Object
689 690 691 692 693 |
# File 'lib/bls/field.rb', line 689 def psi(x, y) x2 = WSQ_INV.multiply_by_fp2(x).frobenius_map(1).multiply(WSQ).coeffs[0].coeffs[0] y2 = WCU_INV.multiply_by_fp2(y).frobenius_map(1).multiply(WCU).coeffs[0].coeffs[0] [x2, y2] end |
.psi2(x, y) ⇒ Object
695 696 697 |
# File 'lib/bls/field.rb', line 695 def psi2(x, y) [x * PSI2_C1, y.negate] end |
.sgn0(x) ⇒ Integer
sgn0 for GF(p^2), from RFC 9380 section 4.1. Combined with | and & rather than || and &&: sign_0 is 0 or 1, and 0 is truthy in Ruby, so a disjunction would always short circuit on it and x_1 would never be reached.
724 725 726 727 728 729 730 |
# File 'lib/bls/field.rb', line 724 def sgn0(x) x0, x1 = x.values sign_0 = x0 % 2 zero_0 = x0.zero? ? 1 : 0 sign_1 = x1 % 2 sign_0 | (zero_0 & sign_1) end |
.sign(message, private_key, sig_type: :g2, scheme: :basic) ⇒ PointG2
Generate BLS signature: s = pk x H(m) If :g1 is specified, the signature is a point on G1 and the public key is a point on G2. If :g2 is specified, the signature is a point on G2 and the public key is a point on G1. do not verify under the other, so signer and verifier must agree on it.
33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/bls.rb', line 33 def sign(, private_key, sig_type: :g2, scheme: :basic) msg_point = case sig_type when :g1 BLS.norm_p1h(, scheme: scheme) when :g2 BLS.norm_p2h(, scheme: scheme) else raise Error, 'sig_type must be :g1 or :g2.' end msg_point * BLS.normalize_priv_key(private_key) end |
.sqrt_div_fp2(u, v) ⇒ Object
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 |
# File 'lib/bls/field.rb', line 732 def sqrt_div_fp2(u, v) uv7 = u * v**7 uv15 = uv7 * v**8 gamma = uv15**P_MINUS_9_DIV_16 * uv7 success = false result = gamma positive_roots_of_unity = Fp2::ROOTS_OF_UNITY[0...4] positive_roots_of_unity.each do |root| candidate = root * gamma if (candidate**2 * v - u).zero? && !success success = true result = candidate end end [success, result] end |
.verify(signature, message, public_key, scheme: :basic) ⇒ Boolean
Verify BLS signature. Verify one of the following:
- Public key is a point on G1, signature is a point on G2 or
- Public key is a point on G2, signature is a point on G1.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/bls.rb', line 68 def verify(signature, , public_key, scheme: :basic) unless signature.is_a?(PointG1) && public_key.is_a?(PointG2) || signature.is_a?(PointG2) && public_key.is_a?(PointG1) 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.' end # KeyValidate of draft-irtf-cfrg-bls-signature section 2.5. The identity is not a public # key anybody holds, and pairing it away would leave the identity signature verifying # against every message. return false if public_key.zero? g = public_key.is_a?(PointG1) ? PointG1::BASE : PointG2::BASE ephm = if public_key.is_a?(PointG1) hm = BLS.norm_p2h(, scheme: scheme) BLS.partial_pairing(public_key.negate, hm) else hm = BLS.norm_p1h(, scheme: scheme) BLS.partial_pairing(hm, public_key.negate) end egs = if public_key.is_a?(PointG1) BLS.partial_pairing(g, signature) else BLS.partial_pairing(signature, g) end exp = (egs * ephm).final_exponentiate exp == Fp12::ONE end |
.verify_batch(signature, messages, public_keys, scheme: :basic) ⇒ Boolean
Verify aggregated signature.
Under the :basic scheme the messages must all be distinct: repeating one is reported as an invalid signature, since that scheme has nothing but message distinctness to stop a rogue key from signing on another key's behalf. Use :pop, whose proofs of possession rule that out, to verify several signatures over the same message.
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/bls.rb', line 147 def verify_batch(signature, , public_keys, scheme: :basic) raise BLS::Error, 'Public keys count should equal msg count.' unless .size == public_keys.size return false if .empty? # nothing was signed, so nothing is verified sig_g2_flag = signature.is_a?(PointG2) public_keys.each do |public_key| if sig_g2_flag && !public_key.is_a?(PointG1) || !sig_g2_flag && !public_key.is_a?(PointG2) raise BLS::Error, "Public key must be #{sig_g2_flag ? 'PointG1' : 'PointG2'}" end end return false if public_keys.any?(&:zero?) # KeyValidate, as in #verify = .map { |m| sig_g2_flag ? BLS.norm_p2h(m, scheme: scheme) : BLS.norm_p1h(m, scheme: scheme)} # Keys that signed the same message are summed so that message is paired exactly once: # e(P1, Q) * e(P2, Q) == e(P1 + P2, Q). Grouping on the serialized point rather than on # the message keeps this independent of how equal messages happened to be spelled. zero = sig_g2_flag ? PointG1::ZERO : PointG2::ZERO grouped = {} .each_with_index do |, i| group = (grouped[.to_hex(compressed: true)] ||= [, zero]) group[1] += public_keys[i] end return false if scheme == :basic && grouped.size < .size paired = grouped.each_value.map do |, group_pubkey| sig_g2_flag ? BLS.partial_pairing(group_pubkey, ) : BLS.partial_pairing(, group_pubkey) end paired << (sig_g2_flag ? BLS.partial_pairing(PointG1::BASE.negate, signature) : BLS.partial_pairing(signature, PointG2::BASE.negate)) product = paired.inject(Fp12::ONE) { |a, b| a * b } product.final_exponentiate == Fp12::ONE end |