mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-11-02 18:26:43 +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 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(" "));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue