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 org.bouncycastle.crypto.params.KeyParameter;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
public class DeterministicSeed implements EncryptableItem {
public static final int DEFAULT_SEED_ENTROPY_BITS = 128;
@ -18,20 +20,44 @@ public class DeterministicSeed implements EncryptableItem {
private final byte[] seed;
private final EncryptedData encryptedSeed;
private final List<String> mnemonicCode;
private final EncryptedData encryptedMnemonicCode;
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.encryptedSeed = null;
this.mnemonicCode = mnemonic;
this.encryptedMnemonicCode = null;
this.creationTimeSeconds = creationTimeSeconds;
}
public DeterministicSeed(EncryptedData encryptedSeed, long creationTimeSeconds) {
public DeterministicSeed(EncryptedData encryptedMnemonic, EncryptedData encryptedSeed, long creationTimeSeconds) {
this.seed = null;
this.encryptedSeed = encryptedSeed;
this.mnemonicCode = null;
this.encryptedMnemonicCode = encryptedMnemonic;
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
* details on this scheme.
@ -63,14 +89,14 @@ public class DeterministicSeed implements EncryptableItem {
passphrase = "";
}
List<String> mnemonicCode;
try {
mnemonicCode = Bip39MnemonicCode.INSTANCE.toMnemonic(entropy);
this.mnemonicCode = Bip39MnemonicCode.INSTANCE.toMnemonic(entropy);
} catch (MnemonicException.MnemonicLengthException e) {
// cannot happen
throw new RuntimeException(e);
}
this.seed = Bip39MnemonicCode.toSeed(mnemonicCode, passphrase);
this.encryptedMnemonicCode = null;
this.encryptedSeed = null;
this.creationTimeSeconds = creationTimeSeconds;
}
@ -87,15 +113,20 @@ public class DeterministicSeed implements EncryptableItem {
@Override
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() {
if(isEncrypted()) {
return encryptedSeed.toString();
}
return Utils.bytesToHex(seed);
return toHexString();
}
/** Returns the seed as hex or null if encrypted. */
@ -105,7 +136,7 @@ public class DeterministicSeed implements EncryptableItem {
@Override
public byte[] getSecretBytes() {
return getSeedBytes();
return getMnemonicAsBytes();
}
public byte[] getSeedBytes() {
@ -114,7 +145,7 @@ public class DeterministicSeed implements EncryptableItem {
@Override
public EncryptedData getEncryptedData() {
return encryptedSeed;
return encryptedMnemonicCode;
}
@Override
@ -122,6 +153,10 @@ public class DeterministicSeed implements EncryptableItem {
return EncryptionType.ENCRYPTED_SCRYPT_AES;
}
public EncryptedData getEncryptedSeedData() {
return encryptedSeed;
}
@Override
public long getCreationTimeSeconds() {
return creationTimeSeconds;
@ -135,17 +170,25 @@ public class DeterministicSeed implements EncryptableItem {
if(encryptedSeed != null) {
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);
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) {
if(!isEncrypted()) {
throw new IllegalStateException("Cannot decrypt unencrypted seed");
}
byte[] seed = crypter.decrypt(encryptedSeed, aesKey);
return new DeterministicSeed(seed, passphrase, creationTimeSeconds);
List<String> mnemonic = decodeMnemonicCode(crypter.decrypt(encryptedMnemonicCode, aesKey));
byte[] seed = encryptedSeed == null ? null : crypter.decrypt(encryptedSeed, aesKey);
return new DeterministicSeed(mnemonic, seed, passphrase, creationTimeSeconds);
}
@Override
@ -154,11 +197,55 @@ public class DeterministicSeed implements EncryptableItem {
if (o == null || getClass() != o.getClass()) return false;
DeterministicSeed other = (DeterministicSeed) o;
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
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(" "));
}
}