diff --git a/assets/src/main/java/bisq/asset/GrinAddressValidator.java b/assets/src/main/java/bisq/asset/GrinAddressValidator.java new file mode 100644 index 0000000000..71c8ec36f9 --- /dev/null +++ b/assets/src/main/java/bisq/asset/GrinAddressValidator.java @@ -0,0 +1,106 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.asset; + +/** + * The supported "address" (better wallet URL) format is IP:port or the grinbox format. + * + * Here is the information from a conversation with the Grinbox developer regarding the Grinbox address format. + * + A Grinbox address is of the format: grinbox://@domain.com:port where everything besides is optional. + If no domain is specified, the default relay grinbox.io will be used. + + The is a base58check encoded value (like in Bitcoin). For Grin mainnet, the first 2 bytes will be [1, 11] and + the following 33 bytes should be a valid secp256k1 compressed public key. + + Some examples of valid addresses are: + + gVvRNiuopubvxPrs1BzJdQjVdFAxmkLzMqiVJzUZ7ubznhdtNTGB + gVvUcSafSTD3YTSqgNf9ojEYWkz3zMZNfsjdpdb9en5mxc6gmja6 + gVvk7rLBg3r3qoWYL3VsREnBbooT7nynxx5HtDvUWCJUaNCnddvY + grinbox://gVtWzX5NTLCBkyNV19QVdnLXue13heAVRD36sfkGD6xpqy7k7e4a + gVw9TWimGFXRjoDXWhWxeNQbu84ZpLkvnenkKvA5aJeDo31eM5tC@somerelay.com + grinbox://gVwjSsYW5vvHpK4AunJ5piKhhQTV6V3Jb818Uqs6PdC3SsB36AsA@somerelay.com:1220 + + Some examples of invalid addresses are: + + gVuBJDKcWkhueMfBLAbFwV4ax55YXPeinWXdRME1Zi3eiC6sFNye (invalid checksum) + geWGCMQjxZMHG3EtTaRbR7rH9rE4DsmLfpm1iiZEa7HFKjjkgpf2 (wrong version bytes) + gVvddC2jYAfxTxnikcbTEQKLjhJZpqpBg39tXkwAKnD2Pys2mWiK (invalid public key) + + We only add the basic validation without checksum, version byte and pubkey validation as that would require much more + effort. Any Grin developer is welcome to add that though! + + */ +public class GrinAddressValidator implements AddressValidator { + // A Grin Wallet URL (address is not the correct term) can be in the form IP:port or a grinbox format. + // The grinbox has the format grinbox://@domain.com:port where everything beside the key is optional. + + + // Regex for IP validation borrowed from https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses + private static final String IPV4 = "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"; + private static final String PORT = "((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))$"; + private static final String DOMAIN = "[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\\.[a-zA-Z]{2,}$"; + private static final String KEY = "[a-km-zA-HJ-NP-Z1-9]{52}$"; + + public GrinAddressValidator() { + } + + @Override + public AddressValidationResult validate(String address) { + if (address == null || address.length() == 0) + return AddressValidationResult.invalidAddress("Address may not be empty"); + + if (address.matches("^" + IPV4 + ":" + PORT)) + return AddressValidationResult.validAddress(); + + + // We might have a grinbox address + String key; + String domain = null; + String port = null; + address = address.replace("grinbox://", ""); + if (address.contains("@")) { + String[] keyAndDomain = address.split("@"); + key = keyAndDomain[0]; + if (keyAndDomain.length > 1) { + domain = keyAndDomain[1]; + if (domain.contains(":")) { + String[] domainAndPort = domain.split(":"); + domain = domainAndPort[0]; + if (domainAndPort.length > 1) + port = domainAndPort[1]; + } + } + } else { + key = address; + } + + if (!key.matches("^" + KEY)) + return AddressValidationResult.invalidAddress("Invalid key"); + + if (domain != null && !domain.matches("^" + DOMAIN)) + return AddressValidationResult.invalidAddress("Invalid domain"); + + if (port != null && !port.matches("^" + PORT)) + return AddressValidationResult.invalidAddress("Invalid port"); + + return AddressValidationResult.validAddress(); + + } +} diff --git a/assets/src/main/java/bisq/asset/coins/Grin.java b/assets/src/main/java/bisq/asset/coins/Grin.java new file mode 100644 index 0000000000..b934077801 --- /dev/null +++ b/assets/src/main/java/bisq/asset/coins/Grin.java @@ -0,0 +1,28 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.asset.coins; + +import bisq.asset.Coin; +import bisq.asset.GrinAddressValidator; + +public class Grin extends Coin { + + public Grin() { + super("Grin", "GRIN", new GrinAddressValidator()); + } +} diff --git a/assets/src/main/resources/META-INF/services/bisq.asset.Asset b/assets/src/main/resources/META-INF/services/bisq.asset.Asset index 760f5ada65..ab26b4df1a 100644 --- a/assets/src/main/resources/META-INF/services/bisq.asset.Asset +++ b/assets/src/main/resources/META-INF/services/bisq.asset.Asset @@ -25,6 +25,7 @@ bisq.asset.coins.Dragonglass bisq.asset.coins.Ether bisq.asset.coins.EtherClassic bisq.asset.coins.GambleCoin +bisq.asset.coins.Grin bisq.asset.coins.FourtyTwo bisq.asset.coins.Gridcoin bisq.asset.coins.Horizen diff --git a/assets/src/test/java/bisq/asset/coins/GrinTest.java b/assets/src/test/java/bisq/asset/coins/GrinTest.java new file mode 100644 index 0000000000..22851f78bb --- /dev/null +++ b/assets/src/test/java/bisq/asset/coins/GrinTest.java @@ -0,0 +1,76 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.asset.coins; + +import bisq.asset.AbstractAssetTest; + +import org.junit.Test; + +public class GrinTest extends AbstractAssetTest { + + public GrinTest() { + super(new Grin()); + } + + @Test + public void testValidAddresses() { + assertValidAddress("0.0.0.0:8080"); + assertValidAddress("173.194.34.134:8080"); + assertValidAddress("127.0.0.1:8080"); + assertValidAddress("192.168.0.1:8080"); + assertValidAddress("18.101.25.153:8080"); + assertValidAddress("173.194.34.134:1"); + assertValidAddress("173.194.34.134:11"); + assertValidAddress("173.194.34.134:1111"); + assertValidAddress("173.194.34.134:65535"); + + // grinbox + assertValidAddress("gVvk7rLBg3r3qoWYL3VsREnBbooT7nynxx5HtDvUWCJUaNCnddvY"); + assertValidAddress("grinbox://gVtWzX5NTLCBkyNV19QVdnLXue13heAVRD36sfkGD6xpqy7k7e4a"); + assertValidAddress("gVw9TWimGFXRjoDXWhWxeNQbu84ZpLkvnenkKvA5aJeDo31eM5tC@somerelay.com"); + assertValidAddress("gVw9TWimGFXRjoDXWhWxeNQbu84ZpLkvnenkKvA5aJeDo31eM5tC@somerelay.com:1220"); + assertValidAddress("grinbox://gVwjSsYW5vvHpK4AunJ5piKhhQTV6V3Jb818Uqs6PdC3SsB36AsA@somerelay.com"); + assertValidAddress("grinbox://gVwjSsYW5vvHpK4AunJ5piKhhQTV6V3Jb818Uqs6PdC3SsB36AsA@somerelay.com:1220"); + } + + @Test + public void testInvalidAddresses() { + assertInvalidAddress("google.com"); + assertInvalidAddress("100.100.100.100"); + assertInvalidAddress(".100.100.100.100:1222"); + assertInvalidAddress("100..100.100.100:1222."); + assertInvalidAddress("100.100.100.100.:1222"); + assertInvalidAddress("999.999.999.999:1222"); + assertInvalidAddress("256.256.256.256:1222"); + assertInvalidAddress("256.100.100.100.100:1222"); + assertInvalidAddress("123.123.123:1222"); + assertInvalidAddress("http://123.123.123:1222"); + assertInvalidAddress("1000.2.3.4:1222"); + assertInvalidAddress("999.2.3.4:1222"); + // too large port + assertInvalidAddress("173.194.34.134:65536"); + + assertInvalidAddress("gVvk7rLBg3r3qoWYL3VsREnBbooT7nynxx5HtDvUWCJUaNCnddvY1111"); + assertInvalidAddress("grinbox:/gVtWzX5NTLCBkyNV19QVdnLXue13heAVRD36sfkGD6xpqy7k7e4a"); + assertInvalidAddress("gVw9TWimGFXRjoDXWhWxeNQbu84ZpLkvnenkKvA5aJeDo31eM5tC@somerelay.com."); + assertInvalidAddress("gVw9TWimGFXRjoDXWhWxeNQbu84ZpLkvnenkKvA5aJeDo31eM5tC@somerelay.com:1220a"); + assertInvalidAddress("grinbox://gVwjSsYW5vvHpK4AunJ5piKhhQTV6V3Jb818Uqs6PdC3SsB36AsAsomerelay.com"); + assertInvalidAddress("grinbox://gVwjSsYW5vvHpK4AunJ5piKhhQTV6V3Jb818Uqs6PdC3SsB36AsA@somerelay.com1220"); + + } +}