From 8994f04b4a25b0c6edc836a6c5ece884c604d516 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 5 Aug 2024 12:29:46 -0600 Subject: [PATCH] feat: keccak address support (#180) --- build.gradle | 1 + .../crypto/identities/Address.java | 62 ++++++++----------- .../transactions/builder/VoteBuilder.java | 2 +- .../crypto/identities/AddressTest.java | 12 ++-- src/test/resources/identity.json | 2 +- .../passphrase-with-vendor-field-hex.json | 2 +- .../passphrase-with-vendor-field.json | 2 +- .../transactions/V1/transfer/passphrase.json | 2 +- ...cond-passphrase-with-vendor-field-hex.json | 2 +- .../second-passphrase-with-vendor-field.json | 2 +- .../V1/transfer/second-passphrase.json | 2 +- .../transactions/V1/vote/passphrase.json | 2 +- .../V1/vote/second-passphrase.json | 2 +- 13 files changed, 42 insertions(+), 53 deletions(-) diff --git a/build.gradle b/build.gradle index fac5a106..a81f6ca9 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,7 @@ repositories { } dependencies { + implementation 'org.web3j:core:4.8.7' implementation 'org.bitcoinj:bitcoinj-core:0.16.3' implementation 'com.google.code.gson:gson:2.11.0' implementation 'com.google.guava:guava:30.2.0-jre' diff --git a/src/main/java/org/arkecosystem/crypto/identities/Address.java b/src/main/java/org/arkecosystem/crypto/identities/Address.java index d9d429eb..e4cb6ba5 100644 --- a/src/main/java/org/arkecosystem/crypto/identities/Address.java +++ b/src/main/java/org/arkecosystem/crypto/identities/Address.java @@ -1,58 +1,50 @@ package org.arkecosystem.crypto.identities; -import com.google.common.primitives.Bytes; -import org.arkecosystem.crypto.configuration.Network; -import org.arkecosystem.crypto.encoding.Base58; import org.arkecosystem.crypto.encoding.Hex; import org.bitcoinj.core.ECKey; -import org.bouncycastle.crypto.digests.RIPEMD160Digest; +import org.web3j.crypto.Hash; +import org.web3j.crypto.Keys; public class Address { - public static String fromPassphrase(String passphrase, Integer networkVersion) { - return fromPrivateKey(PrivateKey.fromPassphrase(passphrase), networkVersion); - } - public static String fromPassphrase(String passphrase) { - return Address.fromPassphrase(passphrase, null); + ECKey privateKey = PrivateKey.fromPassphrase(passphrase); + return fromPrivateKey(privateKey); } - public static String fromPublicKey(String publicKey, Integer networkVersion) { + public static String fromPublicKey(String publicKey) { byte[] publicKeyBytes = Hex.decode(publicKey); - RIPEMD160Digest digest = new RIPEMD160Digest(); - digest.update(publicKeyBytes, 0, publicKeyBytes.length); - byte[] out = new byte[20]; - digest.doFinal(out, 0); + // Ensure the public key is uncompressed + ECKey ecKey = ECKey.fromPublicOnly(publicKeyBytes); + byte[] uncompressedPublicKeyBytes = ecKey.getPubKeyPoint().getEncoded(false); - if (networkVersion == null) { - networkVersion = Network.get().version(); - } + // Remove the prefix (0x04) + byte[] rawPublicKey = new byte[uncompressedPublicKeyBytes.length - 1]; + System.arraycopy(uncompressedPublicKeyBytes, 1, rawPublicKey, 0, rawPublicKey.length); - byte[] bytes = Bytes.concat(new byte[] {networkVersion.byteValue()}, out); - return Base58.encodeChecked(bytes); - } + // Hash the public key using Keccak-256 + byte[] keccakHash = Hash.sha3(rawPublicKey); - public static String fromPublicKey(String publicKey) { - return Address.fromPublicKey(publicKey, null); - } + // Take the last 20 bytes of the Keccak-256 hash + byte[] addressBytes = new byte[20]; - public static String fromPrivateKey(ECKey privateKey, Integer networkVersion) { - return fromPublicKey(privateKey.getPublicKeyAsHex(), networkVersion); - } + System.arraycopy(keccakHash, keccakHash.length - 20, addressBytes, 0, 20); - public static String fromPrivateKey(ECKey privateKey) { - return Address.fromPrivateKey(privateKey, null); - } + // Convert to checksum address + String address = "0x" + Hex.encode(addressBytes); - public static Boolean validate(String address, Integer networkVersion) { - if (networkVersion == null) { - networkVersion = Network.get().version(); - } + return Keys.toChecksumAddress(address); + } - return Base58.decodeChecked(address)[0] == networkVersion; + public static String fromPrivateKey(ECKey privateKey) { + byte[] publicKeyBytes = privateKey.getPubKey(); + return fromPublicKey(Hex.encode(publicKeyBytes)); } public static Boolean validate(String address) { - return Address.validate(address, null); + if (address == null || !address.matches("^0x[a-fA-F0-9]{40}$")) { + return false; + } + return address.equals(Keys.toChecksumAddress(address)); } } diff --git a/src/main/java/org/arkecosystem/crypto/transactions/builder/VoteBuilder.java b/src/main/java/org/arkecosystem/crypto/transactions/builder/VoteBuilder.java index 1cb1b1f2..d0209cf8 100644 --- a/src/main/java/org/arkecosystem/crypto/transactions/builder/VoteBuilder.java +++ b/src/main/java/org/arkecosystem/crypto/transactions/builder/VoteBuilder.java @@ -24,7 +24,7 @@ public VoteBuilder addVote(String vote) { } public VoteBuilder sign(String passphrase) { - this.transaction.recipientId = Address.fromPassphrase(passphrase, this.transaction.network); + this.transaction.recipientId = Address.fromPassphrase(passphrase); super.sign(passphrase); diff --git a/src/test/java/org/arkecosystem/crypto/identities/AddressTest.java b/src/test/java/org/arkecosystem/crypto/identities/AddressTest.java index 33cde63c..774622ad 100644 --- a/src/test/java/org/arkecosystem/crypto/identities/AddressTest.java +++ b/src/test/java/org/arkecosystem/crypto/identities/AddressTest.java @@ -3,8 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.arkecosystem.crypto.configuration.Network; -import org.arkecosystem.crypto.networks.Devnet; import org.bitcoinj.core.ECKey; import org.junit.jupiter.api.Test; @@ -12,9 +10,8 @@ public class AddressTest { @Test public void fromPassphrase() { - Network.set(new Devnet()); String actual = Address.fromPassphrase("this is a top secret passphrase"); - assertEquals("D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", actual); + assertEquals("0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01", actual); } @Test @@ -22,19 +19,18 @@ public void fromPublicKey() { String actual = Address.fromPublicKey( "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192"); - assertEquals("D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", actual); + assertEquals("0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01", actual); } @Test public void fromPrivateKey() { ECKey privateKey = PrivateKey.fromPassphrase("this is a top secret passphrase"); String actual = Address.fromPrivateKey(privateKey); - assertEquals("D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", actual); + assertEquals("0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01", actual); } @Test public void validate() { - Network.set(new Devnet()); - assertTrue(Address.validate("D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib")); + assertTrue(Address.validate("0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01")); } } diff --git a/src/test/resources/identity.json b/src/test/resources/identity.json index 5b566039..662d91b6 100644 --- a/src/test/resources/identity.json +++ b/src/test/resources/identity.json @@ -2,7 +2,7 @@ "data": { "privateKey": "d8839c2432bfd0a67ef10a804ba991eabba19f154a3d707917681d45822a5712", "publicKey": "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192", - "address": "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", + "address": "0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01", "wif": "SGq4xLgZKCGxs7bjmwnBrWcT4C1ADFEermj846KC97FSv1WFD1dA" }, "passphrase": "this is a top secret passphrase" diff --git a/src/test/resources/transactions/V1/transfer/passphrase-with-vendor-field-hex.json b/src/test/resources/transactions/V1/transfer/passphrase-with-vendor-field-hex.json index ebab1ca2..419d700a 100644 --- a/src/test/resources/transactions/V1/transfer/passphrase-with-vendor-field-hex.json +++ b/src/test/resources/transactions/V1/transfer/passphrase-with-vendor-field-hex.json @@ -3,7 +3,7 @@ "type": 0, "amount": 200000000, "fee": 10000000, - "recipientId": "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", + "recipientId": "0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01", "timestamp": 41443847, "asset": {}, "vendorFieldHex": "48656c6c6f20576f726c64", diff --git a/src/test/resources/transactions/V1/transfer/passphrase-with-vendor-field.json b/src/test/resources/transactions/V1/transfer/passphrase-with-vendor-field.json index fac09076..20e50e0f 100644 --- a/src/test/resources/transactions/V1/transfer/passphrase-with-vendor-field.json +++ b/src/test/resources/transactions/V1/transfer/passphrase-with-vendor-field.json @@ -3,7 +3,7 @@ "type": 0, "amount": 200000000, "fee": 10000000, - "recipientId": "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", + "recipientId": "0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01", "timestamp": 41443847, "asset": {}, "vendorField": "Hello World", diff --git a/src/test/resources/transactions/V1/transfer/passphrase.json b/src/test/resources/transactions/V1/transfer/passphrase.json index 70490d28..2ea021d2 100644 --- a/src/test/resources/transactions/V1/transfer/passphrase.json +++ b/src/test/resources/transactions/V1/transfer/passphrase.json @@ -3,7 +3,7 @@ "type": 0, "amount": 200000000, "fee": 10000000, - "recipientId": "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", + "recipientId": "0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01", "timestamp": 41268326, "asset": {}, "senderPublicKey": "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192", diff --git a/src/test/resources/transactions/V1/transfer/second-passphrase-with-vendor-field-hex.json b/src/test/resources/transactions/V1/transfer/second-passphrase-with-vendor-field-hex.json index c30f7ada..02f6b2eb 100644 --- a/src/test/resources/transactions/V1/transfer/second-passphrase-with-vendor-field-hex.json +++ b/src/test/resources/transactions/V1/transfer/second-passphrase-with-vendor-field-hex.json @@ -3,7 +3,7 @@ "type": 0, "amount": 200000000, "fee": 10000000, - "recipientId": "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", + "recipientId": "0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01", "timestamp": 41443865, "asset": {}, "vendorFieldHex": "48656c6c6f20576f726c64", diff --git a/src/test/resources/transactions/V1/transfer/second-passphrase-with-vendor-field.json b/src/test/resources/transactions/V1/transfer/second-passphrase-with-vendor-field.json index c7473f10..8c8e38dd 100644 --- a/src/test/resources/transactions/V1/transfer/second-passphrase-with-vendor-field.json +++ b/src/test/resources/transactions/V1/transfer/second-passphrase-with-vendor-field.json @@ -3,7 +3,7 @@ "type": 0, "amount": 200000000, "fee": 10000000, - "recipientId": "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", + "recipientId": "0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01", "timestamp": 41443865, "asset": {}, "vendorField": "Hello World", diff --git a/src/test/resources/transactions/V1/transfer/second-passphrase.json b/src/test/resources/transactions/V1/transfer/second-passphrase.json index 8a0ebe09..ed6d9fe9 100644 --- a/src/test/resources/transactions/V1/transfer/second-passphrase.json +++ b/src/test/resources/transactions/V1/transfer/second-passphrase.json @@ -3,7 +3,7 @@ "type": 0, "amount": 200000000, "fee": 10000000, - "recipientId": "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", + "recipientId": "0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01", "timestamp": 41268430, "asset": {}, "senderPublicKey": "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192", diff --git a/src/test/resources/transactions/V1/vote/passphrase.json b/src/test/resources/transactions/V1/vote/passphrase.json index 7b0612f1..1a66f0a6 100644 --- a/src/test/resources/transactions/V1/vote/passphrase.json +++ b/src/test/resources/transactions/V1/vote/passphrase.json @@ -3,7 +3,7 @@ "type": 3, "amount": 0, "fee": 100000000, - "recipientId": "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", + "recipientId": "0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01", "senderPublicKey": "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192", "timestamp": 41269349, "asset": { diff --git a/src/test/resources/transactions/V1/vote/second-passphrase.json b/src/test/resources/transactions/V1/vote/second-passphrase.json index c694f360..e5e666c5 100644 --- a/src/test/resources/transactions/V1/vote/second-passphrase.json +++ b/src/test/resources/transactions/V1/vote/second-passphrase.json @@ -3,7 +3,7 @@ "type": 3, "amount": 0, "fee": 100000000, - "recipientId": "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", + "recipientId": "0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01", "senderPublicKey": "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192", "timestamp": 41269366, "asset": {