Skip to content

Commit

Permalink
Falcon precompiled (#10)
Browse files Browse the repository at this point in the history
* Setup GitHub Actions build and Docker deploy

Signed-off-by: Diego López León <[email protected]>

* Add Falcon signature verification precompiled contract

Signed-off-by: Diego López León <[email protected]>

* add error handler for invalid format input in Falcon precompiled

Signed-off-by: eum602 <[email protected]>

* add benchmark for Falcon-512

Signed-off-by: eum602 <[email protected]>

* add falcon512 precompiled gas cost

Signed-off-by: eum602 <[email protected]>

* return value '1' instead of throwing on falcon precompiled

Signed-off-by: eum602 <[email protected]>

* fix: return precompiled error for malformed method signature

Signed-off-by: eum602 <[email protected]>

* rename variables and change License details

Signed-off-by: eum602 <[email protected]>

* update falcon Address

Signed-off-by: eum602 <[email protected]>

---------

Signed-off-by: eum602 <[email protected]>
Co-authored-by: Diego López León <[email protected]>
  • Loading branch information
eum602 and diega authored Nov 4, 2023
1 parent eef40bd commit d9239e1
Show file tree
Hide file tree
Showing 14 changed files with 316 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,13 @@ default boolean isConsensusMigration() {
*/
OptionalLong getThanosBlockNumber();

/**
* Block number to enable Falcon signature verifier precompiled feature.
*
* @return block number of falcon signature precompiled verifier
*/
OptionalLong getFalcon512BlockNumber();

/**
* Block number to activate Magneto on Classic networks.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,11 @@ public OptionalLong getThanosBlockNumber() {
return getOptionalLong("thanosblock");
}

@Override
public OptionalLong getFalcon512BlockNumber() {
return getOptionalLong("falcon512block");
}

@Override
public OptionalLong getMagnetoBlockNumber() {
return getOptionalLong("magnetoblock");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions, Cloneable
private OptionalLong istanbulBlockNumber = OptionalLong.empty();
private OptionalLong muirGlacierBlockNumber = OptionalLong.empty();
private OptionalLong berlinBlockNumber = OptionalLong.empty();
private final OptionalLong falcon512BlockNumber = OptionalLong.empty();
private OptionalLong londonBlockNumber = OptionalLong.empty();
private OptionalLong arrowGlacierBlockNumber = OptionalLong.empty();
private OptionalLong grayGlacierBlockNumber = OptionalLong.empty();
Expand Down Expand Up @@ -306,6 +307,11 @@ public OptionalLong getThanosBlockNumber() {
return thanosBlockNumber;
}

@Override
public OptionalLong getFalcon512BlockNumber() {
return falcon512BlockNumber;
}

@Override
public OptionalLong getMagnetoBlockNumber() {
return magnetoBlockNumber;
Expand Down
1 change: 1 addition & 0 deletions config/src/main/resources/dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"config": {
"chainId": 1337,
"londonBlock": 0,
"falcon512block": 0,
"contractSizeLimit": 2147483647,
"ethash": {
"fixeddifficulty": 100
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ public class Address extends DelegatingBytes {
public static final Address BLS12_MAP_FP_TO_G1 = Address.precompiled(0x12);
/** The constant BLS12_MAP_FP2_TO_G2. */
public static final Address BLS12_MAP_FP2_TO_G2 = Address.precompiled(0x13);
/** Constant for Precompiled Falcon verification signature. */
public static final Address FALCON512 = Address.precompiled(0x65);
/** The constant ZERO. */
public static final Address ZERO = Address.fromHexString("0x0");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright contributors to Hyperledger Besu
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.mainnet;

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.evm.precompile.FalconPrecompiledContract;
import org.hyperledger.besu.evm.precompile.PrecompileContractRegistry;

import java.math.BigInteger;
import java.util.Optional;
import java.util.OptionalInt;

public class Falcon512ProtocolSpecs {
public static ProtocolSpecBuilder postQuantumDefinition(
final Optional<BigInteger> chainId,
final OptionalInt contractSizeLimit,
final OptionalInt configStackSizeLimit,
final boolean enableRevertReason) {
return MainnetProtocolSpecs.istanbulDefinition(
chainId,
contractSizeLimit,
configStackSizeLimit,
enableRevertReason,
EvmConfiguration.DEFAULT)
.precompileContractRegistryBuilder(
precompiledContractConfiguration -> {
PrecompileContractRegistry falcon512ContractsRegistry =
MainnetPrecompiledContractRegistries.istanbul(precompiledContractConfiguration);
falcon512ContractsRegistry.put(
Address.FALCON512,
new FalconPrecompiledContract(
precompiledContractConfiguration.getGasCalculator()));
return falcon512ContractsRegistry;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ public ProtocolSpecBuilder experimentalEipsDefinition(
evmConfiguration);
}

public ProtocolSpecBuilder falcon512Definition() {
return Falcon512ProtocolSpecs.postQuantumDefinition(
chainId, contractSizeLimit, evmStackSize, isRevertReasonEnabled);
}

////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////
// Classic Protocol Specs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ private Stream<Optional<BuilderMapEntry>> createMilestones(
config.getGrayGlacierBlockNumber(), specFactory.grayGlacierDefinition(config)),
blockNumberMilestone(
config.getMergeNetSplitBlockNumber(), specFactory.parisDefinition(config)),
blockNumberMilestone(config.getFalcon512BlockNumber(), specFactory.falcon512Definition()),
// Timestamp Forks
timestampMilestone(config.getShanghaiTime(), specFactory.shanghaiDefinition(config)),
timestampMilestone(config.getCancunTime(), specFactory.cancunDefinition(config)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ public class FrontierGasCalculator implements GasCalculator {

private static final long SHA256_PRECOMPILED_WORD_GAS_COST = 12L;

private static final long FALCON512_VERIFY_PRECOMPILED_BASE_GAS_COST = 1465L;

private static final long FALCON512_VERIFY_PRECOMPILED_WORD_GAS_COST = 6L;

private static final long RIPEMD160_PRECOMPILED_WORD_GAS_COST = 120L;

private static final long RIPEMD160_PRECOMPILED_BASE_GAS_COST = 600L;
Expand Down Expand Up @@ -164,6 +168,12 @@ public long sha256PrecompiledContractGasCost(final Bytes input) {
+ SHA256_PRECOMPILED_BASE_GAS_COST;
}

@Override
public long falconVerifyPrecompiledContractGasCost(final Bytes input) {
return FALCON512_VERIFY_PRECOMPILED_WORD_GAS_COST * Words.numWords(input)
+ FALCON512_VERIFY_PRECOMPILED_BASE_GAS_COST;
}

@Override
public long ripemd160PrecompiledContractGasCost(final Bytes input) {
return RIPEMD160_PRECOMPILED_WORD_GAS_COST * Words.numWords(input)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.hyperledger.besu.evm.operation.SLoadOperation;
import org.hyperledger.besu.evm.operation.SelfDestructOperation;
import org.hyperledger.besu.evm.precompile.ECRECPrecompiledContract;
import org.hyperledger.besu.evm.precompile.FalconPrecompiledContract;
import org.hyperledger.besu.evm.precompile.IDPrecompiledContract;
import org.hyperledger.besu.evm.precompile.RIPEMD160PrecompiledContract;
import org.hyperledger.besu.evm.precompile.SHA256PrecompiledContract;
Expand Down Expand Up @@ -75,6 +76,14 @@ public interface GasCalculator {
*/
long getEcrecPrecompiledContractGasCost();

/**
* Returns the gas cost to execute the {@link FalconPrecompiledContract}.
*
* @param input The input representing the message, signature and the correspondent public key
* @return the gas cost to execute the Falcon Signature verification precompiled contract
*/
long falconVerifyPrecompiledContractGasCost(Bytes input);

/**
* Returns the gas cost to execute the {@link SHA256PrecompiledContract}.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright contributors to Hyperledger Besu
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.evm.precompile;

import static java.nio.charset.StandardCharsets.UTF_8;

import org.hyperledger.besu.crypto.Hash;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;

import java.util.Optional;
import javax.annotation.Nonnull;

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.bouncycastle.pqc.crypto.falcon.FalconParameters;
import org.bouncycastle.pqc.crypto.falcon.FalconPublicKeyParameters;
import org.bouncycastle.pqc.crypto.falcon.FalconSigner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** The Falcon precompiled contract. */
public class FalconPrecompiledContract extends AbstractPrecompiledContract {

private static final Logger LOG = LoggerFactory.getLogger(AbstractBLS12PrecompiledContract.class);

private static final Bytes METHOD_ABI =
Hash.keccak256(Bytes.of("verify(bytes,bytes,bytes)".getBytes(UTF_8))).slice(0, 4);
private static final String SIGNATURE_ALGORITHM = "Falcon-512";

private final FalconSigner falconSigner = new FalconSigner();

/**
* Instantiates a new Falcon precompiled contract.
*
* @param gasCalculator the gas calculator
*/
public FalconPrecompiledContract(final GasCalculator gasCalculator) {
super("Falcon", gasCalculator);
}

@Override
public long gasRequirement(final Bytes input) {
return gasCalculator().falconVerifyPrecompiledContractGasCost(input);
}

@Nonnull
@Override
public PrecompileContractResult computePrecompile(
final Bytes methodInput, @Nonnull final MessageFrame messageFrame) {
Bytes methodAbi;
try {
methodAbi = methodInput.slice(0, METHOD_ABI.size());
if (!methodAbi.xor(METHOD_ABI).isZero()) {
LOG.trace("Unexpected method ABI: " + methodAbi.toHexString());
return PrecompileContractResult.halt(
null, Optional.of(ExceptionalHaltReason.PRECOMPILE_ERROR));
}
} catch (Exception e) {
return PrecompileContractResult.halt(
null, Optional.of(ExceptionalHaltReason.PRECOMPILE_ERROR));
}
Bytes signatureSlice;
Bytes pubKeySlice;
Bytes dataSlice;

try {
Bytes input = methodInput.slice(METHOD_ABI.size());
int signatureOffset = input.slice(0, 32).trimLeadingZeros().toInt();
int pubKeyOffset = input.slice(32, 32).trimLeadingZeros().toInt();
int dataOffset = input.slice(64, 32).trimLeadingZeros().toInt();

int signatureLength = input.slice(signatureOffset, 32).trimLeadingZeros().toInt();
int pubKeyLength = input.slice(pubKeyOffset, 32).trimLeadingZeros().toInt();
int dataLength = input.slice(dataOffset, 32).trimLeadingZeros().toInt();

signatureSlice = input.slice(signatureOffset + 32, signatureLength);
pubKeySlice =
input.slice(
pubKeyOffset + 32 + 1,
pubKeyLength - 1); // BouncyCastle omits the first byte since it is always zero
dataSlice = input.slice(dataOffset + 32, dataLength);
} catch (Exception e) {
LOG.trace("Error executing Falcon-512 precompiled contract: '{}'", "invalid input");
return PrecompileContractResult.success(Bytes32.leftPad(Bytes.of(1)));
}

if (LOG.isTraceEnabled()) {
LOG.trace(
"{} verify: signature={}, pubKey={}, data={}",
SIGNATURE_ALGORITHM,
signatureSlice.toHexString(),
pubKeySlice.toHexString(),
dataSlice.toHexString());
}
FalconPublicKeyParameters falconPublicKeyParameters =
new FalconPublicKeyParameters(FalconParameters.falcon_512, pubKeySlice.toArray());
falconSigner.init(false, falconPublicKeyParameters);
final boolean verifies =
falconSigner.verifySignature(dataSlice.toArray(), signatureSlice.toArray());

if (verifies) {
LOG.debug("Signature is VALID");
return PrecompileContractResult.success(Bytes32.leftPad(Bytes.of(0)));
} else {
LOG.debug("Signature is INVALID");
return PrecompileContractResult.success(Bytes32.leftPad(Bytes.of(1)));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,15 @@
import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator;
import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableMap;
import org.apache.tuweni.bytes.Bytes;
Expand Down Expand Up @@ -628,6 +632,41 @@ private static void benchBLS12MapFP2TOG2() {
(int) gasSpent, contract.gasRequirement(arg));
}

private static void benchFalcon512() {
final FalconPrecompiledContract contract =
new FalconPrecompiledContract(new IstanbulGasCalculator());
final JsonNode falconVectors;
try (final InputStream testVectors =
Benchmarks.class.getResourceAsStream("falconBenchVectors.json")) {
falconVectors = new ObjectMapper().readTree(testVectors);

} catch (IOException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < MATH_WARMUP; i++) {
contract.computePrecompile(
Bytes.fromHexString(falconVectors.get(0).get("Input").asText()), fakeFrame);
}
for (int len = 0; len < falconVectors.size(); len += 1) {
Bytes bytes = Bytes.fromHexString(falconVectors.get(len).get("Input").asText());
final Stopwatch timer = Stopwatch.createStarted();
for (int i = 0; i < HASH_ITERATIONS; i++) {
contract.computePrecompile(bytes, fakeFrame);
}
timer.stop();

final double elapsed = timer.elapsed(TimeUnit.NANOSECONDS) / 1.0e9D;
final double perCall = elapsed / HASH_ITERATIONS;
final double gasSpent = perCall * GAS_PER_SECOND_STANDARD;
System.out.printf(
"falcon512 - iter #%,d - %,d message bytes for %,d gas. Charging %,d gas.%n",
len,
(len + 1) * 33,
(int) gasSpent,
contract.gasRequirement(bytes)); // every message increases in 33 bytes
}
}

private static double runBenchmark(final Bytes arg, final PrecompiledContract contract) {
if (contract.computePrecompile(arg, fakeFrame).getOutput() == null) {
throw new RuntimeException("Input is Invalid");
Expand Down Expand Up @@ -665,5 +704,6 @@ public static void main(final String[] args) {
benchBLS12Pair();
benchBLS12MapFPTOG1();
benchBLS12MapFP2TOG2();
benchFalcon512();
}
}
Loading

0 comments on commit d9239e1

Please sign in to comment.