mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 10:06:45 +00:00
store mnemonic in determininistic seed
This commit is contained in:
parent
dc569979e1
commit
0f008b8985
1 changed files with 101 additions and 14 deletions
|
@ -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(" "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue