From e4cb515694b893b9f384b757e0012fd08aa31fd3 Mon Sep 17 00:00:00 2001 From: grossiwm Date: Sun, 20 Oct 2024 00:32:59 -0300 Subject: [PATCH 1/3] BTC transaction hashes and address generation --- .../java/com/github/javafaker/Bitcoin.java | 108 ++++++++++++++++++ .../com/github/javafaker/BitcoinTest.java | 33 ++++++ 2 files changed, 141 insertions(+) create mode 100644 src/main/java/com/github/javafaker/Bitcoin.java create mode 100644 src/test/java/com/github/javafaker/BitcoinTest.java diff --git a/src/main/java/com/github/javafaker/Bitcoin.java b/src/main/java/com/github/javafaker/Bitcoin.java new file mode 100644 index 00000000..73efa44e --- /dev/null +++ b/src/main/java/com/github/javafaker/Bitcoin.java @@ -0,0 +1,108 @@ +package com.github.javafaker; + + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +public class Bitcoin { + private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + private static final int BASE = ALPHABET.length(); + + private static final Map PROTOCOL_VERSIONS = new HashMap() {{ + put("main", 0); + put("testnet", 111); + }}; + + public static String address() { + return addressFor("main"); + } + + public static String testnetAddress() { + return addressFor("testnet"); + } + + public static String generateTransactionHash() { + try { + byte[] randomData = new byte[32]; + SecureRandom random = new SecureRandom(); + random.nextBytes(randomData); + + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(randomData); + + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) hexString.append('0'); + hexString.append(hex); + } + return hexString.toString(); + + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 algorithm not available", e); + } + } + + private static String addressFor(String network) { + Integer version = PROTOCOL_VERSIONS.get(network); + if (version == null) { + throw new IllegalArgumentException("Invalid network specified"); + } + + byte[] addressBytes = new byte[21]; + addressBytes[0] = version.byteValue(); + + Random random = new Random(); + byte[] randomBytes = new byte[20]; + random.nextBytes(randomBytes); + System.arraycopy(randomBytes, 0, addressBytes, 1, 20); + + byte[] checksum = calculateChecksum(addressBytes); + + byte[] fullAddress = new byte[25]; + System.arraycopy(addressBytes, 0, fullAddress, 0, 21); + System.arraycopy(checksum, 0, fullAddress, 21, 4); + + return encodeBase58(fullAddress); + } + + private static byte[] calculateChecksum(byte[] data) { + + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + byte[] hash1 = sha256.digest(data); + byte[] hash2 = sha256.digest(hash1); + byte[] checksum = new byte[4]; + System.arraycopy(hash2, 0, checksum, 0, 4); + return checksum; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 algorithm not found", e); + } + } + + private static String encodeBase58(byte[] input) { + java.math.BigInteger lv = java.math.BigInteger.ZERO; + for (int i = 0; i < input.length; i++) { + int value = input[input.length - 1 - i] & 0xFF; + lv = lv.add(java.math.BigInteger.valueOf(value).multiply(java.math.BigInteger.valueOf(256).pow(i))); + } + + StringBuilder ret = new StringBuilder(); + while (lv.compareTo(java.math.BigInteger.ZERO) > 0) { + java.math.BigInteger[] divmod = lv.divideAndRemainder(java.math.BigInteger.valueOf(BASE)); + lv = divmod[0]; + int mod = divmod[1].intValue(); + ret.append(ALPHABET.charAt(mod)); + } + + for (int i = 0; i < input.length && input[i] == 0; i++) { + ret.append(ALPHABET.charAt(0)); + } + + return ret.reverse().toString(); + } +} diff --git a/src/test/java/com/github/javafaker/BitcoinTest.java b/src/test/java/com/github/javafaker/BitcoinTest.java new file mode 100644 index 00000000..46d2a0f3 --- /dev/null +++ b/src/test/java/com/github/javafaker/BitcoinTest.java @@ -0,0 +1,33 @@ +package com.github.javafaker; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class BitcoinTest extends AbstractFakerTest { + + @Test + public void testMainnetAddressGeneration() { + String address = Bitcoin.address(); + assertNotNull("Address should not be null.", address); + assertFalse("Address should not be empty.", address.isEmpty()); + assertTrue("Address should start with '1' or '3'.", address.startsWith("1") || address.startsWith("3")); + } + + @Test + public void testTestnetAddressGeneration() { + String address = Bitcoin.testnetAddress(); + assertNotNull("Address should not be null.", address); + assertFalse("Address should not be empty.", address.isEmpty()); + assertTrue("Address should start with 'm' or 'n'.", address.startsWith("m") || address.startsWith("n")); + } + + @Test + public void testGenerateTransactionHash() { + String transactionHash = Bitcoin.generateTransactionHash(); + assertNotNull("Transaction hash should not be null.", transactionHash); + assertEquals("Transaction hash should be 64 characters long.", 64, transactionHash.length()); + assertTrue("Transaction hash should contain only hexadecimal characters.", transactionHash.matches("^[0-9a-fA-F]+$")); + } + +} From 6e83aea51c29af0faba05f08ddab9f799c4de6fb Mon Sep 17 00:00:00 2001 From: grossiwm Date: Sun, 20 Oct 2024 00:51:54 -0300 Subject: [PATCH 2/3] test generated addresses length --- src/test/java/com/github/javafaker/BitcoinTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/com/github/javafaker/BitcoinTest.java b/src/test/java/com/github/javafaker/BitcoinTest.java index 46d2a0f3..f03c3fdc 100644 --- a/src/test/java/com/github/javafaker/BitcoinTest.java +++ b/src/test/java/com/github/javafaker/BitcoinTest.java @@ -12,6 +12,8 @@ public void testMainnetAddressGeneration() { assertNotNull("Address should not be null.", address); assertFalse("Address should not be empty.", address.isEmpty()); assertTrue("Address should start with '1' or '3'.", address.startsWith("1") || address.startsWith("3")); + assertTrue("Address length should be between 26 and 35 characters.", + address.length() >= 26 && address.length() <= 35); } @Test @@ -20,6 +22,8 @@ public void testTestnetAddressGeneration() { assertNotNull("Address should not be null.", address); assertFalse("Address should not be empty.", address.isEmpty()); assertTrue("Address should start with 'm' or 'n'.", address.startsWith("m") || address.startsWith("n")); + assertTrue("Address length should be between 26 and 35 characters.", + address.length() >= 26 && address.length() <= 35); } @Test From 3c9112319a46de00eb667602bd1dc3dcf96dcf3e Mon Sep 17 00:00:00 2001 From: grossiwm Date: Sun, 20 Oct 2024 01:08:26 -0300 Subject: [PATCH 3/3] initialize Bitcoin faker on faker class and README --- README.md | 1 + .../java/com/github/javafaker/Bitcoin.java | 19 ++++++++++--------- src/main/java/com/github/javafaker/Faker.java | 5 +++++ .../com/github/javafaker/BitcoinTest.java | 6 +++--- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index d1385324..7b1455c2 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ Fakers * Witcher * Yoda * Zelda +* Bitcoin Usage with Locales ----- diff --git a/src/main/java/com/github/javafaker/Bitcoin.java b/src/main/java/com/github/javafaker/Bitcoin.java index 73efa44e..cb161e6c 100644 --- a/src/main/java/com/github/javafaker/Bitcoin.java +++ b/src/main/java/com/github/javafaker/Bitcoin.java @@ -9,23 +9,24 @@ import java.util.Random; public class Bitcoin { - private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; - private static final int BASE = ALPHABET.length(); - private static final Map PROTOCOL_VERSIONS = new HashMap() {{ + private final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + private final int BASE = ALPHABET.length(); + + private final Map PROTOCOL_VERSIONS = new HashMap() {{ put("main", 0); put("testnet", 111); }}; - public static String address() { + public String address() { return addressFor("main"); } - public static String testnetAddress() { + public String testnetAddress() { return addressFor("testnet"); } - public static String generateTransactionHash() { + public String generateTransactionHash() { try { byte[] randomData = new byte[32]; SecureRandom random = new SecureRandom(); @@ -47,7 +48,7 @@ public static String generateTransactionHash() { } } - private static String addressFor(String network) { + private String addressFor(String network) { Integer version = PROTOCOL_VERSIONS.get(network); if (version == null) { throw new IllegalArgumentException("Invalid network specified"); @@ -70,7 +71,7 @@ private static String addressFor(String network) { return encodeBase58(fullAddress); } - private static byte[] calculateChecksum(byte[] data) { + private byte[] calculateChecksum(byte[] data) { try { MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); @@ -84,7 +85,7 @@ private static byte[] calculateChecksum(byte[] data) { } } - private static String encodeBase58(byte[] input) { + private String encodeBase58(byte[] input) { java.math.BigInteger lv = java.math.BigInteger.ZERO; for (int i = 0; i < input.length; i++) { int value = input[input.length - 1 - i] & 0xFF; diff --git a/src/main/java/com/github/javafaker/Faker.java b/src/main/java/com/github/javafaker/Faker.java index 751fb779..ac85feb9 100644 --- a/src/main/java/com/github/javafaker/Faker.java +++ b/src/main/java/com/github/javafaker/Faker.java @@ -107,6 +107,7 @@ public class Faker { private final Sip sip; private final EnglandFootBall englandfootball; private final Mountain mountain; + private final Bitcoin bitcoin; public Faker() { this(Locale.ENGLISH); @@ -223,6 +224,8 @@ public Faker(FakeValuesService fakeValuesService, RandomService random) { this.sip = new Sip(this); this.englandfootball = new EnglandFootBall(this); this.mountain = new Mountain(this); + this.bitcoin = new Bitcoin(); + } /** @@ -691,6 +694,8 @@ public StarCraft starCraft() { public Mountain mountain() { return mountain; } + public Bitcoin bitcoin() { return bitcoin; } + public String resolve(String key) { return this.fakeValuesService.resolve(key, this, this); } diff --git a/src/test/java/com/github/javafaker/BitcoinTest.java b/src/test/java/com/github/javafaker/BitcoinTest.java index f03c3fdc..9e949ffb 100644 --- a/src/test/java/com/github/javafaker/BitcoinTest.java +++ b/src/test/java/com/github/javafaker/BitcoinTest.java @@ -8,7 +8,7 @@ public class BitcoinTest extends AbstractFakerTest { @Test public void testMainnetAddressGeneration() { - String address = Bitcoin.address(); + String address = faker.bitcoin().address(); assertNotNull("Address should not be null.", address); assertFalse("Address should not be empty.", address.isEmpty()); assertTrue("Address should start with '1' or '3'.", address.startsWith("1") || address.startsWith("3")); @@ -18,7 +18,7 @@ public void testMainnetAddressGeneration() { @Test public void testTestnetAddressGeneration() { - String address = Bitcoin.testnetAddress(); + String address = faker.bitcoin().testnetAddress(); assertNotNull("Address should not be null.", address); assertFalse("Address should not be empty.", address.isEmpty()); assertTrue("Address should start with 'm' or 'n'.", address.startsWith("m") || address.startsWith("n")); @@ -28,7 +28,7 @@ public void testTestnetAddressGeneration() { @Test public void testGenerateTransactionHash() { - String transactionHash = Bitcoin.generateTransactionHash(); + String transactionHash = faker.bitcoin().generateTransactionHash(); assertNotNull("Transaction hash should not be null.", transactionHash); assertEquals("Transaction hash should be 64 characters long.", 64, transactionHash.length()); assertTrue("Transaction hash should contain only hexadecimal characters.", transactionHash.matches("^[0-9a-fA-F]+$"));