mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-27 02:26:44 +00:00
wallet import and export needs
This commit is contained in:
parent
282628e455
commit
294649de66
6 changed files with 168 additions and 23 deletions
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Reference in a new issue