refactor extendedpublickey and keyderivation

This commit is contained in:
Craig Raw 2020-04-18 10:42:51 +02:00
parent 764841635c
commit 075707f1ad
11 changed files with 243 additions and 85 deletions

View file

@ -16,16 +16,13 @@ public class ExtendedPublicKey {
private static final int bip32HeaderP2WHSHPub = 0x2AA7ED3; // 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 static final int bip32HeaderTestnetPub = 0x43587CF; // The 4 byte header that serializes in base58 to "tpub"
private KeyDerivation keyDerivation; private final byte[] parentFingerprint;
private byte[] parentFingerprint; private final DeterministicKey pubKey;
private DeterministicKey pubKey; private final String childDerivationPath;
private String childDerivationPath; private final ChildNumber pubKeyChildNumber;
private ChildNumber pubKeyChildNumber; private final DeterministicHierarchy hierarchy;
private DeterministicHierarchy hierarchy; public ExtendedPublicKey(DeterministicKey pubKey, byte[] parentFingerprint, String childDerivationPath, ChildNumber pubKeyChildNumber) {
public ExtendedPublicKey(String masterFingerprint, byte[] parentFingerprint, String keyDerivationPath, DeterministicKey pubKey, String childDerivationPath, ChildNumber pubKeyChildNumber) {
this.keyDerivation = new KeyDerivation(masterFingerprint, keyDerivationPath);
this.parentFingerprint = parentFingerprint; this.parentFingerprint = parentFingerprint;
this.pubKey = pubKey; this.pubKey = pubKey;
this.childDerivationPath = childDerivationPath; this.childDerivationPath = childDerivationPath;
@ -34,10 +31,6 @@ public class ExtendedPublicKey {
this.hierarchy = new DeterministicHierarchy(pubKey); this.hierarchy = new DeterministicHierarchy(pubKey);
} }
public String getMasterFingerprint() {
return keyDerivation.getMasterFingerprint();
}
public byte[] getParentFingerprint() { public byte[] getParentFingerprint() {
return parentFingerprint; return parentFingerprint;
} }
@ -46,14 +39,6 @@ public class ExtendedPublicKey {
return pubKey.getFingerprint(); return pubKey.getFingerprint();
} }
public String getKeyDerivationPath() {
return keyDerivation.getDerivationPath();
}
public List<ChildNumber> getKeyDerivation() {
return keyDerivation.getParsedDerivationPath();
}
public DeterministicKey getPubKey() { public DeterministicKey getPubKey() {
return pubKey; return pubKey;
} }
@ -139,7 +124,7 @@ public class ExtendedPublicKey {
return buffer.array(); return buffer.array();
} }
public static ExtendedPublicKey fromDescriptor(String masterFingerprint, String keyDerivationPath, String extPubKey, String childDerivationPath) { public static ExtendedPublicKey fromDescriptor(String extPubKey, String childDerivationPath) {
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();
@ -177,7 +162,17 @@ public class ExtendedPublicKey {
} }
DeterministicKey pubKey = new DeterministicKey(path, chainCode, new LazyECPoint(ECKey.CURVE.getCurve(), data), depth, parentFingerprint); DeterministicKey pubKey = new DeterministicKey(path, chainCode, new LazyECPoint(ECKey.CURVE.getCurve(), data), depth, parentFingerprint);
return new ExtendedPublicKey(masterFingerprint, parentFingerprint, keyDerivationPath, pubKey, childDerivationPath, childNumber); return new ExtendedPublicKey(pubKey, parentFingerprint, childDerivationPath, childNumber);
}
public static boolean isValid(String extPubKey) {
try {
ExtendedPublicKey.fromDescriptor(extPubKey, null);
} catch (Exception e) {
return false;
}
return true;
} }
@Override @Override

View file

@ -3,15 +3,18 @@ package com.sparrowwallet.drongo;
import com.sparrowwallet.drongo.crypto.ChildNumber; import com.sparrowwallet.drongo.crypto.ChildNumber;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
public class KeyDerivation { public class KeyDerivation {
private String masterFingerprint; private final String masterFingerprint;
private String derivationPath; private final String derivationPath;
private final List<ChildNumber> derivation;
public KeyDerivation(String masterFingerprint, String derivationPath) { public KeyDerivation(String masterFingerprint, String derivationPath) {
this.masterFingerprint = masterFingerprint; this.masterFingerprint = masterFingerprint;
this.derivationPath = derivationPath; this.derivationPath = derivationPath;
this.derivation = parsePath(derivationPath);
} }
public String getMasterFingerprint() { public String getMasterFingerprint() {
@ -22,8 +25,8 @@ public class KeyDerivation {
return derivationPath; return derivationPath;
} }
public List<ChildNumber> getParsedDerivationPath() { public List<ChildNumber> getDerivation() {
return parsePath(derivationPath); return Collections.unmodifiableList(derivation);
} }
public static List<ChildNumber> parsePath(String path) { public static List<ChildNumber> parsePath(String path) {
@ -31,9 +34,12 @@ public class KeyDerivation {
} }
public static List<ChildNumber> parsePath(String path, int wildcardReplacement) { public static List<ChildNumber> parsePath(String path, int wildcardReplacement) {
String[] parsedNodes = path.replace("M", "").replace("m", "").split("/");
List<ChildNumber> nodes = new ArrayList<>(); List<ChildNumber> nodes = new ArrayList<>();
if(path == null) {
return nodes;
}
String[] parsedNodes = path.replace("M", "").replace("m", "").split("/");
for (String n : parsedNodes) { for (String n : parsedNodes) {
n = n.replaceAll(" ", ""); n = n.replaceAll(" ", "");
if (n.length() == 0) continue; if (n.length() == 0) continue;
@ -57,6 +63,16 @@ public class KeyDerivation {
return path; return path;
} }
public static boolean isValid(String derivationPath) {
try {
parsePath(derivationPath);
} catch (Exception e) {
return false;
}
return true;
}
public String toString() { public String toString() {
return masterFingerprint + (derivationPath != null ? derivationPath.replace("m", "") : ""); return masterFingerprint + (derivationPath != null ? derivationPath.replace("m", "") : "");
} }

View file

@ -8,10 +8,7 @@ import com.sparrowwallet.drongo.protocol.ScriptChunk;
import com.sparrowwallet.drongo.protocol.ScriptOpCodes; import com.sparrowwallet.drongo.protocol.ScriptOpCodes;
import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.List;
import java.util.StringJoiner;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -20,26 +17,30 @@ public class OutputDescriptor {
private static final Pattern MULTI_PATTERN = Pattern.compile("multi\\(([\\d+])"); private static final Pattern MULTI_PATTERN = Pattern.compile("multi\\(([\\d+])");
private static final Pattern KEY_ORIGIN_PATTERN = Pattern.compile("\\[([a-f0-9]+)([/\\d']+)\\]"); private static final Pattern KEY_ORIGIN_PATTERN = Pattern.compile("\\[([a-f0-9]+)([/\\d']+)\\]");
private String script; private final String script;
private int multisigThreshold; private final int multisigThreshold;
private List<ExtendedPublicKey> extendedPublicKeys; private final Map<ExtendedPublicKey, KeyDerivation> extendedPublicKeys;
public OutputDescriptor(String script, ExtendedPublicKey extendedPublicKey) { public OutputDescriptor(String script, ExtendedPublicKey extendedPublicKey, KeyDerivation keyDerivation) {
this(script, Collections.singletonList(extendedPublicKey)); this(script, Collections.singletonMap(extendedPublicKey, keyDerivation));
} }
public OutputDescriptor(String script, List<ExtendedPublicKey> extendedPublicKeys) { public OutputDescriptor(String script, Map<ExtendedPublicKey, KeyDerivation> extendedPublicKeys) {
this(script, 0, extendedPublicKeys); this(script, 0, extendedPublicKeys);
} }
public OutputDescriptor(String script, int multisigThreshold, List<ExtendedPublicKey> extendedPublicKeys) { public OutputDescriptor(String script, int multisigThreshold, Map<ExtendedPublicKey, KeyDerivation> extendedPublicKeys) {
this.script = script; this.script = script;
this.multisigThreshold = multisigThreshold; this.multisigThreshold = multisigThreshold;
this.extendedPublicKeys = extendedPublicKeys; this.extendedPublicKeys = extendedPublicKeys;
} }
public List<ExtendedPublicKey> getExtendedPublicKeys() { public Set<ExtendedPublicKey> getExtendedPublicKeys() {
return extendedPublicKeys; return Collections.unmodifiableSet(extendedPublicKeys.keySet());
}
public KeyDerivation getKeyDerivation(ExtendedPublicKey extendedPublicKey) {
return extendedPublicKeys.get(extendedPublicKey);
} }
public boolean isMultisig() { public boolean isMultisig() {
@ -51,7 +52,7 @@ public class OutputDescriptor {
throw new IllegalStateException("Output descriptor contains multiple public keys but singleton requested"); throw new IllegalStateException("Output descriptor contains multiple public keys but singleton requested");
} }
return extendedPublicKeys.get(0); return extendedPublicKeys.keySet().iterator().next();
} }
public String getScript() { public String getScript() {
@ -59,7 +60,7 @@ public class OutputDescriptor {
} }
public boolean describesMultipleAddresses() { public boolean describesMultipleAddresses() {
for(ExtendedPublicKey pubKey : extendedPublicKeys) { for(ExtendedPublicKey pubKey : extendedPublicKeys.keySet()) {
if(!pubKey.describesMultipleAddresses()) { if(!pubKey.describesMultipleAddresses()) {
return false; return false;
} }
@ -70,7 +71,7 @@ public class OutputDescriptor {
public List<ChildNumber> getChildDerivation() { public List<ChildNumber> getChildDerivation() {
List<ChildNumber> lastDerivation = null; List<ChildNumber> lastDerivation = null;
for(ExtendedPublicKey pubKey : extendedPublicKeys) { for(ExtendedPublicKey pubKey : extendedPublicKeys.keySet()) {
List<ChildNumber> derivation = pubKey.getChildDerivation(); List<ChildNumber> derivation = pubKey.getChildDerivation();
if(lastDerivation != null && !lastDerivation.subList(1, lastDerivation.size()).equals(derivation.subList(1, derivation.size()))) { if(lastDerivation != null && !lastDerivation.subList(1, lastDerivation.size()).equals(derivation.subList(1, derivation.size()))) {
throw new IllegalStateException("Cannot determine multisig derivation: constituent derivations do not match"); throw new IllegalStateException("Cannot determine multisig derivation: constituent derivations do not match");
@ -146,7 +147,7 @@ public class OutputDescriptor {
List<ScriptChunk> chunks = new ArrayList<>(); List<ScriptChunk> chunks = new ArrayList<>();
chunks.add(new ScriptChunk(Script.encodeToOpN(multisigThreshold), null)); chunks.add(new ScriptChunk(Script.encodeToOpN(multisigThreshold), null));
for(ExtendedPublicKey pubKey : extendedPublicKeys) { for(ExtendedPublicKey pubKey : extendedPublicKeys.keySet()) {
List<ChildNumber> keyPath = null; List<ChildNumber> keyPath = null;
if(path.get(0).num() == 0) { if(path.get(0).num() == 0) {
keyPath = pubKey.getReceivingDerivation(path.get(1).num()); keyPath = pubKey.getReceivingDerivation(path.get(1).num());
@ -193,8 +194,8 @@ public class OutputDescriptor {
} }
} }
private static List<ExtendedPublicKey> getExtendedPublicKeys(String descriptor) { private static Map<ExtendedPublicKey, KeyDerivation> getExtendedPublicKeys(String descriptor) {
List<ExtendedPublicKey> keys = new ArrayList<>(); Map<ExtendedPublicKey, KeyDerivation> keys = new LinkedHashMap<>();
Matcher matcher = XPUB_PATTERN.matcher(descriptor); Matcher matcher = XPUB_PATTERN.matcher(descriptor);
while(matcher.find()) { while(matcher.find()) {
String masterFingerprint = null; String masterFingerprint = null;
@ -216,8 +217,9 @@ public class OutputDescriptor {
childDerivationPath = matcher.group(3); childDerivationPath = matcher.group(3);
} }
ExtendedPublicKey extendedPublicKey = ExtendedPublicKey.fromDescriptor(masterFingerprint, keyDerivationPath, extPubKey, childDerivationPath); KeyDerivation keyDerivation = new KeyDerivation(masterFingerprint, keyDerivationPath);
keys.add(extendedPublicKey); ExtendedPublicKey extendedPublicKey = ExtendedPublicKey.fromDescriptor(extPubKey, childDerivationPath);
keys.put(extendedPublicKey, keyDerivation);
} }
return keys; return keys;
@ -231,7 +233,7 @@ public class OutputDescriptor {
if(isMultisig()) { if(isMultisig()) {
StringJoiner joiner = new StringJoiner(","); StringJoiner joiner = new StringJoiner(",");
joiner.add(Integer.toString(multisigThreshold)); joiner.add(Integer.toString(multisigThreshold));
for(ExtendedPublicKey pubKey : extendedPublicKeys) { for(ExtendedPublicKey pubKey : extendedPublicKeys.keySet()) {
joiner.add(pubKey.toString()); joiner.add(pubKey.toString());
} }
builder.append(joiner.toString()); builder.append(joiner.toString());

View file

@ -0,0 +1,38 @@
package com.sparrowwallet.drongo.policy;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Miniscript {
private static final Pattern SINGLE_PATTERN = Pattern.compile("pkh?\\(");
private static final Pattern MULTI_PATTERN = Pattern.compile("multi\\(([\\d+])");
private String script;
public Miniscript(String script) {
this.script = script;
}
public String getScript() {
return script;
}
public void setScript(String script) {
this.script = script;
}
public int getNumSignaturesRequired() {
Matcher singleMatcher = SINGLE_PATTERN.matcher(script);
if(singleMatcher.find()) {
return 1;
}
Matcher multiMatcher = MULTI_PATTERN.matcher(script);
if(multiMatcher.find()) {
String threshold = multiMatcher.group(1);
return Integer.parseInt(threshold);
} else {
throw new IllegalArgumentException("Could not find multisig threshold in " + this);
}
}
}

View file

@ -1,35 +1,56 @@
package com.sparrowwallet.drongo.policy; package com.sparrowwallet.drongo.policy;
import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Keystore;
import java.util.List;
import static com.sparrowwallet.drongo.protocol.ScriptType.*; import static com.sparrowwallet.drongo.protocol.ScriptType.*;
import static com.sparrowwallet.drongo.policy.PolicyType.*; import static com.sparrowwallet.drongo.policy.PolicyType.*;
public class Policy { public class Policy {
private String policy; private static final String DEFAULT_NAME = "Default";
public Policy(String policy) { private String name;
this.policy = policy; private Miniscript miniscript;
public Policy(Miniscript miniscript) {
this(DEFAULT_NAME, miniscript);
} }
public String getPolicy() { public Policy(String name, Miniscript miniscript) {
return policy; this.name = name;
this.miniscript = miniscript;
} }
public void setPolicy(String policy) { public Miniscript getMiniscript() {
this.policy = policy; return miniscript;
} }
public static Policy getPolicy(PolicyType policyType, ScriptType scriptType, Integer threshold, Integer numCosigners) { public void setMiniscript(Miniscript miniscript) {
this.miniscript = miniscript;
}
public int getNumSignaturesRequired() {
return getMiniscript().getNumSignaturesRequired();
}
public static Policy getPolicy(PolicyType policyType, ScriptType scriptType, List<Keystore> keystores, Integer threshold) {
if(SINGLE.equals(policyType)) { if(SINGLE.equals(policyType)) {
if(P2PK.equals(scriptType)) { if(P2PK.equals(scriptType)) {
return new Policy("pk(<key1>)"); return new Policy(new Miniscript("pk(" + keystores.get(0).getScriptName() + ")"));
} }
return new Policy("pkh(<key1>)"); return new Policy(new Miniscript("pkh(" + keystores.get(0).getScriptName() + ")"));
} }
if(MULTI.equals(policyType)) { if(MULTI.equals(policyType)) {
return new Policy("multi(<threshold>,<key1>,<key2>)"); StringBuilder builder = new StringBuilder("multi(");
builder.append(threshold);
for(Keystore keystore : keystores) {
builder.append(",").append(keystore.getScriptName());
}
builder.append(")");
return new Policy(new Miniscript(builder.toString()));
} }
throw new PolicyException("No standard policy for " + policyType + " policy with script type " + scriptType); throw new PolicyException("No standard policy for " + policyType + " policy with script type " + scriptType);

View file

@ -6,14 +6,16 @@ import com.sparrowwallet.drongo.policy.PolicyType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import static com.sparrowwallet.drongo.policy.PolicyType.*; import static com.sparrowwallet.drongo.policy.PolicyType.*;
import static com.sparrowwallet.drongo.protocol.Script.decodeFromOpN; 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", new PolicyType[]{SINGLE}) { P2PK("P2PK") {
@Override @Override
public Address getAddress(byte[] pubKey) { public Address getAddress(byte[] pubKey) {
return new P2PKAddress(pubKey); return new P2PKAddress(pubKey);
@ -59,8 +61,13 @@ public enum ScriptType {
public ECKey getPublicKeyFromScript(Script script) { public ECKey getPublicKeyFromScript(Script script) {
return ECKey.fromPublicOnly(script.chunks.get(0).data); return ECKey.fromPublicOnly(script.chunks.get(0).data);
} }
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(SINGLE);
}
}, },
P2PKH("P2PKH", new PolicyType[]{SINGLE}) { P2PKH("P2PKH") {
@Override @Override
public Address getAddress(byte[] pubKeyHash) { public Address getAddress(byte[] pubKeyHash) {
return new P2PKHAddress(pubKeyHash); return new P2PKHAddress(pubKeyHash);
@ -103,8 +110,13 @@ public enum ScriptType {
public byte[] getHashFromScript(Script script) { public byte[] getHashFromScript(Script script) {
return script.chunks.get(2).data; return script.chunks.get(2).data;
} }
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(SINGLE);
}
}, },
MULTISIG("Bare Multisig", new PolicyType[]{MULTI}) { MULTISIG("Bare Multisig") {
@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");
@ -178,8 +190,13 @@ public enum ScriptType {
public int getThreshold(Script script) { public int getThreshold(Script script) {
return decodeFromOpN(script.chunks.get(0).opcode); return decodeFromOpN(script.chunks.get(0).opcode);
} }
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(MULTI);
}
}, },
P2SH("P2SH", new PolicyType[]{MULTI}) { P2SH("P2SH") {
@Override @Override
public Address getAddress(byte[] bytes) { public Address getAddress(byte[] bytes) {
return new P2SHAddress(bytes); return new P2SHAddress(bytes);
@ -224,8 +241,13 @@ public enum ScriptType {
public byte[] getHashFromScript(Script script) { public byte[] getHashFromScript(Script script) {
return script.chunks.get(1).data; return script.chunks.get(1).data;
} }
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(MULTI);
}
}, },
P2SH_P2WPKH("P2SH-P2WPKH", new PolicyType[]{SINGLE}) { P2SH_P2WPKH("P2SH-P2WPKH") {
@Override @Override
public Address getAddress(byte[] bytes) { public Address getAddress(byte[] bytes) {
return P2SH.getAddress(bytes); return P2SH.getAddress(bytes);
@ -245,8 +267,13 @@ public enum ScriptType {
public byte[] getHashFromScript(Script script) { public byte[] getHashFromScript(Script script) {
return P2SH.getHashFromScript(script); return P2SH.getHashFromScript(script);
} }
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(SINGLE);
}
}, },
P2SH_P2WSH("P2SH-P2WSH", new PolicyType[]{MULTI, CUSTOM}) { P2SH_P2WSH("P2SH-P2WSH") {
@Override @Override
public Address getAddress(byte[] bytes) { public Address getAddress(byte[] bytes) {
return P2SH.getAddress(bytes); return P2SH.getAddress(bytes);
@ -266,8 +293,13 @@ public enum ScriptType {
public byte[] getHashFromScript(Script script) { public byte[] getHashFromScript(Script script) {
return P2SH.getHashFromScript(script); return P2SH.getHashFromScript(script);
} }
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(MULTI, CUSTOM);
}
}, },
P2WPKH("P2WPKH", new PolicyType[]{SINGLE}) { P2WPKH("P2WPKH") {
@Override @Override
public Address getAddress(byte[] bytes) { public Address getAddress(byte[] bytes) {
return new P2WPKHAddress(bytes); return new P2WPKHAddress(bytes);
@ -301,8 +333,13 @@ public enum ScriptType {
public byte[] getHashFromScript(Script script) { public byte[] getHashFromScript(Script script) {
return script.chunks.get(1).data; return script.chunks.get(1).data;
} }
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(SINGLE);
}
}, },
P2WSH("P2WSH", new PolicyType[]{MULTI, CUSTOM}) { P2WSH("P2WSH") {
@Override @Override
public Address getAddress(byte[] bytes) { public Address getAddress(byte[] bytes) {
return new P2WSHAddress(bytes); return new P2WSHAddress(bytes);
@ -336,22 +373,27 @@ public enum ScriptType {
public byte[] getHashFromScript(Script script) { public byte[] getHashFromScript(Script script) {
return script.chunks.get(1).data; return script.chunks.get(1).data;
} }
@Override
public List<PolicyType> getAllowedPolicyTypes() {
return List.of(MULTI, CUSTOM);
}
}; };
private final String name; private final String name;
private final PolicyType[] allowedPolicyTypes;
ScriptType(String name, PolicyType[] allowedPolicyTypes) { ScriptType(String name) {
this.name = name; this.name = name;
this.allowedPolicyTypes = allowedPolicyTypes;
} }
public String getName() { public String getName() {
return name; return name;
} }
public PolicyType[] getAllowedPolicyTypes() { public abstract List<PolicyType> getAllowedPolicyTypes();
return allowedPolicyTypes;
public boolean isAllowed(PolicyType policyType) {
return getAllowedPolicyTypes().contains(policyType);
} }
public abstract Address getAddress(byte[] bytes); public abstract Address getAddress(byte[] bytes);
@ -380,6 +422,10 @@ public enum ScriptType {
public static final ScriptType[] SINGLE_HASH_TYPES = {P2PKH, P2SH, P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH}; public static final ScriptType[] SINGLE_HASH_TYPES = {P2PKH, P2SH, P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH};
public static List<ScriptType> getScriptTypesForPolicyType(PolicyType policyType) {
return Arrays.stream(values()).filter(scriptType -> scriptType.isAllowed(policyType)).collect(Collectors.toList());
}
@Override @Override
public String toString() { public String toString() {
return name; return name;

View file

@ -221,9 +221,9 @@ public class PSBT {
case PSBT_GLOBAL_BIP32_PUBKEY: case PSBT_GLOBAL_BIP32_PUBKEY:
entry.checkOneBytePlusXpubKey(); entry.checkOneBytePlusXpubKey();
KeyDerivation keyDerivation = parseKeyDerivation(entry.getData()); KeyDerivation keyDerivation = parseKeyDerivation(entry.getData());
ExtendedPublicKey pubKey = ExtendedPublicKey.fromDescriptor(keyDerivation.getMasterFingerprint(), keyDerivation.getDerivationPath(), Base58.encodeChecked(entry.getKeyData()), null); ExtendedPublicKey pubKey = ExtendedPublicKey.fromDescriptor(Base58.encodeChecked(entry.getKeyData()), null);
this.extendedPublicKeys.put(pubKey, keyDerivation); this.extendedPublicKeys.put(pubKey, keyDerivation);
log.debug("Pubkey with master fingerprint " + pubKey.getMasterFingerprint() + " at path " + pubKey.getKeyDerivationPath() + ": " + pubKey.getExtendedPublicKey()); log.debug("Pubkey with master fingerprint " + keyDerivation.getMasterFingerprint() + " at path " + keyDerivation.getDerivationPath() + ": " + pubKey.getExtendedPublicKey());
break; break;
case PSBT_GLOBAL_VERSION: case PSBT_GLOBAL_VERSION:
entry.checkOneByteKey(); entry.checkOneByteKey();

View file

@ -4,6 +4,39 @@ import com.sparrowwallet.drongo.ExtendedPublicKey;
import com.sparrowwallet.drongo.KeyDerivation; import com.sparrowwallet.drongo.KeyDerivation;
public class Keystore { public class Keystore {
private String label;
private KeyDerivation keyDerivation; private KeyDerivation keyDerivation;
private ExtendedPublicKey extendedPublicKey; private ExtendedPublicKey extendedPublicKey;
public Keystore(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
public String getScriptName() {
return label.replace(" ", "").toLowerCase();
}
public void setLabel(String label) {
this.label = label;
}
public KeyDerivation getKeyDerivation() {
return keyDerivation;
}
public void setKeyDerivation(KeyDerivation keyDerivation) {
this.keyDerivation = keyDerivation;
}
public ExtendedPublicKey getExtendedPublicKey() {
return extendedPublicKey;
}
public void setExtendedPublicKey(ExtendedPublicKey extendedPublicKey) {
this.extendedPublicKey = extendedPublicKey;
}
} }

View file

@ -1,5 +1,6 @@
package com.sparrowwallet.drongo.wallet; package com.sparrowwallet.drongo.wallet;
import com.sparrowwallet.drongo.policy.Policy;
import com.sparrowwallet.drongo.policy.PolicyType; import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
@ -9,7 +10,7 @@ import java.util.List;
public class Wallet { public class Wallet {
private PolicyType policyType; private PolicyType policyType;
private ScriptType scriptType; private ScriptType scriptType;
private String policy; private Policy defaultPolicy;
private List<Keystore> keystores = new ArrayList<>(); private List<Keystore> keystores = new ArrayList<>();
public PolicyType getPolicyType() { public PolicyType getPolicyType() {
@ -28,12 +29,12 @@ public class Wallet {
this.scriptType = scriptType; this.scriptType = scriptType;
} }
public String getPolicy() { public Policy getDefaultPolicy() {
return policy; return defaultPolicy;
} }
public void setPolicy(String policy) { public void setDefaultPolicy(Policy defaultPolicy) {
this.policy = policy; this.defaultPolicy = defaultPolicy;
} }
public List<Keystore> getKeystores() { public List<Keystore> getKeystores() {

View file

@ -45,7 +45,9 @@ public class OutputDescriptorTest {
public void masterP2PKH() { public void masterP2PKH() {
OutputDescriptor descriptor = OutputDescriptor.getOutputDescriptor("pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)"); OutputDescriptor descriptor = OutputDescriptor.getOutputDescriptor("pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)");
Assert.assertEquals("pkh(xpub6CY2xt3vG5BhUS7krcphJprmHCh3jHYB1A8bxtJocU8NyQttKUCLp5izorV1wdXbp7XSSEcaFiKzUroEAL5tD1de8iAUeHP76byTWZu79SD/1/*)", descriptor.toString()); Assert.assertEquals("pkh(xpub6CY2xt3vG5BhUS7krcphJprmHCh3jHYB1A8bxtJocU8NyQttKUCLp5izorV1wdXbp7XSSEcaFiKzUroEAL5tD1de8iAUeHP76byTWZu79SD/1/*)", descriptor.toString());
Assert.assertEquals("d34db33f", descriptor.getSingletonExtendedPublicKey().getMasterFingerprint()); ExtendedPublicKey extendedPublicKey = descriptor.getSingletonExtendedPublicKey();
Assert.assertEquals("m/44'/0'/0'", descriptor.getSingletonExtendedPublicKey().getKeyDerivationPath()); KeyDerivation derivation = descriptor.getKeyDerivation(extendedPublicKey);
Assert.assertEquals("d34db33f", derivation.getMasterFingerprint());
Assert.assertEquals("m/44'/0'/0'", derivation.getDerivationPath());
} }
} }

View file

@ -1,5 +1,7 @@
package com.sparrowwallet.drongo.psbt; package com.sparrowwallet.drongo.psbt;
import com.sparrowwallet.drongo.ExtendedPublicKey;
import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.protocol.NonStandardScriptException; import com.sparrowwallet.drongo.protocol.NonStandardScriptException;
import com.sparrowwallet.drongo.protocol.Transaction; import com.sparrowwallet.drongo.protocol.Transaction;
import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.encoders.Hex;
@ -214,8 +216,10 @@ public class PSBTTest {
String psbt = "cHNidP8BAJ0BAAAAAnEOp2q0XFy2Q45gflnMA3YmmBgFrp4N/ZCJASq7C+U1AQAAAAD/////GQmU1qizyMgsy8+y+6QQaqBmObhyqNRHRlwNQliNbWcAAAAAAP////8CAOH1BQAAAAAZdqkUtrwsDuVlWoQ9ea/t0MzD991kNAmIrGBa9AUAAAAAFgAUEYjvjkzgRJ6qyPsUHL9aEXbmoIgAAAAATwEEiLIeA55TDKyAAAAAPbyKXJdp8DGxfnf+oVGGAyIaGP0Y8rmlTGyMGsdcvDUC8jBYSxVdHH8c1FEgplPEjWULQxtnxbLBPyfXFCA3wWkQJ1acUDEAAIAAAACAAAAAgAABAR8A4fUFAAAAABYAFDO5gvkbKPFgySC0q5XljOUN2jpKIgIDMJaA8zx9446mpHzU7NZvH1pJdHxv+4gI7QkDkkPjrVxHMEQCIC1wTO2DDFapCTRL10K2hS3M0QPpY7rpLTjnUlTSu0JFAiAthsQ3GV30bAztoITyopHD2i1kBw92v5uQsZXn7yj3cgEiBgMwloDzPH3jjqakfNTs1m8fWkl0fG/7iAjtCQOSQ+OtXBgnVpxQMQAAgAAAAIAAAACAAAAAAAEAAAAAAQEfAOH1BQAAAAAWABQ4j7lEMH63fvRRl9CwskXgefAR3iICAsd3Fh9z0LfHK57nveZQKT0T8JW8dlatH1Jdpf0uELEQRzBEAiBMsftfhpyULg4mEAV2ElQ5F5rojcqKncO6CPeVOYj6pgIgUh9JynkcJ9cOJzybFGFphZCTYeJb4nTqIA1+CIJ+UU0BIgYCx3cWH3PQt8crnue95lApPRPwlbx2Vq0fUl2l/S4QsRAYJ1acUDEAAIAAAACAAAAAgAAAAAAAAAAAAAAiAgLSDKUC7iiWhtIYFb1DqAY3sGmOH7zb5MrtRF9sGgqQ7xgnVpxQMQAAgAAAAIAAAACAAAAAAAQAAAAA"; String psbt = "cHNidP8BAJ0BAAAAAnEOp2q0XFy2Q45gflnMA3YmmBgFrp4N/ZCJASq7C+U1AQAAAAD/////GQmU1qizyMgsy8+y+6QQaqBmObhyqNRHRlwNQliNbWcAAAAAAP////8CAOH1BQAAAAAZdqkUtrwsDuVlWoQ9ea/t0MzD991kNAmIrGBa9AUAAAAAFgAUEYjvjkzgRJ6qyPsUHL9aEXbmoIgAAAAATwEEiLIeA55TDKyAAAAAPbyKXJdp8DGxfnf+oVGGAyIaGP0Y8rmlTGyMGsdcvDUC8jBYSxVdHH8c1FEgplPEjWULQxtnxbLBPyfXFCA3wWkQJ1acUDEAAIAAAACAAAAAgAABAR8A4fUFAAAAABYAFDO5gvkbKPFgySC0q5XljOUN2jpKIgIDMJaA8zx9446mpHzU7NZvH1pJdHxv+4gI7QkDkkPjrVxHMEQCIC1wTO2DDFapCTRL10K2hS3M0QPpY7rpLTjnUlTSu0JFAiAthsQ3GV30bAztoITyopHD2i1kBw92v5uQsZXn7yj3cgEiBgMwloDzPH3jjqakfNTs1m8fWkl0fG/7iAjtCQOSQ+OtXBgnVpxQMQAAgAAAAIAAAACAAAAAAAEAAAAAAQEfAOH1BQAAAAAWABQ4j7lEMH63fvRRl9CwskXgefAR3iICAsd3Fh9z0LfHK57nveZQKT0T8JW8dlatH1Jdpf0uELEQRzBEAiBMsftfhpyULg4mEAV2ElQ5F5rojcqKncO6CPeVOYj6pgIgUh9JynkcJ9cOJzybFGFphZCTYeJb4nTqIA1+CIJ+UU0BIgYCx3cWH3PQt8crnue95lApPRPwlbx2Vq0fUl2l/S4QsRAYJ1acUDEAAIAAAACAAAAAgAAAAAAAAAAAAAAiAgLSDKUC7iiWhtIYFb1DqAY3sGmOH7zb5MrtRF9sGgqQ7xgnVpxQMQAAgAAAAIAAAACAAAAAAAQAAAAA";
PSBT psbt1 = PSBT.fromString(psbt); PSBT psbt1 = PSBT.fromString(psbt);
Assert.assertEquals("27569c50", psbt1.getExtendedPublicKeys().get(0).getMasterFingerprint()); ExtendedPublicKey extendedPublicKey = psbt1.getExtendedPublicKeys().get(0);
Assert.assertEquals("m/49'/0'/0'", psbt1.getExtendedPublicKeys().get(0).getKeyDerivationPath()); KeyDerivation keyDerivation = psbt1.getKeyDerivation(extendedPublicKey);
Assert.assertEquals("27569c50", keyDerivation.getMasterFingerprint());
Assert.assertEquals("m/49'/0'/0'", keyDerivation.getDerivationPath());
Assert.assertEquals(2, psbt1.getPsbtInputs().size()); Assert.assertEquals(2, psbt1.getPsbtInputs().size());
} }