Skip to content

Commit

Permalink
Authcall (hyperledger#7028)
Browse files Browse the repository at this point in the history
adds AUTH and AUTHCALL opcodes to Prague evms.
adds a mutable authorizedBy:Address field to the MessageFrame
adds AUTH opcode costing to gas calculators

---------

Signed-off-by: Justin Florentine <justin+github@florentine.us>
  • Loading branch information
jflo authored May 7, 2024
1 parent 87df2db commit 2e7b876
Show file tree
Hide file tree
Showing 9 changed files with 717 additions and 0 deletions.
6 changes: 6 additions & 0 deletions evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import org.hyperledger.besu.evm.operation.AddOperation;
import org.hyperledger.besu.evm.operation.AddressOperation;
import org.hyperledger.besu.evm.operation.AndOperation;
import org.hyperledger.besu.evm.operation.AuthCallOperation;
import org.hyperledger.besu.evm.operation.AuthOperation;
import org.hyperledger.besu.evm.operation.BalanceOperation;
import org.hyperledger.besu.evm.operation.BaseFeeOperation;
import org.hyperledger.besu.evm.operation.BlobBaseFeeOperation;
Expand Down Expand Up @@ -948,6 +950,10 @@ public static void registerPragueOperations(
final GasCalculator gasCalculator,
final BigInteger chainID) {
registerCancunOperations(registry, gasCalculator, chainID);

// EIP-3074 AUTH and AUTHCALL
registry.put(new AuthOperation(gasCalculator));
registry.put(new AuthCallOperation(gasCalculator));
}

/**
Expand Down
21 changes: 21 additions & 0 deletions evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ public enum Type {
/** The mark of the undoable collections at the creation of this message frame */
private final long undoMark;

/** mutated by AUTH operation */
private Address authorizedBy = null;

/**
* Builder builder.
*
Expand Down Expand Up @@ -1371,6 +1374,24 @@ public Optional<List<VersionedHash>> getVersionedHashes() {
return txValues.versionedHashes();
}

/**
* Accessor for address that authorized future AUTHCALLs.
*
* @return the revert reason
*/
public Address getAuthorizedBy() {
return authorizedBy;
}

/**
* Mutator for address that authorizes future AUTHCALLs, set by AUTH opcode
*
* @param authorizedBy the address that authorizes future AUTHCALLs
*/
public void setAuthorizedBy(final Address authorizedBy) {
this.authorizedBy = authorizedBy;
}

/** Reset. */
public void reset() {
maybeUpdatedMemory = Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,35 @@ long callOperationGasCost(
Address contract,
boolean accountIsWarm);

/**
* Returns the gas cost for AUTHCALL.
*
* @param frame The current frame
* @param stipend The gas stipend being provided by the CALL caller
* @param inputDataOffset The offset in memory to retrieve the CALL input data
* @param inputDataLength The CALL input data length
* @param outputDataOffset The offset in memory to place the CALL output data
* @param outputDataLength The CALL output data length
* @param transferValue The wei being transferred
* @param invoker The contract calling out on behalf of the authority
* @param invokee The address of the recipient (never null)
* @param accountIsWarm The address of the contract is "warm" as per EIP-2929
* @return The gas cost for the CALL operation
*/
default long authCallOperationGasCost(
final MessageFrame frame,
final long stipend,
final long inputDataOffset,
final long inputDataLength,
final long outputDataOffset,
final long outputDataLength,
final Wei transferValue,
final Account invoker,
final Address invokee,
final boolean accountIsWarm) {
return 0L;
}

/**
* Gets additional call stipend.
*
Expand Down Expand Up @@ -617,4 +646,18 @@ default long computeExcessBlobGas(final long parentExcessBlobGas, final int newB
default long computeExcessBlobGas(final long parentExcessBlobGas, final long blobGasUsed) {
return 0L;
}

/**
* Returns the gas cost of validating an auth commitment for an AUTHCALL
*
* @param frame the current frame, with memory to be read from
* @param offset start of memory read
* @param length amount of memory read
* @param authority address to check for warmup
* @return total gas cost for the operation
*/
default long authOperationGasCost(
final MessageFrame frame, final long offset, final long length, final Address authority) {
return 0L;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@

import static org.hyperledger.besu.datatypes.Address.KZG_POINT_EVAL;

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.frame.MessageFrame;

/**
* Gas Calculator for Prague
*
Expand All @@ -27,6 +32,8 @@
* </UL>
*/
public class PragueGasCalculator extends CancunGasCalculator {
private final int AUTH_OP_FIXED_FEE = 3100;
private final long AUTH_CALL_VALUE_TRANSFER_GAS_COST = 6700;

/** Instantiates a new Prague Gas Calculator. */
public PragueGasCalculator() {
Expand All @@ -41,4 +48,55 @@ public PragueGasCalculator() {
protected PragueGasCalculator(final int maxPrecompile) {
super(maxPrecompile);
}

@Override
public long authOperationGasCost(
final MessageFrame frame, final long offset, final long length, final Address authority) {
final long memoryExpansionGasCost = memoryExpansionGasCost(frame, offset, length);
final long accessFee = frame.isAddressWarm(authority) ? 100 : 2600;
final long gasCost = AUTH_OP_FIXED_FEE + memoryExpansionGasCost + accessFee;
return gasCost;
}

/**
* Returns the gas cost to call another contract on behalf of an authority
*
* @return the gas cost to call another contract on behalf of an authority
*/
@Override
public long authCallOperationGasCost(
final MessageFrame frame,
final long stipend,
final long inputDataOffset,
final long inputDataLength,
final long outputDataOffset,
final long outputDataLength,
final Wei transferValue,
final Account invoker,
final Address invokee,
final boolean accountIsWarm) {

final long inputDataMemoryExpansionCost =
memoryExpansionGasCost(frame, inputDataOffset, inputDataLength);
final long outputDataMemoryExpansionCost =
memoryExpansionGasCost(frame, outputDataOffset, outputDataLength);
final long memoryExpansionCost =
Math.max(inputDataMemoryExpansionCost, outputDataMemoryExpansionCost);

final long staticGasCost = getWarmStorageReadCost();

long dynamicGasCost = accountIsWarm ? 0 : getColdAccountAccessCost() - getWarmStorageReadCost();

if (!transferValue.isZero()) {
dynamicGasCost += AUTH_CALL_VALUE_TRANSFER_GAS_COST;
}

if ((invoker == null || invoker.isEmpty()) && !transferValue.isZero()) {
dynamicGasCost += newAccountGasCost();
}

long cost = staticGasCost + memoryExpansionCost + dynamicGasCost;

return cost;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 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.operation;

import static org.hyperledger.besu.evm.internal.Words.clampedToLong;

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.Words;

import org.apache.tuweni.bytes.Bytes32;

/** Introduced via EIP-3074 to call another contract with a different authorization context. */
public class AuthCallOperation extends AbstractCallOperation {

/**
* Instantiates a new AuthCallOperation.
*
* @param gasCalculator a Prague or later gas calculator
*/
public AuthCallOperation(final GasCalculator gasCalculator) {
super(0xF7, "AUTHCALL", 7, 1, gasCalculator);
}

@Override
protected Address to(final MessageFrame frame) {
return Words.toAddress(frame.getStackItem(1));
}

@Override
protected Wei value(final MessageFrame frame) {
return Wei.wrap(frame.getStackItem(2));
}

@Override
protected Wei apparentValue(final MessageFrame frame) {
return value(frame);
}

@Override
protected long inputDataOffset(final MessageFrame frame) {
return clampedToLong(frame.getStackItem(3));
}

@Override
protected long inputDataLength(final MessageFrame frame) {
return clampedToLong(frame.getStackItem(4));
}

@Override
protected long outputDataOffset(final MessageFrame frame) {
return clampedToLong(frame.getStackItem(5));
}

@Override
protected long outputDataLength(final MessageFrame frame) {
return clampedToLong(frame.getStackItem(6));
}

@Override
protected Address address(final MessageFrame frame) {
return to(frame);
}

@Override
protected Address sender(final MessageFrame frame) {
return frame.getAuthorizedBy();
}

@Override
public long gasAvailableForChildCall(final MessageFrame frame) {
return gasCalculator().gasAvailableForChildCall(frame, gas(frame), !value(frame).isZero());
}

@Override
public OperationResult execute(final MessageFrame frame, final EVM evm) {
if (frame.isStatic() && !value(frame).isZero()) {
return new OperationResult(cost(frame, true), ExceptionalHaltReason.ILLEGAL_STATE_CHANGE);
} else if (frame.getAuthorizedBy() != null) {
return super.execute(frame, evm);
} else {
frame.pushStackItem(Bytes32.ZERO);
return new OperationResult(cost(frame, true), null);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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.operation;

import static org.hyperledger.besu.evm.internal.Words.clampedToLong;

import org.hyperledger.besu.crypto.Hash;
import org.hyperledger.besu.crypto.SECPPublicKey;
import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.Words;

import java.util.Optional;

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** The AUTH operation. */
public class AuthOperation extends AbstractOperation {

/** The constant MAGIC defined by EIP-3074 */
public static final byte MAGIC = 0x4;

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

private static final SignatureAlgorithm signatureAlgorithm =
SignatureAlgorithmFactory.getInstance();

/**
* Instantiates a new AuthOperation.
*
* @param gasCalculator a Prague or later gas calculator
*/
public AuthOperation(final GasCalculator gasCalculator) {
super(0xF6, "AUTH", 3, 1, gasCalculator);
}

@Override
public OperationResult execute(final MessageFrame frame, final EVM evm) {
// create authority from stack
Address authority = Words.toAddress(frame.getStackItem(0));
long offset = clampedToLong(frame.getStackItem(1));
long length = clampedToLong(frame.getStackItem(2));

byte yParity = frame.readMemory(offset, 1).get(0);
Bytes32 r = Bytes32.wrap(frame.readMemory(offset + 1, 32));
Bytes32 s = Bytes32.wrap(frame.readMemory(offset + 33, 32));
Bytes32 commit = Bytes32.wrap(frame.readMemory(offset + 65, 32));
Bytes32 invoker = Bytes32.leftPad(frame.getContractAddress());
Bytes32 senderNonce =
Bytes32.leftPad(
Bytes.ofUnsignedLong(frame.getWorldUpdater().getAccount(authority).getNonce()));
if (evm.getChainId().isEmpty()) {
frame.pushStackItem(UInt256.ZERO);
LOG.error("ChainId is not set");
return new OperationResult(0, null);
}
Bytes authPreImage =
Bytes.concatenate(
Bytes.ofUnsignedShort(MAGIC), evm.getChainId().get(), senderNonce, invoker, commit);
Bytes32 messageHash = Hash.keccak256(authPreImage);

final long gasCost =
super.gasCalculator().authOperationGasCost(frame, offset, length, authority);
Optional<SECPPublicKey> publicKey;
try {
SECPSignature signature =
signatureAlgorithm.createSignature(
r.toUnsignedBigInteger(), s.toUnsignedBigInteger(), yParity);
publicKey = signatureAlgorithm.recoverPublicKeyFromSignature(messageHash, signature);
} catch (IllegalArgumentException e) {

frame.pushStackItem(UInt256.ZERO);
return new OperationResult(gasCost, null);
}
if (publicKey.isPresent()) {
Address signerAddress = Address.extract(publicKey.get());
if (signerAddress.equals(authority)) {
frame.setAuthorizedBy(authority);
frame.pushStackItem(UInt256.ONE);
} else {
frame.pushStackItem(UInt256.ZERO);
}
} else {
frame.pushStackItem(UInt256.ZERO);
}
return new OperationResult(gasCost, null);
}
}
Loading

0 comments on commit 2e7b876

Please sign in to comment.