diff --git a/build.gradle b/build.gradle index 1bc811a1..edfb66a2 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,7 @@ dependencies { implementation 'org.bouncycastle:bcprov-jdk15on:1.70' implementation 'org.web3j:core:4.8.7' implementation 'com.google.code.gson:gson:2.11.0' + implementation 'foundation.icon:blst-java:0.3.2' // Test dependencies testImplementation 'org.hamcrest:hamcrest-library:3.0' diff --git a/src/main/java/org/arkecosystem/crypto/identities/BlsPublicKey.java b/src/main/java/org/arkecosystem/crypto/identities/BlsPublicKey.java new file mode 100644 index 00000000..a8016129 --- /dev/null +++ b/src/main/java/org/arkecosystem/crypto/identities/BlsPublicKey.java @@ -0,0 +1,31 @@ +package org.arkecosystem.crypto.identities; + +import org.arkecosystem.crypto.encoding.Hex; +import supranational.blst.P1_Affine; + +public class BlsPublicKey { + + public static boolean validate(String publicKeyHex) { + try { + if (publicKeyHex == null) { + return false; + } + + if (publicKeyHex.startsWith("0x")) { + publicKeyHex = publicKeyHex.substring(2); + } + + if (publicKeyHex.length() != 96) { + return false; + } + + byte[] publicKeyBytes = Hex.decode(publicKeyHex); + + P1_Affine point = new P1_Affine(publicKeyBytes); + + return point.in_group(); + } catch (Exception e) { + return false; + } + } +} diff --git a/src/main/java/org/arkecosystem/crypto/transactions/builder/ValidatorRegistrationBuilder.java b/src/main/java/org/arkecosystem/crypto/transactions/builder/ValidatorRegistrationBuilder.java index 01049455..34fbda5d 100644 --- a/src/main/java/org/arkecosystem/crypto/transactions/builder/ValidatorRegistrationBuilder.java +++ b/src/main/java/org/arkecosystem/crypto/transactions/builder/ValidatorRegistrationBuilder.java @@ -1,11 +1,16 @@ package org.arkecosystem.crypto.transactions.builder; +import org.arkecosystem.crypto.identities.BlsPublicKey; import org.arkecosystem.crypto.transactions.types.AbstractTransaction; import org.arkecosystem.crypto.transactions.types.ValidatorRegistration; public class ValidatorRegistrationBuilder extends AbstractTransactionBuilder { public ValidatorRegistrationBuilder validatorPublicKey(String validatorPublicKey) { + if (!BlsPublicKey.validate(validatorPublicKey)) { + throw new IllegalArgumentException("Invalid BLS public key"); + } + this.transaction.validatorPublicKey = validatorPublicKey; this.transaction.refreshPayloadData(); diff --git a/src/test/java/org/arkecosystem/crypto/identities/BlsPublicKeyTest.java b/src/test/java/org/arkecosystem/crypto/identities/BlsPublicKeyTest.java new file mode 100644 index 00000000..98e096b5 --- /dev/null +++ b/src/test/java/org/arkecosystem/crypto/identities/BlsPublicKeyTest.java @@ -0,0 +1,118 @@ +package org.arkecosystem.crypto.identities; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class BlsPublicKeyTest { + + @Test + public void validBlsPublicKeyFromMnemonic() { + assertTrue( + BlsPublicKey.validate( + "b4865127896c3c5286296a7b26e7c8002586a3ecf5832bfb59e689336f1f4c75e10491b9dfaed8dfb2c2fbe22d11fa93")); + } + + @Test + public void validG1GeneratorPoint() { + assertTrue( + BlsPublicKey.validate( + "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb")); + } + + @Test + public void validSecondBlsPublicKey() { + assertTrue( + BlsPublicKey.validate( + "95af988701a6fb60e09da41d2ca1a9e0b49e43501bda4255b3ca01073f490c34102b6bbcafde6333185e9980745d72cb")); + } + + @Test + public void validBlsPublicKeyWith0xPrefix() { + assertTrue( + BlsPublicKey.validate( + "0xb4865127896c3c5286296a7b26e7c8002586a3ecf5832bfb59e689336f1f4c75e10491b9dfaed8dfb2c2fbe22d11fa93")); + } + + @Test + public void emptyString() { + assertFalse(BlsPublicKey.validate("")); + } + + @Test + public void singleCharacter() { + assertFalse(BlsPublicKey.validate("0")); + } + + @Test + public void nonHexString() { + assertFalse(BlsPublicKey.validate("NOT A VALID PUBLICKEY")); + } + + @Test + public void invalidHexCharactersMixedWithValid() { + assertFalse(BlsPublicKey.validate("02b5Gf")); + } + + @Test + public void tooShort33BytesZeroPadded() { + assertFalse( + BlsPublicKey.validate( + "000000000000000000000000000000000000000000000000000000000000000000")); + } + + @Test + public void secp256k1CompressedPublicKeyPrefix02() { + assertFalse( + BlsPublicKey.validate( + "02e0f7449c5588f24492c338f2bc8f7865f755b958d48edb0f2d0056e50c3fd5b7")); + } + + @Test + public void secp256k1CompressedPublicKeyPrefix03() { + assertFalse( + BlsPublicKey.validate( + "038c14b793cb19137e323a6d2e2a870bca2e7a493ec1153b3a95feb8a4873f8d08")); + } + + @Test + public void secp256k1CompressedKeyWithInvalidHex() { + assertFalse( + BlsPublicKey.validate( + "02b5Gf00d9de5a3ace28913fe78a15afcfe242926e94d9b517d06d2705b261f992")); + } + + @Test + public void secp256k1UncompressedLengthKeyPrefix32() { + assertFalse( + BlsPublicKey.validate( + "32337416a26d8d49ec27059bd0589c49bb474029c3627715380f4df83fb431aece")); + } + + @Test + public void secp256k1UncompressedLengthKeyPrefix22() { + assertFalse( + BlsPublicKey.validate( + "22337416a26d8d49ec27059bd0589c49bb474029c3627715380f4df83fb431aece")); + } + + @Test + public void correctLengthButInvalidPoint() { + assertFalse( + BlsPublicKey.validate( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001")); + } + + @Test + public void tooLongBy1Byte() { + assertFalse( + BlsPublicKey.validate( + "b4865127896c3c5286296a7b26e7c8002586a3ecf5832bfb59e689336f1f4c75e10491b9dfaed8dfb2c2fbe22d11fa9300")); + } + + @Test + public void nullInput() { + assertFalse(BlsPublicKey.validate(null)); + } +} diff --git a/src/test/java/org/arkecosystem/crypto/transactions/builder/ValidatorRegistrationBuilderTest.java b/src/test/java/org/arkecosystem/crypto/transactions/builder/ValidatorRegistrationBuilderTest.java index 340fa7e5..5843dc44 100644 --- a/src/test/java/org/arkecosystem/crypto/transactions/builder/ValidatorRegistrationBuilderTest.java +++ b/src/test/java/org/arkecosystem/crypto/transactions/builder/ValidatorRegistrationBuilderTest.java @@ -33,4 +33,13 @@ public void it_should_sign_it_with_a_passphrase() throws Exception { assertEquals(data.get("id"), builder.transaction.getId()); assertTrue(builder.verify()); } + + @Test + public void it_should_throw_on_invalid_bls_public_key() { + assertThrows( + IllegalArgumentException.class, + () -> { + new ValidatorRegistrationBuilder().validatorPublicKey("invalid-bls-key"); + }); + } }