Permalink
Browse files

some refactoring

  • Loading branch information...
1 parent fd35cc7 commit 7cadf5f17c7cb3c686866666ad14820aa3702825 @dbasch committed Jan 18, 2014
Showing with 49 additions and 31 deletions.
  1. +1 −1 README.md
  2. +31 −30 src/main/java/com/fruitcat/bitcoin/BIP38.java
  3. +17 −0 src/main/java/com/fruitcat/bitcoin/Utils.java
View
@@ -12,7 +12,7 @@ If you don't care about lot/sequence (if you don't know what they are, you don't
`BIP38.decrypt(password, encryptedKey)` yields the decrypted get.
-## Example:
+## Example
Key generation:
@@ -1,10 +1,11 @@
/**
* Implementation of BIP38 encryption / decryption / key-address generation
* Based on https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki
- *
- * Copyright 2014 Diego Basch
+ *
* Tips much appreciated: 1EmwBbfgH7BPMoCpcFzyzgAN9Ya7jm8L1Z :)
*
+ * Copyright 2014 Diego Basch
+ *
* 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
@@ -33,7 +34,10 @@
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.security.*;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.SecureRandom;
+import java.security.Security;
import java.util.Arrays;
public class BIP38 {
@@ -47,7 +51,7 @@
/**
* Generates an encrypted key with EC multiplication.
* Only uncompressed format for now.
- * if lot is less than 0, lot and sequence are ignored.j
+ * if lot is less than 0, lot and sequence are ignored.
* @param password
* @return
* @throws UnsupportedEncodingException
@@ -81,36 +85,31 @@ public static String encryptEC(String password, int lot, int sequence) throws Un
byte[] derivedHalf1 = Arrays.copyOfRange(secondKey, 0, 32);
byte[] derivedHalf2 = Arrays.copyOfRange(secondKey, 32, 64);
- Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "BC");
-
byte[] m1 = new byte[16];
byte[] m2 = new byte[16];
for (int i = 0; i < 16; i++) {
m1[i] = (byte) (seedB[i] ^ derivedHalf1[i]);
+ m2[i] = (byte) (m2[i] ^ derivedHalf1[16 + i]);
}
+
+ Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "BC");
Key aesKey = new SecretKeySpec(derivedHalf2, "AES");
cipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] encryptedPart1 = cipher.doFinal(m1);
System.arraycopy(encryptedPart1, 8, m2, 0, 8);
System.arraycopy(seedB, 16, m2, 8, 8);
- for (int i = 0; i < 16; i++) {
- m2[i] = (byte) (m2[i] ^ derivedHalf1[16 + i]);
- }
-
byte[] encryptedPart2 = cipher.doFinal(m2);
- byte[] encryptedPrivateKey = new byte[43];
+ byte[] encryptedPrivateKey = new byte[39];
encryptedPrivateKey[0] = 0x01;
encryptedPrivateKey[1] = 0x43;
encryptedPrivateKey[2] = flagByte;
System.arraycopy(addressHash, 0, encryptedPrivateKey, 3, 4);
System.arraycopy(ownerEntropy, 0, encryptedPrivateKey, 7, 8);
System.arraycopy(encryptedPart1, 0, encryptedPrivateKey, 15, 8);
System.arraycopy(encryptedPart2, 0, encryptedPrivateKey, 23, 16);
- System.arraycopy(addressHash, 0, encryptedPrivateKey, 39, 4);
- System.arraycopy(Utils.doubleHash(encryptedPrivateKey, 0, 39), 0, encryptedPrivateKey, 39, 4);
- return Base58.encode(encryptedPrivateKey);
+ return Utils.base58Check(encryptedPrivateKey);
}
/**
@@ -131,7 +130,7 @@ public static String intermediatePassphrase(String password, int lot, int sequen
byte[] ownerSalt;
byte[] passPoint;
byte[] preFactor;
- byte[] result = new byte[53];
+ byte[] result = new byte[49];
byte[] magicBytes = { (byte) 0x2c, (byte) (0xe9), (byte) 0xb3, (byte) 0xe1, (byte) 0xff, (byte) 0x39, (byte) 0xe2, (byte) 0x51 };
byte[] passFactor;
@@ -165,10 +164,8 @@ public static String intermediatePassphrase(String password, int lot, int sequen
System.arraycopy(magicBytes, 0, result, 0, 8);
System.arraycopy(ownerEntropy, 0, result, 8, 8);
System.arraycopy(passPoint, 0, result, 16, 33);
- byte[] checksum = Arrays.copyOfRange(Utils.doubleHash(result, 0, 49), 0, 4);
- System.arraycopy(checksum, 0, result, 49, 4);
- return Base58.encode(result);
+ return Utils.base58Check(result);
}
/**
@@ -190,7 +187,7 @@ public static String decrypt(String password, String encryptedKey) throws
break;
case 0x42: result = decryptNoEC(password, encryptedKeyBytes);
break;
- default: throw new RuntimeException("Invalid Key");
+ default: throw new RuntimeException("Invalid key - second byte is: " + ec);
}
return result;
}
@@ -231,10 +228,10 @@ public static String decryptEC(String password, byte[] encryptedKey) throws Unsu
System.arraycopy(encryptedKey, 7, salt, 4, 8);
byte[] secondKey = SCrypt.scrypt(passPoint, salt, 1024, 1, 1, 64);
- Cipher cipher = Cipher.getInstance ("AES/ECB/NoPadding");
byte[] derivedHalf1 = Arrays.copyOfRange(secondKey, 0, 32);
byte[] derivedHalf2 = Arrays.copyOfRange(secondKey, 32, 64);
SecretKeySpec aesKey = new SecretKeySpec(derivedHalf2, "AES");
+ Cipher cipher = Cipher.getInstance ("AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, aesKey);
byte[] m2 = cipher.doFinal(encryptedPart2);
@@ -267,7 +264,7 @@ public static String decryptEC(String password, byte[] encryptedKey) throws Unsu
* Encrypts a key without using EC multiplication.
* @param encodedPrivateKey
* @param password
- * @param compressed
+ * @param isCompressed
* @return
* @throws GeneralSecurityException
* @throws UnsupportedEncodingException
@@ -296,36 +293,40 @@ public static String encryptNoEC(String encodedPrivateKey, String password, bool
k2[i] = (byte) (keyBytes[i+16] ^ derivedHalf1[i+16]);
}
- Cipher cipher = Cipher.getInstance ("AES/ECB/NoPadding", "BC");
Key aesKey = new SecretKeySpec(derivedHalf2, "AES");
-
+ Cipher cipher = Cipher.getInstance ("AES/ECB/NoPadding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] encryptedHalf1 = cipher.doFinal(k1);
byte[] encryptedHalf2 = cipher.doFinal(k2);
- byte[] encryptedPrivateKey = new byte[43];
+ byte[] encryptedPrivateKey = new byte[39];
encryptedPrivateKey[0] = 0x01;
encryptedPrivateKey[1] = 0x42;
encryptedPrivateKey[2] = (byte) (isCompressed ? 0xe0 : 0xc0);
System.arraycopy(addressHash, 0, encryptedPrivateKey, 3, 4);
System.arraycopy(encryptedHalf1, 0, encryptedPrivateKey, 7, 16);
System.arraycopy(encryptedHalf2, 0, encryptedPrivateKey, 23, 16);
- byte[] checksum = Utils.doubleHash(encryptedPrivateKey, 0, 39);
- System.arraycopy(checksum, 0, encryptedPrivateKey, 39, 4);
- return Base58.encode(encryptedPrivateKey);
+ return Utils.base58Check(encryptedPrivateKey);
}
-
+ /**
+ * Decrypts a key that was encrypted without EC multiplication.
+ * @param password
+ * @param encryptedKey
+ * @return the key, Base58-encoded
+ * @throws UnsupportedEncodingException
+ * @throws GeneralSecurityException
+ */
public static String decryptNoEC(String password, byte[] encryptedKey) throws UnsupportedEncodingException, GeneralSecurityException{
byte[] addressHash = Arrays.copyOfRange(encryptedKey, 3, 7);
byte[] scryptKey = SCrypt.scrypt(password.getBytes("UTF8"), addressHash, 16384, 8, 8, 64);
byte[] derivedHalf1 = Arrays.copyOfRange(scryptKey, 0, 32);
byte[] derivedHalf2 = Arrays.copyOfRange(scryptKey, 32, 64);
- Cipher cipher = Cipher.getInstance ("AES/ECB/NoPadding", "BC");
Key aesKey = new SecretKeySpec(derivedHalf2, "AES");
+ Cipher cipher = Cipher.getInstance ("AES/ECB/NoPadding", "BC");
cipher.init(Cipher.DECRYPT_MODE, aesKey);
byte[] encryptedHalf1 = Arrays.copyOfRange(encryptedKey, 7, 23);
byte[] encryptedHalf2 = Arrays.copyOfRange(encryptedKey, 23, 39);
@@ -337,7 +338,7 @@ public static String decryptNoEC(String password, byte[] encryptedKey) throws Un
keyBytes[i] = (byte) (k1[i] ^ derivedHalf1[i]);
keyBytes[i+16] = (byte) (k2[i] ^ derivedHalf1[i+16]);
}
- boolean compressed = (keyBytes[2] & (byte)0xe0) == 0;
+ boolean compressed = (keyBytes[2] & (byte) 0xe0) == 0;
ECKey k = new ECKey(new BigInteger(1, keyBytes), null, compressed);
return k.getPrivateKeyEncoded(MainNetParams.get()).toString();
}
@@ -1,6 +1,7 @@
/**
*
* Copyright 2014 Diego Basch
+ *
* 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
@@ -16,7 +17,9 @@
package com.fruitcat.bitcoin;
+import com.google.bitcoin.core.Base58;
import org.bouncycastle.math.ec.ECPoint;
+
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -65,4 +68,18 @@ protected static void pb(byte [] x) {
}
System.out.println();
}
+
+ /**
+ * Appends a Bitcoin-style checksum to a byte array and encodes the result as Base58.
+ * @param b a byte array
+ * @return the encoded concatenation of the byte array and its checksum
+ * @throws NoSuchAlgorithmException
+ */
+
+ public static String base58Check(byte [] b) throws NoSuchAlgorithmException {
+ byte[] r = new byte[b.length + 4];
+ System.arraycopy(b, 0, r, 0, b.length);
+ System.arraycopy(doubleHash(b, 0, b.length), 0, r, b.length, 4);
+ return Base58.encode(r);
+ }
}

0 comments on commit 7cadf5f

Please sign in to comment.