store mnemonic in determininistic seed

This commit is contained in:
Craig Raw 2020-05-10 15:30:40 +02:00
parent dc569979e1
commit 0f008b8985

View file

@ -7,10 +7,12 @@ import com.sparrowwallet.drongo.crypto.EncryptionType;
import com.sparrowwallet.drongo.crypto.KeyCrypter; import com.sparrowwallet.drongo.crypto.KeyCrypter;
import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.KeyParameter;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; 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;
@ -18,20 +20,44 @@ public class DeterministicSeed implements EncryptableItem {
private final byte[] seed; private final byte[] seed;
private final EncryptedData encryptedSeed; private final EncryptedData encryptedSeed;
private final List<String> mnemonicCode;
private final EncryptedData encryptedMnemonicCode;
private long creationTimeSeconds; private long creationTimeSeconds;
public DeterministicSeed(byte[] seed, long creationTimeSeconds) { public DeterministicSeed(String mnemonicString, byte[] seed, String passphrase, long creationTimeSeconds) {
this(decodeMnemonicCode(mnemonicString), seed, passphrase, creationTimeSeconds);
}
public DeterministicSeed(byte[] seed, List<String> mnemonic, long creationTimeSeconds) {
this.seed = seed; this.seed = seed;
this.encryptedSeed = null; this.encryptedSeed = null;
this.mnemonicCode = mnemonic;
this.encryptedMnemonicCode = null;
this.creationTimeSeconds = creationTimeSeconds; this.creationTimeSeconds = creationTimeSeconds;
} }
public DeterministicSeed(EncryptedData encryptedSeed, long creationTimeSeconds) { public DeterministicSeed(EncryptedData encryptedMnemonic, EncryptedData encryptedSeed, long creationTimeSeconds) {
this.seed = null; this.seed = null;
this.encryptedSeed = encryptedSeed; this.encryptedSeed = encryptedSeed;
this.mnemonicCode = null;
this.encryptedMnemonicCode = encryptedMnemonic;
this.creationTimeSeconds = creationTimeSeconds; this.creationTimeSeconds = creationTimeSeconds;
} }
/**
* Constructs a seed from a BIP 39 mnemonic code. See {@link Bip39MnemonicCode} 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) {
this((seed != null ? seed : Bip39MnemonicCode.toSeed(mnemonicCode, passphrase)), mnemonicCode, creationTimeSeconds);
}
/** /**
* Constructs a seed from a BIP 39 mnemonic code. See {@link Bip39MnemonicCode} for more * Constructs a seed from a BIP 39 mnemonic code. See {@link Bip39MnemonicCode} for more
* details on this scheme. * details on this scheme.
@ -63,14 +89,14 @@ public class DeterministicSeed implements EncryptableItem {
passphrase = ""; passphrase = "";
} }
List<String> mnemonicCode;
try { try {
mnemonicCode = Bip39MnemonicCode.INSTANCE.toMnemonic(entropy); this.mnemonicCode = Bip39MnemonicCode.INSTANCE.toMnemonic(entropy);
} catch (MnemonicException.MnemonicLengthException e) { } catch (MnemonicException.MnemonicLengthException e) {
// cannot happen // cannot happen
throw new RuntimeException(e); throw new RuntimeException(e);
} }
this.seed = Bip39MnemonicCode.toSeed(mnemonicCode, passphrase); this.seed = Bip39MnemonicCode.toSeed(mnemonicCode, passphrase);
this.encryptedMnemonicCode = null;
this.encryptedSeed = null; this.encryptedSeed = null;
this.creationTimeSeconds = creationTimeSeconds; this.creationTimeSeconds = creationTimeSeconds;
} }
@ -87,15 +113,20 @@ public class DeterministicSeed implements EncryptableItem {
@Override @Override
public boolean isEncrypted() { public boolean isEncrypted() {
return encryptedSeed != null; if(mnemonicCode != null && encryptedMnemonicCode != null) {
throw new IllegalStateException("Cannot be in a encrypted and unencrypted state");
}
return encryptedMnemonicCode != null;
} }
@Override
public String toString() { public String toString() {
if(isEncrypted()) { if(isEncrypted()) {
return encryptedSeed.toString(); return encryptedSeed.toString();
} }
return Utils.bytesToHex(seed); return toHexString();
} }
/** Returns the seed as hex or null if encrypted. */ /** Returns the seed as hex or null if encrypted. */
@ -105,7 +136,7 @@ public class DeterministicSeed implements EncryptableItem {
@Override @Override
public byte[] getSecretBytes() { public byte[] getSecretBytes() {
return getSeedBytes(); return getMnemonicAsBytes();
} }
public byte[] getSeedBytes() { public byte[] getSeedBytes() {
@ -114,7 +145,7 @@ public class DeterministicSeed implements EncryptableItem {
@Override @Override
public EncryptedData getEncryptedData() { public EncryptedData getEncryptedData() {
return encryptedSeed; return encryptedMnemonicCode;
} }
@Override @Override
@ -122,6 +153,10 @@ 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;
@ -135,17 +170,25 @@ public class DeterministicSeed implements EncryptableItem {
if(encryptedSeed != null) { if(encryptedSeed != null) {
throw new IllegalArgumentException("Trying to encrypt seed twice"); throw new IllegalArgumentException("Trying to encrypt seed 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); EncryptedData encryptedSeed = keyCrypter.encrypt(seed, null, aesKey);
return new DeterministicSeed(encryptedSeed, creationTimeSeconds); return new DeterministicSeed(encryptedMnemonic, encryptedSeed, creationTimeSeconds);
}
private byte[] getMnemonicAsBytes() {
return getMnemonicString().getBytes(StandardCharsets.UTF_8);
} }
public DeterministicSeed decrypt(KeyCrypter crypter, String passphrase, KeyParameter aesKey) { public DeterministicSeed decrypt(KeyCrypter crypter, String passphrase, KeyParameter aesKey) {
if(!isEncrypted()) { if(!isEncrypted()) {
throw new IllegalStateException("Cannot decrypt unencrypted seed"); throw new IllegalStateException("Cannot decrypt unencrypted seed");
} }
byte[] seed = crypter.decrypt(encryptedSeed, aesKey); List<String> mnemonic = decodeMnemonicCode(crypter.decrypt(encryptedMnemonicCode, aesKey));
return new DeterministicSeed(seed, passphrase, creationTimeSeconds); byte[] seed = encryptedSeed == null ? null : crypter.decrypt(encryptedSeed, aesKey);
return new DeterministicSeed(mnemonic, seed, passphrase, creationTimeSeconds);
} }
@Override @Override
@ -154,11 +197,55 @@ public class DeterministicSeed implements EncryptableItem {
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
DeterministicSeed other = (DeterministicSeed) o; DeterministicSeed other = (DeterministicSeed) o;
return creationTimeSeconds == other.creationTimeSeconds return creationTimeSeconds == other.creationTimeSeconds
&& (isEncrypted() ? encryptedSeed.equals(other.encryptedSeed) : Arrays.equals(seed, other.seed)); && Objects.equals(encryptedMnemonicCode, other.encryptedMnemonicCode)
&& Objects.equals(mnemonicCode, other.mnemonicCode);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(creationTimeSeconds, isEncrypted() ? encryptedSeed : seed); return Objects.hash(creationTimeSeconds, encryptedMnemonicCode, mnemonicCode);
}
/**
* Check if our mnemonic is a valid mnemonic phrase for our word list.
* Does nothing if we are encrypted.
*
* @throws MnemonicException if check fails
*/
public void check() throws MnemonicException {
if (mnemonicCode != null) {
Bip39MnemonicCode.INSTANCE.check(mnemonicCode);
}
}
byte[] getEntropyBytes() throws MnemonicException {
return Bip39MnemonicCode.INSTANCE.toEntropy(mnemonicCode);
}
/** Get the mnemonic code, or null if unknown. */
public List<String> getMnemonicCode() {
return mnemonicCode;
}
/** Get the mnemonic code as string, or null if unknown. */
public String getMnemonicString() {
StringJoiner joiner = new StringJoiner(" ");
if(mnemonicCode != null) {
for(String word : mnemonicCode) {
joiner.add(word);
}
return joiner.toString();
}
return null;
}
private static List<String> decodeMnemonicCode(byte[] mnemonicCode) {
return decodeMnemonicCode(new String(mnemonicCode, StandardCharsets.UTF_8));
}
private static List<String> decodeMnemonicCode(String mnemonicCode) {
return Arrays.asList(mnemonicCode.split(" "));
} }
} }