mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 10:06:45 +00:00
wallet export, settings fixes
This commit is contained in:
parent
0c9a100c6d
commit
f6414a4475
5 changed files with 86 additions and 11 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue