Permalink
Browse files

BIP-44 implementation

1. implement BIP-32 keypair
2. implement BIP-44 wallet on top of BIP-39 seed
  • Loading branch information...
AmiMobilechain committed Aug 27, 2018
1 parent d38054a commit 77946ca35ebd0e9e1a0086f77e91334256203295
@@ -29,7 +29,10 @@
<property name="eachLine" value="true"/>
</module>
<module name="SuppressWarningsFilter" />
<module name="TreeWalker">
<module name="SuppressWarningsHolder" />
<module name="OuterTypeFilename"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
@@ -232,4 +235,4 @@
<module name="SuppressionFilter">
<property name="file" value="config/checkstyle/suppressions.xml"/>
</module>
</module>
</module>
@@ -0,0 +1,173 @@
package org.web3j.crypto;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.bouncycastle.math.ec.ECPoint;
import org.web3j.utils.Numeric;
import static org.web3j.crypto.Hash.hmacSha512;
import static org.web3j.crypto.Hash.sha256hash160;
/**
* BIP-32 key pair.
*
* <p>Adapted from:
* https://github.com/bitcoinj/bitcoinj/blob/master/core/src/main/java/org/bitcoinj/crypto/DeterministicKey.java
*/
public class Bip32ECKeyPair extends ECKeyPair {
static final int HARDENED_BIT = 0x80000000;
private final boolean parentHasPrivate;
private final int childNumber;
private final int depth;
private final byte[] chainCode;
private int parentFingerprint;
private ECPoint publicKeyPoint;
public Bip32ECKeyPair(BigInteger privateKey, BigInteger publicKey, int childNumber,
byte[] chainCode, Bip32ECKeyPair parent) {
super(privateKey, publicKey);
this.parentHasPrivate = parent != null && parent.hasPrivateKey();
this.childNumber = childNumber;
this.depth = parent == null ? 0 : parent.depth + 1;
this.chainCode = Arrays.copyOf(chainCode, chainCode.length);
this.parentFingerprint = parent != null ? parent.getFingerprint() : 0;
}
public static Bip32ECKeyPair create(BigInteger privateKey, byte[] chainCode) {
return new Bip32ECKeyPair(privateKey, Sign.publicKeyFromPrivate(privateKey),
0, chainCode, null);
}
public static Bip32ECKeyPair create(byte[] privateKey, byte[] chainCode) {
return create(Numeric.toBigInt(privateKey), chainCode);
}
public static Bip32ECKeyPair generateKeyPair(byte[] seed) {
byte[] i = hmacSha512("Bitcoin seed".getBytes(), seed);
byte[] il = Arrays.copyOfRange(i, 0, 32);
byte[] ir = Arrays.copyOfRange(i, 32, 64);
Arrays.fill(i, (byte) 0);
Bip32ECKeyPair keypair = Bip32ECKeyPair.create(il, ir);
Arrays.fill(il, (byte) 0);
Arrays.fill(ir, (byte) 0);
return keypair;
}
public static Bip32ECKeyPair deriveKeyPair(Bip32ECKeyPair master, int[] path) {
Bip32ECKeyPair curr = master;
if (path != null) {
for (int childNumber : path) {
curr = curr.deriveChildKey(childNumber);
}
}
return curr;
}
private Bip32ECKeyPair deriveChildKey(int childNumber) {
if (!hasPrivateKey()) {
byte[] parentPublicKey = getPublicKeyPoint().getEncoded(true);
ByteBuffer data = ByteBuffer.allocate(37);
data.put(parentPublicKey);
data.putInt(childNumber);
byte[] i = hmacSha512(getChainCode(), data.array());
byte[] il = Arrays.copyOfRange(i, 0, 32);
byte[] chainCode = Arrays.copyOfRange(i, 32, 64);
Arrays.fill(i, (byte) 0);
BigInteger ilInt = new BigInteger(1, il);
Arrays.fill(il, (byte) 0);
ECPoint ki = Sign.publicPointFromPrivate(ilInt).add(getPublicKeyPoint());
return new Bip32ECKeyPair(null,
Sign.publicFromPoint(Sign.CURVE.getCurve(), ki.getEncoded(true)),
childNumber, chainCode, this);
} else {
ByteBuffer data = ByteBuffer.allocate(37);
if (isHardened(childNumber)) {
data.put(getPrivateKeyBytes33());
} else {
byte[] parentPublicKey = getPublicKeyPoint().getEncoded(true);
data.put(parentPublicKey);
}
data.putInt(childNumber);
byte[] i = hmacSha512(getChainCode(), data.array());
byte[] il = Arrays.copyOfRange(i, 0, 32);
byte[] chainCode = Arrays.copyOfRange(i, 32, 64);
Arrays.fill(i, (byte) 0);
BigInteger ilInt = new BigInteger(1, il);
Arrays.fill(il, (byte) 0);
BigInteger privateKey = getPrivateKey().add(ilInt).mod(Sign.CURVE.getN());
return new Bip32ECKeyPair(privateKey, Sign.publicKeyFromPrivate(privateKey),
childNumber, chainCode, this);
}
}
private int getFingerprint() {
byte[] id = getIdentifier();
return id[3] & 0xFF | (id[2] & 0xFF) << 8 | (id[1] & 0xFF) << 16 | (id[0] & 0xFF) << 24;
}
public int getDepth() {
return depth;
}
public int getParentFingerprint() {
return parentFingerprint;
}
public byte[] getChainCode() {
return chainCode;
}
public int getChildNumber() {
return childNumber;
}
private byte[] getIdentifier() {
return sha256hash160(getPublicKeyPoint().getEncoded(true));
}
public ECPoint getPublicKeyPoint() {
if (publicKeyPoint == null) {
publicKeyPoint = Sign.publicPointFromPrivate(getPrivateKey());
}
return publicKeyPoint;
}
public byte[] getPrivateKeyBytes33() {
final int numBytes = 33;
byte[] bytes33 = new byte[numBytes];
byte[] priv = bigIntegerToBytes32(getPrivateKey());
System.arraycopy(priv, 0, bytes33, numBytes - priv.length, priv.length);
return bytes33;
}
private boolean hasPrivateKey() {
return this.getPrivateKey() != null || parentHasPrivate;
}
private static byte[] bigIntegerToBytes32(BigInteger b) {
final int numBytes = 32;
byte[] src = b.toByteArray();
byte[] dest = new byte[numBytes];
boolean isFirstByteOnlyForSign = src[0] == 0;
int length = isFirstByteOnlyForSign ? src.length - 1 : src.length;
int srcPos = isFirstByteOnlyForSign ? 1 : 0;
int destPos = numBytes - length;
System.arraycopy(src, srcPos, dest, destPos, length);
return dest;
}
private static boolean isHardened(int a) {
return (a & HARDENED_BIT) != 0;
}
}
@@ -0,0 +1,47 @@
package org.web3j.crypto;
import java.io.File;
import java.io.IOException;
import static org.web3j.crypto.Bip32ECKeyPair.HARDENED_BIT;
public class Bip44WalletUtils extends WalletUtils {
/**
* Generates a BIP-44 compatible Ethereum wallet on top of BIP-39 generated seed.
*
* @param password Will be used for both wallet encryption and passphrase for BIP-39 seed
* @param destinationDirectory The directory containing the wallet
* @return A BIP-39 compatible Ethereum wallet
* @throws CipherException if the underlying cipher is not available
* @throws IOException if the destination cannot be written to
*/
public static Bip39Wallet generateBip44Wallet(String password, File destinationDirectory)
throws CipherException, IOException {
byte[] initialEntropy = new byte[16];
SecureRandomUtils.secureRandom().nextBytes(initialEntropy);
String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy);
byte[] seed = MnemonicUtils.generateSeed(mnemonic, null);
Bip32ECKeyPair masterKeypair = Bip32ECKeyPair.generateKeyPair(seed);
Bip32ECKeyPair bip44Keypair = generateBip44KeyPair(masterKeypair);
String walletFile = generateWalletFile(password, bip44Keypair, destinationDirectory, false);
return new Bip39Wallet(walletFile, mnemonic);
}
public static Bip32ECKeyPair generateBip44KeyPair(Bip32ECKeyPair master) {
// m/44'/60'/0'/0
final int[] path = { 44 | HARDENED_BIT, 60 | HARDENED_BIT, 0 | HARDENED_BIT, 0 };
return Bip32ECKeyPair.deriveKeyPair(master, path);
}
public static Credentials loadBip44Credentials(String password, String mnemonic) {
byte[] seed = MnemonicUtils.generateSeed(mnemonic, password);
Bip32ECKeyPair masterKeypair = Bip32ECKeyPair.generateKeyPair(seed);
Bip32ECKeyPair bip44Keypair = generateBip44KeyPair(masterKeypair);
return Credentials.create(bip44Keypair);
}
}
@@ -14,6 +14,7 @@
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
import org.bouncycastle.math.ec.ECAlgorithms;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
@@ -216,8 +217,11 @@ public static BigInteger publicKeyFromPrivate(BigInteger privKey) {
/**
* Returns public key point from the given private key.
*
* @param privKey the private key to derive the public key from
* @return ECPoint public key
*/
private static ECPoint publicPointFromPrivate(BigInteger privKey) {
public static ECPoint publicPointFromPrivate(BigInteger privKey) {
/*
* TODO: FixedPointCombMultiplier currently doesn't support scalars longer than the group
* order, but that could change in future versions.
@@ -228,6 +232,17 @@ private static ECPoint publicPointFromPrivate(BigInteger privKey) {
return new FixedPointCombMultiplier().multiply(CURVE.getG(), privKey);
}
/**
* Returns public key point from the given curve.
*
* @param curve the curve
* @param bits representing the point on the curve
* @return BigInteger encoded public key
*/
public static BigInteger publicFromPoint(ECCurve curve, byte[] bits) {
return new BigInteger(1, Arrays.copyOfRange(bits, 1, bits.length)); // remove prefix
}
public static class SignatureData {
private final byte v;
private final byte[] r;
@@ -0,0 +1,75 @@
/*
* Copyright 2011 Google Inc.
* Copyright 2018 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.web3j.crypto;
import java.util.Arrays;
public class Base58 {
private static final char[] ALPHABET =
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
private static final char ENCODED_ZERO = ALPHABET[0];
/**
* Encodes the given bytes as a base58 string (no checksum is appended).
*
* @param input the bytes to encode
* @return the base58-encoded string
*/
public static String encode(byte[] input) {
if (input.length == 0) {
return "";
}
// Count leading zeros.
int zeros = 0;
while (zeros < input.length && input[zeros] == 0) {
++zeros;
}
// Convert base-256 digits to base-58 digits (plus conversion to ASCII characters)
input = Arrays.copyOf(input, input.length); // since we modify it in-place
char[] encoded = new char[input.length * 2]; // upper bound
int outputStart = encoded.length;
for (int inputStart = zeros; inputStart < input.length; ) {
encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)];
if (input[inputStart] == 0) {
++inputStart; // optimization - skip leading zeros
}
}
// Preserve exactly as many leading encoded zeros in output as there
// were leading zeros in input.
while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) {
++outputStart;
}
while (--zeros >= 0) {
encoded[--outputStart] = ENCODED_ZERO;
}
// Return encoded string (including encoded leading zeros).
return new String(encoded, outputStart, encoded.length - outputStart);
}
private static byte divmod(byte[] number, int firstDigit, int base, int divisor) {
// this is just long division which accounts for the base of the input digits
int remainder = 0;
for (int i = firstDigit; i < number.length; i++) {
int digit = (int) number[i] & 0xFF;
int temp = remainder * base + digit;
number[i] = (byte) (temp / divisor);
remainder = temp % divisor;
}
return (byte) remainder;
}
}
Oops, something went wrong.

0 comments on commit 77946ca

Please sign in to comment.