Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Fake Bitcoin Address and Transaction Hash Generation #782

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ Fakers
* Witcher
* Yoda
* Zelda
* Bitcoin

Usage with Locales
-----
Expand Down
109 changes: 109 additions & 0 deletions src/main/java/com/github/javafaker/Bitcoin.java
Original file line number Diff line number Diff line change
@@ -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<String, Integer> PROTOCOL_VERSIONS = new HashMap<String, Integer>() {{
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();
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/github/javafaker/Faker.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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();

}

/**
Expand Down Expand Up @@ -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);
}
Expand Down
37 changes: 37 additions & 0 deletions src/test/java/com/github/javafaker/BitcoinTest.java
Original file line number Diff line number Diff line change
@@ -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]+$"));
}

}