rework deterministicseed, keycrypter

This commit is contained in:
Craig Raw 2020-05-16 15:09:15 +02:00
parent 9e5a7d0e8d
commit 312143cb61
17 changed files with 221 additions and 174 deletions

View file

@ -1,9 +1,6 @@
package com.sparrowwallet.drongo; package com.sparrowwallet.drongo;
import com.sparrowwallet.drongo.crypto.AESKeyCrypter; import com.sparrowwallet.drongo.crypto.*;
import com.sparrowwallet.drongo.crypto.ChildNumber;
import com.sparrowwallet.drongo.crypto.EncryptedData;
import com.sparrowwallet.drongo.crypto.KeyCrypter;
import com.sparrowwallet.drongo.protocol.ProtocolException; import com.sparrowwallet.drongo.protocol.ProtocolException;
import com.sparrowwallet.drongo.protocol.Ripemd160; import com.sparrowwallet.drongo.protocol.Ripemd160;
import com.sparrowwallet.drongo.protocol.Sha256Hash; import com.sparrowwallet.drongo.protocol.Sha256Hash;
@ -258,8 +255,8 @@ public class Utils {
public static byte[] decryptAesCbcPkcs7(byte[] initializationVector, byte[] encryptedBytes, byte[] keyBytes) { public static byte[] decryptAesCbcPkcs7(byte[] initializationVector, byte[] encryptedBytes, byte[] keyBytes) {
KeyCrypter keyCrypter = new AESKeyCrypter(); KeyCrypter keyCrypter = new AESKeyCrypter();
EncryptedData data = new EncryptedData(initializationVector, encryptedBytes); EncryptedData data = new EncryptedData(initializationVector, encryptedBytes, null);
return keyCrypter.decrypt(data, new KeyParameter(keyBytes)); return keyCrypter.decrypt(data, new Key(keyBytes, null));
} }
/** Convert to a string path, starting with "M/" */ /** Convert to a string path, starting with "M/" */

View file

@ -29,7 +29,7 @@ public class AESKeyCrypter implements KeyCrypter {
} }
@Override @Override
public KeyParameter deriveKey(CharSequence password) throws KeyCrypterException { public Key deriveKey(CharSequence password) throws KeyCrypterException {
throw new UnsupportedOperationException("AESKeyCrypter does not define a key derivation function, but keys must be either 128, 192 or 256 bits long"); throw new UnsupportedOperationException("AESKeyCrypter does not define a key derivation function, but keys must be either 128, 192 or 256 bits long");
} }
@ -42,13 +42,13 @@ public class AESKeyCrypter implements KeyCrypter {
* @throws KeyCrypterException if bytes could not be decrypted * @throws KeyCrypterException if bytes could not be decrypted
*/ */
@Override @Override
public byte[] decrypt(EncryptedData dataToDecrypt, KeyParameter aesKey) throws KeyCrypterException { public byte[] decrypt(EncryptedData dataToDecrypt, Key aesKey) throws KeyCrypterException {
if(dataToDecrypt == null || aesKey == null) { if(dataToDecrypt == null || aesKey == null) {
throw new KeyCrypterException("Data and key to decrypt cannot be null"); throw new KeyCrypterException("Data and key to decrypt cannot be null");
} }
try { try {
ParametersWithIV keyWithIv = new ParametersWithIV(new KeyParameter(aesKey.getKey()), dataToDecrypt.getInitialisationVector()); ParametersWithIV keyWithIv = new ParametersWithIV(new KeyParameter(aesKey.getKeyBytes()), dataToDecrypt.getInitialisationVector());
// Decrypt the message. // Decrypt the message.
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine())); BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
@ -71,7 +71,7 @@ public class AESKeyCrypter implements KeyCrypter {
* Password based encryption using AES - CBC - PKCS7 * Password based encryption using AES - CBC - PKCS7
*/ */
@Override @Override
public EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, KeyParameter aesKey) throws KeyCrypterException { public EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, Key aesKey) throws KeyCrypterException {
if(plainBytes == null || aesKey == null) { if(plainBytes == null || aesKey == null) {
throw new KeyCrypterException("Data and key to encrypt cannot be null"); throw new KeyCrypterException("Data and key to encrypt cannot be null");
} }
@ -84,7 +84,7 @@ public class AESKeyCrypter implements KeyCrypter {
secureRandom.nextBytes(iv); secureRandom.nextBytes(iv);
} }
ParametersWithIV keyWithIv = new ParametersWithIV(aesKey, iv); ParametersWithIV keyWithIv = new ParametersWithIV(new KeyParameter(aesKey.getKeyBytes()), iv);
// Encrypt using AES. // Encrypt using AES.
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine())); BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
@ -93,7 +93,7 @@ public class AESKeyCrypter implements KeyCrypter {
final int length1 = cipher.processBytes(plainBytes, 0, plainBytes.length, encryptedBytes, 0); final int length1 = cipher.processBytes(plainBytes, 0, plainBytes.length, encryptedBytes, 0);
final int length2 = cipher.doFinal(encryptedBytes, length1); final int length2 = cipher.doFinal(encryptedBytes, length1);
return new EncryptedData(iv, Arrays.copyOf(encryptedBytes, length1 + length2)); return new EncryptedData(iv, Arrays.copyOf(encryptedBytes, length1 + length2), aesKey.getSalt());
} catch (Exception e) { } catch (Exception e) {
throw new KeyCrypterException("Could not encrypt bytes.", e); throw new KeyCrypterException("Could not encrypt bytes.", e);
} }

View file

@ -8,9 +8,9 @@ public interface AsymmetricKeyCrypter {
EncryptionType getUnderstoodEncryptionType(); EncryptionType getUnderstoodEncryptionType();
/** /**
* Create a KeyParameter (which typically contains an AES key) * Create a ECKey based on the provided password
* @param password * @param password
* @return KeyParameter The KeyParameter which typically contains the AES key to use for encrypting and decrypting * @return ECKey The ECKey to use for encrypting and decrypting
* @throws KeyCrypterException * @throws KeyCrypterException
*/ */
ECKey deriveECKey(CharSequence password) throws KeyCrypterException; ECKey deriveECKey(CharSequence password) throws KeyCrypterException;

View file

@ -65,13 +65,13 @@ public class ECIESKeyCrypter implements AsymmetricKeyCrypter {
throw new InvalidPasswordException(); throw new InvalidPasswordException();
} }
return aesKeyCrypter.decrypt(new EncryptedData(iv, ciphertext), new KeyParameter(key_e)); return aesKeyCrypter.decrypt(new EncryptedData(iv, ciphertext, null), new Key(key_e, null));
} }
@Override @Override
public EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, ECKey key) throws KeyCrypterException { public EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, ECKey key) throws KeyCrypterException {
byte[] encryptedBytes = encryptEcies(key, plainBytes, initializationVector); byte[] encryptedBytes = encryptEcies(key, plainBytes, initializationVector);
return new EncryptedData(initializationVector, encryptedBytes); return new EncryptedData(initializationVector, encryptedBytes, null);
} }
public byte[] encryptEcies(ECKey key, byte[] message, byte[] magic) { public byte[] encryptEcies(ECKey key, byte[] message, byte[] magic) {
@ -83,7 +83,7 @@ public class ECIESKeyCrypter implements AsymmetricKeyCrypter {
byte[] key_e = Arrays.copyOfRange(hash, 16, 32); byte[] key_e = Arrays.copyOfRange(hash, 16, 32);
byte[] key_m = Arrays.copyOfRange(hash, 32, 64); byte[] key_m = Arrays.copyOfRange(hash, 32, 64);
byte[] ciphertext = aesKeyCrypter.encrypt(message, iv, new KeyParameter(key_e)).getEncryptedBytes(); byte[] ciphertext = aesKeyCrypter.encrypt(message, iv, new Key(key_e, null)).getEncryptedBytes();
byte[] encrypted = concat(magic, ephemeral.getPubKey(), ciphertext); byte[] encrypted = concat(magic, ephemeral.getPubKey(), ciphertext);
byte[] result = hmac256(key_m, encrypted); byte[] result = hmac256(key_m, encrypted);
return Base64.getEncoder().encode(concat(encrypted, result)); return Base64.getEncoder().encode(concat(encrypted, result));

View file

@ -7,14 +7,11 @@ import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.ec.CustomNamedCurves; import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator; import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.*; import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.ECDSASigner; import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.HMacDSAKCalculator; import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier; import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.math.ec.FixedPointUtil; import org.bouncycastle.math.ec.FixedPointUtil;
@ -499,7 +496,7 @@ public class ECKey implements EncryptableItem {
* @throws KeyCrypterException if there's something wrong with aesKey. * @throws KeyCrypterException if there's something wrong with aesKey.
* @throws ECKey.MissingPrivateKeyException if this key cannot sign because it's pubkey only. * @throws ECKey.MissingPrivateKeyException if this key cannot sign because it's pubkey only.
*/ */
public ECDSASignature sign(Sha256Hash input, KeyParameter aesKey) throws KeyCrypterException { public ECDSASignature sign(Sha256Hash input, Key aesKey) throws KeyCrypterException {
KeyCrypter crypter = getKeyCrypter(); KeyCrypter crypter = getKeyCrypter();
if (crypter != null) { if (crypter != null) {
if (aesKey == null) { if (aesKey == null) {
@ -727,10 +724,10 @@ public class ECKey implements EncryptableItem {
* This method returns a new encrypted key and leaves the original unchanged. * This method returns a new encrypted key and leaves the original unchanged.
* *
* @param keyCrypter The keyCrypter that specifies exactly how the encrypted bytes are created. * @param keyCrypter The keyCrypter that specifies exactly how the encrypted bytes are created.
* @param aesKey The KeyParameter with the AES encryption key (usually constructed with keyCrypter#deriveKey and cached as it is slow to create). * @param aesKey The Key with the AES encryption key (usually constructed with keyCrypter#deriveKey and cached as it is slow to create).
* @return encryptedKey * @return encryptedKey
*/ */
public ECKey encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) throws KeyCrypterException { public ECKey encrypt(KeyCrypter keyCrypter, Key aesKey) throws KeyCrypterException {
if(keyCrypter == null) { if(keyCrypter == null) {
throw new KeyCrypterException("Keycrypter cannot be null"); throw new KeyCrypterException("Keycrypter cannot be null");
} }
@ -748,9 +745,9 @@ public class ECKey implements EncryptableItem {
* just yield a garbage key. * just yield a garbage key.
* *
* @param keyCrypter The keyCrypter that specifies exactly how the decrypted bytes are created. * @param keyCrypter The keyCrypter that specifies exactly how the decrypted bytes are created.
* @param aesKey The KeyParameter with the AES encryption key (usually constructed with keyCrypter#deriveKey and cached). * @param aesKey The Key with the AES encryption key (usually constructed with keyCrypter#deriveKey and cached).
*/ */
public ECKey decrypt(KeyCrypter keyCrypter, KeyParameter aesKey) throws KeyCrypterException { public ECKey decrypt(KeyCrypter keyCrypter, Key aesKey) throws KeyCrypterException {
if(keyCrypter == null) { if(keyCrypter == null) {
throw new KeyCrypterException("Keycrypter cannot be null"); throw new KeyCrypterException("Keycrypter cannot be null");
} }
@ -780,9 +777,9 @@ public class ECKey implements EncryptableItem {
* has some chance of throwing KeyCrypterException due to the corrupted padding that will result, but it can also * has some chance of throwing KeyCrypterException due to the corrupted padding that will result, but it can also
* just yield a garbage key. * just yield a garbage key.
* *
* @param aesKey The KeyParameter with the AES encryption key (usually constructed with keyCrypter#deriveKey and cached). * @param aesKey The Key with the AES encryption key (usually constructed with keyCrypter#deriveKey and cached).
*/ */
public ECKey decrypt(KeyParameter aesKey) throws KeyCrypterException { public ECKey decrypt(Key aesKey) throws KeyCrypterException {
final KeyCrypter crypter = getKeyCrypter(); final KeyCrypter crypter = getKeyCrypter();
if (crypter == null) { if (crypter == null) {
throw new KeyCrypterException("No key crypter available"); throw new KeyCrypterException("No key crypter available");
@ -794,7 +791,7 @@ public class ECKey implements EncryptableItem {
/** /**
* Creates decrypted private key if needed. * Creates decrypted private key if needed.
*/ */
public ECKey maybeDecrypt(KeyParameter aesKey) throws KeyCrypterException { public ECKey maybeDecrypt(Key aesKey) throws KeyCrypterException {
return isEncrypted() && aesKey != null ? decrypt(aesKey) : this; return isEncrypted() && aesKey != null ? decrypt(aesKey) : this;
} }
@ -807,7 +804,7 @@ public class ECKey implements EncryptableItem {
* *
* @return true if the encrypted key can be decrypted back to the original key successfully. * @return true if the encrypted key can be decrypted back to the original key successfully.
*/ */
public static boolean encryptionIsReversible(ECKey originalKey, ECKey encryptedKey, KeyCrypter keyCrypter, KeyParameter aesKey) { public static boolean encryptionIsReversible(ECKey originalKey, ECKey encryptedKey, KeyCrypter keyCrypter, Key aesKey) {
try { try {
ECKey rebornUnencryptedKey = encryptedKey.decrypt(keyCrypter, aesKey); ECKey rebornUnencryptedKey = encryptedKey.decrypt(keyCrypter, aesKey);
byte[] originalPrivateKeyBytes = originalKey.getPrivKeyBytes(); byte[] originalPrivateKeyBytes = originalKey.getPrivKeyBytes();

View file

@ -13,10 +13,12 @@ import java.util.Objects;
public final class EncryptedData { public final class EncryptedData {
private final byte[] initialisationVector; private final byte[] initialisationVector;
private final byte[] encryptedBytes; private final byte[] encryptedBytes;
private final byte[] keySalt;
public EncryptedData(byte[] initialisationVector, byte[] encryptedBytes) { public EncryptedData(byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt) {
this.initialisationVector = Arrays.copyOf(initialisationVector, initialisationVector.length); this.initialisationVector = Arrays.copyOf(initialisationVector, initialisationVector.length);
this.encryptedBytes = Arrays.copyOf(encryptedBytes, encryptedBytes.length); this.encryptedBytes = Arrays.copyOf(encryptedBytes, encryptedBytes.length);
this.keySalt = keySalt == null ? null : Arrays.copyOf(keySalt, keySalt.length);
} }
public byte[] getInitialisationVector() { public byte[] getInitialisationVector() {
@ -27,27 +29,33 @@ public final class EncryptedData {
return encryptedBytes; return encryptedBytes;
} }
public byte[] getKeySalt() {
return keySalt;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
EncryptedData other = (EncryptedData) o; EncryptedData other = (EncryptedData) o;
return Arrays.equals(encryptedBytes, other.encryptedBytes) && Arrays.equals(initialisationVector, other.initialisationVector); return Arrays.equals(encryptedBytes, other.encryptedBytes) && Arrays.equals(initialisationVector, other.initialisationVector) && Arrays.equals(keySalt, other.keySalt);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(Arrays.hashCode(encryptedBytes), Arrays.hashCode(initialisationVector)); return Objects.hash(Arrays.hashCode(encryptedBytes), Arrays.hashCode(initialisationVector), Arrays.hashCode(keySalt));
} }
@Override @Override
public String toString() { public String toString() {
return "EncryptedData [initialisationVector=" + Arrays.toString(initialisationVector) return "EncryptedData [initialisationVector=" + Arrays.toString(initialisationVector)
+ ", encryptedPrivateKey=" + Arrays.toString(encryptedBytes) + "]"; + ", encryptedPrivateKey=" + Arrays.toString(encryptedBytes)
+ ", keySalt=" + Arrays.toString(keySalt) + "]";
} }
public EncryptedData copy() { public EncryptedData copy() {
return new EncryptedData(Arrays.copyOf(initialisationVector, initialisationVector.length), return new EncryptedData(Arrays.copyOf(initialisationVector, initialisationVector.length),
Arrays.copyOf(encryptedBytes, encryptedBytes.length)); Arrays.copyOf(encryptedBytes, encryptedBytes.length),
Arrays.copyOf(keySalt, keySalt.length));
} }
} }

View file

@ -0,0 +1,19 @@
package com.sparrowwallet.drongo.crypto;
public class Key {
private final byte[] keyBytes;
private final byte[] salt;
public Key(byte[] keyBytes, byte[] salt) {
this.keyBytes = keyBytes;
this.salt = salt;
}
public byte[] getKeyBytes() {
return keyBytes;
}
public byte[] getSalt() {
return salt;
}
}

View file

@ -1,18 +1,14 @@
package com.sparrowwallet.drongo.crypto; package com.sparrowwallet.drongo.crypto;
import org.bouncycastle.crypto.params.KeyParameter;
import java.io.Serializable;
/** /**
* <p>A KeyCrypter can be used to encrypt and decrypt a message. The sequence of events to encrypt and then decrypt * <p>A KeyCrypter can be used to encrypt and decrypt a message. The sequence of events to encrypt and then decrypt
* a message are as follows:</p> * a message are as follows:</p>
* *
* <p>(1) Ask the user for a password. deriveKey() is then called to create an KeyParameter. This contains the AES * <p>(1) Ask the user for a password. deriveKey() is then called to create an Key. This contains the AES
* key that will be used for encryption.</p> * key that will be used for encryption.</p>
* <p>(2) Encrypt the message using encrypt(), providing the message bytes and the KeyParameter from (1). This returns * <p>(2) Encrypt the message using encrypt(), providing the message bytes and the Key from (1). This returns
* an EncryptedData which contains the encryptedPrivateKey bytes and an initialisation vector.</p> * an EncryptedData which contains the encryptedPrivateKey bytes and an initialisation vector.</p>
* <p>(3) To decrypt an EncryptedData, repeat step (1) to get a KeyParameter, then call decrypt().</p> * <p>(3) To decrypt an EncryptedData, repeat step (1) to get a Key, then call decrypt().</p>
* *
* <p>There can be different algorithms used for encryption/ decryption so the getUnderstoodEncryptionType is used * <p>There can be different algorithms used for encryption/ decryption so the getUnderstoodEncryptionType is used
* to determine whether any given KeyCrypter can understand the type of encrypted data you have.</p> * to determine whether any given KeyCrypter can understand the type of encrypted data you have.</p>
@ -26,19 +22,19 @@ public interface KeyCrypter {
EncryptionType getUnderstoodEncryptionType(); EncryptionType getUnderstoodEncryptionType();
/** /**
* Create a KeyParameter (which typically contains an AES key) * Create a Key (which typically contains an AES key)
* @param password * @param password
* @return KeyParameter The KeyParameter which typically contains the AES key to use for encrypting and decrypting * @return Key The Key which typically contains the AES key to use for encrypting and decrypting
* @throws KeyCrypterException * @throws KeyCrypterException
*/ */
KeyParameter deriveKey(CharSequence password) throws KeyCrypterException; Key deriveKey(CharSequence password) throws KeyCrypterException;
/** /**
* Decrypt the provided encrypted bytes, converting them into unencrypted bytes. * Decrypt the provided encrypted bytes, converting them into unencrypted bytes.
* *
* @throws KeyCrypterException if decryption was unsuccessful. * @throws KeyCrypterException if decryption was unsuccessful.
*/ */
byte[] decrypt(EncryptedData encryptedBytesToDecode, KeyParameter key) throws KeyCrypterException; byte[] decrypt(EncryptedData encryptedBytesToDecode, Key key) throws KeyCrypterException;
/** /**
* Encrypt the supplied bytes, converting them into ciphertext. * Encrypt the supplied bytes, converting them into ciphertext.
@ -46,5 +42,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 key) throws KeyCrypterException; EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, Key key) throws KeyCrypterException;
} }

View file

@ -1,7 +1,6 @@
package com.sparrowwallet.drongo.crypto; package com.sparrowwallet.drongo.crypto;
import org.bouncycastle.crypto.generators.SCrypt; import org.bouncycastle.crypto.generators.SCrypt;
import org.bouncycastle.crypto.params.KeyParameter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -54,6 +53,13 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
this.scryptParameters = new ScryptParameters(randomSalt()); this.scryptParameters = new ScryptParameters(randomSalt());
} }
/**
* Encryption/Decryption using default parameters and provided salt.
*/
public ScryptKeyCrypter(byte[] salt) {
this.scryptParameters = new ScryptParameters(salt);
}
/** /**
* Encryption/Decryption using custom number of iterations parameters and a random salt. * Encryption/Decryption using custom number of iterations parameters and a random salt.
* As of August 2016, a useful value for mobile devices is 4096 (derivation takes about 1 second). * As of August 2016, a useful value for mobile devices is 4096 (derivation takes about 1 second).
@ -84,11 +90,11 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
* This is a very slow operation compared to encrypt/ decrypt so it is normally worth caching the result. * This is a very slow operation compared to encrypt/ decrypt so it is normally worth caching the result.
* *
* @param password The password to use in key generation * @param password The password to use in key generation
* @return The KeyParameter containing the created AES key * @return The Key containing the created AES key
* @throws KeyCrypterException * @throws KeyCrypterException
*/ */
@Override @Override
public KeyParameter deriveKey(CharSequence password) throws KeyCrypterException { public Key deriveKey(CharSequence password) throws KeyCrypterException {
byte[] passwordBytes = null; byte[] passwordBytes = null;
try { try {
passwordBytes = convertToByteArray(password); passwordBytes = convertToByteArray(password);
@ -100,7 +106,7 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
} }
byte[] keyBytes = SCrypt.generate(passwordBytes, salt, (int) scryptParameters.getN(), scryptParameters.getR(), scryptParameters.getP(), KEY_LENGTH); byte[] keyBytes = SCrypt.generate(passwordBytes, salt, (int) scryptParameters.getN(), scryptParameters.getR(), scryptParameters.getP(), KEY_LENGTH);
return new KeyParameter(keyBytes); return new Key(keyBytes, scryptParameters.getSalt());
} catch (Exception e) { } catch (Exception e) {
throw new KeyCrypterException("Could not generate key from password and salt.", e); throw new KeyCrypterException("Could not generate key from password and salt.", e);
} finally { } finally {

View file

@ -95,7 +95,7 @@ public class Bip39MnemonicCode {
*/ */
public static byte[] toSeed(List<String> words, String passphrase) { public static byte[] toSeed(List<String> words, String passphrase) {
if(passphrase == null) { if(passphrase == null) {
throw new IllegalArgumentException("A null passphrase is not allowed."); passphrase = "";
} }
// To create binary seed from mnemonic, we use PBKDF2 function // To create binary seed from mnemonic, we use PBKDF2 function

View file

@ -1,11 +1,7 @@
package com.sparrowwallet.drongo.wallet; package com.sparrowwallet.drongo.wallet;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.crypto.EncryptableItem; import com.sparrowwallet.drongo.crypto.*;
import com.sparrowwallet.drongo.crypto.EncryptedData;
import com.sparrowwallet.drongo.crypto.EncryptionType;
import com.sparrowwallet.drongo.crypto.KeyCrypter;
import org.bouncycastle.crypto.params.KeyParameter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.SecureRandom; import java.security.SecureRandom;
@ -16,52 +12,39 @@ public class DeterministicSeed implements EncryptableItem {
public static final int MAX_SEED_ENTROPY_BITS = 512; public static final int MAX_SEED_ENTROPY_BITS = 512;
private final Type type; private final Type type;
private final byte[] seed;
private final List<String> mnemonicCode; private final List<String> mnemonicCode;
private final EncryptedData encryptedSeed;
private final EncryptedData encryptedMnemonicCode; private final EncryptedData encryptedMnemonicCode;
private final boolean needsPassphrase;
private long creationTimeSeconds; private long creationTimeSeconds;
//Session only storage //Session only storage
private transient String passphrase; private transient String passphrase;
public DeterministicSeed(String mnemonicString, byte[] seed, String passphrase, long creationTimeSeconds, Type type) { public DeterministicSeed(String mnemonicString, String passphrase, long creationTimeSeconds, Type type) {
this(decodeMnemonicCode(mnemonicString), seed, passphrase, creationTimeSeconds, type); this(decodeMnemonicCode(mnemonicString), passphrase, creationTimeSeconds, type);
} }
public DeterministicSeed(byte[] seed, List<String> mnemonic, long creationTimeSeconds, Type type) { public DeterministicSeed(List<String> mnemonic, String passphrase, long creationTimeSeconds, Type type) {
this.seed = seed; this(mnemonic, needsPassphrase(passphrase), creationTimeSeconds, type);
this.encryptedSeed = null; this.passphrase = passphrase;
}
public DeterministicSeed(List<String> mnemonic, boolean needsPassphrase, long creationTimeSeconds, Type type) {
this.mnemonicCode = mnemonic; this.mnemonicCode = mnemonic;
this.encryptedMnemonicCode = null; this.encryptedMnemonicCode = null;
this.needsPassphrase = needsPassphrase;
this.creationTimeSeconds = creationTimeSeconds; this.creationTimeSeconds = creationTimeSeconds;
this.type = type; this.type = type;
} }
public DeterministicSeed(EncryptedData encryptedMnemonic, EncryptedData encryptedSeed, long creationTimeSeconds, Type type) { public DeterministicSeed(EncryptedData encryptedMnemonic, boolean needsPassphrase, long creationTimeSeconds, Type type) {
this.seed = null;
this.encryptedSeed = encryptedSeed;
this.mnemonicCode = null; this.mnemonicCode = null;
this.encryptedMnemonicCode = encryptedMnemonic; this.encryptedMnemonicCode = encryptedMnemonic;
this.needsPassphrase = needsPassphrase;
this.creationTimeSeconds = creationTimeSeconds; this.creationTimeSeconds = creationTimeSeconds;
this.type = type; this.type = type;
} }
/**
* Constructs a seed from a mnemonic code. See {@link Bip39MnemonicCode} or {@link ElectrumMnemonicCode} for more
* details on this scheme.
* @param mnemonicCode A list of words.
* @param seed The derived seed, or pass null to derive it from mnemonicCode (slow)
* @param passphrase A user supplied passphrase, or an empty string if there is no passphrase
* @param creationTimeSeconds When the seed was originally created, UNIX time.
*/
public DeterministicSeed(List<String> mnemonicCode, byte[] seed, String passphrase, long creationTimeSeconds, Type type) {
this((seed != null ? seed : type.toSeed(mnemonicCode, passphrase)), mnemonicCode, creationTimeSeconds, type);
}
/** /**
* Constructs a new BIP39 seed. See {@link Bip39MnemonicCode} for more * Constructs a new BIP39 seed. See {@link Bip39MnemonicCode} for more
* details on this scheme. * details on this scheme.
@ -99,20 +82,26 @@ public class DeterministicSeed implements EncryptableItem {
// cannot happen // cannot happen
throw new RuntimeException(e); throw new RuntimeException(e);
} }
this.seed = Bip39MnemonicCode.toSeed(mnemonicCode, passphrase);
this.encryptedMnemonicCode = null; this.encryptedMnemonicCode = null;
this.encryptedSeed = null; this.needsPassphrase = needsPassphrase(passphrase);
this.creationTimeSeconds = creationTimeSeconds; this.creationTimeSeconds = creationTimeSeconds;
this.type = Type.BIP39; this.type = Type.BIP39;
} }
public boolean usesPassphrase() { public static boolean needsPassphrase(String passphrase) {
if(isEncrypted()) { return passphrase != null && !passphrase.isEmpty();
throw new IllegalArgumentException("Cannot determine if passphrase is required in encrypted state");
} }
byte[] mnemonicOnlySeed = type.toSeed(mnemonicCode, ""); public boolean needsPassphrase() {
return Arrays.equals(mnemonicOnlySeed, seed); return needsPassphrase;
}
public String getPassphrase() {
return passphrase;
}
public void setPassphrase(String passphrase) {
this.passphrase = passphrase;
} }
private static byte[] getEntropy(SecureRandom random, int bits) { private static byte[] getEntropy(SecureRandom random, int bits) {
@ -137,14 +126,15 @@ public class DeterministicSeed implements EncryptableItem {
@Override @Override
public String toString() { public String toString() {
if(isEncrypted()) { if(isEncrypted()) {
return encryptedSeed.toString(); return encryptedMnemonicCode.toString();
} }
return toHexString(); return getMnemonicString();
} }
/** Returns the seed as hex or null if encrypted. */ /** Returns the seed as hex or null if encrypted. */
public String toHexString() { public String toHexString() throws MnemonicException {
byte[] seed = getSeedBytes();
return seed != null ? Utils.bytesToHex(seed) : null; return seed != null ? Utils.bytesToHex(seed) : null;
} }
@ -153,8 +143,12 @@ public class DeterministicSeed implements EncryptableItem {
return getMnemonicAsBytes(); return getMnemonicAsBytes();
} }
public byte[] getSeedBytes() { public byte[] getSeedBytes() throws MnemonicException {
return seed; if(passphrase == null && needsPassphrase) {
throw new MnemonicException("Passphrase required but not provided");
}
return type.toSeed(mnemonicCode, passphrase);
} }
@Override @Override
@ -167,10 +161,6 @@ public class DeterministicSeed implements EncryptableItem {
return EncryptionType.ENCRYPTED_SCRYPT_AES; return EncryptionType.ENCRYPTED_SCRYPT_AES;
} }
public EncryptedData getEncryptedSeedData() {
return encryptedSeed;
}
@Override @Override
public long getCreationTimeSeconds() { public long getCreationTimeSeconds() {
return creationTimeSeconds; return creationTimeSeconds;
@ -184,37 +174,27 @@ public class DeterministicSeed implements EncryptableItem {
return type; return type;
} }
public DeterministicSeed encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) { public DeterministicSeed encrypt(KeyCrypter keyCrypter, Key aesKey) {
if(encryptedSeed != null) { if(encryptedMnemonicCode != null) {
throw new IllegalArgumentException("Trying to encrypt seed twice"); throw new IllegalArgumentException("Trying to encrypt twice");
} }
if(mnemonicCode == null) { if(mnemonicCode == null) {
throw new IllegalArgumentException("Mnemonic missing so cannot encrypt"); throw new IllegalArgumentException("Mnemonic missing so cannot encrypt");
} }
EncryptedData encryptedMnemonic = keyCrypter.encrypt(getMnemonicAsBytes(), null, aesKey); EncryptedData encryptedMnemonic = keyCrypter.encrypt(getMnemonicAsBytes(), null, aesKey);
EncryptedData encryptedSeed = keyCrypter.encrypt(seed, null, aesKey); return new DeterministicSeed(encryptedMnemonic, needsPassphrase, creationTimeSeconds, type);
return new DeterministicSeed(encryptedMnemonic, encryptedSeed, creationTimeSeconds, type);
} }
private byte[] getMnemonicAsBytes() { private byte[] getMnemonicAsBytes() {
return getMnemonicString().getBytes(StandardCharsets.UTF_8); return getMnemonicString().getBytes(StandardCharsets.UTF_8);
} }
public DeterministicSeed decrypt(KeyCrypter crypter, String passphrase, KeyParameter aesKey) { public DeterministicSeed decrypt(KeyCrypter crypter, Key aesKey) {
if(!isEncrypted()) { if(!isEncrypted()) {
throw new IllegalStateException("Cannot decrypt unencrypted seed"); throw new IllegalStateException("Cannot decrypt unencrypted seed");
} }
List<String> mnemonic = decodeMnemonicCode(crypter.decrypt(encryptedMnemonicCode, aesKey)); List<String> mnemonic = decodeMnemonicCode(crypter.decrypt(encryptedMnemonicCode, aesKey));
byte[] seed = encryptedSeed == null ? null : crypter.decrypt(encryptedSeed, aesKey); return new DeterministicSeed(mnemonic, needsPassphrase, creationTimeSeconds, type);
return new DeterministicSeed(mnemonic, seed, passphrase, creationTimeSeconds, type);
}
public String getPassphrase() {
return passphrase;
}
public void setPassphrase(String passphrase) {
this.passphrase = passphrase;
} }
@Override @Override
@ -276,11 +256,16 @@ public class DeterministicSeed implements EncryptableItem {
} }
public DeterministicSeed copy() { public DeterministicSeed copy() {
DeterministicSeed seed;
if(isEncrypted()) { if(isEncrypted()) {
return new DeterministicSeed(encryptedMnemonicCode.copy(), encryptedSeed.copy(), creationTimeSeconds, type); seed = new DeterministicSeed(encryptedMnemonicCode.copy(), needsPassphrase, creationTimeSeconds, type);
} else {
seed = new DeterministicSeed(new ArrayList<>(mnemonicCode), needsPassphrase, creationTimeSeconds, type);
} }
return new DeterministicSeed(Arrays.copyOf(seed, seed.length), new ArrayList<>(mnemonicCode), creationTimeSeconds, type); seed.setPassphrase(passphrase);
return seed;
} }
public enum Type { public enum Type {

View file

@ -4,7 +4,6 @@ import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.KeyDerivation; import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.crypto.*; import com.sparrowwallet.drongo.crypto.*;
import org.bouncycastle.crypto.params.KeyParameter;
import java.util.List; import java.util.List;
@ -78,7 +77,7 @@ public class Keystore {
this.seed = seed; this.seed = seed;
} }
public DeterministicKey getMasterPrivateKey() { public DeterministicKey getMasterPrivateKey() throws MnemonicException {
if(seed == null) { if(seed == null) {
throw new IllegalArgumentException("Keystore does not contain a seed"); throw new IllegalArgumentException("Keystore does not contain a seed");
} }
@ -90,15 +89,15 @@ public class Keystore {
return HDKeyDerivation.createMasterPrivateKey(seed.getSeedBytes()); return HDKeyDerivation.createMasterPrivateKey(seed.getSeedBytes());
} }
public ExtendedKey getExtendedMasterPrivateKey() { public ExtendedKey getExtendedMasterPrivateKey() throws MnemonicException {
return new ExtendedKey(getMasterPrivateKey(), new byte[4], ChildNumber.ZERO); return new ExtendedKey(getMasterPrivateKey(), new byte[4], ChildNumber.ZERO);
} }
public ExtendedKey getExtendedMasterPublicKey() { public ExtendedKey getExtendedMasterPublicKey() throws MnemonicException {
return new ExtendedKey(getMasterPrivateKey().dropPrivateBytes(), new byte[4], ChildNumber.ZERO); return new ExtendedKey(getMasterPrivateKey().dropPrivateBytes(), new byte[4], ChildNumber.ZERO);
} }
public ExtendedKey getExtendedPrivateKey() { public ExtendedKey getExtendedPrivateKey() throws MnemonicException {
List<ChildNumber> derivation = getKeyDerivation().getDerivation(); List<ChildNumber> derivation = getKeyDerivation().getDerivation();
DeterministicKey derivedKey = getExtendedMasterPrivateKey().getKey(derivation); DeterministicKey derivedKey = getExtendedMasterPrivateKey().getKey(derivation);
return new ExtendedKey(derivedKey, derivedKey.getParentFingerprint(), derivation.get(derivation.size() - 1)); return new ExtendedKey(derivedKey, derivedKey.getParentFingerprint(), derivation.get(derivation.size() - 1));
@ -126,6 +125,8 @@ public class Keystore {
return false; return false;
} }
if(!seed.isEncrypted()) {
try {
List<ChildNumber> derivation = getKeyDerivation().getDerivation(); List<ChildNumber> derivation = getKeyDerivation().getDerivation();
DeterministicKey derivedKey = getExtendedMasterPrivateKey().getKey(derivation); DeterministicKey derivedKey = getExtendedMasterPrivateKey().getKey(derivation);
DeterministicKey derivedKeyPublicOnly = derivedKey.dropPrivateBytes().dropParent(); DeterministicKey derivedKeyPublicOnly = derivedKey.dropPrivateBytes().dropParent();
@ -133,6 +134,10 @@ public class Keystore {
if(!xpub.equals(getExtendedPublicKey())) { if(!xpub.equals(getExtendedPublicKey())) {
return false; return false;
} }
} catch(MnemonicException e) {
return false;
}
}
} }
return true; return true;
@ -154,7 +159,7 @@ public class Keystore {
return copy; return copy;
} }
public static Keystore fromSeed(DeterministicSeed seed, List<ChildNumber> derivation) { public static Keystore fromSeed(DeterministicSeed seed, List<ChildNumber> derivation) throws MnemonicException {
Keystore keystore = new Keystore(); Keystore keystore = new Keystore();
keystore.setSeed(seed); keystore.setSeed(seed);
ExtendedKey xprv = keystore.getExtendedMasterPrivateKey(); ExtendedKey xprv = keystore.getExtendedMasterPrivateKey();
@ -181,24 +186,16 @@ public class Keystore {
} }
public void encrypt(String password) { public void encrypt(String password) {
KeyCrypter keyCrypter = new ScryptKeyCrypter();
encrypt(keyCrypter, keyCrypter.deriveKey(password));
}
public void encrypt(KeyCrypter keyCrypter, KeyParameter key) {
if(seed != null && !seed.isEncrypted()) { if(seed != null && !seed.isEncrypted()) {
seed = seed.encrypt(keyCrypter, key);
}
}
public void decrypt(String password, String passphrase) {
KeyCrypter keyCrypter = new ScryptKeyCrypter(); KeyCrypter keyCrypter = new ScryptKeyCrypter();
decrypt(keyCrypter, passphrase, keyCrypter.deriveKey(password)); seed = seed.encrypt(keyCrypter, keyCrypter.deriveKey(password));
}
} }
public void decrypt(KeyCrypter keyCrypter, String passphrase, KeyParameter key) { public void decrypt(String password) {
if(seed != null && seed.isEncrypted()) { if(seed != null && seed.isEncrypted()) {
seed = seed.decrypt(keyCrypter, passphrase, key); KeyCrypter keyCrypter = new ScryptKeyCrypter(seed.getEncryptedData().getKeySalt());
seed = seed.decrypt(keyCrypter, keyCrypter.deriveKey(password));
} }
} }
} }

View file

@ -1,11 +1,8 @@
package com.sparrowwallet.drongo.wallet; package com.sparrowwallet.drongo.wallet;
import com.sparrowwallet.drongo.crypto.KeyCrypter;
import com.sparrowwallet.drongo.crypto.ScryptKeyCrypter;
import com.sparrowwallet.drongo.policy.Policy; import com.sparrowwallet.drongo.policy.Policy;
import com.sparrowwallet.drongo.policy.PolicyType; import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
import org.bouncycastle.crypto.params.KeyParameter;
import java.util.*; import java.util.*;
@ -139,6 +136,16 @@ public class Wallet {
return copy; return copy;
} }
public boolean containsSeeds() {
for(Keystore keystore : keystores) {
if(keystore.hasSeed()) {
return true;
}
}
return false;
}
public boolean isEncrypted() { public boolean isEncrypted() {
for(Keystore keystore : keystores) { for(Keystore keystore : keystores) {
if(keystore.isEncrypted()) { if(keystore.isEncrypted()) {
@ -150,18 +157,14 @@ public class Wallet {
} }
public void encrypt(String password) { public void encrypt(String password) {
KeyCrypter keyCrypter = new ScryptKeyCrypter();
KeyParameter key = keyCrypter.deriveKey(password);
for(Keystore keystore : keystores) { for(Keystore keystore : keystores) {
keystore.encrypt(keyCrypter, key); keystore.encrypt(password);
} }
} }
public void decrypt(String password, String passphrase) { public void decrypt(String password) {
KeyCrypter keyCrypter = new ScryptKeyCrypter();
KeyParameter key = keyCrypter.deriveKey(password);
for(Keystore keystore : keystores) { for(Keystore keystore : keystores) {
keystore.decrypt(keyCrypter, passphrase, key); keystore.decrypt(password);
} }
} }
} }

View file

@ -1,36 +1,33 @@
package com.sparrowwallet.drongo.crypto; package com.sparrowwallet.drongo.crypto;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Security;
public class ScryptKeyCrypterTest { public class ScryptKeyCrypterTest {
@Test @Test
public void testScrypt() { public void testScrypt() {
ScryptKeyCrypter scryptKeyCrypter = new ScryptKeyCrypter(); ScryptKeyCrypter scryptKeyCrypter = new ScryptKeyCrypter();
KeyParameter keyParameter = scryptKeyCrypter.deriveKey("password"); Key key = scryptKeyCrypter.deriveKey("pass");
String message = "testastringmessage"; String message = "absent essay fox snake vast pumpkin height crouch silent bulb excuse razor";
byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8); byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
byte[] iv = new byte[16]; byte[] iv = new byte[16];
SecureRandom secureRandom = new SecureRandom(); SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(iv); secureRandom.nextBytes(iv);
EncryptedData scrypted = scryptKeyCrypter.encrypt(messageBytes, iv, keyParameter); EncryptedData scrypted = scryptKeyCrypter.encrypt(messageBytes, iv, key);
AESKeyCrypter aesKeyCrypter = new AESKeyCrypter(); AESKeyCrypter aesKeyCrypter = new AESKeyCrypter();
EncryptedData aescrypted = aesKeyCrypter.encrypt(messageBytes, iv, keyParameter); EncryptedData aescrypted = aesKeyCrypter.encrypt(messageBytes, iv, key);
Assert.assertArrayEquals(scrypted.getEncryptedBytes(), aescrypted.getEncryptedBytes()); Assert.assertArrayEquals(scrypted.getEncryptedBytes(), aescrypted.getEncryptedBytes());
byte[] sdecrypted = scryptKeyCrypter.decrypt(scrypted, keyParameter); byte[] sdecrypted = scryptKeyCrypter.decrypt(scrypted, key);
byte[] aesdecrypted = aesKeyCrypter.decrypt(aescrypted, keyParameter); byte[] aesdecrypted = aesKeyCrypter.decrypt(aescrypted, key);
Assert.assertArrayEquals(sdecrypted, aesdecrypted); Assert.assertArrayEquals(sdecrypted, aesdecrypted);

View file

@ -0,0 +1,28 @@
package com.sparrowwallet.drongo.wallet;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.crypto.Key;
import com.sparrowwallet.drongo.crypto.KeyCrypter;
import com.sparrowwallet.drongo.crypto.ScryptKeyCrypter;
import org.junit.Assert;
import org.junit.Test;
public class DeterministicSeedTest {
@Test
public void testEncryption() {
String words = "absent essay fox snake vast pumpkin height crouch silent bulb excuse razor";
KeyCrypter keyCrypter = new ScryptKeyCrypter();
Key key = keyCrypter.deriveKey("pass");
DeterministicSeed seed = new DeterministicSeed(words, "pp", 0, DeterministicSeed.Type.BIP39);
DeterministicSeed encryptedSeed = seed.encrypt(keyCrypter, key);
System.out.println(Utils.bytesToHex(encryptedSeed.getEncryptedData().getInitialisationVector()));
System.out.println(Utils.bytesToHex(encryptedSeed.getEncryptedData().getEncryptedBytes()));
KeyCrypter keyCrypter2 = new ScryptKeyCrypter();
Key key2 = keyCrypter2.deriveKey("pass");
seed = encryptedSeed.decrypt(keyCrypter2, key2);
Assert.assertEquals(words, seed.getMnemonicString());
}
}

View file

@ -9,29 +9,20 @@ import java.util.Collections;
public class KeystoreTest { public class KeystoreTest {
@Test @Test
public void testExtendedPrivateKey() { public void testExtendedPrivateKey() throws MnemonicException {
Keystore keystore = new Keystore(); Keystore keystore = new Keystore();
DeterministicSeed seed = new DeterministicSeed(Utils.hexToBytes("727ecfcf0bce9d8ec0ef066f7aeb845c271bdd4ee06a37398cebd40dc810140bb620b6c10a8ad671afdceaf37aa55d92d6478f747e8b92430dd938ab5be961dd"), Collections.emptyList(), 0); DeterministicSeed seed = new DeterministicSeed("absent essay fox snake vast pumpkin height crouch silent bulb excuse razor", "", 0, DeterministicSeed.Type.BIP39);
keystore.setSeed(seed); keystore.setSeed(seed);
Assert.assertEquals("xprv9s21ZrQH143K3rN5vhm4bKDKsk1PmUK1mzxSMwkVSp2GbomwGmjLaGqrs8Nn9r14jCsfCNWfTR6pAtCsJutUH6QSHX65CePNW3YVyGxqvJa", keystore.getExtendedMasterPrivateKey().toString()); Assert.assertEquals("xprv9s21ZrQH143K3rN5vhm4bKDKsk1PmUK1mzxSMwkVSp2GbomwGmjLaGqrs8Nn9r14jCsfCNWfTR6pAtCsJutUH6QSHX65CePNW3YVyGxqvJa", keystore.getExtendedMasterPrivateKey().toString());
} }
@Test @Test
public void testExtendedPrivateKeyTwo() { public void testFromSeed() throws MnemonicException {
Keystore keystore = new Keystore();
DeterministicSeed seed = new DeterministicSeed(Utils.hexToBytes("4d8c47d0d6241169d0b17994219211c4a980f7146bb70dbc897416790e9de23a6265c708b88176e24f6eb7378a7c55cd4bdc067cafe574eaf3480f9a41c3c43c"), Collections.emptyList(), 0);
keystore.setSeed(seed);
Assert.assertEquals("xprv9s21ZrQH143K4AkrxAyivDeTCWhZV6fdLfBRR8QerWe9hHiRqMjBMj9MFNef7oFufgcDcW54LhguPNm6MVLEMWPX5qxKhmNjCzi9Zy6yhkc", keystore.getExtendedMasterPrivateKey().toString());
}
@Test
public void testFromSeed() {
ScriptType p2pkh = ScriptType.P2PKH; ScriptType p2pkh = ScriptType.P2PKH;
DeterministicSeed seed = new DeterministicSeed(Utils.hexToBytes("4d8c47d0d6241169d0b17994219211c4a980f7146bb70dbc897416790e9de23a6265c708b88176e24f6eb7378a7c55cd4bdc067cafe574eaf3480f9a41c3c43c"), Collections.emptyList(), 0); DeterministicSeed seed = new DeterministicSeed("absent essay fox snake vast pumpkin height crouch silent bulb excuse razor", "", 0, DeterministicSeed.Type.BIP39);
Keystore keystore = Keystore.fromSeed(seed, p2pkh.getDefaultDerivation()); Keystore keystore = Keystore.fromSeed(seed, p2pkh.getDefaultDerivation());
Assert.assertEquals("xpub6DCH2YkjweBu5zQheCWgSu6o26AENhApkS2taXaJBsi6vthRytPTaY2Sh4zDHj7oCVhYxx5974HbSbKxh26ah7N6VVw1U8kS2H5HfPUXecq", keystore.getExtendedPublicKey().toString()); Assert.assertEquals("xpub6D9jqMkBdgTqrzTxXVo2w8yZCa7HvzJTybFevJ2StHSxBRhs8dzsVEke9TQ9QjZCKbWZvzbc8iSScBbsCiA11wT28hZmCv3YmjSFEqCLmMn", keystore.getExtendedPublicKey().toString());
} }
} }

View file

@ -0,0 +1,23 @@
package com.sparrowwallet.drongo.wallet;
import com.sparrowwallet.drongo.policy.Policy;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType;
import org.junit.Test;
public class WalletTest {
@Test
public void encryptTest() throws MnemonicException {
String words = "absent essay fox snake vast pumpkin height crouch silent bulb excuse razor";
DeterministicSeed seed = new DeterministicSeed(words, "pp", 0, DeterministicSeed.Type.BIP39);
Wallet wallet = new Wallet();
wallet.setPolicyType(PolicyType.SINGLE);
wallet.setScriptType(ScriptType.P2PKH);
Keystore keystore = Keystore.fromSeed(seed, wallet.getScriptType().getDefaultDerivation());
wallet.getKeystores().add(keystore);
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2PKH, wallet.getKeystores(), 1));
wallet.encrypt("pass");
wallet.decrypt("pass");
}
}