Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: dbasch/BIP38
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: Litecoin-Java/BIP38
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Can’t automatically merge. Don’t worry, you can still create the pull request.
  • 5 commits
  • 5 files changed
  • 1 contributor

Commits on Feb 7, 2014

  1. Ignores, dependencies

    hank committed Feb 7, 2014
    Copy the full SHA
    2ec25c4 View commit details
  2. Switching to SpongyCastle AES

    hank committed Feb 7, 2014
    Copy the full SHA
    1dd9bdf View commit details
  3. New tests with litecoin netparams

    All tests pass - used addresses from liteaddress.
    hank committed Feb 7, 2014
    Copy the full SHA
    1345c39 View commit details
  4. Fixing logging warning

    Just had to add an slf4j library to gradle
    hank committed Feb 7, 2014
    Copy the full SHA
    345ee92 View commit details
  5. Adding maven install

    hank committed Feb 7, 2014
    Copy the full SHA
    5172a5f View commit details
Showing with 105 additions and 19 deletions.
  1. +4 −0 .gitignore
  2. +10 −6 build.gradle
  3. +6 −1 src/main/java/com/fruitcat/bitcoin/BIP38.java
  4. +56 −12 src/main/java/com/fruitcat/bitcoin/Utils.java
  5. +29 −0 src/test/java/com/fruitcat/bitcoin/BIP38Test.java
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build
.gradle
.idea
*.iml
16 changes: 10 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
apply plugin: 'java'

repositories {
mavenCentral()
maven {
url "http://distribution.bitcoinj.googlecode.com/git/releases/"
}
mavenCentral()
mavenLocal()
}

allprojects {
apply plugin: 'maven'
group = 'com.fruitcat.bitcoin'
version = '0.1-SNAPSHOT'
}

dependencies {
compile 'com.madgag:scprov-jdk15on:1.47.0.3'
compile 'com.google.guava:guava:15.0'
compile 'com.google:bitcoinj:0.10.3'
compile 'com.google:bitcoinj:0.11-SNAPSHOT'
compile 'org.bouncycastle:bcprov-jdk16:1.46'
testCompile 'org.testng:testng:6.8.7'
testCompile 'org.testng:testng:6.8.7'
testCompile 'org.slf4j:slf4j-simple:1.6.1'
}

test {
7 changes: 6 additions & 1 deletion src/main/java/com/fruitcat/bitcoin/BIP38.java
Original file line number Diff line number Diff line change
@@ -21,7 +21,12 @@

package com.fruitcat.bitcoin;

import com.google.bitcoin.core.*;
import com.google.bitcoin.core.Address;
import com.google.bitcoin.core.AddressFormatException;
import com.google.bitcoin.core.Base58;
import com.google.bitcoin.core.DumpedPrivateKey;
import com.google.bitcoin.core.ECKey;
import com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.params.MainNetParams;
import com.lambdaworks.crypto.SCrypt;
import org.bouncycastle.asn1.sec.SECNamedCurves;
68 changes: 56 additions & 12 deletions src/main/java/com/fruitcat/bitcoin/Utils.java
Original file line number Diff line number Diff line change
@@ -20,10 +20,19 @@
import com.google.bitcoin.core.AddressFormatException;
import com.google.bitcoin.core.Base58;
import org.bouncycastle.math.ec.ECPoint;
import org.spongycastle.crypto.BufferedBlockCipher;
import org.spongycastle.crypto.DataLengthException;
import org.spongycastle.crypto.InvalidCipherTextException;
import org.spongycastle.crypto.engines.AESFastEngine;
import org.spongycastle.crypto.modes.CBCBlockCipher;
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.GeneralSecurityException;
@@ -84,30 +93,65 @@ public static String base58Check(byte [] b) throws NoSuchAlgorithmException {

/**
* Encrypts plaintext with AES
* @param plaintext
* @param plainTextAsBytes
* @param key
* @return
* @throws GeneralSecurityException
*/
public static byte[] AESEncrypt(byte[] plaintext, byte[] key) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "BC");
Key aesKey = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, aesKey);
return cipher.doFinal(plaintext);
public static byte[] AESEncrypt(byte[] plainTextAsBytes, byte[] key) throws GeneralSecurityException {
try
{
final BufferedBlockCipher cipher = new BufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
KeyParameter aesKey = new KeyParameter(key);

cipher.init(true, aesKey);

final byte[] encryptedBytes = new byte[cipher.getOutputSize(plainTextAsBytes.length)];
final int length = cipher.processBytes(plainTextAsBytes, 0, plainTextAsBytes.length, encryptedBytes, 0);

cipher.doFinal(encryptedBytes, length);

return encryptedBytes;
}
catch (final InvalidCipherTextException x)
{
throw new GeneralSecurityException("Could not encrypt bytes", x);
}
catch (final DataLengthException x)
{
throw new GeneralSecurityException("Could not encrypt bytes", x);
}
}

/**
* Decrypts ciphertext with AES
* @param ciphertext
* @param cipherBytes
* @param key
* @return
* @throws GeneralSecurityException
*/
public static byte[] AESDecrypt(byte[] ciphertext, byte[] key) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "BC");
Key aesKey = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, aesKey);
return cipher.doFinal(ciphertext);
public static byte[] AESDecrypt(byte[] cipherBytes, byte[] key) throws GeneralSecurityException {
try
{
final BufferedBlockCipher cipher = new BufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
KeyParameter aesKey = new KeyParameter(key);
cipher.init(false, aesKey);

final byte[] decryptedBytes = new byte[cipher.getOutputSize(cipherBytes.length)];
final int length = cipher.processBytes(cipherBytes, 0, cipherBytes.length, decryptedBytes, 0);

cipher.doFinal(decryptedBytes, length);

return decryptedBytes;
}
catch (final InvalidCipherTextException x)
{
throw new GeneralSecurityException("Could not decrypt input string", x);
}
catch (final DataLengthException x)
{
throw new GeneralSecurityException("Could not decrypt input string", x);
}
}

/**
29 changes: 29 additions & 0 deletions src/test/java/com/fruitcat/bitcoin/BIP38Test.java
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ public class BIP38Test {

@Test
public void noCompressionNoECMultiply() throws Exception {
BIP38.setNetParams(com.google.bitcoin.params.MainNetParams.get());
//test 1
String encryptedKey = "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg";
String key = "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR";
@@ -33,6 +34,7 @@ public void noCompressionNoECMultiply() throws Exception {

@Test
public void compressionNoECMultiply() throws Exception {
BIP38.setNetParams(com.google.bitcoin.params.MainNetParams.get());
//test 1
String encryptedKey = "6PYNKZ1EAgYgmQfmNVamxyXVWHzK5s6DGhwP4J5o44cvXdoY7sRzhtpUeo";
String key = "L44B5gGEpqEDRS9vVPz7QT35jcBG2r3CZwSwQ4fCewXAhAhqGVpP";
@@ -48,6 +50,7 @@ public void compressionNoECMultiply() throws Exception {
//EC multiply, no compression, no lot/sequence numbers
@Test
public void ecMultiplyNoCompressionNoLot() throws Exception {
BIP38.setNetParams(com.google.bitcoin.params.MainNetParams.get());
//test 1
String encryptedKey = "6PfQu77ygVyJLZjfvMLyhLMQbYnu5uguoJJ4kMCLqWwPEdfpwANVS76gTX";
String key = "5K4caxezwjGCGfnoPTZ8tMcJBLB7Jvyjv4xxeacadhq8nLisLR2";
@@ -70,6 +73,7 @@ public void ecMultiplyNoCompressionNoLot() throws Exception {
//EC multiply, no compression, lot/sequence
@Test
public void ecMultiplyNoCompressionLot() throws Exception {
BIP38.setNetParams(com.google.bitcoin.params.MainNetParams.get());
//test 1
String encryptedKey = "6PgNBNNzDkKdhkT6uJntUXwwzQV8Rr2tZcbkDcuC9DZRsS6AtHts4Ypo1j";
String key = "5JLdxTtcTHcfYcmJsNVy1v2PMDx432JPoYcBTVVRHpPaxUrdtf8";
@@ -92,6 +96,7 @@ public void ecMultiplyNoCompressionLot() throws Exception {
//round encrypt and decrypt with a random ascii password
@Test
public void randomRoundTripNoEC() throws Exception {
BIP38.setNetParams(com.google.bitcoin.params.MainNetParams.get());
byte[] r = new byte[16];
(new Random()).nextBytes(r);
String randomPass = new String(r, "ASCII");
@@ -103,6 +108,7 @@ public void randomRoundTripNoEC() throws Exception {
//generate an encrypted key and make sure it looks ok.
@Test
public void generateEncryptedKey() throws Exception {
BIP38.setNetParams(com.google.bitcoin.params.MainNetParams.get());
String k = BIP38.generateEncryptedKey(testPass);
String dk = BIP38.decrypt(testPass, k);
assertEquals(dk.charAt(0), '5');
@@ -111,9 +117,32 @@ public void generateEncryptedKey() throws Exception {
//check confirmation code
@Test
public void checkConfirmation() throws Exception {
BIP38.setNetParams(com.google.bitcoin.params.MainNetParams.get());
byte[] intermediate = Arrays.copyOfRange(Base58.decode(BIP38.intermediatePassphrase(testPass, -1, -1)), 0, 53);
GeneratedKey gk = BIP38.encryptedKeyFromIntermediate(intermediate);
assert(BIP38.verify(testPass, gk));
assert(!BIP38.verify("garbage", gk));
}

@Test
public void litecoinRandomRoundTripNoEC() throws Exception {
BIP38.setNetParams(org.litecoin.LitecoinParams.get());
byte[] r = new byte[16];
(new Random()).nextBytes(r);
String randomPass = new String(r, "ASCII");
String key = "6uRPj6B3bPCqf9KZLzc1VWHsmZgXXhx5qdm69BV9hg454LNwGwX";
String encryptedKey = BIP38.encryptNoEC(randomPass, key, false);
assertEquals(key, (BIP38.decrypt(randomPass, encryptedKey)));
}

@Test
public void litecoinCompressionNoECMultiply() throws Exception {
BIP38.setNetParams(org.litecoin.LitecoinParams.get());
// 6PfMZr9CPt4JvcEZnSRRD8F9a2dYJ3H999sLoLJD3Lxh5gF5gC64TY8j1Q + "Satoshi"
// == 6vHEw38JPP6fvNBxVUeb499S7JBfRX5dMGadWrjv3w78UHPD2ac
String encryptedKey = "6PfMZr9CPt4JvcEZnSRRD8F9a2dYJ3H999sLoLJD3Lxh5gF5gC64TY8j1Q";
String key = "6vHEw38JPP6fvNBxVUeb499S7JBfRX5dMGadWrjv3w78UHPD2ac";
String decryptedKey = BIP38.decrypt("Satoshi", encryptedKey);
assertEquals(decryptedKey, key);
}
}