diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/CodeDelegation.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/CodeDelegation.java index 0fb8818fa58..0d7b3737866 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/CodeDelegation.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/CodeDelegation.java @@ -95,7 +95,7 @@ public static org.hyperledger.besu.datatypes.CodeDelegation createCodeDelegation Bytes.fromHexStringLenient(nonce).toLong(), SIGNATURE_ALGORITHM .get() - .createSignature( + .createCodeDelegationSignature( Bytes.fromHexStringLenient(r).toUnsignedBigInteger(), Bytes.fromHexStringLenient(s).toUnsignedBigInteger(), Bytes.fromHexStringLenient(v).get(0))); @@ -121,6 +121,12 @@ public SECPSignature signature() { @Override public Optional
authorizer() { + // recId needs to be between 0 and 3, otherwise the signature is invalid + // which means we can't recover the authorizer. + if (signature.getRecId() < 0 || signature.getRecId() > 3) { + return Optional.empty(); + } + return authorizerSupplier.get(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java index f8bb18b031c..d66ca97289e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java @@ -105,11 +105,6 @@ private void processCodeDelegation( return; } - if (codeDelegation.signature().getRecId() != 0 && codeDelegation.signature().getRecId() != 1) { - LOG.trace("Invalid signature for code delegation. RecId must be 0 or 1."); - return; - } - final Optional
authorizer = codeDelegation.authorizer(); if (authorizer.isEmpty()) { LOG.trace("Invalid signature for code delegation"); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/CodeDelegationTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/CodeDelegationTest.java new file mode 100644 index 00000000000..2da820e7555 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/CodeDelegationTest.java @@ -0,0 +1,169 @@ +/* + * 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.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.hyperledger.besu.crypto.KeyPair; +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 java.math.BigInteger; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class CodeDelegationTest { + + private BigInteger chainId; + private Address address; + private long nonce; + private SECPSignature signature; + private SignatureAlgorithm signatureAlgorithm; + + @BeforeEach + void setUp() { + chainId = BigInteger.valueOf(1); + address = Address.fromHexString("0x1234567890abcdef1234567890abcdef12345678"); + nonce = 100; + + signatureAlgorithm = SignatureAlgorithmFactory.getInstance(); + KeyPair keyPair = signatureAlgorithm.generateKeyPair(); + signature = signatureAlgorithm.sign(Bytes32.fromHexStringLenient("deadbeef"), keyPair); + } + + @Test + void shouldCreateCodeDelegationSuccessfully() { + CodeDelegation delegation = new CodeDelegation(chainId, address, nonce, signature); + + assertThat(delegation.chainId()).isEqualTo(chainId); + assertThat(delegation.address()).isEqualTo(address); + assertThat(delegation.nonce()).isEqualTo(nonce); + assertThat(delegation.signature()).isEqualTo(signature); + } + + @Test + void shouldBuildCodeDelegationWithBuilder() { + CodeDelegation delegation = + (CodeDelegation) + CodeDelegation.builder() + .chainId(chainId) + .address(address) + .nonce(nonce) + .signature(signature) + .build(); + + assertThat(delegation).isNotNull(); + assertThat(delegation.chainId()).isEqualTo(chainId); + assertThat(delegation.address()).isEqualTo(address); + assertThat(delegation.nonce()).isEqualTo(nonce); + assertThat(delegation.signature()).isEqualTo(signature); + } + + @Test + void shouldThrowWhenBuildingWithoutAddress() { + assertThatThrownBy( + () -> + CodeDelegation.builder().chainId(chainId).nonce(nonce).signature(signature).build()) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Address must be set"); + } + + @Test + void shouldThrowWhenBuildingWithoutNonce() { + assertThatThrownBy( + () -> + CodeDelegation.builder() + .chainId(chainId) + .address(address) + .signature(signature) + .build()) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Nonce must be set"); + } + + @Test + void shouldThrowWhenBuildingWithoutSignature() { + assertThatThrownBy( + () -> CodeDelegation.builder().chainId(chainId).address(address).nonce(nonce).build()) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Signature must be set"); + } + + @Test + void shouldCreateCodeDelegationUsingFactoryMethod() { + CodeDelegation delegation = + (CodeDelegation) + CodeDelegation.createCodeDelegation( + chainId, address, "0x64", "0x1b", "0xabcdef", "0x123456"); + + assertThat(delegation).isNotNull(); + assertThat(delegation.chainId()).isEqualTo(chainId); + assertThat(delegation.address()).isEqualTo(address); + assertThat(delegation.nonce()).isEqualTo(100); + } + + @Test + void shouldReturnAuthorizerWhenSignatureIsValid() { + CodeDelegation delegation = new CodeDelegation(chainId, address, nonce, signature); + + Optional
authorizer = delegation.authorizer(); + + assertThat(authorizer).isNotEmpty(); + } + + @Test + void shouldReturnEmptyAuthorizerWhenSignatureInvalid() { + SECPSignature invalidSignature = Mockito.mock(SECPSignature.class); + Mockito.when(invalidSignature.getRecId()).thenReturn((byte) 5); // Invalid recId (>3) + + CodeDelegation delegation = new CodeDelegation(chainId, address, nonce, invalidSignature); + + Optional
authorizer = delegation.authorizer(); + + assertThat(authorizer).isEmpty(); + } + + @Test + void shouldReturnCorrectSignatureValues() { + CodeDelegation delegation = new CodeDelegation(chainId, address, nonce, signature); + + assertThat(delegation.v()).isEqualTo(signature.getRecId()); + assertThat(delegation.r()).isEqualTo(signature.getR()); + assertThat(delegation.s()).isEqualTo(signature.getS()); + } + + @Test + void shouldSignAndBuildUsingKeyPair() { + KeyPair keyPair = signatureAlgorithm.generateKeyPair(); + + CodeDelegation delegation = + (CodeDelegation) + CodeDelegation.builder() + .chainId(chainId) + .address(address) + .nonce(nonce) + .signAndBuild(keyPair); + + assertThat(delegation).isNotNull(); + assertThat(delegation.signature()).isNotNull(); + } +}