package org.bitseal.crypt;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import org.bitseal.data.Pubkey;
import org.bitseal.util.ArrayCopier;
import org.bitseal.util.Base58;
import org.bitseal.util.ByteUtils;
import org.spongycastle.jce.ECNamedCurveTable;
import org.spongycastle.jce.interfaces.ECPrivateKey;
import org.spongycastle.jce.interfaces.ECPublicKey;
import org.spongycastle.jce.provider.asymmetric.ec.EC5Util;
import org.spongycastle.jce.spec.ECNamedCurveParameterSpec;
import org.spongycastle.math.ec.ECCurve;
/**
* Offers methods for converting cryptographic keys
* between different formats.
*
* @author Jonathan Coe
*/
public class KeyConverter
{
private static final String ALGORITHM = "ECDSA";
private static final String PROVIDER = "SC"; // Spongy Castle
private static final String CURVE = "secp256k1";
/**
* Calculates the ripe hash for the public signing and public encryption keys of a given Pubkey object.
*
* Note: In Bitmessage the ripe hash is the result of RIPEMD160(SHA512(public_signing_key || public_encryption_key)
*
* @param pubkey The Pubkey object containing the keys which we want to calculate the ripe hash of.
*
* @return A byte[] containing the ripe hash.
*/
public byte[] calculateRipeHashFromPubkey (Pubkey pubkey)
{
byte[] publicSigningKey = pubkey.getPublicSigningKey();
byte[] publicEncryptionKey = pubkey.getPublicEncryptionKey();
byte[] mConcatenatedPublicKeys = ByteUtils.concatenateByteArrays(publicSigningKey, publicEncryptionKey);
byte[] ripeHash = SHA512.sha512hash160(mConcatenatedPublicKeys);
return ripeHash;
}
/**
* Takes an encryption key derived from the double hash of a Bitmessage address
* and uses it to create a public encryption key
*
* @param encryptionKey - A byte[] containing the encryption key
*
* @return An ECPublicKey object containing the new public key
*/
public ECPublicKey calculatePublicKeyFromDoubleHashKey (byte[] encryptionKey)
{
// First calculate the private key, using the 'encryption key' derived from the double
// hash of the address data, and extract its 'D' value.
ECPrivateKey privKey = reconstructPrivateKey(encryptionKey);
BigInteger privKeyDValue = privKey.getD();
// Use the 'D' value from the private key to create a new ECKeyPair object
ECKeyPair keyPair = new ECKeyPair(privKeyDValue);
// Takes the public key from the new key pair.
byte[] publicKeyBytes = keyPair.getPubKey();
// Convert the public key bytes into a new ECPublicKey object
ECPublicKey publicKey = reconstructPublicKey(publicKeyBytes);
return publicKey;
}
/**
* Takes an encryption key derived from the double hash of a Bitmessage address
* and uses it to create a private encryption key
*
* @param encryptionKey - A byte[] containing the encryption key
*
* @return An ECPrivateKey object containing the new private key
*/
public ECPrivateKey calculatePrivateKeyFromDoubleHashKey (byte[] encryptionKey)
{
return reconstructPrivateKey(encryptionKey);
}
/**
* Converts a private key in byte array form into a Bitcoin-esque "Wallet Import Format" string. The
* process is as follows: <br><br>
*
* 1) Prepend the decimal value 128 to the private key <br><br>
* 2) Calculate the double SHA-256 hash of the private key with the extra byte <br><br>
* 3) Take the first 4 bytes of that hash as a checksum for the private key<br><br>
* 4) Add the checksum bytes onto the end of the private key with its extra byte<br><br>
* 5) Convert the byte array containing the private key, extra byte, and checksum into a Base 58 encoded String.<br><br>
*
* <b>NOTE:</b> Somewhat confusingly, Bitmessage uses SHA-512 for its address generation and proof of work,
* but uses SHA-256 for converting private keys into wallet import format.
*
* @param privateKey - The private key in byte[] format
*
* @return WIFPrivateKey - A String representation of the private key in "Wallet Import Format"
*/
public String encodePrivateKeyToWIF (byte[] privateKey)
{
// If first byte of the private encryption key generated is zero, remove it.
if (privateKey[0] == 0)
{
privateKey = ArrayCopier.copyOfRange(privateKey, 1, privateKey.length);
}
byte[] valueToPrepend = new byte[1];
valueToPrepend[0] = (byte) 128;
byte[] privateKeyWithExtraByte = ByteUtils.concatenateByteArrays(valueToPrepend, privateKey);
byte[] hashOfPrivateKey = SHA256.doubleDigest(privateKeyWithExtraByte);
byte[] checksum = ArrayCopier.copyOfRange(hashOfPrivateKey, 0, 4);
byte[] convertedPrivateKey = ByteUtils.concatenateByteArrays(privateKeyWithExtraByte, checksum);
String walletImportFormatPrivateKey = Base58.encode(convertedPrivateKey);
return walletImportFormatPrivateKey;
}
/**
* Converts a private key "Wallet Import Format" (as used by Bitcoin) into an ECPrivateKey object. The process to do
* so is as follows: <br><br>
*
* 1) Convert the Base58 encoded String into byte[] form.<br><br>
* 2) Drop the last four bytes, which are the checksum.<br><br>
* 3) Check that the checksum is valid for the remaining bytes.<br><br>
* 4) Drop the first byte, which is the special value prepended to the key bytes during the WIF encoding process.<br><br>
* 5) Check that the first byte equates to the decimal value 128.<br><br>
* 6) The remaining bytes are the private key in two's complement form. Convert them into a BigInteger <br><br>
* 7) Use newly created BigInteger value to create a new ECPrivateKey object.<br><br>
*
* <b>NOTE:</b> Somewhat confusingly, Bitmessage uses SHA-512 for its address generation and proof of work,
* but uses SHA-256 for converting private keys into wallet import format.
*
* @param wifPrivateKey - A String representation of the private key in "Wallet Import Format"
*
* @return An ECPrivateKey object containing the private key
*/
public ECPrivateKey decodePrivateKeyFromWIF (String wifPrivateKey)
{
byte[] privateKeyBytes = Base58.decode(wifPrivateKey);
byte[] privateKeyWithoutChecksum = ArrayCopier.copyOfRange(privateKeyBytes, 0, (privateKeyBytes.length - 4));
byte[] checksum = ArrayCopier.copyOfRange(privateKeyBytes, (privateKeyBytes.length - 4), privateKeyBytes.length);
byte[] hashOfPrivateKey = SHA256.doubleDigest(privateKeyWithoutChecksum);
byte[] testChecksum = ArrayCopier.copyOfRange(hashOfPrivateKey, 0, 4);
if (Arrays.equals(checksum, testChecksum) == false)
{
throw new RuntimeException("While decoding a private key from WIF in KeyConverter.decodePrivateKeyFromWIF(), the checksum was " +
"found to be invalid. Something is wrong!");
}
// Check that the prepended 128 byte is in place
if (privateKeyWithoutChecksum[0] != (byte) 128)
{
throw new RuntimeException("While decoding a private key from WIF in KeyConverter.decodePrivateKeyFromWIF(), its prepended value " +
"was found to be invalid. Something is wrong!");
}
// Drop the prepended 128 byte
byte[] privateKeyFinalBytes = ArrayCopier.copyOfRange(privateKeyWithoutChecksum, 1, privateKeyWithoutChecksum.length);
// If the decoded private key has a negative value, this means that it originally
// began with a zero byte which was stripped off during the encodeToWIF process. We
// must now restore this leading zero byte.
BigInteger privateKeyBigIntegerValue = new BigInteger(privateKeyFinalBytes);
if (privateKeyBigIntegerValue.signum() < 1)
{
byte[] valueToPrepend = new byte[1];
valueToPrepend[0] = (byte) 0;
privateKeyFinalBytes = ByteUtils.concatenateByteArrays(valueToPrepend, privateKeyFinalBytes);
}
ECPrivateKey ecPrivateKey = reconstructPrivateKey(privateKeyFinalBytes);
return ecPrivateKey;
}
/**
* Converts an encoded private key in byte[] form into a new ECPrivateKey object.
*
* @param encodedPrivateKey - A byte[] containing the encoded private key
*
* @return An ECPrivateKey object containing the private key
*/
public ECPrivateKey reconstructPrivateKey(byte[] encodedPrivateKey)
{
// If the encoded private key has a negative value, this means that it originally
// began with a zero byte which was stripped off during the encoding process. We
// must now restore this leading zero byte.
BigInteger privateKeyDValue = new BigInteger(encodedPrivateKey);
if (privateKeyDValue.signum() < 1)
{
byte[] valueToPrepend = new byte[1];
valueToPrepend[0] = (byte) 0;
byte[] privateKeyFinalBytes = ByteUtils.concatenateByteArrays(valueToPrepend, encodedPrivateKey);
privateKeyDValue = new BigInteger(privateKeyFinalBytes);
}
// Reconstruct the encoded private key, giving us a new ECPrivateKey object
ECPrivateKey ecPrivateKey = null;
try
{
Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());
ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec(CURVE);
KeyFactory fact = KeyFactory.getInstance(ALGORITHM, PROVIDER);
ECCurve curve = params.getCurve();
java.security.spec.EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, params.getSeed());
java.security.spec.ECParameterSpec params2 = EC5Util.convertSpec(ellipticCurve, params);
java.security.spec.ECPrivateKeySpec keySpec = new java.security.spec.ECPrivateKeySpec(privateKeyDValue, params2);
ecPrivateKey = (ECPrivateKey) fact.generatePrivate(keySpec);
// Log.i(TAG, "New ECPrivateKey D value bytes: " + ByteFormatter.byteArrayToHexString(ecPrivateKey.getD().toByteArray()));
}
catch (InvalidKeySpecException e)
{
throw new RuntimeException("InvalidKeySpecException occurred in KeyConverter.reconstructPrivateKey()", e);
}
catch (NoSuchAlgorithmException e)
{
throw new RuntimeException("NoSuchAlgorithmException occurred in KeyConverter.reconstructPrivateKey()", e);
}
catch (NoSuchProviderException e)
{
throw new RuntimeException("NoSuchProviderException occurred in KeyConverter.reconstructPrivateKey()", e);
}
return ecPrivateKey;
}
/**
* Converts an encoded public key in byte[] form into a new ECPublicKey object.
*
* @param encodedpublicKey - A byte[] containing the encoded public key.
*
* @return An ECPublicKey object containing the public key.
*/
public ECPublicKey reconstructPublicKey(byte[] encodedPublicKey)
{
int keyLength = encodedPublicKey.length;
// The public key should be either 64 or 65 bytes, depending upon whether or not the 0x04 value has been prepended to it
if (keyLength < 64 || keyLength > 65)
{
throw new RuntimeException("While reconstructing a public key in KeyConverter.reconstructPublicKey(), the " +
"encoded public key is not between 64 and 65 bytes in length. Something is wrong!");
}
if (keyLength == 65 && encodedPublicKey[0] != (byte) 4)
{
throw new RuntimeException("While reconstructing a public key in KeyConverter.reconstructPublicKey(), the encoded " +
"public key is 65 bytes in length, but the first byte is not 0x04. Something is wrong!");
}
byte[] xBytes = null;
byte[] yBytes = null;
if (encodedPublicKey[0] == (byte) 4)
{
xBytes = ArrayCopier.copyOfRange(encodedPublicKey, 1, 33);
yBytes = ArrayCopier.copyOfRange(encodedPublicKey, 33, 65);
}
else
{
xBytes = ArrayCopier.copyOfRange(encodedPublicKey, 0, 32);
yBytes = ArrayCopier.copyOfRange(encodedPublicKey, 32, 64);
}
BigInteger x = ByteUtils.getUnsignedBigInteger(xBytes, 0, 32);
BigInteger y = ByteUtils.getUnsignedBigInteger(yBytes, 0, 32);
ECPublicKey reconstructedECPublicKey = null;
try
{
Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());
java.security.spec.ECPoint w = new java.security.spec.ECPoint(x, y);
ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec(CURVE);
KeyFactory fact = KeyFactory.getInstance(ALGORITHM, PROVIDER);
ECCurve curve = params.getCurve();
java.security.spec.EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, params.getSeed());
java.security.spec.ECParameterSpec params2 = EC5Util.convertSpec(ellipticCurve, params);
java.security.spec.ECPublicKeySpec keySpec = new java.security.spec.ECPublicKeySpec(w, params2);
reconstructedECPublicKey = (ECPublicKey) fact.generatePublic(keySpec);
}
catch (InvalidKeySpecException e)
{
throw new RuntimeException("InvalidKeySpecException occurred in KeyConverter.reconstructPrivateKey()", e);
}
catch (NoSuchAlgorithmException e)
{
throw new RuntimeException("NoSuchAlgorithmException occurred in KeyConverter.reconstructPrivateKey()", e);
}
catch (NoSuchProviderException e)
{
throw new RuntimeException("NoSuchProviderException occurred in KeyConverter.reconstructPrivateKey()", e);
}
return reconstructedECPublicKey;
}
}