kzg 0.3.2 → 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/.github/workflows/main.yml +1 -2
- data/CHANGELOG.md +30 -0
- data/README.md +51 -0
- data/kzg.gemspec +1 -1
- data/lib/kzg/commitment.rb +15 -5
- data/lib/kzg/polynomial.rb +32 -2
- data/lib/kzg/setting.rb +70 -16
- data/lib/kzg/version.rb +1 -1
- data/lib/kzg.rb +49 -2
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ed4f0b47a0f3f5b5de96f7cd6a931aa979b6ebc84e48eaf7c696755f7caf990f
|
|
4
|
+
data.tar.gz: ae70fe07a4eb798b08669d0f891b9a83bf0d48f27311d859623ae3b3eaf9fa0c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3b8a04dadb5dc5260debcff77a7b2982f15434e7f3c610189430d53abee3b8d6ac0be2ee0c46b5f7bda7bda52e1551e62e862decc4fa258491d3596301b0d853
|
|
7
|
+
data.tar.gz: 3ca8ab11a1f10b9644c1580ea2b3fbf0714f33ffb5e8460e85155c59ae8ede6f49c1a50ec11dcbc1b45ab2de90e4718a3f4483adb53db1a1d91273f1e7de6c2c
|
data/.github/workflows/main.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.4.0] - 2026-08-11
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `KZG.decode_field_element`, which reads the canonical 32 byte big endian encoding of a
|
|
8
|
+
field element and refuses everything else. `BLS::Fr` reduces modulo the group order, which
|
|
9
|
+
is right for an element built in memory and wrong for one arriving as bytes: it would
|
|
10
|
+
otherwise leave `1` and `1 + BLS::Curve::R` as two spellings of a single claim, either of
|
|
11
|
+
which verifies against the other's proof. This is the scalar counterpart of
|
|
12
|
+
`BLS::PointG1.from_hex`.
|
|
13
|
+
- The EIP-4844 reference vectors for `verify_kzg_proof` are run against
|
|
14
|
+
`KZG::Setting#valid_proof?`, which verifies with the same pairing equation.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Require bls12-381 0.4.0, whose `from_hex` refuses a point outside the prime-order subgroup
|
|
19
|
+
and a non-canonical encoding of one.
|
|
20
|
+
- `KZG::Setting#valid_proof?` and `#valid_multi_proof?` answer `false` where they used to
|
|
21
|
+
raise, so that untrusted input cannot turn into an exception: for a proof or a commitment
|
|
22
|
+
at infinity, outside the prime-order subgroup, or of the wrong type, and for an `x` that
|
|
23
|
+
repeats, that does not pair up with `y`, or that the setup is too small to open. They also
|
|
24
|
+
check the subgroup themselves, which only `from_hex` did before and only for callers that
|
|
25
|
+
deserialize from hex.
|
|
26
|
+
- `KZG::Commitment` commits the zero polynomial to the point at infinity rather than to
|
|
27
|
+
`nil`, which `#compute_proof` returns for a constant polynomial.
|
|
28
|
+
- `KZG::Setting.new`, `KZG::Commitment.new`, `KZG::Polynomial#div` and `#multiply` raise
|
|
29
|
+
`KZG::Error` for arguments that used to fail later as arithmetic on a nil.
|
|
30
|
+
- `KZG.setup_params` raises `KZG::Error` for an `n` below 2, which built a setting that could
|
|
31
|
+
verify nothing, and for a secret that is a multiple of the group order.
|
|
32
|
+
|
|
3
33
|
## [0.1.0] - 2021-04-11
|
|
4
34
|
|
|
5
35
|
- Initial release
|
data/README.md
CHANGED
|
@@ -95,6 +95,35 @@ y = [55, 9217, 280483]
|
|
|
95
95
|
setting.valid_multi_proof?(commitment.value, multi_proof, x, y)
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
#### Untrusted input
|
|
99
|
+
|
|
100
|
+
`valid_proof?` and `valid_multi_proof?` answer `false` for anything they will not accept, so
|
|
101
|
+
a commitment or a proof that arrives over the wire cannot turn into an exception. Both check
|
|
102
|
+
that the points they are given are in the prime-order subgroup, which being on the curve does
|
|
103
|
+
not imply and which proofs are forgeable without.
|
|
104
|
+
|
|
105
|
+
What they cannot check is how `x` and `y` were spelled, because they never see it. `BLS::Fr`
|
|
106
|
+
reduces modulo the group order, which is what makes `-6` and `r - 6` one element, and also
|
|
107
|
+
what leaves `1` and `1 + BLS::Curve::R` as two encodings of a single claim: a proof for
|
|
108
|
+
either verifies against the other. Decode field elements that arrive as bytes with
|
|
109
|
+
`KZG.decode_field_element`, which refuses everything but the canonical 32 byte big endian
|
|
110
|
+
form, in the same way `BLS::PointG1.from_hex` refuses a non-canonical point:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
x = KZG.decode_field_element(x_hex) # 32 bytes, big endian, no 0x prefix
|
|
114
|
+
y = KZG.decode_field_element(y_hex) # raises KZG::Error if x_hex is not canonical
|
|
115
|
+
commitment = BLS::PointG1.from_hex(commitment_hex)
|
|
116
|
+
proof = BLS::PointG1.from_hex(proof_hex)
|
|
117
|
+
|
|
118
|
+
setting.valid_proof?(commitment, proof, x, y)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The [EIP-4844 reference vectors for `verify_kzg_proof`][eip4844-vectors] are run against
|
|
122
|
+
`valid_proof?` in the specs; that function verifies with the same pairing equation and reads
|
|
123
|
+
nothing from the trusted setup but `[s]_2.`
|
|
124
|
+
|
|
125
|
+
[eip4844-vectors]: https://github.com/ethereum/c-kzg-4844/tree/main/tests/verify_kzg_proof/kzg-mainnet
|
|
126
|
+
|
|
98
127
|
### Use as vector commitment
|
|
99
128
|
|
|
100
129
|
When used as a Vector commitment, the value to be committed is encoded in a polynomial expression as the evaluated value of the polynomial.
|
|
@@ -117,3 +146,25 @@ proof = commitment.compute_proof(3)
|
|
|
117
146
|
# verify
|
|
118
147
|
setting.valid_proof?(commitment.value, proof, 3, 9)
|
|
119
148
|
```
|
|
149
|
+
|
|
150
|
+
## Side channels
|
|
151
|
+
|
|
152
|
+
**Committing is not safe against an attacker who can measure it.** A commitment multiplies a
|
|
153
|
+
setup point by each coefficient of the polynomial, and those coefficients are the very thing
|
|
154
|
+
the commitment hides — the vector above among them. The multiplication underneath is
|
|
155
|
+
[not constant time][bls-side-channels]: the scalar decides which precomputed point each
|
|
156
|
+
window adds and so which memory is touched, point addition returns early on the identity, and
|
|
157
|
+
Ruby's bignum arithmetic takes time that depends on its operands.
|
|
158
|
+
|
|
159
|
+
A pure Ruby implementation cannot fix this, and neither can the library underneath. Treat
|
|
160
|
+
committing as suitable where it cannot be observed. Verifying is fine either way, since it
|
|
161
|
+
touches nothing secret.
|
|
162
|
+
|
|
163
|
+
[bls-side-channels]: https://github.com/azuchi/bls12-381#side-channels
|
|
164
|
+
|
|
165
|
+
## Threads
|
|
166
|
+
|
|
167
|
+
Verifying from several threads gives the right answers. `valid_proof?` pairs against
|
|
168
|
+
`BLS::PointG2::BASE`, which memoises its pairing coefficients on first use without a lock, so
|
|
169
|
+
racing callers can each compute them; what is assigned is always a finished array, so the
|
|
170
|
+
cost is the repeated work rather than a wrong result.
|
data/kzg.gemspec
CHANGED
|
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
|
|
|
29
29
|
|
|
30
30
|
# Uncomment to register a new dependency of your gem
|
|
31
31
|
spec.add_dependency 'bigdecimal'
|
|
32
|
-
spec.add_dependency 'bls12-381', '~> 0.
|
|
32
|
+
spec.add_dependency 'bls12-381', '~> 0.4.0'
|
|
33
33
|
|
|
34
34
|
# For more information and examples about making a new gem, checkout our
|
|
35
35
|
# guide at: https://bundler.io/guides/creating_gem.html
|
data/lib/kzg/commitment.rb
CHANGED
|
@@ -8,9 +8,22 @@ module KZG
|
|
|
8
8
|
# Create commitment
|
|
9
9
|
# @param [KZG::Setting] setting
|
|
10
10
|
# @param [KZG::Polynomial] polynomial
|
|
11
|
+
# @raise [KZG::Error] Occur when the polynomial has more coefficients than the setup has
|
|
12
|
+
# points to commit them with.
|
|
11
13
|
def initialize(setting, polynomial)
|
|
14
|
+
# Checked here rather than only in from_coeffs, which is not the only way in: the
|
|
15
|
+
# verification path builds its interpolation straight through new, and a coefficient
|
|
16
|
+
# with no point to meet is otherwise a nil that fails as arithmetic.
|
|
17
|
+
if polynomial.coeffs.length > setting.g1_points.length
|
|
18
|
+
raise KZG::Error,
|
|
19
|
+
"coeffs length is greater than the number of secret parameters."
|
|
20
|
+
end
|
|
21
|
+
|
|
12
22
|
@setting = setting
|
|
13
23
|
@polynomial = polynomial
|
|
24
|
+
# Seeded with the point at infinity so that the zero polynomial commits to it rather
|
|
25
|
+
# than to nil. compute_proof reaches here with an empty quotient whenever the
|
|
26
|
+
# polynomial is a constant, and that proof goes on to a pairing.
|
|
14
27
|
@value =
|
|
15
28
|
polynomial
|
|
16
29
|
.coeffs
|
|
@@ -19,17 +32,14 @@ module KZG
|
|
|
19
32
|
c = c.is_a?(BLS::Fr) ? c : BLS::Fr.new(c)
|
|
20
33
|
c.value.zero? ? BLS::PointG1::ZERO : setting.g1_points[i] * c
|
|
21
34
|
end
|
|
22
|
-
.inject(&:+)
|
|
35
|
+
.inject(BLS::PointG1::ZERO, &:+)
|
|
23
36
|
end
|
|
24
37
|
|
|
25
38
|
# Create commitment using coefficients.
|
|
26
39
|
# @param [KZG::Setting] setting
|
|
27
40
|
# @param [Array(Integer | BLS::Fr)] coeffs Coefficients of polynomial equation.
|
|
41
|
+
# @raise [KZG::Error] Occur when there are more coefficients than setup points, as in new.
|
|
28
42
|
def self.from_coeffs(setting, coeffs)
|
|
29
|
-
if coeffs.length > setting.g1_points.length
|
|
30
|
-
raise KZG::Error,
|
|
31
|
-
"coeffs length is greater than the number of secret parameters."
|
|
32
|
-
end
|
|
33
43
|
Commitment.new(setting, KZG::Polynomial.new(coeffs))
|
|
34
44
|
end
|
|
35
45
|
|
data/lib/kzg/polynomial.rb
CHANGED
|
@@ -12,13 +12,27 @@ module KZG
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
# Create polynomial using lagrange interpolation using (x, y) list.
|
|
15
|
-
# @param [Array(Integer)] x The array of x coordinate.
|
|
16
|
-
#
|
|
15
|
+
# @param [Array(Integer)] x The array of x coordinate. Must not contain the same value
|
|
16
|
+
# twice: no polynomial passes through two different y at one x, and one that repeats an
|
|
17
|
+
# (x, y) pair is under-determined either way.
|
|
18
|
+
# @param [Array(Integer)] y The array of y coordinate. Must be as long as +x+.
|
|
17
19
|
# @return [KZG::Polynomial]
|
|
20
|
+
# @raise [KZG::Error] Occur when x contains a duplicate value, or x and y differ in
|
|
21
|
+
# length.
|
|
18
22
|
def self.lagrange_interpolate(x, y)
|
|
19
23
|
n = x.length
|
|
24
|
+
unless n == y.length
|
|
25
|
+
raise KZG::Error, "x and y must have the same length."
|
|
26
|
+
end
|
|
27
|
+
|
|
20
28
|
x = x.map { |i| i.is_a?(BLS::Fr) ? i : BLS::Fr.new(i) }
|
|
21
29
|
y = y.map { |i| i.is_a?(BLS::Fr) ? i : BLS::Fr.new(i) }
|
|
30
|
+
# The product below runs over the differences between x[i] and every other x, so a
|
|
31
|
+
# repeat makes it zero and the division by it undefined. Caught here rather than left
|
|
32
|
+
# to BLS::Fr#invert so that it reads as the input error it is.
|
|
33
|
+
if x.map(&:value).uniq.length < n
|
|
34
|
+
raise KZG::Error, "x contains duplicate value."
|
|
35
|
+
end
|
|
22
36
|
coeffs = Array.new(n, BLS::Fr::ZERO)
|
|
23
37
|
n.times do |i|
|
|
24
38
|
prod = BLS::Fr::ONE
|
|
@@ -40,7 +54,10 @@ module KZG
|
|
|
40
54
|
# Create polynomial from array of x coordinate like f(x) = (x - x0)(x - x1)...(x - xn)
|
|
41
55
|
# @param [Array(Integer)] x An array of x coordinate.
|
|
42
56
|
# @return [KZG::Polynomial]
|
|
57
|
+
# @raise [KZG::Error] Occur when x is empty, which names no roots to build from.
|
|
43
58
|
def self.zero_poly(x)
|
|
59
|
+
raise KZG::Error, "x must not be empty." if x.empty?
|
|
60
|
+
|
|
44
61
|
poleis =
|
|
45
62
|
x.map { |v| Polynomial.new([BLS::Fr.new(v).negate, BLS::Fr::ONE]) }
|
|
46
63
|
poleis[1..].inject(poleis.first) { |result, poly| result * poly }
|
|
@@ -96,6 +113,11 @@ module KZG
|
|
|
96
113
|
unless other.is_a?(Polynomial)
|
|
97
114
|
raise ArgumentError, "multiply target must be Polynomial"
|
|
98
115
|
end
|
|
116
|
+
# An empty coefficient list is the zero polynomial, and zero times anything is zero.
|
|
117
|
+
# Left to the loop below, an empty operand sizes the result at -1 when both are empty,
|
|
118
|
+
# and otherwise leaves it a row of nils, since the inner loop never runs to fill them.
|
|
119
|
+
return Polynomial.new([]) if coeffs.empty? || other.coeffs.empty?
|
|
120
|
+
|
|
99
121
|
new_coeffs = Array.new(coeffs.length + other.coeffs.length - 1)
|
|
100
122
|
coeffs.each.with_index do |a, i|
|
|
101
123
|
other.coeffs.each.with_index do |b, j|
|
|
@@ -111,10 +133,18 @@ module KZG
|
|
|
111
133
|
# @param [KZG::Polynomial] other Other polynomial
|
|
112
134
|
# @return [KZG::Polynomial] Divided polynomial
|
|
113
135
|
# @return ArgumentError
|
|
136
|
+
# @raise [KZG::Error] Occur when +other+ has no non-zero leading coefficient to divide by.
|
|
114
137
|
def div(other)
|
|
115
138
|
unless other.is_a?(Polynomial)
|
|
116
139
|
raise ArgumentError, "divide target must be Polynomial"
|
|
117
140
|
end
|
|
141
|
+
# The long division below divides by this coefficient once per step. Zero has no
|
|
142
|
+
# inverse, and an empty divisor has no coefficient at all; neither denominator names a
|
|
143
|
+
# polynomial of any degree, so refuse both here rather than reach BLS::Fr#invert.
|
|
144
|
+
if other.coeffs.empty? || other.coeffs.last.value.zero?
|
|
145
|
+
raise KZG::Error, "divisor must have a non-zero leading coefficient."
|
|
146
|
+
end
|
|
147
|
+
|
|
118
148
|
a = coeffs.dup
|
|
119
149
|
a_pos = a.length - 1
|
|
120
150
|
b_pos = other.coeffs.length - 1
|
data/lib/kzg/setting.rb
CHANGED
|
@@ -6,21 +6,33 @@ module KZG
|
|
|
6
6
|
attr_reader :g1_points, :g2_points
|
|
7
7
|
|
|
8
8
|
# @param [Array(BLS::PointG1)] g1_points
|
|
9
|
-
# @param [Array(BLS::PointG2)] g2_points
|
|
9
|
+
# @param [Array(BLS::PointG2)] g2_points at least [1]_2 and [s]_2, the two a proof is
|
|
10
|
+
# checked against.
|
|
11
|
+
# @raise [KZG::Error] Occur when either array holds something other than the points of
|
|
12
|
+
# its group, or when g2_points is too short to verify with.
|
|
10
13
|
def initialize(g1_points, g2_points)
|
|
11
14
|
raise KZG::Error, "g1_points must be array." unless g1_points.is_a?(Array)
|
|
15
|
+
raise KZG::Error, "g2_points must be array." unless g2_points.is_a?(Array)
|
|
12
16
|
unless g1_points.all? { |g| g.is_a?(BLS::PointG1) }
|
|
13
17
|
raise KZG::Error, "All elements of g1_points must be BLS::PointG1."
|
|
14
18
|
end
|
|
15
19
|
unless g2_points.all? { |g| g.is_a?(BLS::PointG2) }
|
|
16
20
|
raise KZG::Error, "All elements of g2_points must be BLS::PointG2."
|
|
17
21
|
end
|
|
22
|
+
# valid_proof? reads g2_points[1], so a setting short of it can verify nothing. Refused
|
|
23
|
+
# here rather than at verification time, where it is a nil that has travelled some way
|
|
24
|
+
# from the setup that omitted it.
|
|
25
|
+
if g2_points.length < 2
|
|
26
|
+
raise KZG::Error, "g2_points must have at least 2 elements."
|
|
27
|
+
end
|
|
18
28
|
|
|
19
29
|
@g1_points = g1_points
|
|
20
30
|
@g2_points = g2_points
|
|
21
31
|
end
|
|
22
32
|
|
|
23
33
|
def ==(other)
|
|
34
|
+
return false unless other.is_a?(Setting)
|
|
35
|
+
|
|
24
36
|
g1_points == other.g1_points && g2_points == other.g2_points
|
|
25
37
|
end
|
|
26
38
|
|
|
@@ -29,20 +41,22 @@ module KZG
|
|
|
29
41
|
# @param [BLS::PointG1] proof
|
|
30
42
|
# @param [Integer|BLS::Fr] x
|
|
31
43
|
# @param [Integer|BLS::Fr] y
|
|
44
|
+
# @return [Boolean] verification result.
|
|
32
45
|
def valid_proof?(commit_point, proof, x, y)
|
|
46
|
+
return false unless g1_element?(commit_point) && g1_element?(proof)
|
|
47
|
+
|
|
33
48
|
x = x.is_a?(BLS::Fr) ? x : BLS::Fr.new(x)
|
|
34
49
|
y = y.is_a?(BLS::Fr) ? y : BLS::Fr.new(y)
|
|
35
50
|
xg2 = x.value.zero? ? BLS::PointG2::ZERO : BLS::PointG2::BASE * x
|
|
36
51
|
yg = y.value.zero? ? BLS::PointG1::ZERO : BLS::PointG1::BASE * y
|
|
37
52
|
|
|
38
53
|
# e([commitment - y]^(-1), [1]) * e([proof], [s - x]) = 1
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
rhs = BLS.pairing(proof, g2_points[1] - xg2, with_final_exp: false)
|
|
54
|
+
# partial_pairing rather than pairing: a proof at infinity, or a commitment that
|
|
55
|
+
# cancels with [y], is a degenerate input a verifier can be handed and has to come out
|
|
56
|
+
# as false rather than as a BLS::PairingError. e(O, Q) = e(P, O) = 1 leaves the
|
|
57
|
+
# equation above intact.
|
|
58
|
+
lhs = BLS.partial_pairing((commit_point - yg).negate, BLS::PointG2::BASE)
|
|
59
|
+
rhs = BLS.partial_pairing(proof, g2_points[1] - xg2)
|
|
46
60
|
exp = (lhs * rhs).final_exponentiate
|
|
47
61
|
exp == BLS::Fp12::ONE
|
|
48
62
|
end
|
|
@@ -52,21 +66,36 @@ module KZG
|
|
|
52
66
|
# @param [BLS::PointG1] proof
|
|
53
67
|
# @param [Array(Integer|BLS::Fr)] x
|
|
54
68
|
# @param [Array(Integer|BLS::Fr)] y
|
|
69
|
+
# @return [Boolean] verification result. A claim this method cannot interpolate through
|
|
70
|
+
# is a claim that does not verify, so a malformed x/y is false rather than an exception.
|
|
55
71
|
def valid_multi_proof?(commit_point, proof, x, y)
|
|
72
|
+
return false unless g1_element?(commit_point) && g1_element?(proof)
|
|
73
|
+
# Nothing is claimed over an empty set of points, and a y that does not pair up with an
|
|
74
|
+
# x leaves the interpolation below indexing past the end of it.
|
|
75
|
+
return false if x.empty? || x.length != y.length
|
|
76
|
+
# Both commitments below take one setup point per coefficient, and z(x) is of degree
|
|
77
|
+
# x.length, so it needs one G2 point more than there are x. A claim larger than that is
|
|
78
|
+
# one this setting cannot weigh, rather than one it has weighed and rejected, but it
|
|
79
|
+
# reaches here from whoever is being verified and so cannot raise either.
|
|
80
|
+
if x.length > g1_points.length || x.length >= g2_points.length
|
|
81
|
+
return false
|
|
82
|
+
end
|
|
83
|
+
|
|
56
84
|
x = x.map { |v| v.is_a?(BLS::Fr) ? v.value : v }
|
|
57
85
|
y = y.map { |v| v.is_a?(BLS::Fr) ? v.value : v }
|
|
86
|
+
# Interpolation through a repeated x is undefined, and lagrange_interpolate raises on
|
|
87
|
+
# it. The x here comes from whoever the proof is being verified for, so a repeat has to
|
|
88
|
+
# be reported as a failed proof instead of letting the exception escape. Compared after
|
|
89
|
+
# reduction, since x and x + BLS::Curve::R are the same field element.
|
|
90
|
+
return false if x.map { |v| BLS::Fr.new(v).value }.uniq.length < x.length
|
|
91
|
+
|
|
58
92
|
# compute i(x)
|
|
59
93
|
i_poly = Polynomial.lagrange_interpolate(x, y)
|
|
60
94
|
# compute z(x)
|
|
61
95
|
z_poly = Polynomial.zero_poly(x)
|
|
62
96
|
# e([commitment - interpolation_polynomial(s)]^(-1), [1]) * e([proof], [s^n - x^n]) = 1
|
|
63
97
|
is = Commitment.new(self, i_poly).value
|
|
64
|
-
lhs =
|
|
65
|
-
BLS.pairing(
|
|
66
|
-
(commit_point - is).negate,
|
|
67
|
-
BLS::PointG2::BASE,
|
|
68
|
-
with_final_exp: false
|
|
69
|
-
)
|
|
98
|
+
lhs = BLS.partial_pairing((commit_point - is).negate, BLS::PointG2::BASE)
|
|
70
99
|
z_commit =
|
|
71
100
|
z_poly
|
|
72
101
|
.coeffs
|
|
@@ -74,10 +103,35 @@ module KZG
|
|
|
74
103
|
.with_index do |c, i|
|
|
75
104
|
c.value.zero? ? BLS::PointG2::ZERO : g2_points[i] * c
|
|
76
105
|
end
|
|
77
|
-
.inject(&:+)
|
|
78
|
-
rhs = BLS.
|
|
106
|
+
.inject(BLS::PointG2::ZERO, &:+)
|
|
107
|
+
rhs = BLS.partial_pairing(proof, z_commit)
|
|
79
108
|
exp = (lhs * rhs).final_exponentiate
|
|
80
109
|
exp == BLS::Fp12::ONE
|
|
81
110
|
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# Whether +point+ is an element of G1, rather than merely a point on the curve over Fp.
|
|
115
|
+
#
|
|
116
|
+
# The subgroup check is what carries weight here. E(Fp) is bigger than the prime-order
|
|
117
|
+
# subgroup by the cofactor, and a point taken from the rest of it makes the pairing
|
|
118
|
+
# bilinear over a group the soundness argument never covered, which is what lets forged
|
|
119
|
+
# proofs through. BLS::PointG1.from_hex runs this check as of bls12-381 0.4.0, but only
|
|
120
|
+
# for callers that deserialize their points from hex; a commitment or a proof reaching
|
|
121
|
+
# this class by any other route has never been checked.
|
|
122
|
+
#
|
|
123
|
+
# The curve check has to come first: multiplying a point that satisfies no curve equation
|
|
124
|
+
# is meaningless, and its result landing on infinity would pass the subgroup check on an
|
|
125
|
+
# input that is not a point at all.
|
|
126
|
+
# @param [Object] point
|
|
127
|
+
# @return [Boolean]
|
|
128
|
+
def g1_element?(point)
|
|
129
|
+
return false unless point.is_a?(BLS::PointG1)
|
|
130
|
+
|
|
131
|
+
point.validate! # raises unless on the curve over Fp
|
|
132
|
+
point.in_group?
|
|
133
|
+
rescue BLS::PointError
|
|
134
|
+
false
|
|
135
|
+
end
|
|
82
136
|
end
|
|
83
137
|
end
|
data/lib/kzg/version.rb
CHANGED
data/lib/kzg.rb
CHANGED
|
@@ -12,18 +12,65 @@ module KZG
|
|
|
12
12
|
autoload :Polynomial, "kzg/polynomial"
|
|
13
13
|
autoload :Commitment, "kzg/commitment"
|
|
14
14
|
|
|
15
|
+
# Size of the canonical encoding of a field element, as used by EIP-4844 and its test
|
|
16
|
+
# vectors: 32 bytes, big endian.
|
|
17
|
+
FIELD_ELEMENT_BYTE_SIZE = 32
|
|
18
|
+
FIELD_ELEMENT_HEX =
|
|
19
|
+
/\A(?:[0-9a-fA-F]{2}){#{FIELD_ELEMENT_BYTE_SIZE}}\z/.freeze
|
|
20
|
+
|
|
15
21
|
module_function
|
|
16
22
|
|
|
23
|
+
# Decode a field element from its canonical representation.
|
|
24
|
+
#
|
|
25
|
+
# BLS::Fr.new reduces whatever it is handed modulo the group order. That is the right
|
|
26
|
+
# answer for an element built in memory, where -6 and r - 6 are two ways of writing one
|
|
27
|
+
# element, and the wrong one for an element arriving as bytes: it leaves 1 and r + 1 as two
|
|
28
|
+
# spellings of a single claim, and a proof for either verifies against the other. Anything
|
|
29
|
+
# that deduplicates, indexes or compares by the encoding then sees two claims where there
|
|
30
|
+
# is one.
|
|
31
|
+
#
|
|
32
|
+
# This is the scalar counterpart of BLS::PointG1.from_hex, which refuses a non-canonical
|
|
33
|
+
# point for the same reason. Together they cover both halves of the wire format; neither
|
|
34
|
+
# KZG::Setting#valid_proof? nor BLS::Fr can, since neither is handed the encoding.
|
|
35
|
+
#
|
|
36
|
+
# @param [String] hex a field element, 32 bytes big endian in hex, with no 0x prefix.
|
|
37
|
+
# @return [BLS::Fr] the decoded field element.
|
|
38
|
+
# @raise [KZG::Error] Occur when +hex+ is not 32 bytes of hex digits, or encodes a value at
|
|
39
|
+
# or above the group order.
|
|
40
|
+
def decode_field_element(hex)
|
|
41
|
+
unless hex.is_a?(String) && hex.match?(FIELD_ELEMENT_HEX)
|
|
42
|
+
raise KZG::Error,
|
|
43
|
+
"Field element must be #{FIELD_ELEMENT_BYTE_SIZE} bytes in hex."
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
value = hex.to_i(16)
|
|
47
|
+
unless value < BLS::Curve::R
|
|
48
|
+
raise KZG::Error, "Field element must be less than the group order."
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
BLS::Fr.new(value)
|
|
52
|
+
end
|
|
53
|
+
|
|
17
54
|
# Setup elements of elliptic curve from +secret+.
|
|
18
55
|
# Note: Since the random secret must not be known to anyone,
|
|
19
56
|
# this Trusted Setup usually needs to be performed using an MPC or similar.
|
|
20
57
|
# @param [Integer] secret random secret.
|
|
21
|
-
# @param [Integer] n number of parameters.
|
|
58
|
+
# @param [Integer] n number of parameters. At least 2, since a setting needs [1]_2 and
|
|
59
|
+
# [s]_2 to check a proof against.
|
|
22
60
|
# @return [KZG::Setting]
|
|
61
|
+
# @raise [KZG::Error] Occur when n is below 2, or when the secret reduces to zero.
|
|
23
62
|
def setup_params(secret, n)
|
|
63
|
+
raise KZG::Error, "n must be 2 or more." unless n.is_a?(Integer) && n >= 2
|
|
64
|
+
|
|
65
|
+
s = BLS::Fr.new(secret)
|
|
66
|
+
# A secret of zero, or any multiple of the group order, leaves every point above [s^0] at
|
|
67
|
+
# infinity: a setup that commits to nothing but the constant term.
|
|
68
|
+
if s.zero?
|
|
69
|
+
raise KZG::Error, "secret must not be a multiple of the group order."
|
|
70
|
+
end
|
|
71
|
+
|
|
24
72
|
s1 = Array.new(n)
|
|
25
73
|
s2 = Array.new(n)
|
|
26
|
-
s = BLS::Fr.new(secret)
|
|
27
74
|
s_pow = BLS::Fr::ONE
|
|
28
75
|
n.times do |i|
|
|
29
76
|
s1[i] = BLS::PointG1::BASE * s_pow
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kzg
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- azuchi
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
32
|
+
version: 0.4.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.
|
|
39
|
+
version: 0.4.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: bundler
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|