Skip to content

Commit

Permalink
v 0.6.3, minor improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
PawelGorny committed Dec 4, 2020
1 parent b9bde04 commit 5dcc34d
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 19 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.pawelgorny</groupId>
<artifactId>lostword</artifactId>
<version>0.6.2</version>
<version>0.6.3</version>
<packaging>jar</packaging>

<dependencies>
Expand Down
95 changes: 83 additions & 12 deletions src/main/java/com/pawelgorny/lostword/Worker.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package com.pawelgorny.lostword;

import com.google.common.base.Preconditions;
import com.pawelgorny.lostword.util.PBKDF2SHA512;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Utils;
import org.bitcoinj.crypto.*;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.HDDerivationException;
import org.bitcoinj.crypto.HDKeyDerivation;
import org.bitcoinj.crypto.MnemonicException;
import org.bitcoinj.script.Script;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CountDownLatch;
Expand All @@ -26,7 +32,7 @@ public class Worker {
private final byte[] BITCOIN_SEED_BYTES = "Bitcoin seed".getBytes();
private final HMac SHA_512_DIGEST;
private final long CREATION_SECONDS = Utils.currentTimeSeconds();
private static final String SALT = "mnemonic";
private static final byte[] SALT = "mnemonic".getBytes(StandardCharsets.UTF_8);

public Worker(Configuration configuration) {
this.configuration = configuration;
Expand Down Expand Up @@ -65,16 +71,14 @@ public void run() throws InterruptedException, MnemonicException {
}

protected boolean check(final List<String> mnemonic) throws MnemonicException {
return check(mnemonic, null);
return check(mnemonic, null, null);
}

protected boolean check(final List<String> mnemonic, HMac SHA512DIGEST) throws MnemonicException {
try {
Configuration.MNEMONIC_CODE.check(mnemonic);
} catch (MnemonicException.MnemonicChecksumException checksumException) {
protected boolean check(final List<String> mnemonic, HMac SHA512DIGEST, MessageDigest sha256) throws MnemonicException {
if (!checksumCheck(mnemonic, sha256)){
return false;
}
byte[] seed = PBKDF2SHA512.derive(Utils.SPACE_JOINER.join(mnemonic), SALT, 2048, 64);
byte[] seed = PBKDF2SHA512.derive(Utils.SPACE_JOINER.join(mnemonic).getBytes(StandardCharsets.UTF_8), SALT, 2048, 64);
DeterministicKey deterministicKey = createMasterPrivateKey(seed, SHA512DIGEST==null?this.SHA_512_DIGEST:SHA512DIGEST);
DeterministicKey receiving = HDKeyDerivation.deriveChildKey(deterministicKey, configuration.getDPchild0());
DeterministicKey new_address_key = HDKeyDerivation.deriveChildKey(receiving, configuration.getDPchild1());
Expand All @@ -89,6 +93,72 @@ protected boolean check(final List<String> mnemonic, HMac SHA512DIGEST) throws M
// return false;
}

protected boolean checksumCheck(final List<String> mnemonic, MessageDigest sha256){
if (sha256 == null){
try {
sha256 = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException var1) {
throw new RuntimeException(var1);
}
}
return checksumCheckBcJ(mnemonic, sha256);
}

public boolean checksumCheckBcJ(List<String> words, MessageDigest sha256){
int concatLenBits = words.size() * 11;
boolean[] concatBits = new boolean[concatLenBits];
int wordindex = 0;

int hash;
for(Iterator checksumLengthBits = words.iterator(); checksumLengthBits.hasNext(); ++wordindex) {
String entropyLengthBits = (String)checksumLengthBits.next();
int entropy = Collections.binarySearch(Configuration.MNEMONIC_CODE.getWordList(), entropyLengthBits);
if(entropy < 0) {
return false;
}
for(hash = 0; hash < 11; ++hash) {
concatBits[wordindex * 11 + hash] = (entropy & 1 << 10 - hash) != 0;
}
}

int var11 = concatLenBits / 33;
int var12 = concatLenBits - var11;
byte[] var13 = new byte[var12 / 8];

for(hash = 0; hash < var13.length; ++hash) {
for(int hashBits = 0; hashBits < 8; ++hashBits) {
if(concatBits[hash * 8 + hashBits]) {
var13[hash] = (byte)(var13[hash] | 1 << 7 - hashBits);
}
}
}

byte[] var14 = hash(var13, 0, var13.length, sha256);
boolean[] var15 = bytesToBits(var14);

for(int i = 0; i < var11; ++i) {
if(concatBits[var12 + i] != var15[i]) {
return false;
}
}
return true;
}
private static boolean[] bytesToBits(byte[] data) {
boolean[] bits = new boolean[data.length * 8];

for(int i = 0; i < data.length; ++i) {
for(int j = 0; j < 8; ++j) {
bits[i * 8 + j] = (data[i] & 1 << 7 - j) != 0;
}
}
return bits;
}
public static byte[] hash(byte[] input, int offset, int length, MessageDigest sha256) {
sha256.reset();
sha256.update(input, offset, length);
return sha256.digest();
}

public HMac createHmacSha512Digest() {
SHA512Digest digest = new SHA512Digest();
HMac hMac = new HMac(digest);
Expand All @@ -105,7 +175,6 @@ private byte[] hmacSha512(HMac hmacSha512, byte[] input) {

private DeterministicKey createMasterPrivateKey(byte[] seed, HMac SHA512DIGEST) throws HDDerivationException {
byte[] i = hmacSha512(SHA512DIGEST, seed);
Preconditions.checkState(i.length == 64, Integer.valueOf(i.length));
byte[] il = Arrays.copyOfRange(i, 0, 32);
byte[] ir = Arrays.copyOfRange(i, 32, 64);
Arrays.fill(i, (byte)0);
Expand Down Expand Up @@ -155,9 +224,11 @@ protected void processPosition(int position) throws InterruptedException {
final List<String> SEED = new ArrayList<>(mnemonic);
executorService.submit(() -> {
try {
final MessageDigest LOCAL_SHA_256_DIGEST = MessageDigest.getInstance("SHA-256");
final HMac LOCAL_SHA_512_DIGEST = createHmacSha512Digest();
for (int bipPosition = 0; RESULT == null && bipPosition < WORDS_TO_WORK.size(); bipPosition++) {
SEED.set(WORKING_POSITION, WORDS_TO_WORK.get(bipPosition));
if (check(SEED)) {
if (check(SEED, LOCAL_SHA_512_DIGEST, LOCAL_SHA_256_DIGEST)) {
RESULT = new Result(1 + WORKING_POSITION, WORDS_TO_WORK.get(bipPosition));
}
}
Expand Down Expand Up @@ -191,7 +262,7 @@ public String toString() {
if (seed==null){
return "position=" + position + ", word='" + word + '\'';
}else {
return seed.toString();
return Utils.SPACE_JOINER.join(seed);
}
}
}
Expand Down
24 changes: 18 additions & 6 deletions src/main/java/com/pawelgorny/lostword/WorkerKnownPosition.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import org.bitcoinj.crypto.MnemonicException;
import org.bouncycastle.crypto.macs.HMac;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
Expand Down Expand Up @@ -74,12 +76,18 @@ private void checkUnknown(int position) throws InterruptedException {
if (REPORTER) {
start = System.currentTimeMillis();
}
final HMac SHA_512_DIGEST = createHmacSha512Digest();
final HMac LOCAL_SHA_512_DIGEST = createHmacSha512Digest();
final MessageDigest LOCAL_SHA_256_DIGEST;
try {
LOCAL_SHA_256_DIGEST = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException var1) {
throw new RuntimeException(var1);
}
try {
int WORKING_POSITION_PLUS = WORKING_POSITION+1;
for (int bipPosition = 0; RESULT == null && bipPosition < WORDS_TO_WORK.size(); bipPosition++) {
SEED.set(WORKING_POSITION, WORDS_TO_WORK.get(bipPosition));
processSeed(SEED, 2, WORKING_POSITION_PLUS, REPORTER, SHA_512_DIGEST);
processSeed(SEED, 2, WORKING_POSITION_PLUS, REPORTER, LOCAL_SHA_512_DIGEST, LOCAL_SHA_256_DIGEST);
}
} catch (Exception e) {
Thread.currentThread().interrupt();
Expand All @@ -92,9 +100,9 @@ private void checkUnknown(int position) throws InterruptedException {
}
}

private void processSeed(final List<String> seed, int depth, int positionStartSearch, boolean reporter, HMac SHA_512_DIGEST) throws MnemonicException {
private void processSeed(final List<String> seed, int depth, int positionStartSearch, boolean reporter, HMac SHA_512_DIGEST, MessageDigest SHA_256_DIGEST) throws MnemonicException {
if (NUMBER_UNKNOWN==depth){
if (check(seed, SHA_512_DIGEST)){
if (check(seed, SHA_512_DIGEST, SHA_256_DIGEST)){
System.out.println(seed);
RESULT = new Result(seed);
return;
Expand All @@ -104,15 +112,19 @@ private void processSeed(final List<String> seed, int depth, int positionStartSe
start = System.currentTimeMillis();
}
}else{
int nextDepth = depth + 1;
int position = getNextUnknown(positionStartSearch, seed);
if(position == -1){
check(seed, SHA_512_DIGEST, SHA_256_DIGEST);
return;
}
int positionStartNextSearch = 0;
int nextDepth = depth + 1;
if (nextDepth <NUMBER_UNKNOWN ){
positionStartNextSearch = position+1;
}
for (int w = 0; RESULT==null && w<DICTIONARY_SIZE; w++){
seed.set(position, Configuration.MNEMONIC_CODE.getWordList().get(w));
processSeed(seed, nextDepth, positionStartNextSearch, reporter, SHA_512_DIGEST);
processSeed(seed, nextDepth, positionStartNextSearch, reporter, SHA_512_DIGEST, SHA_256_DIGEST);
}
}
}
Expand Down
68 changes: 68 additions & 0 deletions src/main/java/com/pawelgorny/lostword/util/PBKDF2SHA512.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.pawelgorny.lostword.util;


import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;

public class PBKDF2SHA512 {
public PBKDF2SHA512() {
}

public static byte[] derive(byte[] mnemonicBytes, byte[] salt, int c, int dkLen) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
try {
Mac mac = Mac.getInstance("HmacSHA512");
SecretKeySpec key = new SecretKeySpec(mnemonicBytes, mac.getAlgorithm());
mac.init(key);

for(int i = 1; i <= 4; ++i) {
byte[] T = F(salt, c, i, mac);
baos.write(T);
}
} catch (Exception var9) {
throw new RuntimeException(var9);
}

byte[] var10 = new byte[dkLen];
System.arraycopy(baos.toByteArray(), 0, var10, 0, var10.length);

return var10;
}

private static byte[] F(byte[] salt, int c, int i, Mac mac) throws Exception {
byte[] U_LAST = null;
byte[] U_XOR = null;
for(int j = 0; j < c; ++j) {
byte[] baU;
if(j == 0) {
baU = salt;
byte[] var12 = INT(i);
byte[] baU1 = new byte[12];
System.arraycopy(baU, 0, baU1, 0, 8);
System.arraycopy(var12, 0, baU1, 8, 4);
U_XOR = mac.doFinal(baU1);
U_LAST = U_XOR;
} else {
baU = mac.doFinal(U_LAST);
for(int k = 0; k < U_XOR.length; ++k) {
U_XOR[k] ^= baU[k];
}
U_LAST = baU;
}
mac.reset();
}

return U_XOR;
}

private static byte[] INT(int i) {
byte[] bytes = new byte[4];
bytes[3] = (byte)i;
return bytes;
// ByteBuffer bb = ByteBuffer.allocate(4);
// bb.order(ByteOrder.BIG_ENDIAN);
// bb.putInt(i);
// return bb.array();
}
}
47 changes: 47 additions & 0 deletions src/test/java/Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import com.pawelgorny.lostword.WORK;
import com.pawelgorny.lostword.Worker;
import org.bitcoinj.crypto.MnemonicException;
import org.bouncycastle.crypto.macs.HMac;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -28,12 +31,46 @@ public void testCheck() throws MnemonicException {
WorkerTester workerTester = new WorkerTester(configuration);
assertTrue(workerTester.check(RESULT));
System.out.println("worker.check OK");
}

@org.junit.Test
public void testLoopCheck() throws MnemonicException, NoSuchAlgorithmException {
Configuration configuration = new Configuration(null, TARGET, PATH, RESULT, 0);
WorkerTester workerTester = new WorkerTester(configuration);
Stopwatch stopwatch = Stopwatch.createStarted();
for (int i=0; i<LOOP_SIZE; i++){
workerTester.check(RESULT);
}
stopwatch.stop();
System.out.println(stopwatch.elapsed());
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");

stopwatch = Stopwatch.createStarted();
for (int i=0; i<LOOP_SIZE; i++){
workerTester.check(RESULT, null, sha256);
}
stopwatch.stop();
System.out.println(stopwatch.elapsed());

}
@org.junit.Test
public void testLoopChecksum() throws MnemonicException, NoSuchAlgorithmException {
Configuration configuration = new Configuration(null, TARGET, PATH, RESULT, 0);
WorkerTester workerTester = new WorkerTester(configuration);
Stopwatch stopwatch = Stopwatch.createStarted();
for (int i=0; i<LOOP_SIZE; i++){
workerTester.checksumCheck(RESULT, null);
}
stopwatch.stop();
System.out.println(stopwatch.elapsed());

MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
stopwatch = Stopwatch.createStarted();
for (int i=0; i<LOOP_SIZE; i++){
workerTester.checksumCheck(RESULT, sha256);
}
stopwatch.stop();
System.out.println(stopwatch.elapsed());
}

@org.junit.Test
Expand Down Expand Up @@ -86,6 +123,16 @@ protected boolean check(List<String> mnemonic) throws MnemonicException {
return super.check(mnemonic);
}

@Override
protected boolean check(List<String> mnemonic, HMac SHA512DIGEST, MessageDigest sha256) throws MnemonicException {
return super.check(mnemonic, SHA512DIGEST, sha256);
}

@Override
protected boolean checksumCheck(List<String> mnemonic, MessageDigest sha256) {
return super.checksumCheck(mnemonic, sha256);
}

public boolean isResult(){
return RESULT!=null;
}
Expand Down

0 comments on commit 5dcc34d

Please sign in to comment.