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

View file

@ -98,6 +98,15 @@ public class DeterministicSeed implements EncryptableItem {
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) {
if(bits > MAX_SEED_ENTROPY_BITS) {
throw new IllegalArgumentException("Requested entropy size too large");
@ -188,6 +197,14 @@ public class DeterministicSeed implements EncryptableItem {
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
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -90,10 +90,20 @@ public class Keystore {
return HDKeyDerivation.createMasterPrivateKey(seed.getSeedBytes());
}
public ExtendedKey getExtendedPrivateKey() {
public ExtendedKey getExtendedMasterPrivateKey() {
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() {
if(label == null || source == null || walletModel == null || keyDerivation == null || extendedPublicKey == null) {
return false;
@ -107,7 +117,19 @@ public class Keystore {
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;
}
@ -131,7 +153,7 @@ public class Keystore {
public static Keystore fromSeed(DeterministicSeed seed, List<ChildNumber> derivation) {
Keystore keystore = new Keystore();
keystore.setSeed(seed);
ExtendedKey xprv = keystore.getExtendedPrivateKey();
ExtendedKey xprv = keystore.getExtendedMasterPrivateKey();
String masterFingerprint = Utils.bytesToHex(xprv.getKey().getFingerprint());
DeterministicKey derivedKey = xprv.getKey(derivation);
DeterministicKey derivedKeyPublicOnly = derivedKey.dropPrivateBytes().dropParent();
@ -146,6 +168,18 @@ public class 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() {
return seed != null && seed.isEncrypted();
}

View file

@ -8,6 +8,7 @@ import com.sparrowwallet.drongo.protocol.ScriptType;
import org.bouncycastle.crypto.params.KeyParameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -102,11 +103,22 @@ public class Wallet {
if(!keystore.isValid()) {
return false;
}
if(derivationMatchesAnotherScriptType(keystore.getKeyDerivation().getDerivationPath())) {
return false;
}
}
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() {
Wallet copy = new Wallet(name);
copy.setPolicyType(policyType);

View file

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