mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-11-02 18:26:43 +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.protocol.Base58;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
import static com.sparrowwallet.drongo.KeyDerivation.parsePath;
|
||||
import static com.sparrowwallet.drongo.KeyDerivation.writePath;
|
||||
|
||||
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 DeterministicKey pubKey;
|
||||
private final ChildNumber pubKeyChildNumber;
|
||||
|
@ -44,17 +38,29 @@ public class ExtendedPublicKey {
|
|||
return getExtendedPublicKey();
|
||||
}
|
||||
|
||||
public String toString(XpubHeader xpubHeader) {
|
||||
return getExtendedPublicKey(xpubHeader);
|
||||
}
|
||||
|
||||
public String getExtendedPublicKey() {
|
||||
return Base58.encodeChecked(getExtendedPublicKeyBytes());
|
||||
}
|
||||
|
||||
public String getExtendedPublicKey(XpubHeader xpubHeader) {
|
||||
return Base58.encodeChecked(getExtendedPublicKeyBytes(xpubHeader));
|
||||
}
|
||||
|
||||
public ChildNumber getPubKeyChildNumber() {
|
||||
return pubKeyChildNumber;
|
||||
}
|
||||
|
||||
public byte[] getExtendedPublicKeyBytes() {
|
||||
return getExtendedPublicKeyBytes(XpubHeader.xpub);
|
||||
}
|
||||
|
||||
public byte[] getExtendedPublicKeyBytes(XpubHeader xpubHeader) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(78);
|
||||
buffer.putInt(bip32HeaderP2PKHXPub);
|
||||
buffer.putInt(xpubHeader.header);
|
||||
buffer.put((byte)pubKey.getDepth());
|
||||
buffer.put(parentFingerprint);
|
||||
buffer.putInt(pubKeyChildNumber.i());
|
||||
|
@ -68,7 +74,7 @@ public class ExtendedPublicKey {
|
|||
byte[] serializedKey = Base58.decodeChecked(extPubKey);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(serializedKey);
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -128,4 +134,69 @@ public class ExtendedPublicKey {
|
|||
public int 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;
|
||||
|
||||
public KeyDerivation(String masterFingerprint, String derivationPath) {
|
||||
this.masterFingerprint = masterFingerprint;
|
||||
this.masterFingerprint = masterFingerprint == null ? null : masterFingerprint.toLowerCase();
|
||||
this.derivationPath = derivationPath;
|
||||
this.derivation = parsePath(derivationPath);
|
||||
}
|
||||
|
|
|
@ -39,4 +39,9 @@ public class Miniscript {
|
|||
public Miniscript copy() {
|
||||
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.*;
|
||||
|
||||
public enum ScriptType {
|
||||
P2PK("P2PK") {
|
||||
P2PK("P2PK", "m/44'/0'/0'") {
|
||||
@Override
|
||||
public Address getAddress(byte[] pubKey) {
|
||||
return new P2PKAddress(pubKey);
|
||||
|
@ -67,7 +67,7 @@ public enum ScriptType {
|
|||
return List.of(SINGLE);
|
||||
}
|
||||
},
|
||||
P2PKH("P2PKH") {
|
||||
P2PKH("P2PKH", "m/44'/0'/0'") {
|
||||
@Override
|
||||
public Address getAddress(byte[] pubKeyHash) {
|
||||
return new P2PKHAddress(pubKeyHash);
|
||||
|
@ -116,7 +116,7 @@ public enum ScriptType {
|
|||
return List.of(SINGLE);
|
||||
}
|
||||
},
|
||||
MULTISIG("Bare Multisig") {
|
||||
MULTISIG("Bare Multisig", "m/44'/0'/0'") {
|
||||
@Override
|
||||
public Address getAddress(byte[] bytes) {
|
||||
throw new ProtocolException("No single address for multisig script type");
|
||||
|
@ -196,7 +196,7 @@ public enum ScriptType {
|
|||
return List.of(MULTI);
|
||||
}
|
||||
},
|
||||
P2SH("P2SH") {
|
||||
P2SH("P2SH", "m/45'/0'/0'") {
|
||||
@Override
|
||||
public Address getAddress(byte[] bytes) {
|
||||
return new P2SHAddress(bytes);
|
||||
|
@ -247,7 +247,7 @@ public enum ScriptType {
|
|||
return List.of(MULTI);
|
||||
}
|
||||
},
|
||||
P2SH_P2WPKH("P2SH-P2WPKH") {
|
||||
P2SH_P2WPKH("P2SH-P2WPKH", "m/49'/0'/0'") {
|
||||
@Override
|
||||
public Address getAddress(byte[] bytes) {
|
||||
return P2SH.getAddress(bytes);
|
||||
|
@ -273,7 +273,7 @@ public enum ScriptType {
|
|||
return List.of(SINGLE);
|
||||
}
|
||||
},
|
||||
P2SH_P2WSH("P2SH-P2WSH") {
|
||||
P2SH_P2WSH("P2SH-P2WSH", "m/48'/0'/0'/1'") {
|
||||
@Override
|
||||
public Address getAddress(byte[] bytes) {
|
||||
return P2SH.getAddress(bytes);
|
||||
|
@ -299,7 +299,7 @@ public enum ScriptType {
|
|||
return List.of(MULTI, CUSTOM);
|
||||
}
|
||||
},
|
||||
P2WPKH("P2WPKH") {
|
||||
P2WPKH("P2WPKH", "m/84'/0'/0'") {
|
||||
@Override
|
||||
public Address getAddress(byte[] bytes) {
|
||||
return new P2WPKHAddress(bytes);
|
||||
|
@ -339,7 +339,7 @@ public enum ScriptType {
|
|||
return List.of(SINGLE);
|
||||
}
|
||||
},
|
||||
P2WSH("P2WSH") {
|
||||
P2WSH("P2WSH", "m/48'/0'/0'/2'") {
|
||||
@Override
|
||||
public Address getAddress(byte[] bytes) {
|
||||
return new P2WSHAddress(bytes);
|
||||
|
@ -381,15 +381,21 @@ public enum ScriptType {
|
|||
};
|
||||
|
||||
private final String name;
|
||||
private final String defaultDerivation;
|
||||
|
||||
ScriptType(String name) {
|
||||
ScriptType(String name, String defaultDerivation) {
|
||||
this.name = name;
|
||||
this.defaultDerivation = defaultDerivation;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDefaultDerivation() {
|
||||
return defaultDerivation;
|
||||
}
|
||||
|
||||
public abstract List<PolicyType> getAllowedPolicyTypes();
|
||||
|
||||
public boolean isAllowed(PolicyType policyType) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.sparrowwallet.drongo.wallet;
|
|||
|
||||
import com.sparrowwallet.drongo.ExtendedPublicKey;
|
||||
import com.sparrowwallet.drongo.KeyDerivation;
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
|
||||
public class Keystore {
|
||||
public static final String DEFAULT_LABEL = "Keystore 1";
|
||||
|
@ -46,6 +47,22 @@ public class Keystore {
|
|||
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() {
|
||||
Keystore copy = new Keystore(label);
|
||||
if(keyDerivation != null) {
|
||||
|
|
|
@ -10,22 +10,35 @@ import java.util.List;
|
|||
|
||||
public class Wallet {
|
||||
|
||||
private String name;
|
||||
private PolicyType policyType;
|
||||
private ScriptType scriptType;
|
||||
private Policy defaultPolicy;
|
||||
private List<Keystore> keystores = new ArrayList<>();
|
||||
|
||||
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.scriptType = scriptType;
|
||||
this.keystores = Collections.singletonList(new Keystore());
|
||||
this.defaultPolicy = Policy.getPolicy(policyType, scriptType, keystores, null);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public PolicyType getPolicyType() {
|
||||
return policyType;
|
||||
}
|
||||
|
@ -58,8 +71,41 @@ public class Wallet {
|
|||
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() {
|
||||
Wallet copy = new Wallet();
|
||||
Wallet copy = new Wallet(name);
|
||||
copy.setPolicyType(policyType);
|
||||
copy.setScriptType(scriptType);
|
||||
copy.setDefaultPolicy(defaultPolicy.copy());
|
||||
|
|
Loading…
Reference in a new issue