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

Implement a wrapper around c-kzg jni bindings #6520

Merged
merged 7 commits into from
Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
7 changes: 7 additions & 0 deletions infrastructure/kzg/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
repositories {
maven { url "https://artifacts.consensys.net/public/maven/maven/" }
}

dependencies {
implementation 'org.apache.tuweni:tuweni-bytes'
implementation 'org.apache.tuweni:tuweni-ssz'
implementation("tech.pegasys:jc-kzg-4844:develop")
implementation 'commons-io:commons-io'

implementation project(":infrastructure:io")
}
118 changes: 118 additions & 0 deletions infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright ConsenSys Software Inc., 2022
*
* 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.teku.kzg;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import tech.pegasys.teku.infrastructure.io.resource.ResourceLoader;
import tech.pegasys.teku.kzg.impl.KZG4844;
import tech.pegasys.teku.kzg.impl.KzgException;
import tech.pegasys.teku.kzg.impl.ckzg.CkzgLoader;

/**
* Implements the standard KZG functions needed for the EIP-4844 specification.
*
* <p>This package strives to implement the KZG standard as used in the Eth2 specification and is
* the entry-point for all KZG operations in Teku. Do not rely on any of the classes used by this
* one conforming to the specification or standard.
*/
public final class KZG {
private static final Logger LOG = LogManager.getLogger();

private static KZG4844 kzgImpl;

static {
resetKzgImplementation();
}

public static void setKzgImplementation(final KZG4844 kzgImpl) {
KZG.kzgImpl = kzgImpl;
}

public static void resetKzgImplementation() {
if (CkzgLoader.INSTANCE.isPresent()) {
kzgImpl = CkzgLoader.INSTANCE.get();
LOG.info("KZG: loaded CKZG library");
} else {
throw new KzgException("Failed to load CKZG library.");
}
}

public static KZG4844 getKzgImpl() {
return kzgImpl;
}

public static void resetTrustedSetup() {
try {
kzgImpl.resetTrustedSetup();
} catch (final KzgException ex) {
LOG.trace("Trying to reset KZG trusted setup which is not loaded");
}
}

public static void loadTrustedSetup(final String path) {
final String filePath;
try {
filePath = copyResourceToTempFile(path);
} catch (final IOException ex) {
throw new KzgException(
String.format("Failed to copy resource '%s' to temporary file", path), ex);
}
kzgImpl.loadTrustedSetup(filePath);
}

public static KZGProof computeAggregateKzgProof(final List<Bytes> blobs) {
return kzgImpl.computeAggregateKzgProof(blobs);
}

public static boolean verifyAggregateKzgProof(
final List<Bytes> blobs, final List<KZGCommitment> kzgCommitments, final KZGProof kzgProof) {
return kzgImpl.verifyAggregateKzgProof(blobs, kzgCommitments, kzgProof);
}

public static KZGCommitment blobToKzgCommitment(final Bytes blob) {
return kzgImpl.blobToKzgCommitment(blob);
}

public static boolean verifyKzgProof(
final KZGCommitment kzgCommitment,
final Bytes32 z,
final Bytes32 y,
final KZGProof kzgProof) {
return kzgImpl.verifyKzgProof(kzgCommitment, z, y, kzgProof);
}

private static String copyResourceToTempFile(final String path) throws IOException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we pass-through the file if it is a local path?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but I guess we will need absolute path
also how could I recognize it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is you switch path from String to URI and then getScheme().equalsIgnoreCase("file") ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you
also I decided to pass URL there instead of String, so it's clear that it comes with scheme and not just path

final Bytes resource =
ResourceLoader.urlOrFile("application/octet-stream")
.loadBytes(path)
.orElseThrow(() -> new FileNotFoundException("Not found"));

File temp = File.createTempFile("resource", ".tmp");

Check warning

Code scanning / CodeQL

Local information disclosure in a temporary directory

Local information disclosure vulnerability due to use of file readable by other local users.
temp.deleteOnExit();

try (final FileOutputStream out = new FileOutputStream(temp)) {
out.write(resource.toArray());
}

return temp.getAbsolutePath();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright ConsenSys Software Inc., 2022
*
* 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.teku.kzg.impl;

import java.util.List;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import tech.pegasys.teku.kzg.KZGCommitment;
import tech.pegasys.teku.kzg.KZGProof;

public interface KZG4844 {

/**
* Free the current trusted setup
*
* @throws KzgException if no trusted setup has been loaded.
*/
void resetTrustedSetup() throws KzgException;

/**
* Loads the trusted setup from a file. Once loaded, the same setup will be used for all the
* calls. To load a new setup, reset the current one by calling {@link #resetTrustedSetup()} and
* then load the new one. If no trusted setup has been loaded, all other API calls will throw an
* exception.
*
* @param path a path to a trusted setup file in filesystem
* @throws KzgException if file not found or arguments from file are incorrect
*/
void loadTrustedSetup(String path) throws KzgException;

/**
* Calculates aggregated proof for the given blobs
*
* @param blobs Blobs
* @return the aggregated proof
*/
KZGProof computeAggregateKzgProof(List<Bytes> blobs) throws KzgException;

/**
* Verify aggregated proof and commitments for the given blobs
*
* @param blobs Blobs
* @param kzgCommitments KZG Commitments
* @param kzgProof The proof that needs verifying
* @return true if the proof is valid and false otherwise
*/
boolean verifyAggregateKzgProof(
List<Bytes> blobs, List<KZGCommitment> kzgCommitments, KZGProof kzgProof) throws KzgException;

/**
* Calculates commitment for a given blob
*
* @param blob Blob
* @return the commitment
*/
KZGCommitment blobToKzgCommitment(Bytes blob) throws KzgException;

/**
* Verify the proof by point evaluation for the given commitment
*
* @param kzgCommitment KZG Commitment
* @param z Z
* @param y Y
* @param kzgProof The proof that needs verifying
* @return true if the proof is valid and false otherwise
*/
boolean verifyKzgProof(KZGCommitment kzgCommitment, Bytes32 z, Bytes32 y, KZGProof kzgProof)
throws KzgException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright ConsenSys Software Inc., 2022
*
* 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.teku.kzg.impl;

public class KzgException extends IllegalArgumentException {

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

public KzgException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright ConsenSys Software Inc., 2022
*
* 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.teku.kzg.impl.ckzg;

import ethereum.ckzg4844.CKzg4844JNI;
import java.util.List;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.bytes.Bytes48;
import tech.pegasys.teku.kzg.KZGCommitment;
import tech.pegasys.teku.kzg.KZGProof;
import tech.pegasys.teku.kzg.impl.KZG4844;
import tech.pegasys.teku.kzg.impl.KzgException;

/**
* Wrapper around JNI C-KZG library implementing stripped down KZG specification needed for EIP-4844
*/
public final class CkzgKZG4844 implements KZG4844 {

@Override
public void resetTrustedSetup() throws KzgException {
try {
CKzg4844JNI.freeTrustedSetup();
} catch (final Exception ex) {
throw new KzgException("Failed to reset trusted setup", ex);
}
}

@Override
public void loadTrustedSetup(final String path) throws KzgException {
try {
CKzg4844JNI.loadTrustedSetup(path);
} catch (final Exception ex) {
throw new KzgException(String.format("Failed to load trusted setup: %s", path), ex);
}
}

@Override
public KZGProof computeAggregateKzgProof(final List<Bytes> blobs) throws KzgException {
try {
final byte[] result =
CKzg4844JNI.computeAggregateKzgProof(
blobs.stream().reduce(Bytes::wrap).orElseThrow().toArray(), blobs.size());
return KZGProof.fromBytesCompressed(Bytes48.wrap(result));
} catch (final Exception ex) {
throw new KzgException("Failed to compute aggregated KZG Proof for Blobs", ex);
}
}

@Override
public boolean verifyAggregateKzgProof(
final List<Bytes> blobs, final List<KZGCommitment> kzgCommitments, final KZGProof kzgProof)
throws KzgException {
try {
return CKzg4844JNI.verifyAggregateKzgProof(
blobs.stream().reduce(Bytes::wrap).orElseThrow().toArray(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty blobs and commitments are actually a thing: ethereum/consensus-specs#3093
I think we should cover the case here and in tests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, doing it already, nice catch!

kzgCommitments.stream()
.map(kzgCommitment -> (Bytes) kzgCommitment.getBytesCompressed())
.reduce(Bytes::wrap)
.orElseThrow()
.toArray(),
blobs.size(),
kzgProof.getBytesCompressed().toArray());
} catch (final Exception ex) {
throw new KzgException(
String.format("Failed to verify blobs against KZG Proof %s", kzgProof), ex);
}
}

@Override
public KZGCommitment blobToKzgCommitment(final Bytes blob) throws KzgException {
try {
return KZGCommitment.fromBytesCompressed(
Bytes48.wrap(CKzg4844JNI.blobToKzgCommitment(blob.toArray())));
} catch (final Exception ex) {
throw new KzgException("Failed to produce KZG Commitment from Blob", ex);
}
}

@Override
public boolean verifyKzgProof(
final KZGCommitment kzgCommitment, final Bytes32 z, final Bytes32 y, final KZGProof kzgProof)
throws KzgException {
try {
return CKzg4844JNI.verifyKzgProof(
kzgCommitment.getBytesCompressed().toArray(),
z.toArray(),
y.toArray(),
kzgProof.getBytesCompressed().toArray());
} catch (final Exception ex) {
throw new KzgException(
String.format(
"Failed to verify KZG Commitment %s against KZG Proof %s", kzgCommitment, kzgProof),
ex);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright ConsenSys Software Inc., 2022
*
* 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.teku.kzg.impl.ckzg;

import java.lang.reflect.InvocationTargetException;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import tech.pegasys.teku.kzg.impl.KZG4844;

public final class CkzgLoader {

private static final Logger LOG = LogManager.getLogger();
public static final Optional<KZG4844> INSTANCE = loadCkzg();

private static Optional<KZG4844> loadCkzg() {
try {
final Class<?> kzgClass = Class.forName("tech.pegasys.teku.kzg.impl.ckzg.CkzgKZG4844");
return Optional.of((KZG4844) kzgClass.getDeclaredConstructor().newInstance());
} catch (final InstantiationException
| ExceptionInInitializerError
| InvocationTargetException
| NoSuchMethodException
| IllegalAccessException
| ClassNotFoundException e) {
LOG.error("Couldn't load native KZG library", e);
return Optional.empty();
}
}
}
Loading