mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 10:06:45 +00:00
refactor keycrypter hierarchy, add ECIESKeyCrypter
This commit is contained in:
parent
c675e395db
commit
492f447c28
14 changed files with 337 additions and 258 deletions
|
@ -1,67 +1,101 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import org.bouncycastle.crypto.BufferedBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.InvalidCipherTextException;
|
||||||
|
import org.bouncycastle.crypto.engines.AESEngine;
|
||||||
|
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||||
import org.bouncycastle.crypto.params.KeyParameter;
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import java.security.SecureRandom;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import java.util.Arrays;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.spec.AlgorithmParameterSpec;
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*/
|
||||||
public class AESKeyCrypter implements KeyCrypter {
|
public class AESKeyCrypter implements KeyCrypter {
|
||||||
|
/**
|
||||||
|
* The size of an AES block in bytes.
|
||||||
|
* This is also the length of the initialisation vector.
|
||||||
|
*/
|
||||||
|
public static final int BLOCK_LENGTH = 16; // = 128 bits.
|
||||||
|
|
||||||
|
private static final SecureRandom secureRandom = new SecureRandom();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EncryptionType getUnderstoodEncryptionType() {
|
public EncryptionType getUnderstoodEncryptionType() {
|
||||||
return null;
|
return EncryptionType.ENCRYPTED_AES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyParameter deriveKey(CharSequence password) throws KeyCrypterException {
|
public KeyParameter deriveKey(CharSequence password) throws KeyCrypterException {
|
||||||
return createKeyPbkdf2HmacSha512(password.toString());
|
throw new UnsupportedOperationException("AESKeyCrypter does not define a key derivation function, but keys must be either 128, 192 or 256 bits long");
|
||||||
}
|
|
||||||
|
|
||||||
public static KeyParameter createKeyPbkdf2HmacSha512(String password) {
|
|
||||||
return createKeyPbkdf2HmacSha512(password, new byte[0], 1024);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static KeyParameter createKeyPbkdf2HmacSha512(String password, byte[] salt, int iterationCount) {
|
|
||||||
byte[] secret = Utils.getPbkdf2HmacSha512Hash(password.getBytes(StandardCharsets.UTF_8), salt, iterationCount);
|
|
||||||
return new KeyParameter(secret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt bytes previously encrypted with this class.
|
||||||
|
*
|
||||||
|
* @param dataToDecrypt The data to decrypt
|
||||||
|
* @param aesKey The AES key to use for decryption
|
||||||
|
* @return The decrypted bytes
|
||||||
|
* @throws KeyCrypterException if bytes could not be decrypted
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public byte[] decrypt(EncryptedData encryptedBytesToDecode, KeyParameter aesKey) throws KeyCrypterException {
|
public byte[] decrypt(EncryptedData dataToDecrypt, KeyParameter aesKey) throws KeyCrypterException {
|
||||||
return decryptAesCbcPkcs7(encryptedBytesToDecode.getEncryptedBytes(), encryptedBytesToDecode.getInitialisationVector(), aesKey.getKey());
|
if(dataToDecrypt == null || aesKey == null) {
|
||||||
}
|
throw new KeyCrypterException("Data and key to decrypt cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] decryptAesCbcPkcs7(byte[] ciphertext, byte[] iv, byte[] key_e) {
|
|
||||||
try {
|
try {
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
|
ParametersWithIV keyWithIv = new ParametersWithIV(new KeyParameter(aesKey.getKey()), dataToDecrypt.getInitialisationVector());
|
||||||
SecretKeySpec secretKeySpec = new SecretKeySpec(key_e, "AES");
|
|
||||||
AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
|
// Decrypt the message.
|
||||||
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, paramSpec);
|
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
|
||||||
return cipher.doFinal(ciphertext);
|
cipher.init(false, keyWithIv);
|
||||||
} catch (Exception e) {
|
|
||||||
throw new KeyCrypterException("Error decrypting", e);
|
byte[] cipherBytes = dataToDecrypt.getEncryptedBytes();
|
||||||
|
byte[] decryptedBytes = new byte[cipher.getOutputSize(cipherBytes.length)];
|
||||||
|
final int length1 = cipher.processBytes(cipherBytes, 0, cipherBytes.length, decryptedBytes, 0);
|
||||||
|
final int length2 = cipher.doFinal(decryptedBytes, length1);
|
||||||
|
|
||||||
|
return Arrays.copyOf(decryptedBytes, length1 + length2);
|
||||||
|
} catch (InvalidCipherTextException e) {
|
||||||
|
throw new KeyCrypterException.InvalidCipherText("Could not decrypt bytes", e);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw new KeyCrypterException("Could not decrypt bytes", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Password based encryption using AES - CBC 256 bits.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, KeyParameter aesKey) throws KeyCrypterException {
|
public EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, KeyParameter aesKey) throws KeyCrypterException {
|
||||||
byte[] encryptedData = encryptAesCbcPkcs7(plainBytes, initializationVector, aesKey.getKey());
|
if(plainBytes == null || aesKey == null) {
|
||||||
return new EncryptedData(initializationVector, encryptedData);
|
throw new KeyCrypterException("Data and key to encrypt cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] encryptAesCbcPkcs7(byte[] message, byte[] iv, byte[] key_e) {
|
|
||||||
try {
|
try {
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
|
// Generate iv - each encryption call has a different iv.
|
||||||
SecretKeySpec secretKeySpec = new SecretKeySpec(key_e, "AES");
|
byte[] iv = initializationVector;
|
||||||
AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
|
if(iv == null) {
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, paramSpec);
|
iv = new byte[BLOCK_LENGTH];
|
||||||
return cipher.doFinal(message);
|
secureRandom.nextBytes(iv);
|
||||||
} catch(Exception e) {
|
}
|
||||||
throw new KeyCrypterException("Could not encrypt", e);
|
|
||||||
|
ParametersWithIV keyWithIv = new ParametersWithIV(aesKey, iv);
|
||||||
|
|
||||||
|
// Encrypt using AES.
|
||||||
|
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
|
||||||
|
cipher.init(true, keyWithIv);
|
||||||
|
byte[] encryptedBytes = new byte[cipher.getOutputSize(plainBytes.length)];
|
||||||
|
final int length1 = cipher.processBytes(plainBytes, 0, plainBytes.length, encryptedBytes, 0);
|
||||||
|
final int length2 = cipher.doFinal(encryptedBytes, length1);
|
||||||
|
|
||||||
|
return new EncryptedData(iv, Arrays.copyOf(encryptedBytes, length1 + length2));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new KeyCrypterException("Could not encrypt bytes.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
|
public interface AsymmetricKeyCrypter {
|
||||||
|
/**
|
||||||
|
* Return the EncryptionType enum value which denotes the type of encryption/ decryption that this KeyCrypter
|
||||||
|
* can understand.
|
||||||
|
*/
|
||||||
|
EncryptionType getUnderstoodEncryptionType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a KeyParameter (which typically contains an AES key)
|
||||||
|
* @param password
|
||||||
|
* @return KeyParameter The KeyParameter which typically contains the AES key to use for encrypting and decrypting
|
||||||
|
* @throws KeyCrypterException
|
||||||
|
*/
|
||||||
|
ECKey deriveECKey(CharSequence password) throws KeyCrypterException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt the provided encrypted bytes, converting them into unencrypted bytes.
|
||||||
|
*
|
||||||
|
* @throws KeyCrypterException if decryption was unsuccessful.
|
||||||
|
*/
|
||||||
|
byte[] decrypt(EncryptedData encryptedBytesToDecode, ECKey key) throws KeyCrypterException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt the supplied bytes, converting them into ciphertext.
|
||||||
|
*
|
||||||
|
* @return encryptedPrivateKey An encryptedPrivateKey containing the encrypted bytes and an initialisation vector.
|
||||||
|
* @throws KeyCrypterException if encryption was unsuccessful
|
||||||
|
*/
|
||||||
|
EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, ECKey key) throws KeyCrypterException;
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.Utils;
|
||||||
|
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||||
|
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||||
|
import org.bouncycastle.crypto.macs.HMac;
|
||||||
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Encrypt data according to Electrum ECIES format (also called BIE1)
|
||||||
|
*/
|
||||||
|
public class ECIESKeyCrypter implements AsymmetricKeyCrypter {
|
||||||
|
private final KeyCrypter aesKeyCrypter = new AESKeyCrypter();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EncryptionType getUnderstoodEncryptionType() {
|
||||||
|
return EncryptionType.ENCRYPTED_ECIES_AES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ECKey deriveECKey(CharSequence password) throws KeyCrypterException {
|
||||||
|
return deriveECKey(password.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ECKey deriveECKey(String password) throws KeyCrypterException {
|
||||||
|
byte[] secret = Utils.getPbkdf2HmacSha512Hash(password.toString().getBytes(StandardCharsets.UTF_8), new byte[0], 1024);
|
||||||
|
return ECKey.fromPrivate(secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] decrypt(EncryptedData encryptedBytesToDecode, ECKey key) throws KeyCrypterException {
|
||||||
|
return decryptEcies(encryptedBytesToDecode.getEncryptedBytes(), encryptedBytesToDecode.getInitialisationVector(), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] decryptEcies(byte[] message, byte[] magic, ECKey key) {
|
||||||
|
byte[] decoded = Base64.getDecoder().decode(message);
|
||||||
|
if(decoded.length < 85) {
|
||||||
|
throw new IllegalArgumentException("Ciphertext is too short at " + decoded.length + " bytes");
|
||||||
|
}
|
||||||
|
byte[] magicFound = Arrays.copyOfRange(decoded, 0, 4); //new byte[4];
|
||||||
|
//System.arraycopy(decoded, 0, magicFound, 0, 4);
|
||||||
|
byte[] ephemeralPubKeyBytes = Arrays.copyOfRange(decoded, 4, 37); //new byte[33];
|
||||||
|
//System.arraycopy(decoded, 4, ephemeralPubKeyBytes, 0, 33);
|
||||||
|
int ciphertextlength = decoded.length - 37 - 32;
|
||||||
|
byte[] ciphertext = Arrays.copyOfRange(decoded, 37, decoded.length - 32); //new byte[ciphertextlength];
|
||||||
|
//System.arraycopy(decoded, 37, ciphertext, 0, ciphertextlength);
|
||||||
|
byte[] mac = Arrays.copyOfRange(decoded, decoded.length - 32, decoded.length); //new byte[32];
|
||||||
|
//System.arraycopy(decoded, decoded.length - 32, mac, 0, 32);
|
||||||
|
|
||||||
|
if(!Arrays.equals(magic, magicFound)) {
|
||||||
|
throw new IllegalArgumentException("Invalid ciphertext: invalid magic bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
ECKey ephemeralPubKey = ECKey.fromPublicOnly(ephemeralPubKeyBytes);
|
||||||
|
byte[] ecdh_key = ephemeralPubKey.getPubKeyPoint().multiply(key.getPrivKey()).getEncoded(true);
|
||||||
|
byte[] hash = sha512(ecdh_key);
|
||||||
|
|
||||||
|
byte[] iv = Arrays.copyOfRange(hash, 0, 16);
|
||||||
|
byte[] key_e = Arrays.copyOfRange(hash, 16, 32);
|
||||||
|
byte[] key_m = Arrays.copyOfRange(hash, 32, 64);
|
||||||
|
byte[] hmacInput = Arrays.copyOfRange(decoded, 0, decoded.length - 32);
|
||||||
|
|
||||||
|
if(!Arrays.equals(mac, hmac256(key_m, hmacInput))) {
|
||||||
|
throw new InvalidPasswordException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return aesKeyCrypter.decrypt(new EncryptedData(iv, ciphertext), new KeyParameter(key_e));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, ECKey key) throws KeyCrypterException {
|
||||||
|
byte[] encryptedBytes = encryptEcies(key, plainBytes, initializationVector);
|
||||||
|
return new EncryptedData(initializationVector, encryptedBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encryptEcies(ECKey key, byte[] message, byte[] magic) {
|
||||||
|
ECKey ephemeral = new ECKey();
|
||||||
|
byte[] ecdh_key = key.getPubKeyPoint().multiply(ephemeral.getPrivKey()).getEncoded(true);
|
||||||
|
byte[] hash = sha512(ecdh_key);
|
||||||
|
|
||||||
|
byte[] iv = Arrays.copyOfRange(hash, 0, 16);
|
||||||
|
byte[] key_e = Arrays.copyOfRange(hash, 16, 32);
|
||||||
|
byte[] key_m = Arrays.copyOfRange(hash, 32, 64);
|
||||||
|
|
||||||
|
byte[] ciphertext = aesKeyCrypter.encrypt(message, iv, new KeyParameter(key_e)).getEncryptedBytes();
|
||||||
|
byte[] encrypted = concat(magic, ephemeral.getPubKey(), ciphertext);
|
||||||
|
byte[] result = hmac256(key_m, encrypted);
|
||||||
|
return Base64.getEncoder().encode(concat(encrypted, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] sha512(byte[] input) {
|
||||||
|
SHA512Digest digest = new SHA512Digest();
|
||||||
|
byte[] hash = new byte[digest.getDigestSize()];
|
||||||
|
digest.update(input, 0, input.length);
|
||||||
|
digest.doFinal(hash, 0);
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] hmac256(byte[] key, byte[] input) {
|
||||||
|
HMac hmac = new HMac(new SHA256Digest());
|
||||||
|
hmac.init(new KeyParameter(key));
|
||||||
|
byte[] result = new byte[hmac.getMacSize()];
|
||||||
|
hmac.update(input, 0, input.length);
|
||||||
|
hmac.doFinal(result, 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] concat(byte[] ...bytes) {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
for(byte[] byteArray : bytes) {
|
||||||
|
out.write(byteArray);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
//can't happen
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InvalidPasswordException extends RuntimeException {
|
||||||
|
}
|
||||||
|
}
|
|
@ -97,8 +97,6 @@ public class ECKey implements EncryptableItem {
|
||||||
CURVE_PARAMS.getH());
|
CURVE_PARAMS.getH());
|
||||||
HALF_CURVE_ORDER = CURVE_PARAMS.getN().shiftRight(1);
|
HALF_CURVE_ORDER = CURVE_PARAMS.getN().shiftRight(1);
|
||||||
secureRandom = new SecureRandom();
|
secureRandom = new SecureRandom();
|
||||||
|
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The two parts of the key. If "pub" is set but not "priv", we can only verify signatures, not make them.
|
// The two parts of the key. If "pub" is set but not "priv", we can only verify signatures, not make them.
|
||||||
|
@ -879,106 +877,6 @@ public class ECKey implements EncryptableItem {
|
||||||
public static class KeyIsEncryptedException extends MissingPrivateKeyException {
|
public static class KeyIsEncryptedException extends MissingPrivateKeyException {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class InvalidPasswordException extends RuntimeException {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ECKey createKeyPbkdf2HmacSha512(String password) {
|
|
||||||
return createKeyPbkdf2HmacSha512(password, new byte[0], 1024);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ECKey createKeyPbkdf2HmacSha512(String password, byte[] salt, int iterationCount) {
|
|
||||||
byte[] secret = Utils.getPbkdf2HmacSha512Hash(password.getBytes(StandardCharsets.UTF_8), salt, iterationCount);
|
|
||||||
return ECKey.fromPrivate(secret);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] encryptEcies(byte[] message, byte[] magic) {
|
|
||||||
ECKey ephemeral = new ECKey();
|
|
||||||
byte[] ecdh_key = this.getPubKeyPoint().multiply(ephemeral.getPrivKey()).getEncoded(true);
|
|
||||||
byte[] hash = sha512(ecdh_key);
|
|
||||||
|
|
||||||
byte[] iv = new byte[16];
|
|
||||||
System.arraycopy(hash, 0, iv, 0, 16);
|
|
||||||
byte[] key_e = new byte[16];
|
|
||||||
System.arraycopy(hash, 16, key_e, 0, 16);
|
|
||||||
byte[] key_m = new byte[hash.length-32];
|
|
||||||
System.arraycopy(hash, 32, key_m, 0, hash.length-32);
|
|
||||||
|
|
||||||
AESKeyCrypter aesKeyCrypter = new AESKeyCrypter();
|
|
||||||
byte[] ciphertext = aesKeyCrypter.encrypt(message, iv, new KeyParameter(key_e)).getEncryptedBytes();
|
|
||||||
byte[] encrypted = concat(magic, ephemeral.getPubKey(), ciphertext);
|
|
||||||
byte[] result = hmac256(key_m, encrypted);
|
|
||||||
return Base64.getEncoder().encode(concat(encrypted, result));
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] decryptEcies(byte[] message, byte[] magic) {
|
|
||||||
byte[] decoded = Base64.getDecoder().decode(message);
|
|
||||||
if(decoded.length < 85) {
|
|
||||||
throw new IllegalArgumentException("Ciphertext is too short at " + decoded.length + " bytes");
|
|
||||||
}
|
|
||||||
byte[] magicFound = new byte[4];
|
|
||||||
System.arraycopy(decoded, 0, magicFound, 0, 4);
|
|
||||||
byte[] ephemeralPubKeyBytes = new byte[33];
|
|
||||||
System.arraycopy(decoded, 4, ephemeralPubKeyBytes, 0, 33);
|
|
||||||
int ciphertextlength = decoded.length - 37 - 32;
|
|
||||||
byte[] ciphertext = new byte[ciphertextlength];
|
|
||||||
System.arraycopy(decoded, 37, ciphertext, 0, ciphertextlength);
|
|
||||||
byte[] mac = new byte[32];
|
|
||||||
System.arraycopy(decoded, decoded.length - 32, mac, 0, 32);
|
|
||||||
|
|
||||||
if(!Arrays.equals(magic, magicFound)) {
|
|
||||||
throw new IllegalArgumentException("Invalid ciphertext: invalid magic bytes");
|
|
||||||
}
|
|
||||||
|
|
||||||
ECKey ephemeralPubKey = ECKey.fromPublicOnly(ephemeralPubKeyBytes);
|
|
||||||
byte[] ecdh_key = ephemeralPubKey.getPubKeyPoint().multiply(this.getPrivKey()).getEncoded(true);
|
|
||||||
byte[] hash = sha512(ecdh_key);
|
|
||||||
|
|
||||||
byte[] iv = new byte[16];
|
|
||||||
System.arraycopy(hash, 0, iv, 0, 16);
|
|
||||||
byte[] key_e = new byte[16];
|
|
||||||
System.arraycopy(hash, 16, key_e, 0, 16);
|
|
||||||
byte[] key_m = new byte[hash.length-32];
|
|
||||||
System.arraycopy(hash, 32, key_m, 0, hash.length-32);
|
|
||||||
byte[] hmacInput = new byte[decoded.length-32];
|
|
||||||
System.arraycopy(decoded, 0, hmacInput, 0, decoded.length - 32);
|
|
||||||
|
|
||||||
if(!Arrays.equals(mac, hmac256(key_m, hmacInput))) {
|
|
||||||
throw new InvalidPasswordException();
|
|
||||||
}
|
|
||||||
|
|
||||||
AESKeyCrypter aesKeyCrypter = new AESKeyCrypter();
|
|
||||||
return aesKeyCrypter.decrypt(new EncryptedData(iv, ciphertext), new KeyParameter(key_e));
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] sha512(byte[] input) {
|
|
||||||
SHA512Digest digest = new SHA512Digest();
|
|
||||||
byte[] hash = new byte[digest.getDigestSize()];
|
|
||||||
digest.update(input, 0, input.length);
|
|
||||||
digest.doFinal(hash, 0);
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] hmac256(byte[] key, byte[] input) {
|
|
||||||
HMac hmac = new HMac(new SHA256Digest());
|
|
||||||
hmac.init(new KeyParameter(key));
|
|
||||||
byte[] result = new byte[hmac.getMacSize()];
|
|
||||||
hmac.update(input, 0, input.length);
|
|
||||||
hmac.doFinal(result, 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] concat(byte[] ...bytes) {
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
try {
|
|
||||||
for(byte[] byteArray : bytes) {
|
|
||||||
out.write(byteArray);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
//can't happen
|
|
||||||
}
|
|
||||||
return out.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -45,4 +45,9 @@ public final class EncryptedData {
|
||||||
return "EncryptedData [initialisationVector=" + Arrays.toString(initialisationVector)
|
return "EncryptedData [initialisationVector=" + Arrays.toString(initialisationVector)
|
||||||
+ ", encryptedPrivateKey=" + Arrays.toString(encryptedBytes) + "]";
|
+ ", encryptedPrivateKey=" + Arrays.toString(encryptedBytes) + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EncryptedData copy() {
|
||||||
|
return new EncryptedData(Arrays.copyOf(initialisationVector, initialisationVector.length),
|
||||||
|
Arrays.copyOf(encryptedBytes, encryptedBytes.length));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
public enum EncryptionType {
|
public enum EncryptionType {
|
||||||
UNENCRYPTED, ENCRYPTED_SCRYPT_AES, ENCRYPTED_ECIES;
|
UNENCRYPTED, ENCRYPTED_AES, ENCRYPTED_SCRYPT_AES, ENCRYPTED_ECIES_AES;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ public interface KeyCrypter {
|
||||||
*
|
*
|
||||||
* @throws KeyCrypterException if decryption was unsuccessful.
|
* @throws KeyCrypterException if decryption was unsuccessful.
|
||||||
*/
|
*/
|
||||||
byte[] decrypt(EncryptedData encryptedBytesToDecode, KeyParameter aesKey) throws KeyCrypterException;
|
byte[] decrypt(EncryptedData encryptedBytesToDecode, KeyParameter key) throws KeyCrypterException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt the supplied bytes, converting them into ciphertext.
|
* Encrypt the supplied bytes, converting them into ciphertext.
|
||||||
|
@ -46,5 +46,5 @@ public interface KeyCrypter {
|
||||||
* @return encryptedPrivateKey An encryptedPrivateKey containing the encrypted bytes and an initialisation vector.
|
* @return encryptedPrivateKey An encryptedPrivateKey containing the encrypted bytes and an initialisation vector.
|
||||||
* @throws KeyCrypterException if encryption was unsuccessful
|
* @throws KeyCrypterException if encryption was unsuccessful
|
||||||
*/
|
*/
|
||||||
EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, KeyParameter aesKey) throws KeyCrypterException;
|
EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, KeyParameter key) throws KeyCrypterException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
import org.bouncycastle.crypto.BufferedBlockCipher;
|
|
||||||
import org.bouncycastle.crypto.InvalidCipherTextException;
|
|
||||||
import org.bouncycastle.crypto.engines.AESEngine;
|
|
||||||
import org.bouncycastle.crypto.generators.SCrypt;
|
import org.bouncycastle.crypto.generators.SCrypt;
|
||||||
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
|
||||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
|
||||||
import org.bouncycastle.crypto.params.KeyParameter;
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,7 +22,7 @@ import java.util.Objects;
|
||||||
* <p>2) Using the AES Key generated above, you then can encrypt and decrypt any bytes using
|
* <p>2) Using the AES Key generated above, you then can encrypt and decrypt any bytes using
|
||||||
* the AES symmetric cipher. Eight bytes of salt is used to prevent dictionary attacks.</p>
|
* the AES symmetric cipher. Eight bytes of salt is used to prevent dictionary attacks.</p>
|
||||||
*/
|
*/
|
||||||
public class ScryptKeyCrypter implements KeyCrypter {
|
public class ScryptKeyCrypter extends AESKeyCrypter {
|
||||||
private static final Logger log = LoggerFactory.getLogger(ScryptKeyCrypter.class);
|
private static final Logger log = LoggerFactory.getLogger(ScryptKeyCrypter.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,12 +30,6 @@ public class ScryptKeyCrypter implements KeyCrypter {
|
||||||
*/
|
*/
|
||||||
public static final int KEY_LENGTH = 32; // = 256 bits.
|
public static final int KEY_LENGTH = 32; // = 256 bits.
|
||||||
|
|
||||||
/**
|
|
||||||
* The size of an AES block in bytes.
|
|
||||||
* This is also the length of the initialisation vector.
|
|
||||||
*/
|
|
||||||
public static final int BLOCK_LENGTH = 16; // = 128 bits.
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The length of the salt used.
|
* The length of the salt used.
|
||||||
*/
|
*/
|
||||||
|
@ -124,72 +111,6 @@ public class ScryptKeyCrypter implements KeyCrypter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Password based encryption using AES - CBC 256 bits.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, KeyParameter aesKey) throws KeyCrypterException {
|
|
||||||
if(plainBytes == null || aesKey == null) {
|
|
||||||
throw new KeyCrypterException("Data and key to encrypt cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Generate iv - each encryption call has a different iv.
|
|
||||||
byte[] iv = initializationVector;
|
|
||||||
if(iv == null) {
|
|
||||||
iv = new byte[BLOCK_LENGTH];
|
|
||||||
secureRandom.nextBytes(iv);
|
|
||||||
}
|
|
||||||
|
|
||||||
ParametersWithIV keyWithIv = new ParametersWithIV(aesKey, iv);
|
|
||||||
|
|
||||||
// Encrypt using AES.
|
|
||||||
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
|
|
||||||
cipher.init(true, keyWithIv);
|
|
||||||
byte[] encryptedBytes = new byte[cipher.getOutputSize(plainBytes.length)];
|
|
||||||
final int length1 = cipher.processBytes(plainBytes, 0, plainBytes.length, encryptedBytes, 0);
|
|
||||||
final int length2 = cipher.doFinal(encryptedBytes, length1);
|
|
||||||
|
|
||||||
return new EncryptedData(iv, Arrays.copyOf(encryptedBytes, length1 + length2));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new KeyCrypterException("Could not encrypt bytes.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypt bytes previously encrypted with this class.
|
|
||||||
*
|
|
||||||
* @param dataToDecrypt The data to decrypt
|
|
||||||
* @param aesKey The AES key to use for decryption
|
|
||||||
* @return The decrypted bytes
|
|
||||||
* @throws KeyCrypterException if bytes could not be decrypted
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public byte[] decrypt(EncryptedData dataToDecrypt, KeyParameter aesKey) throws KeyCrypterException {
|
|
||||||
if(dataToDecrypt == null || aesKey == null) {
|
|
||||||
throw new KeyCrypterException("Data and key to decrypt cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
ParametersWithIV keyWithIv = new ParametersWithIV(new KeyParameter(aesKey.getKey()), dataToDecrypt.getInitialisationVector());
|
|
||||||
|
|
||||||
// Decrypt the message.
|
|
||||||
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
|
|
||||||
cipher.init(false, keyWithIv);
|
|
||||||
|
|
||||||
byte[] cipherBytes = dataToDecrypt.getEncryptedBytes();
|
|
||||||
byte[] decryptedBytes = new byte[cipher.getOutputSize(cipherBytes.length)];
|
|
||||||
final int length1 = cipher.processBytes(cipherBytes, 0, cipherBytes.length, decryptedBytes, 0);
|
|
||||||
final int length2 = cipher.doFinal(decryptedBytes, length1);
|
|
||||||
|
|
||||||
return Arrays.copyOf(decryptedBytes, length1 + length2);
|
|
||||||
} catch (InvalidCipherTextException e) {
|
|
||||||
throw new KeyCrypterException.InvalidCipherText("Could not decrypt bytes", e);
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
throw new KeyCrypterException("Could not decrypt bytes", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a CharSequence (which are UTF16) into a byte array.
|
* Convert a CharSequence (which are UTF16) into a byte array.
|
||||||
*
|
*
|
||||||
|
|
|
@ -9,19 +9,16 @@ import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.StringJoiner;
|
|
||||||
|
|
||||||
public class DeterministicSeed implements EncryptableItem {
|
public class DeterministicSeed implements EncryptableItem {
|
||||||
public static final int DEFAULT_SEED_ENTROPY_BITS = 128;
|
public static final int DEFAULT_SEED_ENTROPY_BITS = 128;
|
||||||
public static final int MAX_SEED_ENTROPY_BITS = 512;
|
public static final int MAX_SEED_ENTROPY_BITS = 512;
|
||||||
|
|
||||||
private final byte[] seed;
|
private final byte[] seed;
|
||||||
private final EncryptedData encryptedSeed;
|
|
||||||
|
|
||||||
private final List<String> mnemonicCode;
|
private final List<String> mnemonicCode;
|
||||||
|
|
||||||
|
private final EncryptedData encryptedSeed;
|
||||||
private final EncryptedData encryptedMnemonicCode;
|
private final EncryptedData encryptedMnemonicCode;
|
||||||
|
|
||||||
private long creationTimeSeconds;
|
private long creationTimeSeconds;
|
||||||
|
@ -248,4 +245,12 @@ public class DeterministicSeed implements EncryptableItem {
|
||||||
private static List<String> decodeMnemonicCode(String mnemonicCode) {
|
private static List<String> decodeMnemonicCode(String mnemonicCode) {
|
||||||
return Arrays.asList(mnemonicCode.split(" "));
|
return Arrays.asList(mnemonicCode.split(" "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DeterministicSeed copy() {
|
||||||
|
if(isEncrypted()) {
|
||||||
|
return new DeterministicSeed(encryptedMnemonicCode.copy(), encryptedSeed.copy(), creationTimeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DeterministicSeed(Arrays.copyOf(seed, seed.length), new ArrayList<>(mnemonicCode), creationTimeSeconds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ public class Keystore {
|
||||||
private WalletModel walletModel = WalletModel.SPARROW;
|
private WalletModel walletModel = WalletModel.SPARROW;
|
||||||
private KeyDerivation keyDerivation;
|
private KeyDerivation keyDerivation;
|
||||||
private ExtendedKey extendedPublicKey;
|
private ExtendedKey extendedPublicKey;
|
||||||
private byte[] seed;
|
private DeterministicSeed seed;
|
||||||
|
|
||||||
public Keystore() {
|
public Keystore() {
|
||||||
this(DEFAULT_LABEL);
|
this(DEFAULT_LABEL);
|
||||||
|
@ -71,11 +71,11 @@ public class Keystore {
|
||||||
this.extendedPublicKey = extendedPublicKey;
|
this.extendedPublicKey = extendedPublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getSeed() {
|
public DeterministicSeed getSeed() {
|
||||||
return seed;
|
return seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSeed(byte[] seed) {
|
public void setSeed(DeterministicSeed seed) {
|
||||||
this.seed = seed;
|
this.seed = seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,11 @@ public class Keystore {
|
||||||
throw new IllegalArgumentException("Keystore does not contain a seed");
|
throw new IllegalArgumentException("Keystore does not contain a seed");
|
||||||
}
|
}
|
||||||
|
|
||||||
return HDKeyDerivation.createMasterPrivateKey(seed);
|
if(seed.isEncrypted()) {
|
||||||
|
throw new IllegalArgumentException("Seed is encrypted");
|
||||||
|
}
|
||||||
|
|
||||||
|
return HDKeyDerivation.createMasterPrivateKey(seed.getSeedBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExtendedKey getExtendedPrivateKey() {
|
public ExtendedKey getExtendedPrivateKey() {
|
||||||
|
@ -119,10 +123,13 @@ public class Keystore {
|
||||||
if(extendedPublicKey != null) {
|
if(extendedPublicKey != null) {
|
||||||
copy.setExtendedPublicKey(extendedPublicKey.copy());
|
copy.setExtendedPublicKey(extendedPublicKey.copy());
|
||||||
}
|
}
|
||||||
|
if(seed != null) {
|
||||||
|
copy.setSeed(seed.copy());
|
||||||
|
}
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Keystore fromSeed(byte[] seed, List<ChildNumber> derivation) {
|
public static Keystore fromSeed(DeterministicSeed seed, List<ChildNumber> derivation) {
|
||||||
Keystore keystore = new Keystore();
|
Keystore keystore = new Keystore();
|
||||||
keystore.setSeed(seed);
|
keystore.setSeed(seed);
|
||||||
ExtendedKey xprv = keystore.getExtendedPrivateKey();
|
ExtendedKey xprv = keystore.getExtendedPrivateKey();
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class ECIESKeyCrypterTest {
|
||||||
|
@Test
|
||||||
|
public void encryptDecrypt() {
|
||||||
|
String testMessage = "thisisatestmessage";
|
||||||
|
byte[] testMessageBytes = testMessage.getBytes(StandardCharsets.UTF_8);
|
||||||
|
byte[] initializationVector = "BIE1".getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
AsymmetricKeyCrypter keyCrypter = new ECIESKeyCrypter();
|
||||||
|
|
||||||
|
ECKey key = keyCrypter.deriveECKey("iampassword");
|
||||||
|
EncryptedData encryptedData = keyCrypter.encrypt(testMessageBytes, initializationVector, key);
|
||||||
|
byte[] crypterDecrypted = keyCrypter.decrypt(encryptedData, key);
|
||||||
|
|
||||||
|
String cryDecStr = new String(crypterDecrypted, StandardCharsets.UTF_8);
|
||||||
|
Assert.assertEquals(testMessage, cryDecStr);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.Security;
|
||||||
|
|
||||||
|
public class ScryptKeyCrypterTest {
|
||||||
|
@Test
|
||||||
|
public void testScrypt() {
|
||||||
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
|
|
||||||
|
KeyCrypter keyDeriver = new AESKeyCrypter();
|
||||||
|
KeyParameter keyParameter = keyDeriver.deriveKey("password");
|
||||||
|
|
||||||
|
String message = "testastringmessage";
|
||||||
|
byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
byte[] iv = new byte[16];
|
||||||
|
SecureRandom secureRandom = new SecureRandom();
|
||||||
|
secureRandom.nextBytes(iv);
|
||||||
|
|
||||||
|
ScryptKeyCrypter scryptKeyCrypter = new ScryptKeyCrypter();
|
||||||
|
EncryptedData scrypted = scryptKeyCrypter.encrypt(messageBytes, iv, keyParameter);
|
||||||
|
|
||||||
|
AESKeyCrypter aesKeyCrypter = new AESKeyCrypter();
|
||||||
|
EncryptedData aescrypted = aesKeyCrypter.encrypt(messageBytes, iv, keyParameter);
|
||||||
|
|
||||||
|
Assert.assertArrayEquals(scrypted.getEncryptedBytes(), aescrypted.getEncryptedBytes());
|
||||||
|
|
||||||
|
byte[] sdecrypted = scryptKeyCrypter.decrypt(scrypted, keyParameter);
|
||||||
|
byte[] aesdecrypted = aesKeyCrypter.decrypt(aescrypted, keyParameter);
|
||||||
|
|
||||||
|
Assert.assertArrayEquals(sdecrypted, aesdecrypted);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +0,0 @@
|
||||||
package com.sparrowwallet.drongo.protocol;
|
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class ECKeyTest {
|
|
||||||
@Test
|
|
||||||
public void encryptDecrypt() {
|
|
||||||
String testMessage = "thisisatestmessage";
|
|
||||||
ECKey pubKey = ECKey.createKeyPbkdf2HmacSha512("iampassword");
|
|
||||||
byte[] encrypted = pubKey.encryptEcies(testMessage.getBytes(StandardCharsets.UTF_8), "BIE1".getBytes(StandardCharsets.UTF_8));
|
|
||||||
|
|
||||||
byte[] decrypted = pubKey.decryptEcies(encrypted, "BIE1".getBytes(StandardCharsets.UTF_8));
|
|
||||||
String decStr = new String(decrypted, StandardCharsets.UTF_8);
|
|
||||||
Assert.assertEquals(testMessage, decStr);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,11 +5,14 @@ import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
public class KeystoreTest {
|
public class KeystoreTest {
|
||||||
@Test
|
@Test
|
||||||
public void testExtendedPrivateKey() {
|
public void testExtendedPrivateKey() {
|
||||||
Keystore keystore = new Keystore();
|
Keystore keystore = new Keystore();
|
||||||
keystore.setSeed(Utils.hexToBytes("727ecfcf0bce9d8ec0ef066f7aeb845c271bdd4ee06a37398cebd40dc810140bb620b6c10a8ad671afdceaf37aa55d92d6478f747e8b92430dd938ab5be961dd"));
|
DeterministicSeed seed = new DeterministicSeed(Utils.hexToBytes("727ecfcf0bce9d8ec0ef066f7aeb845c271bdd4ee06a37398cebd40dc810140bb620b6c10a8ad671afdceaf37aa55d92d6478f747e8b92430dd938ab5be961dd"), Collections.emptyList(), 0);
|
||||||
|
keystore.setSeed(seed);
|
||||||
|
|
||||||
Assert.assertEquals("xprv9s21ZrQH143K3rN5vhm4bKDKsk1PmUK1mzxSMwkVSp2GbomwGmjLaGqrs8Nn9r14jCsfCNWfTR6pAtCsJutUH6QSHX65CePNW3YVyGxqvJa", keystore.getExtendedPrivateKey().toString());
|
Assert.assertEquals("xprv9s21ZrQH143K3rN5vhm4bKDKsk1PmUK1mzxSMwkVSp2GbomwGmjLaGqrs8Nn9r14jCsfCNWfTR6pAtCsJutUH6QSHX65CePNW3YVyGxqvJa", keystore.getExtendedPrivateKey().toString());
|
||||||
}
|
}
|
||||||
|
@ -17,7 +20,8 @@ public class KeystoreTest {
|
||||||
@Test
|
@Test
|
||||||
public void testExtendedPrivateKeyTwo() {
|
public void testExtendedPrivateKeyTwo() {
|
||||||
Keystore keystore = new Keystore();
|
Keystore keystore = new Keystore();
|
||||||
keystore.setSeed(Utils.hexToBytes("4d8c47d0d6241169d0b17994219211c4a980f7146bb70dbc897416790e9de23a6265c708b88176e24f6eb7378a7c55cd4bdc067cafe574eaf3480f9a41c3c43c"));
|
DeterministicSeed seed = new DeterministicSeed(Utils.hexToBytes("4d8c47d0d6241169d0b17994219211c4a980f7146bb70dbc897416790e9de23a6265c708b88176e24f6eb7378a7c55cd4bdc067cafe574eaf3480f9a41c3c43c"), Collections.emptyList(), 0);
|
||||||
|
keystore.setSeed(seed);
|
||||||
|
|
||||||
Assert.assertEquals("xprv9s21ZrQH143K4AkrxAyivDeTCWhZV6fdLfBRR8QerWe9hHiRqMjBMj9MFNef7oFufgcDcW54LhguPNm6MVLEMWPX5qxKhmNjCzi9Zy6yhkc", keystore.getExtendedPrivateKey().toString());
|
Assert.assertEquals("xprv9s21ZrQH143K4AkrxAyivDeTCWhZV6fdLfBRR8QerWe9hHiRqMjBMj9MFNef7oFufgcDcW54LhguPNm6MVLEMWPX5qxKhmNjCzi9Zy6yhkc", keystore.getExtendedPrivateKey().toString());
|
||||||
}
|
}
|
||||||
|
@ -25,7 +29,8 @@ public class KeystoreTest {
|
||||||
@Test
|
@Test
|
||||||
public void testFromSeed() {
|
public void testFromSeed() {
|
||||||
ScriptType p2pkh = ScriptType.P2PKH;
|
ScriptType p2pkh = ScriptType.P2PKH;
|
||||||
Keystore keystore = Keystore.fromSeed(Utils.hexToBytes("4d8c47d0d6241169d0b17994219211c4a980f7146bb70dbc897416790e9de23a6265c708b88176e24f6eb7378a7c55cd4bdc067cafe574eaf3480f9a41c3c43c"), p2pkh.getDefaultDerivation());
|
DeterministicSeed seed = new DeterministicSeed(Utils.hexToBytes("4d8c47d0d6241169d0b17994219211c4a980f7146bb70dbc897416790e9de23a6265c708b88176e24f6eb7378a7c55cd4bdc067cafe574eaf3480f9a41c3c43c"), Collections.emptyList(), 0);
|
||||||
|
Keystore keystore = Keystore.fromSeed(seed, p2pkh.getDefaultDerivation());
|
||||||
|
|
||||||
Assert.assertEquals("xpub6DCH2YkjweBu5zQheCWgSu6o26AENhApkS2taXaJBsi6vthRytPTaY2Sh4zDHj7oCVhYxx5974HbSbKxh26ah7N6VVw1U8kS2H5HfPUXecq", keystore.getExtendedPublicKey().toString());
|
Assert.assertEquals("xpub6DCH2YkjweBu5zQheCWgSu6o26AENhApkS2taXaJBsi6vthRytPTaY2Sh4zDHj7oCVhYxx5974HbSbKxh26ah7N6VVw1U8kS2H5HfPUXecq", keystore.getExtendedPublicKey().toString());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue