Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize: batch BLS verification #1632

Merged
merged 44 commits into from
Apr 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c338993
Make some bls mikuli members accessible
Nashatyrev Apr 10, 2020
cd30c91
Add batch BLS bench and test
Nashatyrev Apr 10, 2020
c2635de
Fix batch BLS verification
Nashatyrev Apr 16, 2020
6acc934
Merge branch 'master' into optimize-batch-bls
Nashatyrev Apr 17, 2020
18747ab
Separate block processing and validation
Nashatyrev Apr 17, 2020
67c093f
Add batch verify methods to BLS
Nashatyrev Apr 20, 2020
92d4ef6
Fix BIG constant initialization
Nashatyrev Apr 20, 2020
3726c6a
Support aggregated signature verification in batching verification me…
Nashatyrev Apr 20, 2020
fca708f
Add BLSSignatureVerifier interface and pass it to spec methods for BL…
Nashatyrev Apr 20, 2020
08ac117
Use BatchBlockValidator in StateTransition by default
Nashatyrev Apr 20, 2020
8d78582
Adjust BLSBenchmark to use public files. Remove obsolete test
Nashatyrev Apr 20, 2020
a19797e
Get rid of obsolete final exp in BLS batch verify
Nashatyrev Apr 21, 2020
0a07005
Update Block dumps to spec v0.11
Nashatyrev Apr 21, 2020
8488047
Make it possible to validate a block with postState: get slot from block
Nashatyrev Apr 21, 2020
a605af4
Use SecureRandomProvider insead of Milagro RAND which has issues
Nashatyrev Apr 21, 2020
ba949f5
Add BLS primitives benchmarks
Nashatyrev Apr 21, 2020
0b8860d
Update BLSBenchmark
Nashatyrev Apr 21, 2020
420423c
Remove circle modules dependency
Nashatyrev Apr 21, 2020
bc19834
Add different batch BLS benchmarks
Nashatyrev Apr 21, 2020
da62b32
Add a variant of batch BLS verification which uses optimized ate2 pai…
Nashatyrev Apr 21, 2020
dd53a1d
Fix BLSPrimitivesBenchmark for more stable results
Nashatyrev Apr 21, 2020
db31a9d
Apply Spotless
Nashatyrev Apr 21, 2020
f5df28d
Temp suppress warning
Nashatyrev Apr 21, 2020
fe7af17
Add a couple of clarification comments
Nashatyrev Apr 22, 2020
89cfb29
Minor updates
Nashatyrev Apr 22, 2020
0fd56f9
Fix the state a block should be validated against
Nashatyrev Apr 22, 2020
0117b88
Make strict spec block processing methods naming: those which just va…
Nashatyrev Apr 22, 2020
4bf903c
Make the right order of processing and BLS verification for ref tests
Nashatyrev Apr 22, 2020
961d401
Merge remote-tracking branch 'pegasys/master' into optimize-batch-bls
Nashatyrev Apr 23, 2020
20f26c1
Add blocks dump for 64K validators
Nashatyrev Apr 23, 2020
faf2051
Add some javadoc
Nashatyrev Apr 23, 2020
f1b6041
Add more javadocs
Nashatyrev Apr 23, 2020
e5dea18
Create random properly
Nashatyrev Apr 23, 2020
9a52919
Remove optimization TODO: GT mul is pretty cheap and the gain would b…
Nashatyrev Apr 23, 2020
8f598c2
Add batch BLS verify related unit tests
Nashatyrev Apr 23, 2020
852361f
Merge remote-tracking branch 'pegasys/master' into optimize-batch-bls
Nashatyrev Apr 23, 2020
6ed1f4e
Fix errorprone warn
Nashatyrev Apr 23, 2020
c054809
Move equals/hashCode for all AbstractImmutableContainer implementatio…
Nashatyrev Apr 24, 2020
c3e8c2e
Revert accidentally committed change
Nashatyrev Apr 24, 2020
d0ce211
Use non-secure Random since hitting Linux secure random issue.
Nashatyrev Apr 25, 2020
cce5e0a
Apply spotless
Nashatyrev Apr 25, 2020
157e81d
Split large test to smaller cases
Nashatyrev Apr 25, 2020
4d82f33
Merge remote-tracking branch 'pegasys/master' into optimize-batch-bls
Nashatyrev Apr 25, 2020
cc733e1
Apply spotless
Nashatyrev Apr 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions bls/src/main/java/tech/pegasys/artemis/bls/BLS.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@

package tech.pegasys.artemis.bls;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.apache.tuweni.bytes.Bytes;
import tech.pegasys.artemis.bls.mikuli.BLS12381;
import tech.pegasys.artemis.bls.mikuli.BLS12381.BatchSemiAggregate;
import tech.pegasys.artemis.bls.mikuli.PublicKey;

/**
Expand Down Expand Up @@ -111,4 +115,169 @@ public static boolean fastAggregateVerify(
publicKeys.stream().map(BLSPublicKey::getPublicKey).collect(Collectors.toList());
return BLS12381.fastAggregateVerify(publicKeyObjects, message, signature.getSignature());
}

/**
* Optimized version for verification of several BLS signatures in a single batch. See
* https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407 for background
*
* <p>Parameters for verification are supplied with 3 lists which should have the same size. Each
* set consists of a message, signature (aggregate or not), and a list of signers' public keys
* (several or just a single one). See {@link #fastAggregateVerify(List, Bytes, BLSSignature)} for
* reference.
*
* <p>Calls {@link #fastAggregateVerify(List, Bytes, BLSSignature)} if just a single signature
* supplied If more than one signature passed then finds optimal parameters and delegates the call
* to the advanced {@link #batchVerify(List, List, List, boolean, boolean)} method
*
* @return True if the verification is successful, false otherwise
*/
public static boolean batchVerify(
List<List<BLSPublicKey>> publicKeys, List<Bytes> messages, List<BLSSignature> signatures) {
Preconditions.checkArgument(
publicKeys.size() == messages.size() && publicKeys.size() == signatures.size(),
"Different collection sizes");

int count = publicKeys.size();
if (count == 0) {
return true;
} else if (count == 1) {
return fastAggregateVerify(publicKeys.get(0), messages.get(0), signatures.get(0));
} else {
// double pairing variant is normally slightly faster, but when the number of
// signatures is relatively small the parallelization of hashToG2 internally
// yields more performance gain than double pairing
boolean doublePairing = count > Runtime.getRuntime().availableProcessors() * 2;
return batchVerify(publicKeys, messages, signatures, doublePairing, true);
}
}

/**
* Optimized version for verification of several BLS signatures in a single batch. See
* https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407 for background
*
* <p>Parameters for verification are supplied with 3 lists which should have the same size. Each
* set consists of a message, signature (aggregate or not), and a list of signers' public keys
* (several or just a single one). See {@link #fastAggregateVerify(List, Bytes, BLSSignature)} for
* reference.
*
* @param doublePairing if true uses the optimized version of ate pairing (ate2) which processes a
* pair of signatures a bit faster than with 2 separate regular ate calls Note that this
* option may not be optimal when a number of signatures is relatively small and the
* [parallel] option is [true]
* @param parallel Uses the default {@link java.util.concurrent.ForkJoinPool} to parallelize the
* work
* @return True if the verification is successful, false otherwise
*/
public static boolean batchVerify(
List<List<BLSPublicKey>> publicKeys,
List<Bytes> messages,
List<BLSSignature> signatures,
boolean doublePairing,
boolean parallel) {
Preconditions.checkArgument(
publicKeys.size() == messages.size() && publicKeys.size() == signatures.size(),
"Different collection sizes");
int count = publicKeys.size();
if (doublePairing) {
Stream<List<Integer>> pairsStream =
Lists.partition(IntStream.range(0, count).boxed().collect(Collectors.toList()), 2)
.stream();

if (parallel) {
pairsStream = pairsStream.parallel();
}
return completeBatchVerify(
pairsStream
.map(
idx ->
idx.size() == 1
? prepareBatchVerify(
idx.get(0),
publicKeys.get(idx.get(0)),
messages.get(idx.get(0)),
signatures.get(idx.get(0)))
: prepareBatchVerify2(
idx.get(0),
publicKeys.get(idx.get(0)),
messages.get(idx.get(0)),
signatures.get(idx.get(0)),
publicKeys.get(idx.get(1)),
messages.get(idx.get(1)),
signatures.get(idx.get(1))))
.collect(Collectors.toList()));
} else {
Stream<Integer> indexStream = IntStream.range(0, count).boxed();

if (parallel) {
indexStream = indexStream.parallel();
}
return completeBatchVerify(
indexStream
.map(
idx ->
prepareBatchVerify(
idx, publicKeys.get(idx), messages.get(idx), signatures.get(idx)))
.collect(Collectors.toList()));
}
}

/**
* {@link #prepareBatchVerify(int, List, Bytes, BLSSignature)} and {@link
* #completeBatchVerify(List)} is just a split of the {@link #batchVerify(List, List, List)} onto
* two separate procedures. {@link #prepareBatchVerify(int, List, Bytes, BLSSignature)} might be
* e.g. called in background for asynchronous stream of signatures. The results should be
* collected and then at some point verified with a final {@link #completeBatchVerify(List)} call
*
* @param index index of the signature in a batch. Used for minor optimization. -1 may be passed
* if no indexes are available
* @param publicKeys The list of public keys, not null
* @param message The message data to verify, not null
* @param signature The aggregate signature, not null
* @return An opaque instance which should be passed to the final step: {@link
* #completeBatchVerify(List)}
*/
public static BatchSemiAggregate prepareBatchVerify(
int index, List<BLSPublicKey> publicKeys, Bytes message, BLSSignature signature) {
return BLS12381.prepareBatchVerify(
index,
publicKeys.stream().map(BLSPublicKey::getPublicKey).collect(Collectors.toList()),
message,
signature.getSignature());
}

/**
* A slightly optimized variant of x2 {@link #prepareBatchVerify(int, List, Bytes, BLSSignature)}
* calls when two signatures are available for processing
*
* <p>The returned instances can be mixed up with the instances returned by {@link
* #prepareBatchVerify(int, List, Bytes, BLSSignature)}
*/
public static BatchSemiAggregate prepareBatchVerify2(
int index,
List<BLSPublicKey> publicKeys1,
Bytes message1,
BLSSignature signature1,
List<BLSPublicKey> publicKeys2,
Bytes message2,
BLSSignature signature2) {
return BLS12381.prepareBatchVerify2(
index,
publicKeys1.stream().map(BLSPublicKey::getPublicKey).collect(Collectors.toList()),
message1,
signature1.getSignature(),
publicKeys2.stream().map(BLSPublicKey::getPublicKey).collect(Collectors.toList()),
message2,
signature2.getSignature());
}

/**
* The final step to verify semi aggregated signatures produced by {@link #prepareBatchVerify(int,
* List, Bytes, BLSSignature)} or {@link #prepareBatchVerify2(int, List, Bytes, BLSSignature,
* List, Bytes, BLSSignature)} or a mix of both
*
* @return True if the verification is successful, false otherwise
*/
public static boolean completeBatchVerify(List<BatchSemiAggregate> preparedSignatures) {
return BLS12381.completeBatchVerify(preparedSignatures);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2020 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

package tech.pegasys.artemis.bls;

import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import org.apache.tuweni.bytes.Bytes;

/**
* Simple interface to enable pluggable variants of BLS verifier. In a {@link #SIMPLE} case it's
* just static {@link BLS} methods
*/
public interface BLSSignatureVerifier {

class InvalidSignatureException extends Exception {

public InvalidSignatureException(String message) {
super(message);
}
}

/** Just delegates verify to {@link BLS#fastAggregateVerify(List, Bytes, BLSSignature)} */
BLSSignatureVerifier SIMPLE = BLS::fastAggregateVerify;

/**
* Verifies an aggregate BLS signature against a message using the list of public keys. In case of
* non-aggregate signature [publicKeys] list should contain just a single entry
*
* @param publicKeys The list of public keys, not null
* @param message The message data to verify, not null
* @param signature The aggregate signature, not null
* @return True if the verification is successful, false otherwise
* @see BLS#fastAggregateVerify(List, Bytes, BLSSignature)
*/
boolean verify(List<BLSPublicKey> publicKeys, Bytes message, BLSSignature signature);

/** Shortcut to {@link #verify(List, Bytes, BLSSignature)} for non-aggregate case */
default boolean verify(BLSPublicKey publicKey, Bytes message, BLSSignature signature) {
return verify(Collections.singletonList(publicKey), message, signature);
}

/**
* Convenient shortcut to throw exception when signature verification fails
*
* @throws InvalidSignatureException when signature is invalid
*/
default void verifyAndThrow(
BLSPublicKey publicKey, Bytes message, BLSSignature signature, Supplier<String> errMessage)
throws InvalidSignatureException {
if (!verify(publicKey, message, signature)) {
throw new InvalidSignatureException(errMessage.get());
}
}

/**
* Convenient shortcut to throw exception when signature verification fails
*
* @throws InvalidSignatureException when signature is invalid
*/
default void verifyAndThrow(
BLSPublicKey publicKey, Bytes message, BLSSignature signature, String errMessage)
throws InvalidSignatureException {
verifyAndThrow(publicKey, message, signature, () -> errMessage);
}

/**
* Convenient shortcut to throw exception when signature verification fails
*
* @throws InvalidSignatureException when signature is invalid
*/
default void verifyAndThrow(BLSPublicKey publicKey, Bytes message, BLSSignature signature)
throws InvalidSignatureException {
verifyAndThrow(publicKey, message, signature, () -> "Invalid signature");
}
}
31 changes: 31 additions & 0 deletions bls/src/main/java/tech/pegasys/artemis/bls/mikuli/AtePairing.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ static GTPoint pair(G1Point p, G2Point q) {
return new GTPoint(PAIR.fexp(e));
}

/**
* Calculate the Ate pairing of points p and q but omits the final exponentiation ({@link
* #fexp(GTPoint)}) <code>
* pair() = fexp(pairNoExp())
* </code>
*
* @param p the point in Group1, not null
* @param q the point in Group2, not null
* @return GTPoint
*/
public static GTPoint pairNoExp(G1Point p, G2Point q) {
FP12 e = PAIR.ate(q.ecp2Point(), p.ecpPoint());
return new GTPoint(e);
}

/**
* Calculates the product of pairings while performing the final exponentiation only once. This
* ought to be more efficient.
Expand All @@ -46,4 +61,20 @@ static GTPoint pair2(G1Point p, G2Point q, G1Point r, G2Point s) {
FP12 e = PAIR.ate2(q.ecp2Point(), p.ecpPoint(), s.ecp2Point(), r.ecpPoint());
return new GTPoint(PAIR.fexp(e));
}

/**
* The same as {@link #pair2(G1Point, G2Point, G1Point, G2Point)} but omits the final
* exponentiation ({@link #fexp(GTPoint)}) <code>
* pair2() = fexp(pair2NoExp())
* </code>
*/
static GTPoint pair2NoExp(G1Point p, G2Point q, G1Point r, G2Point s) {
FP12 e = PAIR.ate2(q.ecp2Point(), p.ecpPoint(), s.ecp2Point(), r.ecpPoint());
return new GTPoint(e);
}

/** Calculates Final exponent */
static GTPoint fexp(GTPoint point) {
return new GTPoint(PAIR.fexp(point.getPoint()));
}
}
Loading