Skip to content

Commit

Permalink
Replaced all strings with char arrays.
Browse files Browse the repository at this point in the history
Removed string normalization. The only used language is English.
  • Loading branch information
Cristi Paval committed Mar 20, 2018
1 parent fc06c23 commit 0c82794
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 96 deletions.
6 changes: 3 additions & 3 deletions src/main/java/com/soneso/stellarmnemonics/Wallet.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@

public class Wallet {

public static String generate12WordMnemonic() throws WalletException {
public static char[] generate12WordMnemonic() throws WalletException {
return Mnemonic.create(Strength.NORMAL, WordList.ENGLISH);
}

public static String generate24WordMnemonic() throws WalletException {
public static char[] generate24WordMnemonic() throws WalletException {
return Mnemonic.create(Strength.HIGH, WordList.ENGLISH);
}

public static KeyPair createKeyPair(String mnemonic, @Nullable String passphrase, int index) throws WalletException {
public static KeyPair createKeyPair(char[] mnemonic, @Nullable char[] passphrase, int index) throws WalletException {
byte[] bip39Seed = Mnemonic.createSeed(mnemonic, passphrase);

Ed25519Derivation masterPrivateKey = Ed25519Derivation.fromSecretSeed(bip39Seed);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.soneso.stellarmnemonics.derivation;

import com.soneso.stellarmnemonics.util.PrimitiveUtil;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

Expand All @@ -15,18 +17,18 @@ public class Ed25519Derivation {
private byte[] chainCode;

public static Ed25519Derivation fromSecretSeed(byte[] seed) throws Ed25519DerivationException {
byte[] output;
try {
output = hMacSha512(seed, "ed25519 seed".getBytes("UTF-8"));
} catch (Exception e) {
throw new Ed25519DerivationException("Fatal error when trying to get bytes from chain code.");
}
byte[] privateKey = ByteUtils.byteSubArray(output, 0, 32);
byte[] chainCode = ByteUtils.byteSubArray(output, 32, 64);

char[] key = new char[]{'e', 'd', '2', '5', '5', '1', '9', ' ', 's', 'e', 'e', 'd'};
byte[] output = hMacSha512(seed, PrimitiveUtil.toBytes(key));

byte[] privateKey = PrimitiveUtil.byteSubArray(output, 0, 32);
byte[] chainCode = PrimitiveUtil.byteSubArray(output, 32, 64);

return new Ed25519Derivation(privateKey, chainCode);
}

private static byte[] hMacSha512(byte[] data, byte[] key) throws Ed25519DerivationException {

try {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, HMAC_SHA512);
Mac mac = Mac.getInstance(HMAC_SHA512);
Expand All @@ -38,6 +40,7 @@ private static byte[] hMacSha512(byte[] data, byte[] key) throws Ed25519Derivati
}

private Ed25519Derivation(byte[] privateKey, byte[] chainCode) {

this.privateKey = privateKey;
this.chainCode = chainCode;
}
Expand All @@ -47,17 +50,19 @@ public byte[] getPrivateKey() {
}

public Ed25519Derivation derived(int index) throws Ed25519DerivationException {

long edge = 0x80000000L;
if ((edge & index) != 0) {
throw new RuntimeException("Invalid index!");
}

byte[] data = ByteUtils.concatByteArrays(new byte[]{0}, privateKey);
byte[] data = PrimitiveUtil.concatByteArrays(new byte[]{0}, privateKey);
long derivingIndex = edge + index;
data = ByteUtils.concatByteArrays(data, ByteUtils.last4BytesFromLong(derivingIndex));
data = PrimitiveUtil.concatByteArrays(data, PrimitiveUtil.last4BytesFromLong(derivingIndex));

byte[] digest = hMacSha512(data, chainCode);
byte[] factor = ByteUtils.byteSubArray(digest, 0, 32);
return new Ed25519Derivation(factor, ByteUtils.byteSubArray(digest, 32, 64));
byte[] factor = PrimitiveUtil.byteSubArray(digest, 0, 32);

return new Ed25519Derivation(factor, PrimitiveUtil.byteSubArray(digest, 32, 64));
}
}
50 changes: 22 additions & 28 deletions src/main/java/com/soneso/stellarmnemonics/mnemonic/Mnemonic.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package com.soneso.stellarmnemonics.mnemonic;

import com.soneso.stellarmnemonics.derivation.ByteUtils;
import com.soneso.stellarmnemonics.util.PrimitiveUtil;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.text.Normalizer;
import java.util.List;

/**
Expand All @@ -17,13 +16,13 @@

public class Mnemonic {

public static String create(Strength strength, WordList wordList) throws MnemonicException {
public static char[] create(Strength strength, WordList wordList) throws MnemonicException {
int byteCount = strength.getRawValue() / 8;
byte[] bytes = SecureRandom.getSeed(byteCount);
return create(bytes, wordList);
}

private static String create(byte[] entropy, WordList wordList) throws MnemonicException {
private static char[] create(byte[] entropy, WordList wordList) throws MnemonicException {

byte[] hashBits;
try {
Expand All @@ -33,44 +32,39 @@ private static String create(byte[] entropy, WordList wordList) throws MnemonicE
throw new MnemonicException("Fatal error! SHA-256 algorithm does not exist!");
}

String checkSum = ByteUtils.bytesToBinaryString(hashBits).substring(0, entropy.length * 8 / 32);
String entropyBits = ByteUtils.bytesToBinaryString(entropy);
String concatenatedBits = String.format("%s%s", entropyBits, checkSum);

List<String> words = wordList.getWords();
char[] binaryHash = PrimitiveUtil.bytesToBinaryAsChars(hashBits);
char[] checkSum = PrimitiveUtil.charSubArray(binaryHash, 0, entropy.length * 8 / 32);
char[] entropyBits = PrimitiveUtil.bytesToBinaryAsChars(entropy);
StringBuilder concatenatedBits = new StringBuilder().append(entropyBits).append(checkSum);

List<char[]> words = wordList.getWordsAsCharArray();
StringBuilder mnemonicBuilder = new StringBuilder();
for (int index = 0; index < concatenatedBits.length() / 11; ++index) {

int startIndex = index * 11;
int endIndex = startIndex + 11;
int wordIndex = Integer.parseInt(concatenatedBits.substring(startIndex, endIndex), 2);
char[] wordIndexAsChars = new char[endIndex - startIndex];
concatenatedBits.getChars(startIndex, endIndex, wordIndexAsChars, 0);
int wordIndex = PrimitiveUtil.binaryCharsToInt(wordIndexAsChars);
mnemonicBuilder.append(words.get(wordIndex)).append(' ');
}
mnemonicBuilder.deleteCharAt(mnemonicBuilder.length() - 1);

return mnemonicBuilder.toString();
char[] mnemonic = new char[mnemonicBuilder.length() - 1];
mnemonicBuilder.getChars(0, mnemonicBuilder.length() - 1, mnemonic, 0);
return mnemonic;
}

public static byte[] createSeed(String mnemonic, String passphrase) throws MnemonicException {
public static byte[] createSeed(char[] mnemonic, char[] passphrase) throws MnemonicException {

char[] password;
try {
password = Normalizer.normalize(mnemonic, Normalizer.Form.NFKD).toCharArray();
} catch (Exception e) {
throw new MnemonicException("Fatal error at mnemonic normalization!");
}

byte[] salt;
if (passphrase == null) {
passphrase = "";
}
try {
salt = Normalizer.normalize(new StringBuilder("mnemonic").append(passphrase), Normalizer.Form.NFKD).getBytes("UTF-8");
} catch (Exception e) {
throw new MnemonicException("Fatal error at passphrase normalization!");
char[] saltChars = new char[]{'m', 'n', 'e', 'm', 'o', 'n', 'i', 'c'};
if (passphrase != null) {
saltChars = PrimitiveUtil.concatCharArrays(saltChars, passphrase);
}
byte[] salt = PrimitiveUtil.toBytes(saltChars);

try {
KeySpec ks = new PBEKeySpec(password, salt, 2048, 512);
KeySpec ks = new PBEKeySpec(mnemonic, salt, 2048, 512);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
return skf.generateSecret(ks).getEncoded();
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.soneso.stellarmnemonics.mnemonic;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

Expand All @@ -17,7 +18,11 @@ public enum WordList {
this.words = words;
}

public List<String> getWords() {
return words;
public List<char[]> getWordsAsCharArray() {
List<char[]> wordList = new ArrayList<char[]>(words.size());
for (String word : words) {
wordList.add(word.toCharArray());
}
return wordList;
}
}
75 changes: 75 additions & 0 deletions src/main/java/com/soneso/stellarmnemonics/util/PrimitiveUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.soneso.stellarmnemonics.util;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;

/**
* Util class.
* Created by cristi.paval on 3/13/18.
*/
public class PrimitiveUtil {
public static byte[] concatByteArrays(byte[] a, byte[] b) {
byte[] c = new byte[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}

public static char[] bytesToBinaryAsChars(byte[] bytes) {
StringBuilder binaryStringBuilder = new StringBuilder();
for (byte b : bytes) {
binaryStringBuilder.append(String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0'));
}
int binaryLength = binaryStringBuilder.length();
char[] binaryChars = new char[binaryLength];
binaryStringBuilder.getChars(0, binaryLength, binaryChars, 0);
return binaryChars;
}

public static byte[] byteSubArray(byte[] source, int startIndex, int endIndex) {
byte[] subArray = new byte[endIndex - startIndex];
System.arraycopy(source, startIndex, subArray, 0, endIndex - startIndex);
return subArray;
}

public static char[] charSubArray(char[] source, int startIndex, int endIndex) {
char[] subArray = new char[endIndex - startIndex];
System.arraycopy(source, startIndex, subArray, 0, endIndex - startIndex);
return subArray;
}

public static byte[] last4BytesFromLong(long x) {
ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.putLong(x);
return byteSubArray(buffer.array(), 4, 8);
}

public static int binaryCharsToInt(char[] binary) {
int result = 0;
for (int i = binary.length - 1; i >= 0; i--)
if (binary[i] == '1')
result += Math.pow(2, (binary.length - i - 1));
return result;
}

public static byte[] toBytes(char[] chars) {

CharBuffer charBuffer = CharBuffer.wrap(chars);
ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
byte[] bytes = Arrays.copyOfRange(byteBuffer.array(),
byteBuffer.position(), byteBuffer.limit());
Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
return bytes;
}

public static char[] concatCharArrays(char[] a, char[] b) {

char[] c = new char[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
}
8 changes: 4 additions & 4 deletions src/test/java/MnemonicGeneration.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ public class MnemonicGeneration {
@Test
public void test12WordMnemonicGeneration() throws WalletException {

String mnemonic = Wallet.generate12WordMnemonic();
String[] words = mnemonic.split(" ");
char[] mnemonic = Wallet.generate12WordMnemonic();
String[] words = String.valueOf(mnemonic).split(" ");

Assert.assertEquals(12, words.length);
}

@Test
public void test24WordMnemonicGeneration() throws WalletException {

String mnemonic = Wallet.generate24WordMnemonic();
String[] words = mnemonic.split(" ");
char[] mnemonic = Wallet.generate24WordMnemonic();
String[] words = String.valueOf(mnemonic).split(" ");

Assert.assertEquals(24, words.length);
}
Expand Down
Loading

0 comments on commit 0c82794

Please sign in to comment.