wallet import and export needs

This commit is contained in:
Craig Raw 2020-04-25 11:38:46 +02:00
parent 282628e455
commit 294649de66
6 changed files with 168 additions and 23 deletions

View file

@ -2,20 +2,14 @@ package com.sparrowwallet.drongo;
import com.sparrowwallet.drongo.crypto.*; import com.sparrowwallet.drongo.crypto.*;
import com.sparrowwallet.drongo.protocol.Base58; import com.sparrowwallet.drongo.protocol.Base58;
import com.sparrowwallet.drongo.protocol.ScriptType;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.*; import java.util.*;
import static com.sparrowwallet.drongo.KeyDerivation.parsePath; import static com.sparrowwallet.drongo.KeyDerivation.parsePath;
import static com.sparrowwallet.drongo.KeyDerivation.writePath;
public class ExtendedPublicKey { public class ExtendedPublicKey {
private static final int bip32HeaderP2PKHXPub = 0x0488B21E; //The 4 byte header that serializes in base58 to "xpub".
private static final int bip32HeaderP2PKHYPub = 0x049D7CB2; //The 4 byte header that serializes in base58 to "ypub".
private static final int bip32HeaderP2WPKHZPub = 0x04B24746; // The 4 byte header that serializes in base58 to "zpub"
private static final int bip32HeaderP2WHSHPub = 0x2AA7ED3; // The 4 byte header that serializes in base58 to "Zpub"
private static final int bip32HeaderTestnetPub = 0x43587CF; // The 4 byte header that serializes in base58 to "tpub"
private final byte[] parentFingerprint; private final byte[] parentFingerprint;
private final DeterministicKey pubKey; private final DeterministicKey pubKey;
private final ChildNumber pubKeyChildNumber; private final ChildNumber pubKeyChildNumber;
@ -44,17 +38,29 @@ public class ExtendedPublicKey {
return getExtendedPublicKey(); return getExtendedPublicKey();
} }
public String toString(XpubHeader xpubHeader) {
return getExtendedPublicKey(xpubHeader);
}
public String getExtendedPublicKey() { public String getExtendedPublicKey() {
return Base58.encodeChecked(getExtendedPublicKeyBytes()); return Base58.encodeChecked(getExtendedPublicKeyBytes());
} }
public String getExtendedPublicKey(XpubHeader xpubHeader) {
return Base58.encodeChecked(getExtendedPublicKeyBytes(xpubHeader));
}
public ChildNumber getPubKeyChildNumber() { public ChildNumber getPubKeyChildNumber() {
return pubKeyChildNumber; return pubKeyChildNumber;
} }
public byte[] getExtendedPublicKeyBytes() { public byte[] getExtendedPublicKeyBytes() {
return getExtendedPublicKeyBytes(XpubHeader.xpub);
}
public byte[] getExtendedPublicKeyBytes(XpubHeader xpubHeader) {
ByteBuffer buffer = ByteBuffer.allocate(78); ByteBuffer buffer = ByteBuffer.allocate(78);
buffer.putInt(bip32HeaderP2PKHXPub); buffer.putInt(xpubHeader.header);
buffer.put((byte)pubKey.getDepth()); buffer.put((byte)pubKey.getDepth());
buffer.put(parentFingerprint); buffer.put(parentFingerprint);
buffer.putInt(pubKeyChildNumber.i()); buffer.putInt(pubKeyChildNumber.i());
@ -68,7 +74,7 @@ public class ExtendedPublicKey {
byte[] serializedKey = Base58.decodeChecked(extPubKey); byte[] serializedKey = Base58.decodeChecked(extPubKey);
ByteBuffer buffer = ByteBuffer.wrap(serializedKey); ByteBuffer buffer = ByteBuffer.wrap(serializedKey);
int header = buffer.getInt(); int header = buffer.getInt();
if(!(header == bip32HeaderP2PKHXPub || header == bip32HeaderP2PKHYPub || header == bip32HeaderP2WPKHZPub || header == bip32HeaderP2WHSHPub || header == bip32HeaderTestnetPub)) { if(!XpubHeader.isValidHeader(header)) {
throw new IllegalArgumentException("Unknown header bytes: " + DeterministicKey.toBase58(serializedKey).substring(0, 4)); throw new IllegalArgumentException("Unknown header bytes: " + DeterministicKey.toBase58(serializedKey).substring(0, 4));
} }
@ -128,4 +134,69 @@ public class ExtendedPublicKey {
public int hashCode() { public int hashCode() {
return toString().hashCode(); return toString().hashCode();
} }
}
public enum XpubHeader {
xpub("xpub", 0x0488B21E, ScriptType.P2PKH),
ypub("ypub", 0x049D7CB2, ScriptType.P2SH_P2WPKH),
zpub("zpub", 0x04B24746, ScriptType.P2WPKH),
Ypub("Ypub", 0x0295b43f, ScriptType.P2SH_P2WSH),
Zpub("Zpub", 0x02aa7ed3, ScriptType.P2WSH),
tpub("tpub", 0x043587cf, ScriptType.P2PKH),
upub("upub", 0x044a5262, ScriptType.P2SH_P2WPKH),
vpub("vpub", 0x045f1cf6, ScriptType.P2WPKH),
Upub("Upub", 0x024289ef, ScriptType.P2SH_P2WSH),
Vpub("Vpub", 0x02575483, ScriptType.P2WSH);
private final String name;
private final int header;
private final ScriptType defaultScriptType;
XpubHeader(String name, int header, ScriptType defaultScriptType) {
this.name = name;
this.header = header;
this.defaultScriptType = defaultScriptType;
}
public String getName() {
return name;
}
public int getHeader() {
return header;
}
public ScriptType getDefaultScriptType() {
return defaultScriptType;
}
public static XpubHeader fromXpub(String xpub) {
for(XpubHeader xpubHeader : XpubHeader.values()) {
if(xpub.startsWith(xpubHeader.name)) {
return xpubHeader;
}
}
throw new IllegalArgumentException("Unrecognised xpub header for xpub: " + xpub);
}
public static XpubHeader fromScriptType(ScriptType scriptType) {
for(XpubHeader xpubHeader : XpubHeader.values()) {
if(xpubHeader.defaultScriptType.equals(scriptType)) {
return xpubHeader;
}
}
return XpubHeader.xpub;
}
public static boolean isValidHeader(int header) {
for(XpubHeader xpubHeader : XpubHeader.values()) {
if(header == xpubHeader.header) {
return true;
}
}
return false;
}
}
}

View file

@ -12,7 +12,7 @@ public class KeyDerivation {
private final transient List<ChildNumber> derivation; private final transient List<ChildNumber> derivation;
public KeyDerivation(String masterFingerprint, String derivationPath) { public KeyDerivation(String masterFingerprint, String derivationPath) {
this.masterFingerprint = masterFingerprint; this.masterFingerprint = masterFingerprint == null ? null : masterFingerprint.toLowerCase();
this.derivationPath = derivationPath; this.derivationPath = derivationPath;
this.derivation = parsePath(derivationPath); this.derivation = parsePath(derivationPath);
} }

View file

@ -39,4 +39,9 @@ public class Miniscript {
public Miniscript copy() { public Miniscript copy() {
return new Miniscript(script); return new Miniscript(script);
} }
@Override
public String toString() {
return getScript();
}
} }

View file

@ -15,7 +15,7 @@ import static com.sparrowwallet.drongo.protocol.Script.decodeFromOpN;
import static com.sparrowwallet.drongo.protocol.ScriptOpCodes.*; import static com.sparrowwallet.drongo.protocol.ScriptOpCodes.*;
public enum ScriptType { public enum ScriptType {
P2PK("P2PK") { P2PK("P2PK", "m/44'/0'/0'") {
@Override @Override
public Address getAddress(byte[] pubKey) { public Address getAddress(byte[] pubKey) {
return new P2PKAddress(pubKey); return new P2PKAddress(pubKey);
@ -67,7 +67,7 @@ public enum ScriptType {
return List.of(SINGLE); return List.of(SINGLE);
} }
}, },
P2PKH("P2PKH") { P2PKH("P2PKH", "m/44'/0'/0'") {
@Override @Override
public Address getAddress(byte[] pubKeyHash) { public Address getAddress(byte[] pubKeyHash) {
return new P2PKHAddress(pubKeyHash); return new P2PKHAddress(pubKeyHash);
@ -116,7 +116,7 @@ public enum ScriptType {
return List.of(SINGLE); return List.of(SINGLE);
} }
}, },
MULTISIG("Bare Multisig") { MULTISIG("Bare Multisig", "m/44'/0'/0'") {
@Override @Override
public Address getAddress(byte[] bytes) { public Address getAddress(byte[] bytes) {
throw new ProtocolException("No single address for multisig script type"); throw new ProtocolException("No single address for multisig script type");
@ -196,7 +196,7 @@ public enum ScriptType {
return List.of(MULTI); return List.of(MULTI);
} }
}, },
P2SH("P2SH") { P2SH("P2SH", "m/45'/0'/0'") {
@Override @Override
public Address getAddress(byte[] bytes) { public Address getAddress(byte[] bytes) {
return new P2SHAddress(bytes); return new P2SHAddress(bytes);
@ -247,7 +247,7 @@ public enum ScriptType {
return List.of(MULTI); return List.of(MULTI);
} }
}, },
P2SH_P2WPKH("P2SH-P2WPKH") { P2SH_P2WPKH("P2SH-P2WPKH", "m/49'/0'/0'") {
@Override @Override
public Address getAddress(byte[] bytes) { public Address getAddress(byte[] bytes) {
return P2SH.getAddress(bytes); return P2SH.getAddress(bytes);
@ -273,7 +273,7 @@ public enum ScriptType {
return List.of(SINGLE); return List.of(SINGLE);
} }
}, },
P2SH_P2WSH("P2SH-P2WSH") { P2SH_P2WSH("P2SH-P2WSH", "m/48'/0'/0'/1'") {
@Override @Override
public Address getAddress(byte[] bytes) { public Address getAddress(byte[] bytes) {
return P2SH.getAddress(bytes); return P2SH.getAddress(bytes);
@ -299,7 +299,7 @@ public enum ScriptType {
return List.of(MULTI, CUSTOM); return List.of(MULTI, CUSTOM);
} }
}, },
P2WPKH("P2WPKH") { P2WPKH("P2WPKH", "m/84'/0'/0'") {
@Override @Override
public Address getAddress(byte[] bytes) { public Address getAddress(byte[] bytes) {
return new P2WPKHAddress(bytes); return new P2WPKHAddress(bytes);
@ -339,7 +339,7 @@ public enum ScriptType {
return List.of(SINGLE); return List.of(SINGLE);
} }
}, },
P2WSH("P2WSH") { P2WSH("P2WSH", "m/48'/0'/0'/2'") {
@Override @Override
public Address getAddress(byte[] bytes) { public Address getAddress(byte[] bytes) {
return new P2WSHAddress(bytes); return new P2WSHAddress(bytes);
@ -381,15 +381,21 @@ public enum ScriptType {
}; };
private final String name; private final String name;
private final String defaultDerivation;
ScriptType(String name) { ScriptType(String name, String defaultDerivation) {
this.name = name; this.name = name;
this.defaultDerivation = defaultDerivation;
} }
public String getName() { public String getName() {
return name; return name;
} }
public String getDefaultDerivation() {
return defaultDerivation;
}
public abstract List<PolicyType> getAllowedPolicyTypes(); public abstract List<PolicyType> getAllowedPolicyTypes();
public boolean isAllowed(PolicyType policyType) { public boolean isAllowed(PolicyType policyType) {

View file

@ -2,6 +2,7 @@ package com.sparrowwallet.drongo.wallet;
import com.sparrowwallet.drongo.ExtendedPublicKey; import com.sparrowwallet.drongo.ExtendedPublicKey;
import com.sparrowwallet.drongo.KeyDerivation; import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.Utils;
public class Keystore { public class Keystore {
public static final String DEFAULT_LABEL = "Keystore 1"; public static final String DEFAULT_LABEL = "Keystore 1";
@ -46,6 +47,22 @@ public class Keystore {
this.extendedPublicKey = extendedPublicKey; this.extendedPublicKey = extendedPublicKey;
} }
public boolean isValid() {
if(label == null || keyDerivation == null || extendedPublicKey == null) {
return false;
}
if(keyDerivation.getDerivationPath() == null || !KeyDerivation.isValid(keyDerivation.getDerivationPath())) {
return false;
}
if(keyDerivation.getMasterFingerprint() == null || keyDerivation.getMasterFingerprint().length() != 8 || !Utils.isHex(keyDerivation.getMasterFingerprint())) {
return false;
}
return true;
}
public Keystore copy() { public Keystore copy() {
Keystore copy = new Keystore(label); Keystore copy = new Keystore(label);
if(keyDerivation != null) { if(keyDerivation != null) {

View file

@ -10,22 +10,35 @@ import java.util.List;
public class Wallet { public class Wallet {
private String name;
private PolicyType policyType; private PolicyType policyType;
private ScriptType scriptType; private ScriptType scriptType;
private Policy defaultPolicy; private Policy defaultPolicy;
private List<Keystore> keystores = new ArrayList<>(); private List<Keystore> keystores = new ArrayList<>();
public Wallet() { public Wallet() {
} }
public Wallet(PolicyType policyType, ScriptType scriptType) { public Wallet(String name) {
this.name = name;
}
public Wallet(String name, PolicyType policyType, ScriptType scriptType) {
this.name = name;
this.policyType = policyType; this.policyType = policyType;
this.scriptType = scriptType; this.scriptType = scriptType;
this.keystores = Collections.singletonList(new Keystore()); this.keystores = Collections.singletonList(new Keystore());
this.defaultPolicy = Policy.getPolicy(policyType, scriptType, keystores, null); this.defaultPolicy = Policy.getPolicy(policyType, scriptType, keystores, null);
} }
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public PolicyType getPolicyType() { public PolicyType getPolicyType() {
return policyType; return policyType;
} }
@ -58,8 +71,41 @@ public class Wallet {
this.keystores = keystores; this.keystores = keystores;
} }
public boolean isValid() {
if(policyType == null || scriptType == null || defaultPolicy == null || keystores.isEmpty()) {
return false;
}
if(!ScriptType.getScriptTypesForPolicyType(policyType).contains(scriptType)) {
return false;
}
int numSigs;
try {
numSigs = defaultPolicy.getNumSignaturesRequired();
} catch (Exception e) {
return false;
}
if(policyType.equals(PolicyType.SINGLE) && (numSigs != 1 || keystores.size() != 1)) {
return false;
}
if(policyType.equals(PolicyType.MULTI) && (numSigs <= 1 || numSigs > keystores.size())) {
return false;
}
for(Keystore keystore : keystores) {
if(!keystore.isValid()) {
return false;
}
}
return true;
}
public Wallet copy() { public Wallet copy() {
Wallet copy = new Wallet(); Wallet copy = new Wallet(name);
copy.setPolicyType(policyType); copy.setPolicyType(policyType);
copy.setScriptType(scriptType); copy.setScriptType(scriptType);
copy.setDefaultPolicy(defaultPolicy.copy()); copy.setDefaultPolicy(defaultPolicy.copy());