mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 10:06:45 +00:00
rework deterministicseed, keycrypter
This commit is contained in:
parent
9e5a7d0e8d
commit
312143cb61
17 changed files with 221 additions and 174 deletions
|
@ -1,9 +1,6 @@
|
|||
package com.sparrowwallet.drongo;
|
||||
|
||||
import com.sparrowwallet.drongo.crypto.AESKeyCrypter;
|
||||
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||
import com.sparrowwallet.drongo.crypto.EncryptedData;
|
||||
import com.sparrowwallet.drongo.crypto.KeyCrypter;
|
||||
import com.sparrowwallet.drongo.crypto.*;
|
||||
import com.sparrowwallet.drongo.protocol.ProtocolException;
|
||||
import com.sparrowwallet.drongo.protocol.Ripemd160;
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
|
@ -258,8 +255,8 @@ public class Utils {
|
|||
|
||||
public static byte[] decryptAesCbcPkcs7(byte[] initializationVector, byte[] encryptedBytes, byte[] keyBytes) {
|
||||
KeyCrypter keyCrypter = new AESKeyCrypter();
|
||||
EncryptedData data = new EncryptedData(initializationVector, encryptedBytes);
|
||||
return keyCrypter.decrypt(data, new KeyParameter(keyBytes));
|
||||
EncryptedData data = new EncryptedData(initializationVector, encryptedBytes, null);
|
||||
return keyCrypter.decrypt(data, new Key(keyBytes, null));
|
||||
}
|
||||
|
||||
/** Convert to a string path, starting with "M/" */
|
||||
|
|
|
@ -29,7 +29,7 @@ public class AESKeyCrypter implements KeyCrypter {
|
|||
}
|
||||
|
||||
@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");
|
||||
}
|
||||
|
||||
|
@ -42,13 +42,13 @@ public class AESKeyCrypter implements KeyCrypter {
|
|||
* @throws KeyCrypterException if bytes could not be decrypted
|
||||
*/
|
||||
@Override
|
||||
public byte[] decrypt(EncryptedData dataToDecrypt, KeyParameter aesKey) throws KeyCrypterException {
|
||||
public byte[] decrypt(EncryptedData dataToDecrypt, Key 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());
|
||||
ParametersWithIV keyWithIv = new ParametersWithIV(new KeyParameter(aesKey.getKeyBytes()), dataToDecrypt.getInitialisationVector());
|
||||
|
||||
// Decrypt the message.
|
||||
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
|
||||
|
@ -71,7 +71,7 @@ public class AESKeyCrypter implements KeyCrypter {
|
|||
* Password based encryption using AES - CBC - PKCS7
|
||||
*/
|
||||
@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) {
|
||||
throw new KeyCrypterException("Data and key to encrypt cannot be null");
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ public class AESKeyCrypter implements KeyCrypter {
|
|||
secureRandom.nextBytes(iv);
|
||||
}
|
||||
|
||||
ParametersWithIV keyWithIv = new ParametersWithIV(aesKey, iv);
|
||||
ParametersWithIV keyWithIv = new ParametersWithIV(new KeyParameter(aesKey.getKeyBytes()), iv);
|
||||
|
||||
// Encrypt using AES.
|
||||
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 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) {
|
||||
throw new KeyCrypterException("Could not encrypt bytes.", e);
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ public interface AsymmetricKeyCrypter {
|
|||
EncryptionType getUnderstoodEncryptionType();
|
||||
|
||||
/**
|
||||
* Create a KeyParameter (which typically contains an AES key)
|
||||
* Create a ECKey based on the provided 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
|
||||
*/
|
||||
ECKey deriveECKey(CharSequence password) throws KeyCrypterException;
|
||||
|
|
|
@ -65,13 +65,13 @@ public class ECIESKeyCrypter implements AsymmetricKeyCrypter {
|
|||
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
|
||||
public EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, ECKey key) throws KeyCrypterException {
|
||||
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) {
|
||||
|
@ -83,7 +83,7 @@ public class ECIESKeyCrypter implements AsymmetricKeyCrypter {
|
|||
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[] ciphertext = aesKeyCrypter.encrypt(message, iv, new Key(key_e, null)).getEncryptedBytes();
|
||||
byte[] encrypted = concat(magic, ephemeral.getPubKey(), ciphertext);
|
||||
byte[] result = hmac256(key_m, encrypted);
|
||||
return Base64.getEncoder().encode(concat(encrypted, result));
|
||||
|
|
|
@ -7,14 +7,11 @@ import org.bouncycastle.asn1.*;
|
|||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||
import org.bouncycastle.crypto.ec.CustomNamedCurves;
|
||||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.macs.HMac;
|
||||
import org.bouncycastle.crypto.params.*;
|
||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
|
||||
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 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();
|
||||
if (crypter != 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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
public ECKey encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) throws KeyCrypterException {
|
||||
public ECKey encrypt(KeyCrypter keyCrypter, Key aesKey) throws KeyCrypterException {
|
||||
if(keyCrypter == null) {
|
||||
throw new KeyCrypterException("Keycrypter cannot be null");
|
||||
}
|
||||
|
@ -748,9 +745,9 @@ public class ECKey implements EncryptableItem {
|
|||
* just yield a garbage key.
|
||||
*
|
||||
* @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) {
|
||||
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
|
||||
* 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();
|
||||
if (crypter == null) {
|
||||
throw new KeyCrypterException("No key crypter available");
|
||||
|
@ -794,7 +791,7 @@ public class ECKey implements EncryptableItem {
|
|||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
@ -807,7 +804,7 @@ public class ECKey implements EncryptableItem {
|
|||
*
|
||||
* @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 {
|
||||
ECKey rebornUnencryptedKey = encryptedKey.decrypt(keyCrypter, aesKey);
|
||||
byte[] originalPrivateKeyBytes = originalKey.getPrivKeyBytes();
|
||||
|
|
|
@ -13,10 +13,12 @@ import java.util.Objects;
|
|||
public final class EncryptedData {
|
||||
private final byte[] initialisationVector;
|
||||
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.encryptedBytes = Arrays.copyOf(encryptedBytes, encryptedBytes.length);
|
||||
this.keySalt = keySalt == null ? null : Arrays.copyOf(keySalt, keySalt.length);
|
||||
}
|
||||
|
||||
public byte[] getInitialisationVector() {
|
||||
|
@ -27,27 +29,33 @@ public final class EncryptedData {
|
|||
return encryptedBytes;
|
||||
}
|
||||
|
||||
public byte[] getKeySalt() {
|
||||
return keySalt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
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
|
||||
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
|
||||
public String toString() {
|
||||
return "EncryptedData [initialisationVector=" + Arrays.toString(initialisationVector)
|
||||
+ ", encryptedPrivateKey=" + Arrays.toString(encryptedBytes) + "]";
|
||||
+ ", encryptedPrivateKey=" + Arrays.toString(encryptedBytes)
|
||||
+ ", keySalt=" + Arrays.toString(keySalt) + "]";
|
||||
}
|
||||
|
||||
public EncryptedData copy() {
|
||||
return new EncryptedData(Arrays.copyOf(initialisationVector, initialisationVector.length),
|
||||
Arrays.copyOf(encryptedBytes, encryptedBytes.length));
|
||||
Arrays.copyOf(encryptedBytes, encryptedBytes.length),
|
||||
Arrays.copyOf(keySalt, keySalt.length));
|
||||
}
|
||||
}
|
||||
|
|
19
src/main/java/com/sparrowwallet/drongo/crypto/Key.java
Normal file
19
src/main/java/com/sparrowwallet/drongo/crypto/Key.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -1,18 +1,14 @@
|
|||
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
|
||||
* 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>
|
||||
* <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>
|
||||
* <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
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Create a KeyParameter (which typically contains an AES key)
|
||||
* Create a Key (which typically contains an AES key)
|
||||
* @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
|
||||
*/
|
||||
KeyParameter deriveKey(CharSequence password) throws KeyCrypterException;
|
||||
Key deriveKey(CharSequence password) throws KeyCrypterException;
|
||||
|
||||
/**
|
||||
* Decrypt the provided encrypted bytes, converting them into unencrypted bytes.
|
||||
*
|
||||
* @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.
|
||||
|
@ -46,5 +42,5 @@ public interface KeyCrypter {
|
|||
* @return encryptedPrivateKey An encryptedPrivateKey containing the encrypted bytes and an initialisation vector.
|
||||
* @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;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.sparrowwallet.drongo.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.generators.SCrypt;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -54,6 +53,13 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
|
|||
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.
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@Override
|
||||
public KeyParameter deriveKey(CharSequence password) throws KeyCrypterException {
|
||||
public Key deriveKey(CharSequence password) throws KeyCrypterException {
|
||||
byte[] passwordBytes = null;
|
||||
try {
|
||||
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);
|
||||
return new KeyParameter(keyBytes);
|
||||
return new Key(keyBytes, scryptParameters.getSalt());
|
||||
} catch (Exception e) {
|
||||
throw new KeyCrypterException("Could not generate key from password and salt.", e);
|
||||
} finally {
|
||||
|
|
|
@ -95,7 +95,7 @@ public class Bip39MnemonicCode {
|
|||
*/
|
||||
public static byte[] toSeed(List<String> words, String passphrase) {
|
||||
if(passphrase == null) {
|
||||
throw new IllegalArgumentException("A null passphrase is not allowed.");
|
||||
passphrase = "";
|
||||
}
|
||||
|
||||
// To create binary seed from mnemonic, we use PBKDF2 function
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
package com.sparrowwallet.drongo.wallet;
|
||||
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.crypto.EncryptableItem;
|
||||
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 com.sparrowwallet.drongo.crypto.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
|
@ -16,52 +12,39 @@ public class DeterministicSeed implements EncryptableItem {
|
|||
public static final int MAX_SEED_ENTROPY_BITS = 512;
|
||||
|
||||
private final Type type;
|
||||
|
||||
private final byte[] seed;
|
||||
private final List<String> mnemonicCode;
|
||||
|
||||
private final EncryptedData encryptedSeed;
|
||||
private final EncryptedData encryptedMnemonicCode;
|
||||
|
||||
private final boolean needsPassphrase;
|
||||
private long creationTimeSeconds;
|
||||
|
||||
//Session only storage
|
||||
private transient String passphrase;
|
||||
|
||||
public DeterministicSeed(String mnemonicString, byte[] seed, String passphrase, long creationTimeSeconds, Type type) {
|
||||
this(decodeMnemonicCode(mnemonicString), seed, passphrase, creationTimeSeconds, type);
|
||||
public DeterministicSeed(String mnemonicString, String passphrase, long creationTimeSeconds, Type type) {
|
||||
this(decodeMnemonicCode(mnemonicString), passphrase, creationTimeSeconds, type);
|
||||
}
|
||||
|
||||
public DeterministicSeed(byte[] seed, List<String> mnemonic, long creationTimeSeconds, Type type) {
|
||||
this.seed = seed;
|
||||
this.encryptedSeed = null;
|
||||
public DeterministicSeed(List<String> mnemonic, String passphrase, long creationTimeSeconds, Type type) {
|
||||
this(mnemonic, needsPassphrase(passphrase), creationTimeSeconds, type);
|
||||
this.passphrase = passphrase;
|
||||
}
|
||||
|
||||
public DeterministicSeed(List<String> mnemonic, boolean needsPassphrase, long creationTimeSeconds, Type type) {
|
||||
this.mnemonicCode = mnemonic;
|
||||
this.encryptedMnemonicCode = null;
|
||||
this.needsPassphrase = needsPassphrase;
|
||||
this.creationTimeSeconds = creationTimeSeconds;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public DeterministicSeed(EncryptedData encryptedMnemonic, EncryptedData encryptedSeed, long creationTimeSeconds, Type type) {
|
||||
this.seed = null;
|
||||
this.encryptedSeed = encryptedSeed;
|
||||
public DeterministicSeed(EncryptedData encryptedMnemonic, boolean needsPassphrase, long creationTimeSeconds, Type type) {
|
||||
this.mnemonicCode = null;
|
||||
this.encryptedMnemonicCode = encryptedMnemonic;
|
||||
this.needsPassphrase = needsPassphrase;
|
||||
this.creationTimeSeconds = creationTimeSeconds;
|
||||
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
|
||||
* details on this scheme.
|
||||
|
@ -99,20 +82,26 @@ public class DeterministicSeed implements EncryptableItem {
|
|||
// cannot happen
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
this.seed = Bip39MnemonicCode.toSeed(mnemonicCode, passphrase);
|
||||
this.encryptedMnemonicCode = null;
|
||||
this.encryptedSeed = null;
|
||||
this.needsPassphrase = needsPassphrase(passphrase);
|
||||
this.creationTimeSeconds = creationTimeSeconds;
|
||||
this.type = Type.BIP39;
|
||||
}
|
||||
|
||||
public boolean usesPassphrase() {
|
||||
if(isEncrypted()) {
|
||||
throw new IllegalArgumentException("Cannot determine if passphrase is required in encrypted state");
|
||||
public static boolean needsPassphrase(String passphrase) {
|
||||
return passphrase != null && !passphrase.isEmpty();
|
||||
}
|
||||
|
||||
byte[] mnemonicOnlySeed = type.toSeed(mnemonicCode, "");
|
||||
return Arrays.equals(mnemonicOnlySeed, seed);
|
||||
public boolean needsPassphrase() {
|
||||
return needsPassphrase;
|
||||
}
|
||||
|
||||
public String getPassphrase() {
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
public void setPassphrase(String passphrase) {
|
||||
this.passphrase = passphrase;
|
||||
}
|
||||
|
||||
private static byte[] getEntropy(SecureRandom random, int bits) {
|
||||
|
@ -137,14 +126,15 @@ public class DeterministicSeed implements EncryptableItem {
|
|||
@Override
|
||||
public String toString() {
|
||||
if(isEncrypted()) {
|
||||
return encryptedSeed.toString();
|
||||
return encryptedMnemonicCode.toString();
|
||||
}
|
||||
|
||||
return toHexString();
|
||||
return getMnemonicString();
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
|
||||
|
@ -153,8 +143,12 @@ public class DeterministicSeed implements EncryptableItem {
|
|||
return getMnemonicAsBytes();
|
||||
}
|
||||
|
||||
public byte[] getSeedBytes() {
|
||||
return seed;
|
||||
public byte[] getSeedBytes() throws MnemonicException {
|
||||
if(passphrase == null && needsPassphrase) {
|
||||
throw new MnemonicException("Passphrase required but not provided");
|
||||
}
|
||||
|
||||
return type.toSeed(mnemonicCode, passphrase);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -167,10 +161,6 @@ public class DeterministicSeed implements EncryptableItem {
|
|||
return EncryptionType.ENCRYPTED_SCRYPT_AES;
|
||||
}
|
||||
|
||||
public EncryptedData getEncryptedSeedData() {
|
||||
return encryptedSeed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreationTimeSeconds() {
|
||||
return creationTimeSeconds;
|
||||
|
@ -184,37 +174,27 @@ public class DeterministicSeed implements EncryptableItem {
|
|||
return type;
|
||||
}
|
||||
|
||||
public DeterministicSeed encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) {
|
||||
if(encryptedSeed != null) {
|
||||
throw new IllegalArgumentException("Trying to encrypt seed twice");
|
||||
public DeterministicSeed encrypt(KeyCrypter keyCrypter, Key aesKey) {
|
||||
if(encryptedMnemonicCode != null) {
|
||||
throw new IllegalArgumentException("Trying to encrypt twice");
|
||||
}
|
||||
if(mnemonicCode == null) {
|
||||
throw new IllegalArgumentException("Mnemonic missing so cannot encrypt");
|
||||
}
|
||||
EncryptedData encryptedMnemonic = keyCrypter.encrypt(getMnemonicAsBytes(), null, aesKey);
|
||||
EncryptedData encryptedSeed = keyCrypter.encrypt(seed, null, aesKey);
|
||||
return new DeterministicSeed(encryptedMnemonic, encryptedSeed, creationTimeSeconds, type);
|
||||
return new DeterministicSeed(encryptedMnemonic, needsPassphrase, creationTimeSeconds, type);
|
||||
}
|
||||
|
||||
private byte[] getMnemonicAsBytes() {
|
||||
return getMnemonicString().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public DeterministicSeed decrypt(KeyCrypter crypter, String passphrase, KeyParameter aesKey) {
|
||||
public DeterministicSeed decrypt(KeyCrypter crypter, Key aesKey) {
|
||||
if(!isEncrypted()) {
|
||||
throw new IllegalStateException("Cannot decrypt unencrypted seed");
|
||||
}
|
||||
List<String> mnemonic = decodeMnemonicCode(crypter.decrypt(encryptedMnemonicCode, aesKey));
|
||||
byte[] seed = encryptedSeed == null ? null : crypter.decrypt(encryptedSeed, aesKey);
|
||||
return new DeterministicSeed(mnemonic, seed, passphrase, creationTimeSeconds, type);
|
||||
}
|
||||
|
||||
public String getPassphrase() {
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
public void setPassphrase(String passphrase) {
|
||||
this.passphrase = passphrase;
|
||||
return new DeterministicSeed(mnemonic, needsPassphrase, creationTimeSeconds, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -276,11 +256,16 @@ public class DeterministicSeed implements EncryptableItem {
|
|||
}
|
||||
|
||||
public DeterministicSeed copy() {
|
||||
DeterministicSeed seed;
|
||||
|
||||
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 {
|
||||
|
|
|
@ -4,7 +4,6 @@ import com.sparrowwallet.drongo.ExtendedKey;
|
|||
import com.sparrowwallet.drongo.KeyDerivation;
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.crypto.*;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -78,7 +77,7 @@ public class Keystore {
|
|||
this.seed = seed;
|
||||
}
|
||||
|
||||
public DeterministicKey getMasterPrivateKey() {
|
||||
public DeterministicKey getMasterPrivateKey() throws MnemonicException {
|
||||
if(seed == null) {
|
||||
throw new IllegalArgumentException("Keystore does not contain a seed");
|
||||
}
|
||||
|
@ -90,15 +89,15 @@ public class Keystore {
|
|||
return HDKeyDerivation.createMasterPrivateKey(seed.getSeedBytes());
|
||||
}
|
||||
|
||||
public ExtendedKey getExtendedMasterPrivateKey() {
|
||||
public ExtendedKey getExtendedMasterPrivateKey() throws MnemonicException {
|
||||
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);
|
||||
}
|
||||
|
||||
public ExtendedKey getExtendedPrivateKey() {
|
||||
public ExtendedKey getExtendedPrivateKey() throws MnemonicException {
|
||||
List<ChildNumber> derivation = getKeyDerivation().getDerivation();
|
||||
DeterministicKey derivedKey = getExtendedMasterPrivateKey().getKey(derivation);
|
||||
return new ExtendedKey(derivedKey, derivedKey.getParentFingerprint(), derivation.get(derivation.size() - 1));
|
||||
|
@ -126,6 +125,8 @@ public class Keystore {
|
|||
return false;
|
||||
}
|
||||
|
||||
if(!seed.isEncrypted()) {
|
||||
try {
|
||||
List<ChildNumber> derivation = getKeyDerivation().getDerivation();
|
||||
DeterministicKey derivedKey = getExtendedMasterPrivateKey().getKey(derivation);
|
||||
DeterministicKey derivedKeyPublicOnly = derivedKey.dropPrivateBytes().dropParent();
|
||||
|
@ -133,6 +134,10 @@ public class Keystore {
|
|||
if(!xpub.equals(getExtendedPublicKey())) {
|
||||
return false;
|
||||
}
|
||||
} catch(MnemonicException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -154,7 +159,7 @@ public class Keystore {
|
|||
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.setSeed(seed);
|
||||
ExtendedKey xprv = keystore.getExtendedMasterPrivateKey();
|
||||
|
@ -181,24 +186,16 @@ public class Keystore {
|
|||
}
|
||||
|
||||
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()) {
|
||||
seed = seed.encrypt(keyCrypter, key);
|
||||
}
|
||||
}
|
||||
|
||||
public void decrypt(String password, String passphrase) {
|
||||
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()) {
|
||||
seed = seed.decrypt(keyCrypter, passphrase, key);
|
||||
KeyCrypter keyCrypter = new ScryptKeyCrypter(seed.getEncryptedData().getKeySalt());
|
||||
seed = seed.decrypt(keyCrypter, keyCrypter.deriveKey(password));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
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.PolicyType;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
|
@ -139,6 +136,16 @@ public class Wallet {
|
|||
return copy;
|
||||
}
|
||||
|
||||
public boolean containsSeeds() {
|
||||
for(Keystore keystore : keystores) {
|
||||
if(keystore.hasSeed()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isEncrypted() {
|
||||
for(Keystore keystore : keystores) {
|
||||
if(keystore.isEncrypted()) {
|
||||
|
@ -150,18 +157,14 @@ public class Wallet {
|
|||
}
|
||||
|
||||
public void encrypt(String password) {
|
||||
KeyCrypter keyCrypter = new ScryptKeyCrypter();
|
||||
KeyParameter key = keyCrypter.deriveKey(password);
|
||||
for(Keystore keystore : keystores) {
|
||||
keystore.encrypt(keyCrypter, key);
|
||||
keystore.encrypt(password);
|
||||
}
|
||||
}
|
||||
|
||||
public void decrypt(String password, String passphrase) {
|
||||
KeyCrypter keyCrypter = new ScryptKeyCrypter();
|
||||
KeyParameter key = keyCrypter.deriveKey(password);
|
||||
public void decrypt(String password) {
|
||||
for(Keystore keystore : keystores) {
|
||||
keystore.decrypt(keyCrypter, passphrase, key);
|
||||
keystore.decrypt(password);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +1,33 @@
|
|||
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() {
|
||||
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[] iv = new byte[16];
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
secureRandom.nextBytes(iv);
|
||||
|
||||
EncryptedData scrypted = scryptKeyCrypter.encrypt(messageBytes, iv, keyParameter);
|
||||
EncryptedData scrypted = scryptKeyCrypter.encrypt(messageBytes, iv, key);
|
||||
|
||||
AESKeyCrypter aesKeyCrypter = new AESKeyCrypter();
|
||||
EncryptedData aescrypted = aesKeyCrypter.encrypt(messageBytes, iv, keyParameter);
|
||||
EncryptedData aescrypted = aesKeyCrypter.encrypt(messageBytes, iv, key);
|
||||
|
||||
Assert.assertArrayEquals(scrypted.getEncryptedBytes(), aescrypted.getEncryptedBytes());
|
||||
|
||||
byte[] sdecrypted = scryptKeyCrypter.decrypt(scrypted, keyParameter);
|
||||
byte[] aesdecrypted = aesKeyCrypter.decrypt(aescrypted, keyParameter);
|
||||
byte[] sdecrypted = scryptKeyCrypter.decrypt(scrypted, key);
|
||||
byte[] aesdecrypted = aesKeyCrypter.decrypt(aescrypted, key);
|
||||
|
||||
Assert.assertArrayEquals(sdecrypted, aesdecrypted);
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -9,29 +9,20 @@ import java.util.Collections;
|
|||
|
||||
public class KeystoreTest {
|
||||
@Test
|
||||
public void testExtendedPrivateKey() {
|
||||
public void testExtendedPrivateKey() throws MnemonicException {
|
||||
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);
|
||||
|
||||
Assert.assertEquals("xprv9s21ZrQH143K3rN5vhm4bKDKsk1PmUK1mzxSMwkVSp2GbomwGmjLaGqrs8Nn9r14jCsfCNWfTR6pAtCsJutUH6QSHX65CePNW3YVyGxqvJa", keystore.getExtendedMasterPrivateKey().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtendedPrivateKeyTwo() {
|
||||
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() {
|
||||
public void testFromSeed() throws MnemonicException {
|
||||
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());
|
||||
|
||||
Assert.assertEquals("xpub6DCH2YkjweBu5zQheCWgSu6o26AENhApkS2taXaJBsi6vthRytPTaY2Sh4zDHj7oCVhYxx5974HbSbKxh26ah7N6VVw1U8kS2H5HfPUXecq", keystore.getExtendedPublicKey().toString());
|
||||
Assert.assertEquals("xpub6D9jqMkBdgTqrzTxXVo2w8yZCa7HvzJTybFevJ2StHSxBRhs8dzsVEke9TQ9QjZCKbWZvzbc8iSScBbsCiA11wT28hZmCv3YmjSFEqCLmMn", keystore.getExtendedPublicKey().toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue