mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-27 02:26:44 +00:00
refactor extendedpublickey and keyderivation
This commit is contained in:
parent
764841635c
commit
075707f1ad
11 changed files with 243 additions and 85 deletions
|
@ -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
|
||||||
|
|
|
@ -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", "") : "");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue