wallet export, settings fixes

This commit is contained in:
Craig Raw 2020-05-15 12:56:15 +02:00
parent 0c9a100c6d
commit f6414a4475
5 changed files with 86 additions and 11 deletions

View file

@ -139,17 +139,25 @@ public class ExtendedKey {
} }
public enum Header { public enum Header {
xprv("xprv", 0x0488ADE4, null), xprv("xprv", 0x0488ADE4, ScriptType.P2PKH),
xpub("xpub", 0x0488B21E, ScriptType.P2PKH), xpub("xpub", 0x0488B21E, ScriptType.P2PKH),
yprv("yprv", 0x049D7878, ScriptType.P2SH_P2WPKH),
ypub("ypub", 0x049D7CB2, ScriptType.P2SH_P2WPKH), ypub("ypub", 0x049D7CB2, ScriptType.P2SH_P2WPKH),
zprv("zprv", 0x04b2430c, ScriptType.P2WPKH),
zpub("zpub", 0x04B24746, ScriptType.P2WPKH), zpub("zpub", 0x04B24746, ScriptType.P2WPKH),
Yprv("Yprv", 0x0295b005, ScriptType.P2SH_P2WSH),
Ypub("Ypub", 0x0295b43f, ScriptType.P2SH_P2WSH), Ypub("Ypub", 0x0295b43f, ScriptType.P2SH_P2WSH),
Zprv("Zprv", 0x02aa7a99, ScriptType.P2WSH),
Zpub("Zpub", 0x02aa7ed3, ScriptType.P2WSH), Zpub("Zpub", 0x02aa7ed3, ScriptType.P2WSH),
tpub("tpub", 0x043587cf, ScriptType.P2PKH), tpub("tpub", 0x043587cf, ScriptType.P2PKH),
tprv("tprv", 0x04358394, null), tprv("tprv", 0x04358394, ScriptType.P2PKH),
uprv("uprv", 0x044a4e28, ScriptType.P2SH_P2WPKH),
upub("upub", 0x044a5262, ScriptType.P2SH_P2WPKH), upub("upub", 0x044a5262, ScriptType.P2SH_P2WPKH),
vprv("vprv", 0x045f18bc, ScriptType.P2WPKH),
vpub("vpub", 0x045f1cf6, ScriptType.P2WPKH), vpub("vpub", 0x045f1cf6, ScriptType.P2WPKH),
Uprv("Uprv", 0x024285b5, ScriptType.P2SH_P2WSH),
Upub("Upub", 0x024289ef, ScriptType.P2SH_P2WSH), Upub("Upub", 0x024289ef, ScriptType.P2SH_P2WSH),
Vprv("Vprv", 0x02575048, ScriptType.P2WSH),
Vpub("Vpub", 0x02575483, ScriptType.P2WSH); Vpub("Vpub", 0x02575483, ScriptType.P2WSH);
private final String name; private final String name;
@ -184,16 +192,20 @@ public class ExtendedKey {
throw new IllegalArgumentException("Unrecognised extended key header for extended key: " + xpub); throw new IllegalArgumentException("Unrecognised extended key header for extended key: " + xpub);
} }
public static Header fromScriptType(ScriptType scriptType) { public static Header fromScriptType(ScriptType scriptType, boolean privateKey) {
for(Header extendedKeyHeader : Header.values()) { for(Header header : Header.values()) {
if(extendedKeyHeader.defaultScriptType != null && extendedKeyHeader.defaultScriptType.equals(scriptType)) { if(header.defaultScriptType != null && header.defaultScriptType.equals(scriptType) && header.isPrivate() == privateKey) {
return extendedKeyHeader; return header;
} }
} }
return Header.xpub; return Header.xpub;
} }
private boolean isPrivate() {
return name.endsWith("prv");
}
public static boolean isValidHeader(int header) { public static boolean isValidHeader(int header) {
for(Header extendedKeyHeader : Header.values()) { for(Header extendedKeyHeader : Header.values()) {
if(header == extendedKeyHeader.header) { if(header == extendedKeyHeader.header) {

View file

@ -98,6 +98,15 @@ public class DeterministicSeed implements EncryptableItem {
this.creationTimeSeconds = creationTimeSeconds; this.creationTimeSeconds = creationTimeSeconds;
} }
public boolean needPassphrase() {
if(isEncrypted()) {
throw new IllegalArgumentException("Cannot determine if passphrase is required in encrypted state");
}
byte[] mnemonicOnlySeed = Bip39MnemonicCode.toSeed(mnemonicCode, "");
return Arrays.equals(mnemonicOnlySeed, seed);
}
private static byte[] getEntropy(SecureRandom random, int bits) { private static byte[] getEntropy(SecureRandom random, int bits) {
if(bits > MAX_SEED_ENTROPY_BITS) { if(bits > MAX_SEED_ENTROPY_BITS) {
throw new IllegalArgumentException("Requested entropy size too large"); throw new IllegalArgumentException("Requested entropy size too large");
@ -188,6 +197,14 @@ public class DeterministicSeed implements EncryptableItem {
return new DeterministicSeed(mnemonic, seed, passphrase, creationTimeSeconds); return new DeterministicSeed(mnemonic, seed, passphrase, creationTimeSeconds);
} }
public DeterministicSeed setPassphrase(String passphrase) {
if(isEncrypted()) {
throw new UnsupportedOperationException("Cannot set passphrase on encrypted seed");
}
return new DeterministicSeed(mnemonicCode, seed, passphrase, creationTimeSeconds);
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View file

@ -90,10 +90,20 @@ public class Keystore {
return HDKeyDerivation.createMasterPrivateKey(seed.getSeedBytes()); return HDKeyDerivation.createMasterPrivateKey(seed.getSeedBytes());
} }
public ExtendedKey getExtendedPrivateKey() { public ExtendedKey getExtendedMasterPrivateKey() {
return new ExtendedKey(getMasterPrivateKey(), new byte[4], ChildNumber.ZERO); return new ExtendedKey(getMasterPrivateKey(), new byte[4], ChildNumber.ZERO);
} }
public ExtendedKey getExtendedMasterPublicKey() {
return new ExtendedKey(getMasterPrivateKey().dropPrivateBytes(), new byte[4], ChildNumber.ZERO);
}
public ExtendedKey getExtendedPrivateKey() {
List<ChildNumber> derivation = getKeyDerivation().getDerivation();
DeterministicKey derivedKey = getExtendedMasterPrivateKey().getKey(derivation);
return new ExtendedKey(derivedKey, derivedKey.getParentFingerprint(), derivation.get(derivation.size() - 1));
}
public boolean isValid() { public boolean isValid() {
if(label == null || source == null || walletModel == null || keyDerivation == null || extendedPublicKey == null) { if(label == null || source == null || walletModel == null || keyDerivation == null || extendedPublicKey == null) {
return false; return false;
@ -107,7 +117,19 @@ public class Keystore {
return false; return false;
} }
//TODO: If source is SW_SEED, check seed field is filled if(source == KeystoreSource.SW_SEED) {
if(seed == null) {
return false;
}
List<ChildNumber> derivation = getKeyDerivation().getDerivation();
DeterministicKey derivedKey = getExtendedMasterPrivateKey().getKey(derivation);
DeterministicKey derivedKeyPublicOnly = derivedKey.dropPrivateBytes().dropParent();
ExtendedKey xpub = new ExtendedKey(derivedKeyPublicOnly, derivedKey.getParentFingerprint(), derivation.get(derivation.size() - 1));
if(!xpub.equals(getExtendedPublicKey())) {
return false;
}
}
return true; return true;
} }
@ -131,7 +153,7 @@ public class Keystore {
public static Keystore fromSeed(DeterministicSeed seed, List<ChildNumber> derivation) { public static Keystore fromSeed(DeterministicSeed seed, List<ChildNumber> derivation) {
Keystore keystore = new Keystore(); Keystore keystore = new Keystore();
keystore.setSeed(seed); keystore.setSeed(seed);
ExtendedKey xprv = keystore.getExtendedPrivateKey(); ExtendedKey xprv = keystore.getExtendedMasterPrivateKey();
String masterFingerprint = Utils.bytesToHex(xprv.getKey().getFingerprint()); String masterFingerprint = Utils.bytesToHex(xprv.getKey().getFingerprint());
DeterministicKey derivedKey = xprv.getKey(derivation); DeterministicKey derivedKey = xprv.getKey(derivation);
DeterministicKey derivedKeyPublicOnly = derivedKey.dropPrivateBytes().dropParent(); DeterministicKey derivedKeyPublicOnly = derivedKey.dropPrivateBytes().dropParent();
@ -146,6 +168,18 @@ public class Keystore {
return keystore; return keystore;
} }
public void setPassphrase(String passphrase) {
if(seed != null) {
seed = seed.setPassphrase(passphrase);
} else {
throw new UnsupportedOperationException("Cannot set passphrase on a keystore without a seed");
}
}
public boolean hasSeed() {
return seed != null;
}
public boolean isEncrypted() { public boolean isEncrypted() {
return seed != null && seed.isEncrypted(); return seed != null && seed.isEncrypted();
} }

View file

@ -8,6 +8,7 @@ import com.sparrowwallet.drongo.protocol.ScriptType;
import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.KeyParameter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -102,11 +103,22 @@ public class Wallet {
if(!keystore.isValid()) { if(!keystore.isValid()) {
return false; return false;
} }
if(derivationMatchesAnotherScriptType(keystore.getKeyDerivation().getDerivationPath())) {
return false;
}
} }
return true; return true;
} }
public boolean derivationMatchesAnotherScriptType(String derivationPath) {
if(scriptType != null && scriptType.getAccount(derivationPath) > -1) {
return false;
}
return Arrays.stream(ScriptType.values()).anyMatch(scriptType -> !scriptType.equals(this.scriptType) && scriptType.getAccount(derivationPath) > -1);
}
public Wallet copy() { public Wallet copy() {
Wallet copy = new Wallet(name); Wallet copy = new Wallet(name);
copy.setPolicyType(policyType); copy.setPolicyType(policyType);

View file

@ -14,7 +14,7 @@ public class KeystoreTest {
DeterministicSeed seed = new DeterministicSeed(Utils.hexToBytes("727ecfcf0bce9d8ec0ef066f7aeb845c271bdd4ee06a37398cebd40dc810140bb620b6c10a8ad671afdceaf37aa55d92d6478f747e8b92430dd938ab5be961dd"), Collections.emptyList(), 0); DeterministicSeed seed = new DeterministicSeed(Utils.hexToBytes("727ecfcf0bce9d8ec0ef066f7aeb845c271bdd4ee06a37398cebd40dc810140bb620b6c10a8ad671afdceaf37aa55d92d6478f747e8b92430dd938ab5be961dd"), Collections.emptyList(), 0);
keystore.setSeed(seed); keystore.setSeed(seed);
Assert.assertEquals("xprv9s21ZrQH143K3rN5vhm4bKDKsk1PmUK1mzxSMwkVSp2GbomwGmjLaGqrs8Nn9r14jCsfCNWfTR6pAtCsJutUH6QSHX65CePNW3YVyGxqvJa", keystore.getExtendedPrivateKey().toString()); Assert.assertEquals("xprv9s21ZrQH143K3rN5vhm4bKDKsk1PmUK1mzxSMwkVSp2GbomwGmjLaGqrs8Nn9r14jCsfCNWfTR6pAtCsJutUH6QSHX65CePNW3YVyGxqvJa", keystore.getExtendedMasterPrivateKey().toString());
} }
@Test @Test
@ -23,7 +23,7 @@ public class KeystoreTest {
DeterministicSeed seed = new DeterministicSeed(Utils.hexToBytes("4d8c47d0d6241169d0b17994219211c4a980f7146bb70dbc897416790e9de23a6265c708b88176e24f6eb7378a7c55cd4bdc067cafe574eaf3480f9a41c3c43c"), Collections.emptyList(), 0); DeterministicSeed seed = new DeterministicSeed(Utils.hexToBytes("4d8c47d0d6241169d0b17994219211c4a980f7146bb70dbc897416790e9de23a6265c708b88176e24f6eb7378a7c55cd4bdc067cafe574eaf3480f9a41c3c43c"), Collections.emptyList(), 0);
keystore.setSeed(seed); keystore.setSeed(seed);
Assert.assertEquals("xprv9s21ZrQH143K4AkrxAyivDeTCWhZV6fdLfBRR8QerWe9hHiRqMjBMj9MFNef7oFufgcDcW54LhguPNm6MVLEMWPX5qxKhmNjCzi9Zy6yhkc", keystore.getExtendedPrivateKey().toString()); Assert.assertEquals("xprv9s21ZrQH143K4AkrxAyivDeTCWhZV6fdLfBRR8QerWe9hHiRqMjBMj9MFNef7oFufgcDcW54LhguPNm6MVLEMWPX5qxKhmNjCzi9Zy6yhkc", keystore.getExtendedMasterPrivateKey().toString());
} }
@Test @Test