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 new file mode 100644 index 00000000..cb161e6c --- /dev/null +++ b/src/main/java/com/github/javafaker/Bitcoin.java @@ -0,0 +1,109 @@ +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 final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + private final int BASE = ALPHABET.length(); + + private final Map PROTOCOL_VERSIONS = new HashMap() {{ + put("main", 0); + put("testnet", 111); + }}; + + public String address() { + return addressFor("main"); + } + + public String testnetAddress() { + return addressFor("testnet"); + } + + public 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 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 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 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/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 new file mode 100644 index 00000000..9e949ffb --- /dev/null +++ b/src/test/java/com/github/javafaker/BitcoinTest.java @@ -0,0 +1,37 @@ +package com.github.javafaker; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class BitcoinTest extends AbstractFakerTest { + + @Test + public void testMainnetAddressGeneration() { + 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")); + assertTrue("Address length should be between 26 and 35 characters.", + address.length() >= 26 && address.length() <= 35); + } + + @Test + public void testTestnetAddressGeneration() { + 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")); + assertTrue("Address length should be between 26 and 35 characters.", + address.length() >= 26 && address.length() <= 35); + } + + @Test + public void testGenerateTransactionHash() { + 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]+$")); + } + +}