From a6bab2da327d178e1620284186b8de2bfd3b4966 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 21 Nov 2024 14:43:15 +1000 Subject: [PATCH 1/8] Refactor EthPublicKeyUtils to convert public key between Java, Bouncycastle and Web3J libraries --- ...SecpV3KeystoresBulkLoadAcceptanceTest.java | 3 +- .../publickeys/YubiHsmKeysAcceptanceTest.java | 2 +- .../signing/SecpSigningAcceptanceTest.java | 5 +- .../core/Eth1AddressSignerIdentifier.java | 7 +- .../Eth1AddressSignerIdentifierTest.java | 64 ++++- .../EthAccountsResultProviderTest.java | 64 ++--- .../EthSignTransactionResultProviderTest.java | 6 +- .../web3signer/core/util/PublicKeyUtils.java | 27 -- .../signing/secp256k1/EthPublicKeyUtils.java | 263 +++++++++++++++--- .../secp256k1/azure/AzureKeyVaultSigner.java | 2 +- .../secp256k1/filebased/CredentialSigner.java | 3 +- .../secp256k1/util/Eth1SignatureUtil.java | 3 +- .../DefaultArtifactSignerProviderTest.java | 9 +- .../secp256k1/EthPublicKeyUtilsTest.java | 146 +++++++--- .../secp256k1/aws/AwsKmsSignerTest.java | 3 +- .../azure/AzureKeyVaultSignerTest.java | 2 +- 16 files changed, 439 insertions(+), 170 deletions(-) delete mode 100644 core/src/test/java/tech/pegasys/web3signer/core/util/PublicKeyUtils.java diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java index 1ef6afba1..b9b4b2a9d 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java @@ -60,7 +60,8 @@ static void initV3Keystores() throws IOException, GeneralSecurityException, Ciph publicKeys = new ArrayList<>(); for (int i = 0; i < 4; i++) { final ECKeyPair ecKeyPair = Keys.createEcKeyPair(); - final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(ecKeyPair.getPublicKey()); + final ECPublicKey ecPublicKey = + EthPublicKeyUtils.bigIntegerToECPublicKey(ecKeyPair.getPublicKey()); final String publicKeyHex = IdentifierUtils.normaliseIdentifier(EthPublicKeyUtils.toHexString(ecPublicKey)); publicKeys.add(publicKeyHex); diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java index 297ad1b13..37dec504d 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java @@ -121,7 +121,7 @@ private void createConfigurationFiles(final Set opaqueDataIds, final Ke private String getPublicKey(final String key) { return normaliseIdentifier( EthPublicKeyUtils.toHexString( - EthPublicKeyUtils.createPublicKey( + EthPublicKeyUtils.bigIntegerToECPublicKey( Credentials.create(key).getEcKeyPair().getPublicKey()))); } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SecpSigningAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SecpSigningAcceptanceTest.java index a4d5769c8..9660d885c 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SecpSigningAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SecpSigningAcceptanceTest.java @@ -188,13 +188,14 @@ private void signAndVerifySignature(final String publicKeyHex) { void verifySignature(final Bytes signature, final String publicKeyHex) { final ECPublicKey expectedPublicKey = - EthPublicKeyUtils.createPublicKey(Bytes.fromHexString(publicKeyHex)); + EthPublicKeyUtils.bytesToECPublicKey(Bytes.fromHexString(publicKeyHex)); final byte[] r = signature.slice(0, 32).toArray(); final byte[] s = signature.slice(32, 32).toArray(); final byte[] v = signature.slice(64).toArray(); final BigInteger messagePublicKey = recoverPublicKey(new SignatureData(v, r, s)); - assertThat(EthPublicKeyUtils.createPublicKey(messagePublicKey)).isEqualTo(expectedPublicKey); + assertThat(EthPublicKeyUtils.bigIntegerToECPublicKey(messagePublicKey)) + .isEqualTo(expectedPublicKey); } private BigInteger recoverPublicKey(final SignatureData signature) { diff --git a/core/src/main/java/tech/pegasys/web3signer/core/Eth1AddressSignerIdentifier.java b/core/src/main/java/tech/pegasys/web3signer/core/Eth1AddressSignerIdentifier.java index 4a1ad0892..14f711301 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/Eth1AddressSignerIdentifier.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/Eth1AddressSignerIdentifier.java @@ -13,6 +13,7 @@ package tech.pegasys.web3signer.core; import static org.web3j.crypto.Keys.getAddress; +import static tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils.ecPublicKeyToBigInteger; import static tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils.toHexString; import static tech.pegasys.web3signer.signing.secp256k1.util.AddressUtil.remove0xPrefix; @@ -31,11 +32,7 @@ public Eth1AddressSignerIdentifier(final String address) { } public static SignerIdentifier fromPublicKey(final ECPublicKey publicKey) { - return new Eth1AddressSignerIdentifier(getAddress(toHexString(publicKey))); - } - - public static SignerIdentifier fromPublicKey(final String publicKey) { - return new Eth1AddressSignerIdentifier(getAddress(publicKey)); + return new Eth1AddressSignerIdentifier(getAddress(ecPublicKeyToBigInteger(publicKey))); } @Override diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java index b33695eb4..cc7b5e687 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java @@ -15,53 +15,89 @@ import static org.assertj.core.api.Assertions.assertThat; import tech.pegasys.web3signer.core.Eth1AddressSignerIdentifier; -import tech.pegasys.web3signer.core.util.PublicKeyUtils; import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; import tech.pegasys.web3signer.signing.secp256k1.SignerIdentifier; -import tech.pegasys.web3signer.signing.secp256k1.util.AddressUtil; +import java.security.KeyPair; +import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; -import java.util.Locale; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.web3j.crypto.Keys; class Eth1AddressSignerIdentifierTest { + private static KeyPair secp256k1KeyPair; + private static KeyPair secp256k1KeyPair2; + + @BeforeAll + static void generateKeyPair() throws Exception { + final SecureRandom random = new SecureRandom(); + secp256k1KeyPair = EthPublicKeyUtils.createSecp256k1KeyPair(random); + secp256k1KeyPair2 = EthPublicKeyUtils.createSecp256k1KeyPair(random); + } @Test void prefixIsRemovedFromAddress() { - final Eth1AddressSignerIdentifier signerIdentifier = new Eth1AddressSignerIdentifier("0xAb"); - assertThat(signerIdentifier.toStringIdentifier()).isEqualTo("ab"); + // web3j.crypto.Keys.getAddress() returns lower case address without 0x prefix + final String address = + Keys.getAddress( + EthPublicKeyUtils.ecPublicKeyToBigInteger((ECPublicKey) secp256k1KeyPair.getPublic())); + // forcefully convert first two alphabets to uppercase and add prefix + final String mixCaseAddress = "0X" + convertAlphabetsToUpperCase(address); + + final Eth1AddressSignerIdentifier signerIdentifier = + new Eth1AddressSignerIdentifier(mixCaseAddress); + assertThat(signerIdentifier.toStringIdentifier()).isEqualTo(address); + assertThat(signerIdentifier.toStringIdentifier()).doesNotStartWithIgnoringCase("0x"); + assertThat(signerIdentifier.toStringIdentifier()).isLowerCase(); } @Test void validateWorksForSamePrimaryKey() { - final ECPublicKey publicKey = PublicKeyUtils.createKeyFrom("0xab"); + final ECPublicKey publicKey = (ECPublicKey) secp256k1KeyPair.getPublic(); final SignerIdentifier signerIdentifier = Eth1AddressSignerIdentifier.fromPublicKey(publicKey); assertThat(signerIdentifier.validate(publicKey)).isTrue(); } @Test void validateFailsForDifferentPrimaryKey() { - final ECPublicKey publicKey = PublicKeyUtils.createKeyFrom("0xab"); + final ECPublicKey publicKey = (ECPublicKey) secp256k1KeyPair.getPublic(); final SignerIdentifier signerIdentifier = Eth1AddressSignerIdentifier.fromPublicKey(publicKey); - assertThat(signerIdentifier.validate(PublicKeyUtils.createKeyFrom("0xbb"))).isFalse(); + assertThat(signerIdentifier.validate((ECPublicKey) secp256k1KeyPair2.getPublic())).isFalse(); } @Test void validateFailsForNullPrimaryKey() { - final ECPublicKey publicKey = PublicKeyUtils.createKeyFrom("0xab"); + final ECPublicKey publicKey = (ECPublicKey) secp256k1KeyPair.getPublic(); final SignerIdentifier signerIdentifier = Eth1AddressSignerIdentifier.fromPublicKey(publicKey); assertThat(signerIdentifier.validate(null)).isFalse(); } @Test void correctEth1AddressIsGeneratedFromPublicKey() { - final ECPublicKey publicKey = PublicKeyUtils.createKeyFrom("0xab"); + final ECPublicKey publicKey = (ECPublicKey) secp256k1KeyPair.getPublic(); final SignerIdentifier signerIdentifier = Eth1AddressSignerIdentifier.fromPublicKey(publicKey); - final String prefixRemovedAddress = - AddressUtil.remove0xPrefix( - Keys.getAddress(EthPublicKeyUtils.toHexString(publicKey)).toLowerCase(Locale.US)); - assertThat(signerIdentifier.toStringIdentifier()).isEqualTo(prefixRemovedAddress); + + // web3j.crypto.Keys.getAddress() returns lower case address without 0x prefix + final String expectedAddress = + Keys.getAddress(EthPublicKeyUtils.ecPublicKeyToBigInteger(publicKey)); + assertThat(signerIdentifier.toStringIdentifier()).isEqualTo(expectedAddress); + assertThat(signerIdentifier.toStringIdentifier()).doesNotStartWithIgnoringCase("0x"); + assertThat(signerIdentifier.toStringIdentifier()).isLowerCase(); + } + + public static String convertAlphabetsToUpperCase(final String input) { + final char[] chars = input.toCharArray(); + int count = 0; + + for (int i = 0; i < chars.length && count < 2; i++) { + if (Character.isLetter(chars[i]) && Character.isLowerCase(chars[i])) { + chars[i] = Character.toUpperCase(chars[i]); + count++; + } + } + + return new String(chars); } } diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java index 6923303dd..f83b4a0a0 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java @@ -22,30 +22,39 @@ import tech.pegasys.web3signer.core.service.jsonrpc.handlers.internalresponse.EthAccountsResultProvider; import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; import java.util.List; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import com.google.common.collect.Sets; -import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.web3j.crypto.Keys; -@SuppressWarnings("unchecked") public class EthAccountsResultProviderTest { - - final ECPublicKey publicKeyA = createKeyFrom("A".repeat(128)); - final ECPublicKey publicKeyB = createKeyFrom("B".repeat(128)); - final ECPublicKey publicKeyC = createKeyFrom("C".repeat(128)); - - final String addressA = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyA)); - final String addressB = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyB)); - final String addressC = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyC)); - - final ECPublicKey createKeyFrom(final String hexString) { - return EthPublicKeyUtils.createPublicKey(Bytes.fromHexString(hexString)); + private ECPublicKey publicKeyA; + private ECPublicKey publicKeyB; + private ECPublicKey publicKeyC; + + private String addressA; + private String addressB; + private String addressC; + + @BeforeEach + public void init() throws GeneralSecurityException { + SecureRandom secureRandom = new SecureRandom(); + publicKeyA = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(secureRandom).getPublic(); + publicKeyB = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(secureRandom).getPublic(); + publicKeyC = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(secureRandom).getPublic(); + + addressA = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyA)); + addressB = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyB)); + addressC = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyC)); } @Test @@ -103,9 +112,7 @@ public void missingParametersIsOk() { final JsonRpcRequest request = new JsonRpcRequest("2.0", "eth_accounts"); request.setId(new JsonRpcRequestId(id)); - final Object body = resultProvider.createResponseResult(request); - assertThat(body).isInstanceOf(List.class); - final List addressses = (List) body; + final List addressses = resultProvider.createResponseResult(request); assertThat(addressses).containsExactly("0x" + addressA); } @@ -120,10 +127,7 @@ public void multipleValueFromBodyProviderInsertedToResult() { request.setId(new JsonRpcRequestId(id)); request.setParams(emptyList()); - final Object body = resultProvider.createResponseResult(request); - - assertThat(body).isInstanceOf(List.class); - final List reportedAddresses = (List) body; + final List reportedAddresses = resultProvider.createResponseResult(request); assertThat(reportedAddresses) .containsExactlyInAnyOrder("0x" + addressA, "0x" + addressB, "0x" + addressC); } @@ -139,25 +143,19 @@ public void accountsReturnedAreDynamicallyFetchedFromProvider() { request.setId(new JsonRpcRequestId(1)); request.setParams(emptyList()); - Object body = resultProvider.createResponseResult(request); - assertThat(body).isInstanceOf(List.class); - List reportedAddresses = (List) body; + List reportedAddresses = resultProvider.createResponseResult(request); assertThat(reportedAddresses) .containsExactlyElementsOf( - List.of("0x" + addressA, "0x" + addressB, "0x" + addressC).stream() + Stream.of("0x" + addressA, "0x" + addressB, "0x" + addressC) .sorted() .collect(Collectors.toList())); addresses.remove(publicKeyA); - body = resultProvider.createResponseResult(request); - assertThat(body).isInstanceOf(List.class); - reportedAddresses = (List) body; + reportedAddresses = resultProvider.createResponseResult(request); assertThat(reportedAddresses) .containsExactlyElementsOf( - List.of("0x" + addressB, "0x" + addressC).stream() - .sorted() - .collect(Collectors.toList())); + Stream.of("0x" + addressB, "0x" + addressC).sorted().collect(Collectors.toList())); } @Test @@ -169,12 +167,10 @@ public void accountsReturnedAreSortedAlphabetically() { request.setId(new JsonRpcRequestId(1)); request.setParams(emptyList()); - final Object body = resultProvider.createResponseResult(request); - assertThat(body).isInstanceOf(List.class); - List reportedAddresses = (List) body; + List reportedAddresses = resultProvider.createResponseResult(request); assertThat(reportedAddresses) .containsExactlyElementsOf( - List.of("0x" + addressA, "0x" + addressB, "0x" + addressC).stream() + Stream.of("0x" + addressA, "0x" + addressB, "0x" + addressC) .sorted() .collect(Collectors.toList())); } diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java index e0aab9d00..59ddc1016 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java @@ -108,7 +108,8 @@ public void ifAddressIsNotUnlockedExceptionIsThrownWithSigningNotUnlocked() { public void signatureHasTheExpectedFormat() { final Credentials cs = Credentials.create("0x1618fc3e47aec7e70451256e033b9edb67f4c469258d8e2fbb105552f141ae41"); - final ECPublicKey key = EthPublicKeyUtils.createPublicKey(cs.getEcKeyPair().getPublicKey()); + final ECPublicKey key = + EthPublicKeyUtils.bigIntegerToECPublicKey(cs.getEcKeyPair().getPublicKey()); final String addr = Keys.getAddress(EthPublicKeyUtils.toHexString(key)); final BigInteger v = BigInteger.ONE; @@ -169,7 +170,8 @@ public void returnsExpectedSignatureForEip1559Transaction() { private String executeEthSignTransaction(final JsonObject params) { final Credentials cs = Credentials.create("0x1618fc3e47aec7e70451256e033b9edb67f4c469258d8e2fbb105552f141ae41"); - final ECPublicKey key = EthPublicKeyUtils.createPublicKey(cs.getEcKeyPair().getPublicKey()); + final ECPublicKey key = + EthPublicKeyUtils.bigIntegerToECPublicKey(cs.getEcKeyPair().getPublicKey()); final String addr = Keys.getAddress(EthPublicKeyUtils.toHexString(key)); doAnswer( diff --git a/core/src/test/java/tech/pegasys/web3signer/core/util/PublicKeyUtils.java b/core/src/test/java/tech/pegasys/web3signer/core/util/PublicKeyUtils.java deleted file mode 100644 index 7f639069f..000000000 --- a/core/src/test/java/tech/pegasys/web3signer/core/util/PublicKeyUtils.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.web3signer.core.util; - -import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; - -import java.security.interfaces.ECPublicKey; - -import org.apache.tuweni.bytes.Bytes; - -public class PublicKeyUtils { - - public static ECPublicKey createKeyFrom(final String hexString) { - Bytes bytes = Bytes.fromHexString(hexString, 64); - return EthPublicKeyUtils.createPublicKey(bytes); - } -} diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java index 2ae294201..8108bb582 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java @@ -12,63 +12,254 @@ */ package tech.pegasys.web3signer.signing.secp256k1; -import static com.google.common.base.Preconditions.checkArgument; -import static org.bouncycastle.util.BigIntegers.asUnsignedByteArray; - import java.math.BigInteger; -import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPublicKeySpec; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.EllipticCurve; import java.security.spec.InvalidKeySpecException; -import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.web3j.utils.Numeric; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.ec.CustomNamedCurves; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.web3j.crypto.ECKeyPair; +/** + * Utility class for working with secp256k1 public keys. This class provides methods for converting + * between Java and Web3J library based SECP keys. + */ public class EthPublicKeyUtils { - private static final int PUBLIC_KEY_SIZE = 64; + private static final BouncyCastleProvider BC_PROVIDER = new BouncyCastleProvider(); + private static final ECDomainParameters SECP256K1_DOMAIN; + private static final ECParameterSpec BC_SECP256K1_SPEC; + private static final java.security.spec.ECParameterSpec JAVA_SECP256K1_SPEC; + private static final String SECP256K1_CURVE = "secp256k1"; + private static final String EC_ALGORITHM = "EC"; + + static { + final X9ECParameters params = CustomNamedCurves.getByName(SECP256K1_CURVE); + SECP256K1_DOMAIN = + new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); + BC_SECP256K1_SPEC = + new ECParameterSpec(params.getCurve(), params.getG(), params.getN(), params.getH()); + final ECCurve bcCurve = BC_SECP256K1_SPEC.getCurve(); + JAVA_SECP256K1_SPEC = + new java.security.spec.ECParameterSpec( + new EllipticCurve( + new java.security.spec.ECFieldFp(bcCurve.getField().getCharacteristic()), + bcCurve.getA().toBigInteger(), + bcCurve.getB().toBigInteger()), + new java.security.spec.ECPoint( + BC_SECP256K1_SPEC.getG().getAffineXCoord().toBigInteger(), + BC_SECP256K1_SPEC.getG().getAffineYCoord().toBigInteger()), + BC_SECP256K1_SPEC.getN(), + BC_SECP256K1_SPEC.getH().intValue()); + } + + /** + * Create a new secp256k1 key pair. + * + * @param random The random number generator to use + * @return The generated java security key pair + * @throws GeneralSecurityException If there is an issue generating the key pair + */ + public static KeyPair createSecp256k1KeyPair(final SecureRandom random) + throws GeneralSecurityException { + final KeyPairGenerator keyPairGenerator = + KeyPairGenerator.getInstance(EC_ALGORITHM, BC_PROVIDER); + final ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(SECP256K1_CURVE); + if (random != null) { + keyPairGenerator.initialize(ecGenParameterSpec, random); + } else { + keyPairGenerator.initialize(ecGenParameterSpec); + } + + return keyPairGenerator.generateKeyPair(); + } - public static ECPublicKey createPublicKey(final ECPoint publicPoint) { + /** + * Convert a Web3J ECKeyPair to a Java security KeyPair using SECP256K1 curve. + * + * @param web3JECKeypair The Web3J keypair to convert + * @return The converted Java security KeyPair + */ + public static KeyPair web3JECKeypairToJavaKeyPair(final ECKeyPair web3JECKeypair) { try { - final AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC"); - parameters.init(new ECGenParameterSpec("secp256k1")); - final ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class); - final ECPublicKeySpec pubSpec = new ECPublicKeySpec(publicPoint, ecParameters); - final KeyFactory kf = KeyFactory.getInstance("EC"); - return (ECPublicKey) kf.generatePublic(pubSpec); - } catch (NoSuchAlgorithmException | InvalidParameterSpecException | InvalidKeySpecException e) { - throw new IllegalStateException("Unable to create Ethereum public key", e); + final PrivateKey ecPrivateKey = + KeyFactory.getInstance("EC", BC_PROVIDER) + .generatePrivate( + new ECPrivateKeySpec(web3JECKeypair.getPrivateKey(), JAVA_SECP256K1_SPEC)); + return new KeyPair(bigIntegerToECPublicKey(web3JECKeypair.getPublicKey()), ecPrivateKey); + } catch (final Exception e) { + throw new RuntimeException("Unable to convert web3j to Java EC keypair", e); } } - public static ECPublicKey createPublicKey(final Bytes value) { - checkArgument(value.size() == PUBLIC_KEY_SIZE, "Invalid public key size must be 64 bytes"); - final Bytes x = value.slice(0, 32); - final Bytes y = value.slice(32, 32); - final ECPoint ecPoint = - new ECPoint(Numeric.toBigInt(x.toArrayUnsafe()), Numeric.toBigInt(y.toArrayUnsafe())); - return createPublicKey(ecPoint); + /** + * Convert a public key in bytes format to a java security ECPublicKey. + * + * @param value The public key in bytes format. This can be either 33 bytes (compressed), 64 bytes + * (uncompressed), or 65 bytes (uncompressed with prefix). + * @return The java security ECPublicKey + */ + public static ECPublicKey bytesToECPublicKey(final Bytes value) { + return bcECPointToECPublicKey(bytesToBCECPoint(value)); } - public static ECPublicKey createPublicKey(final BigInteger value) { - final Bytes ethBytes = Bytes.wrap(Numeric.toBytesPadded(value, PUBLIC_KEY_SIZE)); - return createPublicKey(ethBytes); + /** + * Convert a public key in bytes format to a Bouncy Castle ECPoint on SECP256K1 curve. + * + * @param value The public key in bytes format. This can be either 33 bytes (compressed), 64 bytes + * (uncompressed), or 65 bytes (uncompressed with prefix). + * @return The Bouncy Castle ECPoint on SECP256K1 curve + */ + public static ECPoint bytesToBCECPoint(final Bytes value) { + if (value.size() != 33 && value.size() != 65 && value.size() != 64) { + throw new IllegalArgumentException( + "Invalid public key length. Expected 33, 64, or 65 bytes."); + } + + final ECPoint point; + final byte[] key; + if (value.size() == 64) { + // For 64-byte input, we need to prepend the 0x04 prefix for uncompressed format + key = new byte[65]; + key[0] = 0x04; + System.arraycopy(value.toArrayUnsafe(), 0, key, 1, 64); + } else { + key = value.toArrayUnsafe(); + } + point = SECP256K1_DOMAIN.getCurve().decodePoint(key); + + return point; + } + + /** + * Convert a Bouncy Castle ECPoint to a Java security ECPublicKey. + * + * @param point The Bouncy Castle ECPoint to convert + * @return The converted Java security ECPublicKey on SECP256K1 curve + */ + public static ECPublicKey bcECPointToECPublicKey(final ECPoint point) { + try { + // Convert Bouncy Castle ECPoint to Java ECPoint + final java.security.spec.ECPoint ecPoint = + new java.security.spec.ECPoint( + point.getAffineXCoord().toBigInteger(), point.getAffineYCoord().toBigInteger()); + + final java.security.spec.ECPublicKeySpec pubSpec = + new java.security.spec.ECPublicKeySpec(ecPoint, JAVA_SECP256K1_SPEC); + return (ECPublicKey) + KeyFactory.getInstance(EC_ALGORITHM, BC_PROVIDER).generatePublic(pubSpec); + } catch (final InvalidKeySpecException | NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Unable to create EC public key", e); + } } - public static byte[] toByteArray(final ECPublicKey publicKey) { - final ECPoint ecPoint = publicKey.getW(); - final Bytes xBytes = Bytes32.wrap(asUnsignedByteArray(32, ecPoint.getAffineX())); - final Bytes yBytes = Bytes32.wrap(asUnsignedByteArray(32, ecPoint.getAffineY())); - return Bytes.concatenate(xBytes, yBytes).toArray(); + /** + * Create a java security ECPublicKey from a BigInteger representation of the public key. + * + * @param publicKeyValue The BigInteger representation of the public key (64 bytes, without + * prefix) + * @return The created ECPublicKey + * @throws IllegalArgumentException if the input is invalid + */ + public static ECPublicKey bigIntegerToECPublicKey(final BigInteger publicKeyValue) { + if (publicKeyValue == null) { + throw new IllegalArgumentException("Public key value cannot be null"); + } + + byte[] publicKeyBytes = publicKeyValue.toByteArray(); + + // Ensure we have exactly 64 bytes + if (publicKeyBytes.length < 64) { + byte[] temp = new byte[64]; + System.arraycopy(publicKeyBytes, 0, temp, 64 - publicKeyBytes.length, publicKeyBytes.length); + publicKeyBytes = temp; + } else if (publicKeyBytes.length > 64) { + publicKeyBytes = + Arrays.copyOfRange(publicKeyBytes, publicKeyBytes.length - 64, publicKeyBytes.length); + } + + // Create a new byte array with the uncompressed prefix + byte[] fullPublicKeyBytes = new byte[65]; + fullPublicKeyBytes[0] = 0x04; // Uncompressed point prefix + System.arraycopy(publicKeyBytes, 0, fullPublicKeyBytes, 1, 64); + + // Use the existing createPublicKey method + return bytesToECPublicKey(Bytes.wrap(fullPublicKeyBytes)); } + /** + * Convert a java ECPublicKey to an uncompressed (64 bytes) hex string. + * + * @param publicKey The public key to convert + * @return The public key as a hex string + */ public static String toHexString(final ECPublicKey publicKey) { - return Bytes.wrap(toByteArray(publicKey)).toHexString(); + return getEncoded(publicKey, false).toHexString(); + } + + /** + * Convert a java ECPublicKey to a compressed (33 bytes) hex string. + * + * @param publicKey The public key to convert + * @return The public key as a hex string + */ + public static String toHexStringCompressed(final ECPublicKey publicKey) { + return getEncoded(publicKey, true).toHexString(); + } + + /** + * Convert a java ECPublicKey to a BigInteger. + * + * @param publicKey The public key to convert + * @return The public key as a BigInteger + */ + public static BigInteger ecPublicKeyToBigInteger(final ECPublicKey publicKey) { + // Get the uncompressed public key without prefix (64 bytes) + final Bytes publicKeyBytes = getEncoded(publicKey, false); + + // Convert to BigInteger + return new BigInteger(1, publicKeyBytes.toArrayUnsafe()); + } + + /** + * Convert java ECPublicKey to Bytes. + * + * @param publicKey The public key to convert + * @param compressed Whether to return the compressed form 33 bytes or the uncompressed form 64 + * bytes + * @return The encoded public key. + */ + private static Bytes getEncoded(final ECPublicKey publicKey, boolean compressed) { + final ECPoint point; + if (publicKey instanceof BCECPublicKey) { + // If it's already a Bouncy Castle key, we can get the ECPoint directly + point = ((BCECPublicKey) publicKey).getQ(); + } else { + // If it's not a BC key, we need to create the ECPoint from the coordinates + final BigInteger x = publicKey.getW().getAffineX(); + final BigInteger y = publicKey.getW().getAffineY(); + point = BC_SECP256K1_SPEC.getCurve().createPoint(x, y); + } + + return compressed + ? Bytes.wrap(point.getEncoded(true)) + : Bytes.wrap(point.getEncoded(false), 1, 64); } } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSigner.java index 83e9edfbd..18a9ab749 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSigner.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSigner.java @@ -51,7 +51,7 @@ public class AzureKeyVaultSigner implements Signer { final AzureKeyVault azureKeyVault, final AzureHttpClientFactory azureHttpClientFactory) { this.config = config; - this.publicKey = EthPublicKeyUtils.createPublicKey(publicKey); + this.publicKey = EthPublicKeyUtils.bytesToECPublicKey(publicKey); this.needsToHash = needsToHash; this.signingAlgo = useDeprecatedSignatureAlgorithm diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java index 090f77747..b9f87275c 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java @@ -31,7 +31,8 @@ public class CredentialSigner implements Signer { public CredentialSigner(final Credentials credentials, final boolean needToHash) { this.credentials = credentials; - this.publicKey = EthPublicKeyUtils.createPublicKey(credentials.getEcKeyPair().getPublicKey()); + this.publicKey = + EthPublicKeyUtils.bigIntegerToECPublicKey(credentials.getEcKeyPair().getPublicKey()); this.needToHash = needToHash; } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtil.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtil.java index 0e81f85d9..f17dafeb9 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtil.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtil.java @@ -28,7 +28,6 @@ import org.bouncycastle.asn1.DLSequence; import org.web3j.crypto.ECDSASignature; import org.web3j.crypto.Sign; -import org.web3j.utils.Numeric; public class Eth1SignatureUtil { private static final Logger LOG = LogManager.getLogger(); @@ -92,7 +91,7 @@ private static Signature deriveSignature( private static int recoverKeyIndex( final ECPublicKey ecPublicKey, final ECDSASignature sig, final byte[] hash) { - final BigInteger publicKey = Numeric.toBigInt(EthPublicKeyUtils.toByteArray(ecPublicKey)); + final BigInteger publicKey = EthPublicKeyUtils.ecPublicKeyToBigInteger(ecPublicKey); for (int i = 0; i < 4; i++) { final BigInteger k = Sign.recoverFromSignature(i, sig, hash); LOG.trace("recovered key: {}", k); diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index c28ced922..3d349ce84 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -29,6 +29,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.security.GeneralSecurityException; +import java.security.KeyPair; import java.security.SecureRandom; import java.util.List; import java.util.Map; @@ -39,7 +40,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.web3j.crypto.ECKeyPair; -import org.web3j.crypto.Keys; import org.web3j.crypto.WalletUtils; import org.web3j.crypto.exception.CipherException; @@ -205,7 +205,10 @@ private List randomSecpV3Keystores( .mapToObj( i -> { try { - final ECKeyPair ecKeyPair = Keys.createEcKeyPair(secureRandom); + final KeyPair secp256k1KeyPair = + EthPublicKeyUtils.createSecp256k1KeyPair(secureRandom); + final ECKeyPair ecKeyPair = ECKeyPair.create(secp256k1KeyPair); + WalletUtils.generateWalletFile("password", ecKeyPair, v3Dir.toFile(), false); return ecKeyPair; } catch (GeneralSecurityException | CipherException | IOException e) { @@ -227,7 +230,7 @@ private static String[] getSecpPublicKeysArray(final List ecKeyPairs) .map( keyPair -> EthPublicKeyUtils.toHexString( - EthPublicKeyUtils.createPublicKey(keyPair.getPublicKey()))) + EthPublicKeyUtils.bigIntegerToECPublicKey(keyPair.getPublicKey()))) .toList() .toArray(String[]::new); } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java index c75cfb9a8..4e45e6e80 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java @@ -14,22 +14,29 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.assertj.core.api.Fail.fail; import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.X962Parameters; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.ec.CustomNamedCurves; -import org.bouncycastle.math.ec.ECFieldElement; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.web3j.utils.Numeric; @@ -41,19 +48,10 @@ class EthPublicKeyUtilsTest { "0xaf80b90d25145da28c583359beb47b21796b2fe1a23c1511e443e7a64dfdb27d7434c380f0aa4c500e220aa1a9d068514b1ff4d5019e624e7ba1efe82b340a59"; private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1"); - @Test - public void createsPublicKeyFromECPoint() { - final Bytes publicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); - final ECPoint expectedEcPoint = createEcPoint(publicKeyBytes); - - final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(expectedEcPoint); - verifyPublicKey(ecPublicKey, publicKeyBytes, expectedEcPoint); - } - @Test public void createsPublicKeyFromBytes() { final Bytes expectedPublicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); - final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(expectedPublicKeyBytes); + final ECPublicKey ecPublicKey = EthPublicKeyUtils.bytesToECPublicKey(expectedPublicKeyBytes); final ECPoint expectedEcPoint = createEcPoint(expectedPublicKeyBytes); verifyPublicKey(ecPublicKey, expectedPublicKeyBytes, expectedEcPoint); @@ -62,26 +60,72 @@ public void createsPublicKeyFromBytes() { @Test public void createsPublicKeyFromBigInteger() { final BigInteger publicKey = Numeric.toBigInt(PUBLIC_KEY); - final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(publicKey); + final ECPublicKey ecPublicKey = EthPublicKeyUtils.bigIntegerToECPublicKey(publicKey); final Bytes expectedPublicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); final ECPoint expectedEcPoint = createEcPoint(expectedPublicKeyBytes); verifyPublicKey(ecPublicKey, expectedPublicKeyBytes, expectedEcPoint); } + private static Stream validPublicKeys() { + final KeyPair keyPair; + try { + keyPair = EthPublicKeyUtils.createSecp256k1KeyPair(new SecureRandom()); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + return Stream.of( + // Compressed (33 bytes) + Bytes.fromHexString( + EthPublicKeyUtils.toHexStringCompressed((ECPublicKey) keyPair.getPublic())), + // Uncompressed without prefix (64 bytes) + Bytes.fromHexString(EthPublicKeyUtils.toHexString((ECPublicKey) keyPair.getPublic()))); + } + + @ParameterizedTest + @MethodSource("validPublicKeys") + void acceptsValidPublicKeySizes(final Bytes publicKey) { + assertThatCode(() -> EthPublicKeyUtils.bytesToECPublicKey(publicKey)) + .doesNotThrowAnyException(); + } + @ParameterizedTest - @ValueSource(ints = {0, 63, 65}) - public void throwsInvalidArgumentExceptionForInvalidPublicKeySize(final int size) { - assertThatThrownBy(() -> EthPublicKeyUtils.createPublicKey(Bytes.random(size))) + @ValueSource(ints = {0, 32, 34, 63, 66}) + void throwsIllegalArgumentExceptionForInvalidPublicKeySize(final int size) { + assertThatThrownBy(() -> EthPublicKeyUtils.bytesToECPublicKey(Bytes.random(size))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid public key length. Expected 33, 64, or 65 bytes."); + } + + @Test + void throwsIllegalArgumentExceptionForInvalid33ByteKey() { + Bytes invalidCompressedKey = Bytes.concatenate(Bytes.of(0x00), Bytes.random(32)); + assertThatThrownBy(() -> EthPublicKeyUtils.bytesToECPublicKey(invalidCompressedKey)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Incorrect length for infinity encoding"); + } + + @Test + void throwsIllegalArgumentExceptionForInvalid65ByteKey() { + Bytes invalidUncompressedKey = Bytes.concatenate(Bytes.of(0x00), Bytes.random(64)); + assertThatThrownBy(() -> EthPublicKeyUtils.bytesToECPublicKey(invalidUncompressedKey)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Invalid public key size must be 64 bytes"); + .hasMessageContaining("Incorrect length for infinity encoding"); + } + + @Test + void throwsIllegalArgumentExceptionForInvalidCompressedKeyPrefix() { + Bytes invalidCompressedKey = Bytes.concatenate(Bytes.of(0x04), Bytes.random(32)); + assertThatThrownBy(() -> EthPublicKeyUtils.bytesToECPublicKey(invalidCompressedKey)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Incorrect length for uncompressed encoding"); } @Test public void publicKeyIsConvertedToEthHexString() { final Bytes publicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); - final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(publicKeyBytes); + final ECPublicKey ecPublicKey = EthPublicKeyUtils.bytesToECPublicKey(publicKeyBytes); final String hexString = EthPublicKeyUtils.toHexString(ecPublicKey); assertThat(hexString).isEqualTo(PUBLIC_KEY); } @@ -90,33 +134,73 @@ public void publicKeyIsConvertedToEthHexString() { public void publicKeyIsConvertedToEthBytes() { final Bytes publicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); - final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(publicKeyBytes); - final Bytes bytes = Bytes.wrap(EthPublicKeyUtils.toByteArray(ecPublicKey)); + final ECPublicKey ecPublicKey = EthPublicKeyUtils.bytesToECPublicKey(publicKeyBytes); + final Bytes bytes = Bytes.fromHexString(EthPublicKeyUtils.toHexString(ecPublicKey)); assertThat(bytes).isEqualTo(publicKeyBytes); assertThat(bytes.size()).isEqualTo(64); assertThat(bytes.get(0)).isNotEqualTo(0x4); } + @Test + public void encodePublicKey() { + final Bytes publicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); + final ECPublicKey ecPublicKey = EthPublicKeyUtils.bytesToECPublicKey(publicKeyBytes); + + final Bytes uncompressedWithoutPrefix = + Bytes.fromHexString(EthPublicKeyUtils.toHexString(ecPublicKey)); + final Bytes compressed = + Bytes.fromHexString(EthPublicKeyUtils.toHexStringCompressed(ecPublicKey)); + + assertThat(uncompressedWithoutPrefix.size()).isEqualTo(64); + assertThat(compressed.size()).isEqualTo(33); + } + private void verifyPublicKey( final ECPublicKey ecPublicKey, final Bytes publicKeyBytes, final ECPoint ecPoint) { + // verify public point assertThat(ecPublicKey.getW()).isEqualTo(ecPoint); + + // verify algorithm assertThat(ecPublicKey.getAlgorithm()).isEqualTo("EC"); + // verify curve parameters final ECParameterSpec params = ecPublicKey.getParams(); assertThat(params.getCofactor()).isEqualTo(CURVE_PARAMS.getCurve().getCofactor().intValue()); - assertThat(params.getOrder()).isEqualTo(CURVE_PARAMS.getCurve().getOrder()); - assertThat(params.getGenerator()).isEqualTo(fromBouncyCastleECPoint(CURVE_PARAMS.getG())); - + assertThat(params.getOrder()).isEqualTo(CURVE_PARAMS.getN()); + assertThat(params.getGenerator().getAffineX()) + .isEqualTo(CURVE_PARAMS.getG().getAffineXCoord().toBigInteger()); + assertThat(params.getGenerator().getAffineY()) + .isEqualTo(CURVE_PARAMS.getG().getAffineYCoord().toBigInteger()); + assertThat(params.getCurve().getA()).isEqualTo(CURVE_PARAMS.getCurve().getA().toBigInteger()); + assertThat(params.getCurve().getB()).isEqualTo(CURVE_PARAMS.getCurve().getB().toBigInteger()); + assertThat(params.getCurve().getField().getFieldSize()).isEqualTo(256); + + // Verify format assertThat(ecPublicKey.getFormat()).isEqualTo("X.509"); + // Verify encoded form SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(ecPublicKey.getEncoded())); assertThat(subjectPublicKeyInfo.getPublicKeyData().getBytes()) .isEqualTo(Bytes.concatenate(Bytes.of(0x4), publicKeyBytes).toArray()); + // Verify algorithm identifier final AlgorithmIdentifier algorithm = subjectPublicKeyInfo.getAlgorithm(); assertThat(algorithm.getAlgorithm().getId()).isEqualTo(EC_OID); - assertThat(algorithm.getParameters().toASN1Primitive().toString()).isEqualTo(SECP_OID); + + // Verify curve identifier + X962Parameters x962Params = X962Parameters.getInstance(algorithm.getParameters()); + if (x962Params.isNamedCurve()) { + assertThat(x962Params.getParameters()).isEqualTo(new ASN1ObjectIdentifier(SECP_OID)); + } else if (x962Params.isImplicitlyCA()) { + fail("Implicitly CA parameters are not expected for secp256k1"); + } else { + X9ECParameters ecParams = X9ECParameters.getInstance(x962Params.getParameters()); + assertThat(ecParams.getCurve()).isEqualTo(CURVE_PARAMS.getCurve()); + assertThat(ecParams.getG()).isEqualTo(CURVE_PARAMS.getG()); + assertThat(ecParams.getN()).isEqualTo(CURVE_PARAMS.getN()); + assertThat(ecParams.getH()).isEqualTo(CURVE_PARAMS.getH()); + } } private ECPoint createEcPoint(final Bytes publicKeyBytes) { @@ -124,18 +208,4 @@ private ECPoint createEcPoint(final Bytes publicKeyBytes) { final Bytes y = publicKeyBytes.slice(32, 32); return new ECPoint(Numeric.toBigInt(x.toArrayUnsafe()), Numeric.toBigInt(y.toArrayUnsafe())); } - - private ECPoint fromBouncyCastleECPoint( - final org.bouncycastle.math.ec.ECPoint bouncyCastleECPoint) { - final ECFieldElement xCoord = bouncyCastleECPoint.getAffineXCoord(); - final ECFieldElement yCoord = bouncyCastleECPoint.getAffineYCoord(); - - final Bytes32 xEncoded = Bytes32.wrap(xCoord.getEncoded()); - final Bytes32 yEncoded = Bytes32.wrap(yCoord.getEncoded()); - - final BigInteger x = xEncoded.toUnsignedBigInteger(); - final BigInteger y = yEncoded.toUnsignedBigInteger(); - - return new ECPoint(x, y); - } } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/aws/AwsKmsSignerTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/aws/AwsKmsSignerTest.java index 527c3e854..5e7dd5d12 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/aws/AwsKmsSignerTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/aws/AwsKmsSignerTest.java @@ -116,8 +116,7 @@ void awsSignatureCanBeVerified() throws SignatureException { final Signer signer = new AwsKmsSignerFactory(cachedAwsKmsClientFactory, applySha3Hash) .createSigner(awsKmsMetadata); - final BigInteger publicKey = - Numeric.toBigInt(EthPublicKeyUtils.toByteArray(signer.getPublicKey())); + final BigInteger publicKey = EthPublicKeyUtils.ecPublicKeyToBigInteger(signer.getPublicKey()); final byte[] dataToSign = "Hello".getBytes(UTF_8); diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSignerTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSignerTest.java index 3fa551e11..c9549088a 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSignerTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSignerTest.java @@ -71,7 +71,7 @@ void azureSignerCanSign() throws SignatureException { new AzureKeyVaultSignerFactory(new AzureKeyVaultFactory(), new AzureHttpClientFactory()) .createSigner(config); final BigInteger publicKey = - Numeric.toBigInt(EthPublicKeyUtils.toByteArray(azureNonHashedDataSigner.getPublicKey())); + EthPublicKeyUtils.ecPublicKeyToBigInteger(azureNonHashedDataSigner.getPublicKey()); final byte[] dataToSign = "Hello World".getBytes(UTF_8); From fa291970040f5d262a789d0a92d467405c25b4a2 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 21 Nov 2024 15:41:21 +1000 Subject: [PATCH 2/8] Use static init in EthAccountsResultProviderTest --- .../EthAccountsResultProviderTest.java | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java index f83b4a0a0..a17985d32 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java @@ -32,25 +32,26 @@ import java.util.stream.Stream; import com.google.common.collect.Sets; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.web3j.crypto.Keys; public class EthAccountsResultProviderTest { - private ECPublicKey publicKeyA; - private ECPublicKey publicKeyB; - private ECPublicKey publicKeyC; - - private String addressA; - private String addressB; - private String addressC; - - @BeforeEach - public void init() throws GeneralSecurityException { - SecureRandom secureRandom = new SecureRandom(); - publicKeyA = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(secureRandom).getPublic(); - publicKeyB = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(secureRandom).getPublic(); - publicKeyC = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(secureRandom).getPublic(); + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + + private static ECPublicKey publicKeyA; + private static ECPublicKey publicKeyB; + private static ECPublicKey publicKeyC; + + private static String addressA; + private static String addressB; + private static String addressC; + + @BeforeAll + static void init() throws GeneralSecurityException { + publicKeyA = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(SECURE_RANDOM).getPublic(); + publicKeyB = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(SECURE_RANDOM).getPublic(); + publicKeyC = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(SECURE_RANDOM).getPublic(); addressA = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyA)); addressB = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyB)); From a906b6f994d39ae5983738d3231d725de020b83f Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 21 Nov 2024 16:02:03 +1000 Subject: [PATCH 3/8] Simplify SecureRandom usage in EthPublicKeyUtil --- .../Eth1AddressSignerIdentifierTest.java | 19 ++++++++------ .../EthAccountsResultProviderTest.java | 12 +++------ .../signing/secp256k1/EthPublicKeyUtils.java | 23 ++++++++--------- .../DefaultArtifactSignerProviderTest.java | 25 ++++++++----------- .../secp256k1/EthPublicKeyUtilsTest.java | 9 +------ 5 files changed, 37 insertions(+), 51 deletions(-) diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java index cc7b5e687..3a121beec 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java @@ -19,7 +19,6 @@ import tech.pegasys.web3signer.signing.secp256k1.SignerIdentifier; import java.security.KeyPair; -import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; import org.junit.jupiter.api.BeforeAll; @@ -31,10 +30,9 @@ class Eth1AddressSignerIdentifierTest { private static KeyPair secp256k1KeyPair2; @BeforeAll - static void generateKeyPair() throws Exception { - final SecureRandom random = new SecureRandom(); - secp256k1KeyPair = EthPublicKeyUtils.createSecp256k1KeyPair(random); - secp256k1KeyPair2 = EthPublicKeyUtils.createSecp256k1KeyPair(random); + static void generateKeyPair() { + secp256k1KeyPair = EthPublicKeyUtils.generateK256KeyPair(); + secp256k1KeyPair2 = EthPublicKeyUtils.generateK256KeyPair(); } @Test @@ -44,7 +42,7 @@ void prefixIsRemovedFromAddress() { Keys.getAddress( EthPublicKeyUtils.ecPublicKeyToBigInteger((ECPublicKey) secp256k1KeyPair.getPublic())); // forcefully convert first two alphabets to uppercase and add prefix - final String mixCaseAddress = "0X" + convertAlphabetsToUpperCase(address); + final String mixCaseAddress = "0X" + convertHexToMixCase(address); final Eth1AddressSignerIdentifier signerIdentifier = new Eth1AddressSignerIdentifier(mixCaseAddress); @@ -87,7 +85,14 @@ void correctEth1AddressIsGeneratedFromPublicKey() { assertThat(signerIdentifier.toStringIdentifier()).isLowerCase(); } - public static String convertAlphabetsToUpperCase(final String input) { + /** + * Converts first two alphabets to uppercase that can be used to test the case sensitivity of the + * address + * + * @param input address string in hex, assuming all characters are lowercase. + * @return address with first two alphabets converted to uppercase + */ + private static String convertHexToMixCase(final String input) { final char[] chars = input.toCharArray(); int count = 0; diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java index a17985d32..89cec7c64 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java @@ -22,8 +22,6 @@ import tech.pegasys.web3signer.core.service.jsonrpc.handlers.internalresponse.EthAccountsResultProvider; import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; -import java.security.GeneralSecurityException; -import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; import java.util.List; import java.util.Set; @@ -37,8 +35,6 @@ import org.web3j.crypto.Keys; public class EthAccountsResultProviderTest { - private static final SecureRandom SECURE_RANDOM = new SecureRandom(); - private static ECPublicKey publicKeyA; private static ECPublicKey publicKeyB; private static ECPublicKey publicKeyC; @@ -48,10 +44,10 @@ public class EthAccountsResultProviderTest { private static String addressC; @BeforeAll - static void init() throws GeneralSecurityException { - publicKeyA = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(SECURE_RANDOM).getPublic(); - publicKeyB = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(SECURE_RANDOM).getPublic(); - publicKeyC = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(SECURE_RANDOM).getPublic(); + static void init() { + publicKeyA = (ECPublicKey) EthPublicKeyUtils.generateK256KeyPair().getPublic(); + publicKeyB = (ECPublicKey) EthPublicKeyUtils.generateK256KeyPair().getPublic(); + publicKeyC = (ECPublicKey) EthPublicKeyUtils.generateK256KeyPair().getPublic(); addressA = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyA)); addressB = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyB)); diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java index 8108bb582..7bbe7c8e9 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java @@ -44,10 +44,12 @@ */ public class EthPublicKeyUtils { private static final BouncyCastleProvider BC_PROVIDER = new BouncyCastleProvider(); + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); private static final ECDomainParameters SECP256K1_DOMAIN; private static final ECParameterSpec BC_SECP256K1_SPEC; private static final java.security.spec.ECParameterSpec JAVA_SECP256K1_SPEC; private static final String SECP256K1_CURVE = "secp256k1"; + private static final ECGenParameterSpec EC_KEYGEN_PARAM = new ECGenParameterSpec(SECP256K1_CURVE); private static final String EC_ALGORITHM = "EC"; static { @@ -73,22 +75,17 @@ public class EthPublicKeyUtils { /** * Create a new secp256k1 key pair. * - * @param random The random number generator to use * @return The generated java security key pair - * @throws GeneralSecurityException If there is an issue generating the key pair */ - public static KeyPair createSecp256k1KeyPair(final SecureRandom random) - throws GeneralSecurityException { - final KeyPairGenerator keyPairGenerator = - KeyPairGenerator.getInstance(EC_ALGORITHM, BC_PROVIDER); - final ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(SECP256K1_CURVE); - if (random != null) { - keyPairGenerator.initialize(ecGenParameterSpec, random); - } else { - keyPairGenerator.initialize(ecGenParameterSpec); + public static KeyPair generateK256KeyPair() { + try { + final KeyPairGenerator keyPairGenerator = + KeyPairGenerator.getInstance(EC_ALGORITHM, BC_PROVIDER); + keyPairGenerator.initialize(EC_KEYGEN_PARAM); + return keyPairGenerator.generateKeyPair(); + } catch (final GeneralSecurityException e) { + throw new RuntimeException(e); } - - return keyPairGenerator.generateKeyPair(); } /** diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index 3d349ce84..6b617e1c9 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -18,6 +18,7 @@ import static org.mockito.Mockito.when; import tech.pegasys.teku.bls.BLSKeyPair; +import tech.pegasys.web3signer.BLSTestUtil; import tech.pegasys.web3signer.KeystoreUtil; import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; @@ -28,9 +29,7 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.security.GeneralSecurityException; import java.security.KeyPair; -import java.security.SecureRandom; import java.util.List; import java.util.Map; import java.util.Optional; @@ -109,19 +108,18 @@ void signerProviderCanMapInTwoSigners() { @Test void proxySignersAreLoadedCorrectly() throws IOException { - final SecureRandom secureRandom = new SecureRandom(); // create random proxy signers final KeystoresParameters commitBoostParameters = new TestCommitBoostParameters(commitBoostKeystoresPath, commitBoostPasswordDir); // create random BLS key pairs as proxy keys for public key1 and public key2 - final List key1ProxyKeyPairs = randomBLSV4Keystores(secureRandom, PUBLIC_KEY1); - final List key2ProxyKeyPairs = randomBLSV4Keystores(secureRandom, PUBLIC_KEY2); + final List key1ProxyKeyPairs = randomBLSV4Keystores(PUBLIC_KEY1); + final List key2ProxyKeyPairs = randomBLSV4Keystores(PUBLIC_KEY2); // create random secp key pairs as proxy keys for public key1 and public key2 - final List key1SecpKeyPairs = randomSecpV3Keystores(secureRandom, PUBLIC_KEY1); - final List key2SecpKeyPairs = randomSecpV3Keystores(secureRandom, PUBLIC_KEY2); + final List key1SecpKeyPairs = randomSecpV3Keystores(PUBLIC_KEY1); + final List key2SecpKeyPairs = randomSecpV3Keystores(PUBLIC_KEY2); // set up mock signers final ArtifactSigner mockSigner1 = mock(ArtifactSigner.class); @@ -180,8 +178,7 @@ void emptyProxySignersAreLoadedSuccessfully() { } } - private List randomBLSV4Keystores(SecureRandom secureRandom, String identifier) - throws IOException { + private List randomBLSV4Keystores(final String identifier) throws IOException { final Path v4Dir = Files.createDirectories( commitBoostKeystoresPath.resolve(identifier).resolve(KeyType.BLS.name())); @@ -189,15 +186,14 @@ private List randomBLSV4Keystores(SecureRandom secureRandom, String return IntStream.range(0, 4) .mapToObj( i -> { - final BLSKeyPair blsKeyPair = BLSKeyPair.random(secureRandom); + final BLSKeyPair blsKeyPair = BLSTestUtil.randomKeyPair(i); KeystoreUtil.createKeystoreFile(blsKeyPair, v4Dir, "password"); return blsKeyPair; }) .toList(); } - private List randomSecpV3Keystores( - final SecureRandom secureRandom, final String identifier) throws IOException { + private List randomSecpV3Keystores(final String identifier) throws IOException { final Path v3Dir = Files.createDirectories( commitBoostKeystoresPath.resolve(identifier).resolve(KeyType.SECP256K1.name())); @@ -205,13 +201,12 @@ private List randomSecpV3Keystores( .mapToObj( i -> { try { - final KeyPair secp256k1KeyPair = - EthPublicKeyUtils.createSecp256k1KeyPair(secureRandom); + final KeyPair secp256k1KeyPair = EthPublicKeyUtils.generateK256KeyPair(); final ECKeyPair ecKeyPair = ECKeyPair.create(secp256k1KeyPair); WalletUtils.generateWalletFile("password", ecKeyPair, v3Dir.toFile(), false); return ecKeyPair; - } catch (GeneralSecurityException | CipherException | IOException e) { + } catch (final CipherException | IOException e) { throw new RuntimeException(e); } }) diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java index 4e45e6e80..9c0c6bd79 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java @@ -18,9 +18,7 @@ import static org.assertj.core.api.Fail.fail; import java.math.BigInteger; -import java.security.GeneralSecurityException; import java.security.KeyPair; -import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; @@ -68,12 +66,7 @@ public void createsPublicKeyFromBigInteger() { } private static Stream validPublicKeys() { - final KeyPair keyPair; - try { - keyPair = EthPublicKeyUtils.createSecp256k1KeyPair(new SecureRandom()); - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } + final KeyPair keyPair = EthPublicKeyUtils.generateK256KeyPair(); return Stream.of( // Compressed (33 bytes) Bytes.fromHexString( From 15acc9a8d7a151a30e0835477be8e6c9c0dbcd3f Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 21 Nov 2024 17:00:08 +1000 Subject: [PATCH 4/8] Refactor EthPublicKeyUtils --- ...SecpV3KeystoresBulkLoadAcceptanceTest.java | 2 +- .../publickeys/YubiHsmKeysAcceptanceTest.java | 2 +- .../signing/SecpSigningAcceptanceTest.java | 2 +- .../EthSignTransactionResultProviderTest.java | 4 +- .../signing/secp256k1/EthPublicKeyUtils.java | 53 +++++++++++-------- .../secp256k1/filebased/CredentialSigner.java | 2 +- .../DefaultArtifactSignerProviderTest.java | 2 +- .../secp256k1/EthPublicKeyUtilsTest.java | 4 +- 8 files changed, 41 insertions(+), 30 deletions(-) diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java index b9b4b2a9d..92ea0f7ac 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java @@ -61,7 +61,7 @@ static void initV3Keystores() throws IOException, GeneralSecurityException, Ciph for (int i = 0; i < 4; i++) { final ECKeyPair ecKeyPair = Keys.createEcKeyPair(); final ECPublicKey ecPublicKey = - EthPublicKeyUtils.bigIntegerToECPublicKey(ecKeyPair.getPublicKey()); + EthPublicKeyUtils.web3JPublicKeyToECPublicKey(ecKeyPair.getPublicKey()); final String publicKeyHex = IdentifierUtils.normaliseIdentifier(EthPublicKeyUtils.toHexString(ecPublicKey)); publicKeys.add(publicKeyHex); diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java index 37dec504d..3d0a555fa 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java @@ -121,7 +121,7 @@ private void createConfigurationFiles(final Set opaqueDataIds, final Ke private String getPublicKey(final String key) { return normaliseIdentifier( EthPublicKeyUtils.toHexString( - EthPublicKeyUtils.bigIntegerToECPublicKey( + EthPublicKeyUtils.web3JPublicKeyToECPublicKey( Credentials.create(key).getEcKeyPair().getPublicKey()))); } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SecpSigningAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SecpSigningAcceptanceTest.java index 9660d885c..cc2e3603c 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SecpSigningAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SecpSigningAcceptanceTest.java @@ -194,7 +194,7 @@ void verifySignature(final Bytes signature, final String publicKeyHex) { final byte[] s = signature.slice(32, 32).toArray(); final byte[] v = signature.slice(64).toArray(); final BigInteger messagePublicKey = recoverPublicKey(new SignatureData(v, r, s)); - assertThat(EthPublicKeyUtils.bigIntegerToECPublicKey(messagePublicKey)) + assertThat(EthPublicKeyUtils.web3JPublicKeyToECPublicKey(messagePublicKey)) .isEqualTo(expectedPublicKey); } diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java index 4e37487bf..6562768fa 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java @@ -109,7 +109,7 @@ public void signatureHasTheExpectedFormat() { final Credentials cs = Credentials.create("0x1618fc3e47aec7e70451256e033b9edb67f4c469258d8e2fbb105552f141ae41"); final ECPublicKey key = - EthPublicKeyUtils.bigIntegerToECPublicKey(cs.getEcKeyPair().getPublicKey()); + EthPublicKeyUtils.web3JPublicKeyToECPublicKey(cs.getEcKeyPair().getPublicKey()); final String addr = Keys.getAddress(EthPublicKeyUtils.toHexString(key)); final BigInteger v = BigInteger.ONE; @@ -171,7 +171,7 @@ private String executeEthSignTransaction(final JsonObject params) { final Credentials cs = Credentials.create("0x1618fc3e47aec7e70451256e033b9edb67f4c469258d8e2fbb105552f141ae41"); final ECPublicKey key = - EthPublicKeyUtils.bigIntegerToECPublicKey(cs.getEcKeyPair().getPublicKey()); + EthPublicKeyUtils.web3JPublicKeyToECPublicKey(cs.getEcKeyPair().getPublicKey()); final String addr = Keys.getAddress(EthPublicKeyUtils.toHexString(key)); doAnswer( diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java index 7bbe7c8e9..332b91859 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java @@ -25,7 +25,6 @@ import java.security.spec.ECPrivateKeySpec; import java.security.spec.EllipticCurve; import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; import org.apache.tuweni.bytes.Bytes; import org.bouncycastle.asn1.x9.X9ECParameters; @@ -81,7 +80,7 @@ public static KeyPair generateK256KeyPair() { try { final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(EC_ALGORITHM, BC_PROVIDER); - keyPairGenerator.initialize(EC_KEYGEN_PARAM); + keyPairGenerator.initialize(EC_KEYGEN_PARAM, SECURE_RANDOM); return keyPairGenerator.generateKeyPair(); } catch (final GeneralSecurityException e) { throw new RuntimeException(e); @@ -100,7 +99,7 @@ public static KeyPair web3JECKeypairToJavaKeyPair(final ECKeyPair web3JECKeypair KeyFactory.getInstance("EC", BC_PROVIDER) .generatePrivate( new ECPrivateKeySpec(web3JECKeypair.getPrivateKey(), JAVA_SECP256K1_SPEC)); - return new KeyPair(bigIntegerToECPublicKey(web3JECKeypair.getPublicKey()), ecPrivateKey); + return new KeyPair(web3JPublicKeyToECPublicKey(web3JECKeypair.getPublicKey()), ecPrivateKey); } catch (final Exception e) { throw new RuntimeException("Unable to convert web3j to Java EC keypair", e); } @@ -168,37 +167,49 @@ public static ECPublicKey bcECPointToECPublicKey(final ECPoint point) { } /** - * Create a java security ECPublicKey from a BigInteger representation of the public key. + * Create a java security ECPublicKey from Web3J representation of public key as BigInteger. Web3J + * uses Bouncy castle ECPoint getEncoded with false to get uncompressed public key and then create + * BigInteger from it without using the prefix byte. * * @param publicKeyValue The BigInteger representation of the public key (64 bytes, without * prefix) * @return The created ECPublicKey * @throws IllegalArgumentException if the input is invalid */ - public static ECPublicKey bigIntegerToECPublicKey(final BigInteger publicKeyValue) { + public static ECPublicKey web3JPublicKeyToECPublicKey(final BigInteger publicKeyValue) { if (publicKeyValue == null) { throw new IllegalArgumentException("Public key value cannot be null"); } - byte[] publicKeyBytes = publicKeyValue.toByteArray(); + byte[] publicKeyBytes = ensure64Bytes(publicKeyValue.toByteArray()); - // Ensure we have exactly 64 bytes - if (publicKeyBytes.length < 64) { - byte[] temp = new byte[64]; - System.arraycopy(publicKeyBytes, 0, temp, 64 - publicKeyBytes.length, publicKeyBytes.length); - publicKeyBytes = temp; - } else if (publicKeyBytes.length > 64) { - publicKeyBytes = - Arrays.copyOfRange(publicKeyBytes, publicKeyBytes.length - 64, publicKeyBytes.length); - } + // Use the existing bytesToECPublicKey method + return bytesToECPublicKey(Bytes.wrap(publicKeyBytes)); + } - // Create a new byte array with the uncompressed prefix - byte[] fullPublicKeyBytes = new byte[65]; - fullPublicKeyBytes[0] = 0x04; // Uncompressed point prefix - System.arraycopy(publicKeyBytes, 0, fullPublicKeyBytes, 1, 64); + /** + * Ensures that the given byte array is exactly 64 bytes long. If the input array is shorter than + * 64 bytes, it pads the array with leading zeros. If the input array is longer than 64 bytes, it + * trims the excess leading bytes. + * + * @param publicKeyBytes The input byte array representing the public key. + * @return A byte array of exactly 64 bytes. + */ + private static byte[] ensure64Bytes(final byte[] publicKeyBytes) { + if (publicKeyBytes.length == 64) { + return publicKeyBytes; + } - // Use the existing createPublicKey method - return bytesToECPublicKey(Bytes.wrap(fullPublicKeyBytes)); + final byte[] result = new byte[64]; + if (publicKeyBytes.length < 64) { + // pad with leading 0s + System.arraycopy( + publicKeyBytes, 0, result, 64 - publicKeyBytes.length, publicKeyBytes.length); + } else { + // trim excess bytes + System.arraycopy(publicKeyBytes, publicKeyBytes.length - 64, result, 0, 64); + } + return result; } /** diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java index b9f87275c..0070e52d2 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java @@ -32,7 +32,7 @@ public class CredentialSigner implements Signer { public CredentialSigner(final Credentials credentials, final boolean needToHash) { this.credentials = credentials; this.publicKey = - EthPublicKeyUtils.bigIntegerToECPublicKey(credentials.getEcKeyPair().getPublicKey()); + EthPublicKeyUtils.web3JPublicKeyToECPublicKey(credentials.getEcKeyPair().getPublicKey()); this.needToHash = needToHash; } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index 6b617e1c9..1c72573e1 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -225,7 +225,7 @@ private static String[] getSecpPublicKeysArray(final List ecKeyPairs) .map( keyPair -> EthPublicKeyUtils.toHexString( - EthPublicKeyUtils.bigIntegerToECPublicKey(keyPair.getPublicKey()))) + EthPublicKeyUtils.web3JPublicKeyToECPublicKey(keyPair.getPublicKey()))) .toList() .toArray(String[]::new); } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java index 9c0c6bd79..aed6a51c1 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java @@ -56,9 +56,9 @@ public void createsPublicKeyFromBytes() { } @Test - public void createsPublicKeyFromBigInteger() { + public void createsPublicKeyFromWeb3JBigInteger() { final BigInteger publicKey = Numeric.toBigInt(PUBLIC_KEY); - final ECPublicKey ecPublicKey = EthPublicKeyUtils.bigIntegerToECPublicKey(publicKey); + final ECPublicKey ecPublicKey = EthPublicKeyUtils.web3JPublicKeyToECPublicKey(publicKey); final Bytes expectedPublicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); final ECPoint expectedEcPoint = createEcPoint(expectedPublicKeyBytes); From 2d5c9ed194c925913da6bb06285fa7b7d642efa9 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 21 Nov 2024 18:19:02 +1000 Subject: [PATCH 5/8] Update unit tests --- .../web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java index aed6a51c1..3390a5819 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java @@ -72,7 +72,11 @@ private static Stream validPublicKeys() { Bytes.fromHexString( EthPublicKeyUtils.toHexStringCompressed((ECPublicKey) keyPair.getPublic())), // Uncompressed without prefix (64 bytes) - Bytes.fromHexString(EthPublicKeyUtils.toHexString((ECPublicKey) keyPair.getPublic()))); + Bytes.fromHexString(EthPublicKeyUtils.toHexString((ECPublicKey) keyPair.getPublic())), + // Uncompressed with prefix (65 bytes) + Bytes.concatenate( + Bytes.of(0x04), + Bytes.fromHexString(EthPublicKeyUtils.toHexString((ECPublicKey) keyPair.getPublic())))); } @ParameterizedTest From 5a64ca537baaa44645f4635d9a9907bf658a3203 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 21 Nov 2024 18:35:01 +1000 Subject: [PATCH 6/8] Minor refactoring --- .../pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java index 332b91859..ec96474ff 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java @@ -254,7 +254,7 @@ public static BigInteger ecPublicKeyToBigInteger(final ECPublicKey publicKey) { * bytes * @return The encoded public key. */ - private static Bytes getEncoded(final ECPublicKey publicKey, boolean compressed) { + private static Bytes getEncoded(final ECPublicKey publicKey, final boolean compressed) { final ECPoint point; if (publicKey instanceof BCECPublicKey) { // If it's already a Bouncy Castle key, we can get the ECPoint directly From d6509df409c4317ab82f30a7074f50c50f400d4a Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 21 Nov 2024 18:44:01 +1000 Subject: [PATCH 7/8] Minor refactoring --- .../core/Eth1AddressSignerIdentifier.java | 4 ++-- .../service/Eth1AddressSignerIdentifierTest.java | 5 +++-- .../signing/secp256k1/EthPublicKeyUtils.java | 15 ++++++--------- .../signing/secp256k1/util/Eth1SignatureUtil.java | 2 +- .../signing/secp256k1/aws/AwsKmsSignerTest.java | 3 ++- .../secp256k1/azure/AzureKeyVaultSignerTest.java | 2 +- 6 files changed, 15 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/Eth1AddressSignerIdentifier.java b/core/src/main/java/tech/pegasys/web3signer/core/Eth1AddressSignerIdentifier.java index 14f711301..9e22cefd3 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/Eth1AddressSignerIdentifier.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/Eth1AddressSignerIdentifier.java @@ -13,7 +13,7 @@ package tech.pegasys.web3signer.core; import static org.web3j.crypto.Keys.getAddress; -import static tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils.ecPublicKeyToBigInteger; +import static tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils.ecPublicKeyToWeb3JPublicKey; import static tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils.toHexString; import static tech.pegasys.web3signer.signing.secp256k1.util.AddressUtil.remove0xPrefix; @@ -32,7 +32,7 @@ public Eth1AddressSignerIdentifier(final String address) { } public static SignerIdentifier fromPublicKey(final ECPublicKey publicKey) { - return new Eth1AddressSignerIdentifier(getAddress(ecPublicKeyToBigInteger(publicKey))); + return new Eth1AddressSignerIdentifier(getAddress(ecPublicKeyToWeb3JPublicKey(publicKey))); } @Override diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java index 3a121beec..8f0caf6fd 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java @@ -40,7 +40,8 @@ void prefixIsRemovedFromAddress() { // web3j.crypto.Keys.getAddress() returns lower case address without 0x prefix final String address = Keys.getAddress( - EthPublicKeyUtils.ecPublicKeyToBigInteger((ECPublicKey) secp256k1KeyPair.getPublic())); + EthPublicKeyUtils.ecPublicKeyToWeb3JPublicKey( + (ECPublicKey) secp256k1KeyPair.getPublic())); // forcefully convert first two alphabets to uppercase and add prefix final String mixCaseAddress = "0X" + convertHexToMixCase(address); @@ -79,7 +80,7 @@ void correctEth1AddressIsGeneratedFromPublicKey() { // web3j.crypto.Keys.getAddress() returns lower case address without 0x prefix final String expectedAddress = - Keys.getAddress(EthPublicKeyUtils.ecPublicKeyToBigInteger(publicKey)); + Keys.getAddress(EthPublicKeyUtils.ecPublicKeyToWeb3JPublicKey(publicKey)); assertThat(signerIdentifier.toStringIdentifier()).isEqualTo(expectedAddress); assertThat(signerIdentifier.toStringIdentifier()).doesNotStartWithIgnoringCase("0x"); assertThat(signerIdentifier.toStringIdentifier()).isLowerCase(); diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java index ec96474ff..8a1f18c4e 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java @@ -96,7 +96,7 @@ public static KeyPair generateK256KeyPair() { public static KeyPair web3JECKeypairToJavaKeyPair(final ECKeyPair web3JECKeypair) { try { final PrivateKey ecPrivateKey = - KeyFactory.getInstance("EC", BC_PROVIDER) + KeyFactory.getInstance(EC_ALGORITHM, BC_PROVIDER) .generatePrivate( new ECPrivateKeySpec(web3JECKeypair.getPrivateKey(), JAVA_SECP256K1_SPEC)); return new KeyPair(web3JPublicKeyToECPublicKey(web3JECKeypair.getPublicKey()), ecPrivateKey); @@ -233,17 +233,14 @@ public static String toHexStringCompressed(final ECPublicKey publicKey) { } /** - * Convert a java ECPublicKey to a BigInteger. + * Convert a java ECPublicKey to a Web3J public key as BigInteger. * * @param publicKey The public key to convert - * @return The public key as a BigInteger + * @return The Web3J public key as a BigInteger */ - public static BigInteger ecPublicKeyToBigInteger(final ECPublicKey publicKey) { - // Get the uncompressed public key without prefix (64 bytes) - final Bytes publicKeyBytes = getEncoded(publicKey, false); - - // Convert to BigInteger - return new BigInteger(1, publicKeyBytes.toArrayUnsafe()); + public static BigInteger ecPublicKeyToWeb3JPublicKey(final ECPublicKey publicKey) { + // Convert to BigInteger from uncompressed public key (64 bytes) + return new BigInteger(1, getEncoded(publicKey, false).toArrayUnsafe()); } /** diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtil.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtil.java index f17dafeb9..1e51396a4 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtil.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtil.java @@ -91,7 +91,7 @@ private static Signature deriveSignature( private static int recoverKeyIndex( final ECPublicKey ecPublicKey, final ECDSASignature sig, final byte[] hash) { - final BigInteger publicKey = EthPublicKeyUtils.ecPublicKeyToBigInteger(ecPublicKey); + final BigInteger publicKey = EthPublicKeyUtils.ecPublicKeyToWeb3JPublicKey(ecPublicKey); for (int i = 0; i < 4; i++) { final BigInteger k = Sign.recoverFromSignature(i, sig, hash); LOG.trace("recovered key: {}", k); diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/aws/AwsKmsSignerTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/aws/AwsKmsSignerTest.java index 5e7dd5d12..03e397dbc 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/aws/AwsKmsSignerTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/aws/AwsKmsSignerTest.java @@ -116,7 +116,8 @@ void awsSignatureCanBeVerified() throws SignatureException { final Signer signer = new AwsKmsSignerFactory(cachedAwsKmsClientFactory, applySha3Hash) .createSigner(awsKmsMetadata); - final BigInteger publicKey = EthPublicKeyUtils.ecPublicKeyToBigInteger(signer.getPublicKey()); + final BigInteger publicKey = + EthPublicKeyUtils.ecPublicKeyToWeb3JPublicKey(signer.getPublicKey()); final byte[] dataToSign = "Hello".getBytes(UTF_8); diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSignerTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSignerTest.java index c9549088a..4933fa90c 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSignerTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSignerTest.java @@ -71,7 +71,7 @@ void azureSignerCanSign() throws SignatureException { new AzureKeyVaultSignerFactory(new AzureKeyVaultFactory(), new AzureHttpClientFactory()) .createSigner(config); final BigInteger publicKey = - EthPublicKeyUtils.ecPublicKeyToBigInteger(azureNonHashedDataSigner.getPublicKey()); + EthPublicKeyUtils.ecPublicKeyToWeb3JPublicKey(azureNonHashedDataSigner.getPublicKey()); final byte[] dataToSign = "Hello World".getBytes(UTF_8); From b6bbf0f0e25e577700834e4f6bdf943ce1f30c96 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Fri, 22 Nov 2024 15:30:54 +1000 Subject: [PATCH 8/8] Refactor Eth1SignatureUtilTest --- .../secp256k1/util/Eth1SignatureUtilTest.java | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtilTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtilTest.java index 66a147638..6816d9510 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtilTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtilTest.java @@ -14,37 +14,31 @@ import static org.assertj.core.api.Assertions.assertThat; +import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; + import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.Provider; -import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; -import java.security.spec.ECGenParameterSpec; import org.apache.tuweni.bytes.Bytes; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.DERSequence; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.jupiter.api.Test; import org.web3j.crypto.ECDSASignature; import org.web3j.crypto.ECKeyPair; import org.web3j.crypto.Hash; public class Eth1SignatureUtilTest { - private static final Provider BC_PROVIDER = new BouncyCastleProvider(); + public static final byte[] DATA_TO_SIGN = Hash.sha3("hello".getBytes(StandardCharsets.UTF_8)); @Test void signatureIsDerivedFromDerEncoded() throws Exception { - final byte[] dataToSign = Hash.sha3("hello".getBytes(StandardCharsets.UTF_8)); - - final KeyPair keyPair = generateEC_SECPKeyPair(); + final KeyPair keyPair = EthPublicKeyUtils.generateK256KeyPair(); final ECKeyPair web3jECKeyPair = ECKeyPair.create(keyPair); // sign using web3j (which uses BouncyCastle ECDSA Signer) - final ECDSASignature web3jSig = web3jECKeyPair.sign(dataToSign); + final ECDSASignature web3jSig = web3jECKeyPair.sign(DATA_TO_SIGN); // convert web3j's P1363 to ANS1/DER Encoded signature final ASN1EncodableVector v = new ASN1EncodableVector(); @@ -55,7 +49,7 @@ void signatureIsDerivedFromDerEncoded() throws Exception { // verify our logic final tech.pegasys.web3signer.signing.secp256k1.Signature signature = Eth1SignatureUtil.deriveSignatureFromDerEncoded( - dataToSign, (ECPublicKey) keyPair.getPublic(), derEncodedSignedData); + DATA_TO_SIGN, (ECPublicKey) keyPair.getPublic(), derEncodedSignedData); assertThat(signature.getR()).isEqualTo(web3jSig.r); assertThat(signature.getS()).isEqualTo(web3jSig.s); @@ -63,12 +57,10 @@ void signatureIsDerivedFromDerEncoded() throws Exception { @Test void signatureIsDerivedFromP1363Encoded() throws Exception { - final byte[] dataToSign = Hash.sha3("hello".getBytes(StandardCharsets.UTF_8)); - - final KeyPair keyPair = generateEC_SECPKeyPair(); + final KeyPair keyPair = EthPublicKeyUtils.generateK256KeyPair(); final ECKeyPair web3jECKeyPair = ECKeyPair.create(keyPair); // sign using web3j (which uses BouncyCastle ECDSASigner) - final ECDSASignature web3jSig = web3jECKeyPair.sign(dataToSign); + final ECDSASignature web3jSig = web3jECKeyPair.sign(DATA_TO_SIGN); // concatenate r || s to create byte[] final Bytes rBytes = Bytes.of(web3jSig.r.toByteArray()).trimLeadingZeros(); @@ -78,16 +70,9 @@ void signatureIsDerivedFromP1363Encoded() throws Exception { // verify our logic final tech.pegasys.web3signer.signing.secp256k1.Signature signature = Eth1SignatureUtil.deriveSignatureFromP1363Encoded( - dataToSign, (ECPublicKey) keyPair.getPublic(), p1363Signature); + DATA_TO_SIGN, (ECPublicKey) keyPair.getPublic(), p1363Signature); assertThat(signature.getR()).isEqualTo(web3jSig.r); assertThat(signature.getS()).isEqualTo(web3jSig.s); } - - public static KeyPair generateEC_SECPKeyPair() throws GeneralSecurityException { - // Generate the key pair - final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", BC_PROVIDER); - keyPairGenerator.initialize(new ECGenParameterSpec("secp256k1"), new SecureRandom()); - return keyPairGenerator.generateKeyPair(); - } }