mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 10:06:45 +00:00
move child derivation paths into outputdescriptor
This commit is contained in:
parent
075707f1ad
commit
f7f15cebc7
4 changed files with 101 additions and 94 deletions
|
@ -18,16 +18,13 @@ public class ExtendedPublicKey {
|
||||||
|
|
||||||
private final byte[] parentFingerprint;
|
private final byte[] parentFingerprint;
|
||||||
private final DeterministicKey pubKey;
|
private final DeterministicKey pubKey;
|
||||||
private final String childDerivationPath;
|
|
||||||
private final ChildNumber pubKeyChildNumber;
|
private final ChildNumber pubKeyChildNumber;
|
||||||
private final DeterministicHierarchy hierarchy;
|
private final DeterministicHierarchy hierarchy;
|
||||||
|
|
||||||
public ExtendedPublicKey(DeterministicKey pubKey, byte[] parentFingerprint, String childDerivationPath, ChildNumber pubKeyChildNumber) {
|
public ExtendedPublicKey(DeterministicKey pubKey, byte[] parentFingerprint, ChildNumber pubKeyChildNumber) {
|
||||||
this.parentFingerprint = parentFingerprint;
|
this.parentFingerprint = parentFingerprint;
|
||||||
this.pubKey = pubKey;
|
this.pubKey = pubKey;
|
||||||
this.childDerivationPath = childDerivationPath;
|
|
||||||
this.pubKeyChildNumber = pubKeyChildNumber;
|
this.pubKeyChildNumber = pubKeyChildNumber;
|
||||||
|
|
||||||
this.hierarchy = new DeterministicHierarchy(pubKey);
|
this.hierarchy = new DeterministicHierarchy(pubKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,62 +32,10 @@ public class ExtendedPublicKey {
|
||||||
return parentFingerprint;
|
return parentFingerprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getFingerprint() {
|
|
||||||
return pubKey.getFingerprint();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DeterministicKey getPubKey() {
|
public DeterministicKey getPubKey() {
|
||||||
return pubKey;
|
return pubKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ChildNumber> getChildDerivation() {
|
|
||||||
return getChildDerivation(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ChildNumber> getChildDerivation(int wildCardReplacement) {
|
|
||||||
return getChildDerivation(getPubKey().getChildNumber(), childDerivationPath, wildCardReplacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean describesMultipleAddresses() {
|
|
||||||
return childDerivationPath.endsWith("/*");
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ChildNumber> getReceivingDerivation(int wildCardReplacement) {
|
|
||||||
if(describesMultipleAddresses()) {
|
|
||||||
if(childDerivationPath.endsWith("0/*")) {
|
|
||||||
return getChildDerivation(getPubKey().getChildNumber(), childDerivationPath, wildCardReplacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pubKeyChildNumber.num() == 0 && childDerivationPath.endsWith("/*")) {
|
|
||||||
return getChildDerivation(new ChildNumber(0, getPubKey().getChildNumber().isHardened()), childDerivationPath, wildCardReplacement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException("Cannot derive receiving address from output descriptor " + this.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ChildNumber> getChangeDerivation(int wildCardReplacement) {
|
|
||||||
if(describesMultipleAddresses()) {
|
|
||||||
if(childDerivationPath.endsWith("0/*")) {
|
|
||||||
return getChildDerivation(getPubKey().getChildNumber(), childDerivationPath.replace("0/*", "1/*"), wildCardReplacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pubKeyChildNumber.num() == 1 && childDerivationPath.endsWith("/*")) {
|
|
||||||
return getChildDerivation(new ChildNumber(1, getPubKey().getChildNumber().isHardened()), childDerivationPath, wildCardReplacement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException("Cannot derive change address from output descriptor " + this.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ChildNumber> getChildDerivation(ChildNumber firstChild, String derivationPath, int wildCardReplacement) {
|
|
||||||
List<ChildNumber> path = new ArrayList<>();
|
|
||||||
path.add(firstChild);
|
|
||||||
path.addAll(parsePath(derivationPath, wildCardReplacement));
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DeterministicKey getKey(List<ChildNumber> path) {
|
public DeterministicKey getKey(List<ChildNumber> path) {
|
||||||
return hierarchy.get(path);
|
return hierarchy.get(path);
|
||||||
}
|
}
|
||||||
|
@ -98,7 +43,6 @@ public class ExtendedPublicKey {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
builder.append(getExtendedPublicKey());
|
builder.append(getExtendedPublicKey());
|
||||||
builder.append(childDerivationPath);
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,25 +50,23 @@ public class ExtendedPublicKey {
|
||||||
return Base58.encodeChecked(getExtendedPublicKeyBytes());
|
return Base58.encodeChecked(getExtendedPublicKeyBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChildNumber getPubKeyChildNumber() {
|
||||||
|
return pubKeyChildNumber;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getExtendedPublicKeyBytes() {
|
public byte[] getExtendedPublicKeyBytes() {
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(78);
|
ByteBuffer buffer = ByteBuffer.allocate(78);
|
||||||
buffer.putInt(bip32HeaderP2PKHXPub);
|
buffer.putInt(bip32HeaderP2PKHXPub);
|
||||||
|
buffer.put((byte)pubKey.getDepth());
|
||||||
List<ChildNumber> childPath = parsePath(childDerivationPath);
|
|
||||||
int depth = 5 - childPath.size();
|
|
||||||
buffer.put((byte)depth);
|
|
||||||
|
|
||||||
buffer.put(parentFingerprint);
|
buffer.put(parentFingerprint);
|
||||||
|
|
||||||
buffer.putInt(pubKeyChildNumber.i());
|
buffer.putInt(pubKeyChildNumber.i());
|
||||||
|
|
||||||
buffer.put(pubKey.getChainCode());
|
buffer.put(pubKey.getChainCode());
|
||||||
buffer.put(pubKey.getPubKey());
|
buffer.put(pubKey.getPubKey());
|
||||||
|
|
||||||
return buffer.array();
|
return buffer.array();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ExtendedPublicKey fromDescriptor(String extPubKey, String childDerivationPath) {
|
public static ExtendedPublicKey fromDescriptor(String extPubKey) {
|
||||||
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();
|
||||||
|
@ -147,7 +89,7 @@ public class ExtendedPublicKey {
|
||||||
} else {
|
} else {
|
||||||
childNumber = new ChildNumber(i, false);
|
childNumber = new ChildNumber(i, false);
|
||||||
}
|
}
|
||||||
path = Collections.unmodifiableList(new ArrayList<>(Arrays.asList(childNumber)));
|
path = List.of(childNumber);
|
||||||
|
|
||||||
byte[] chainCode = new byte[32];
|
byte[] chainCode = new byte[32];
|
||||||
buffer.get(chainCode);
|
buffer.get(chainCode);
|
||||||
|
@ -157,17 +99,13 @@ public class ExtendedPublicKey {
|
||||||
throw new IllegalArgumentException("Found unexpected data in key");
|
throw new IllegalArgumentException("Found unexpected data in key");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(childDerivationPath == null) {
|
|
||||||
childDerivationPath = writePath(Collections.singletonList(childNumber));
|
|
||||||
}
|
|
||||||
|
|
||||||
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(pubKey, parentFingerprint, childDerivationPath, childNumber);
|
return new ExtendedPublicKey(pubKey, parentFingerprint, childNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isValid(String extPubKey) {
|
public static boolean isValid(String extPubKey) {
|
||||||
try {
|
try {
|
||||||
ExtendedPublicKey.fromDescriptor(extPubKey, null);
|
ExtendedPublicKey.fromDescriptor(extPubKey);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,13 @@ 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.security.KeyPair;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static com.sparrowwallet.drongo.KeyDerivation.parsePath;
|
||||||
|
|
||||||
public class OutputDescriptor {
|
public class OutputDescriptor {
|
||||||
private static final Pattern XPUB_PATTERN = Pattern.compile("(\\[[^\\]]+\\])?(.pub[^/\\)]+)(/[/\\d*']+)?");
|
private static final Pattern XPUB_PATTERN = Pattern.compile("(\\[[^\\]]+\\])?(.pub[^/\\)]+)(/[/\\d*']+)?");
|
||||||
private static final Pattern MULTI_PATTERN = Pattern.compile("multi\\(([\\d+])");
|
private static final Pattern MULTI_PATTERN = Pattern.compile("multi\\(([\\d+])");
|
||||||
|
@ -20,6 +23,7 @@ public class OutputDescriptor {
|
||||||
private final String script;
|
private final String script;
|
||||||
private final int multisigThreshold;
|
private final int multisigThreshold;
|
||||||
private final Map<ExtendedPublicKey, KeyDerivation> extendedPublicKeys;
|
private final Map<ExtendedPublicKey, KeyDerivation> extendedPublicKeys;
|
||||||
|
private final Map<ExtendedPublicKey, String> mapChildrenDerivations;
|
||||||
|
|
||||||
public OutputDescriptor(String script, ExtendedPublicKey extendedPublicKey, KeyDerivation keyDerivation) {
|
public OutputDescriptor(String script, ExtendedPublicKey extendedPublicKey, KeyDerivation keyDerivation) {
|
||||||
this(script, Collections.singletonMap(extendedPublicKey, keyDerivation));
|
this(script, Collections.singletonMap(extendedPublicKey, keyDerivation));
|
||||||
|
@ -30,9 +34,14 @@ public class OutputDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutputDescriptor(String script, int multisigThreshold, Map<ExtendedPublicKey, KeyDerivation> extendedPublicKeys) {
|
public OutputDescriptor(String script, int multisigThreshold, Map<ExtendedPublicKey, KeyDerivation> extendedPublicKeys) {
|
||||||
|
this(script, multisigThreshold, extendedPublicKeys, new LinkedHashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputDescriptor(String script, int multisigThreshold, Map<ExtendedPublicKey, KeyDerivation> extendedPublicKeys, Map<ExtendedPublicKey, String> mapChildrenDerivations) {
|
||||||
this.script = script;
|
this.script = script;
|
||||||
this.multisigThreshold = multisigThreshold;
|
this.multisigThreshold = multisigThreshold;
|
||||||
this.extendedPublicKeys = extendedPublicKeys;
|
this.extendedPublicKeys = extendedPublicKeys;
|
||||||
|
this.mapChildrenDerivations = mapChildrenDerivations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<ExtendedPublicKey> getExtendedPublicKeys() {
|
public Set<ExtendedPublicKey> getExtendedPublicKeys() {
|
||||||
|
@ -43,6 +52,61 @@ public class OutputDescriptor {
|
||||||
return extendedPublicKeys.get(extendedPublicKey);
|
return extendedPublicKeys.get(extendedPublicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getChildDerivationPath(ExtendedPublicKey extendedPublicKey) {
|
||||||
|
return mapChildrenDerivations.get(extendedPublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean describesMultipleAddresses(ExtendedPublicKey extendedPublicKey) {
|
||||||
|
return getChildDerivationPath(extendedPublicKey).endsWith("/*");
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ChildNumber> getReceivingDerivation(ExtendedPublicKey extendedPublicKey, int wildCardReplacement) {
|
||||||
|
String childDerivationPath = getChildDerivationPath(extendedPublicKey);
|
||||||
|
if(describesMultipleAddresses(extendedPublicKey)) {
|
||||||
|
if(childDerivationPath.endsWith("0/*")) {
|
||||||
|
return getChildDerivation(extendedPublicKey.getPubKey().getChildNumber(), childDerivationPath, wildCardReplacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(extendedPublicKey.getPubKeyChildNumber().num() == 0 && childDerivationPath.endsWith("/*")) {
|
||||||
|
return getChildDerivation(new ChildNumber(0, extendedPublicKey.getPubKey().getChildNumber().isHardened()), childDerivationPath, wildCardReplacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException("Cannot derive receiving address from output descriptor " + this.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ChildNumber> getChangeDerivation(ExtendedPublicKey extendedPublicKey, int wildCardReplacement) {
|
||||||
|
String childDerivationPath = getChildDerivationPath(extendedPublicKey);
|
||||||
|
if(describesMultipleAddresses(extendedPublicKey)) {
|
||||||
|
if(childDerivationPath.endsWith("0/*")) {
|
||||||
|
return getChildDerivation(extendedPublicKey.getPubKey().getChildNumber(), childDerivationPath.replace("0/*", "1/*"), wildCardReplacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(extendedPublicKey.getPubKeyChildNumber().num() == 1 && childDerivationPath.endsWith("/*")) {
|
||||||
|
return getChildDerivation(new ChildNumber(1, extendedPublicKey.getPubKey().getChildNumber().isHardened()), childDerivationPath, wildCardReplacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException("Cannot derive change address from output descriptor " + this.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ChildNumber> getChildDerivation(ChildNumber firstChild, String derivationPath, int wildCardReplacement) {
|
||||||
|
List<ChildNumber> path = new ArrayList<>();
|
||||||
|
path.add(firstChild);
|
||||||
|
path.addAll(parsePath(derivationPath, wildCardReplacement));
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ChildNumber> getChildDerivation(ExtendedPublicKey extendedPublicKey) {
|
||||||
|
return getChildDerivation(extendedPublicKey, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ChildNumber> getChildDerivation(ExtendedPublicKey extendedPublicKey, int wildCardReplacement) {
|
||||||
|
String childDerivationPath = getChildDerivationPath(extendedPublicKey);
|
||||||
|
return getChildDerivation(extendedPublicKey.getPubKey().getChildNumber(), childDerivationPath, wildCardReplacement);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isMultisig() {
|
public boolean isMultisig() {
|
||||||
return extendedPublicKeys.size() > 1;
|
return extendedPublicKeys.size() > 1;
|
||||||
}
|
}
|
||||||
|
@ -61,7 +125,7 @@ public class OutputDescriptor {
|
||||||
|
|
||||||
public boolean describesMultipleAddresses() {
|
public boolean describesMultipleAddresses() {
|
||||||
for(ExtendedPublicKey pubKey : extendedPublicKeys.keySet()) {
|
for(ExtendedPublicKey pubKey : extendedPublicKeys.keySet()) {
|
||||||
if(!pubKey.describesMultipleAddresses()) {
|
if(describesMultipleAddresses(pubKey)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +136,7 @@ public class OutputDescriptor {
|
||||||
public List<ChildNumber> getChildDerivation() {
|
public List<ChildNumber> getChildDerivation() {
|
||||||
List<ChildNumber> lastDerivation = null;
|
List<ChildNumber> lastDerivation = null;
|
||||||
for(ExtendedPublicKey pubKey : extendedPublicKeys.keySet()) {
|
for(ExtendedPublicKey pubKey : extendedPublicKeys.keySet()) {
|
||||||
List<ChildNumber> derivation = pubKey.getChildDerivation();
|
List<ChildNumber> derivation = getChildDerivation(pubKey);
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
@ -90,7 +154,7 @@ public class OutputDescriptor {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getSingletonExtendedPublicKey().getReceivingDerivation(wildCardReplacement);
|
return getReceivingDerivation(getSingletonExtendedPublicKey(), wildCardReplacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ChildNumber> getChangeDerivation(int wildCardReplacement) {
|
public List<ChildNumber> getChangeDerivation(int wildCardReplacement) {
|
||||||
|
@ -101,7 +165,7 @@ public class OutputDescriptor {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getSingletonExtendedPublicKey().getChangeDerivation(wildCardReplacement);
|
return getChangeDerivation(getSingletonExtendedPublicKey(), wildCardReplacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Address getAddress(List<ChildNumber> path) {
|
public Address getAddress(List<ChildNumber> path) {
|
||||||
|
@ -150,11 +214,11 @@ public class OutputDescriptor {
|
||||||
for(ExtendedPublicKey pubKey : extendedPublicKeys.keySet()) {
|
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 = getReceivingDerivation(pubKey, path.get(1).num());
|
||||||
} else if(path.get(0).num() == 1) {
|
} else if(path.get(0).num() == 1) {
|
||||||
keyPath = pubKey.getChangeDerivation(path.get(1).num());
|
keyPath = getChangeDerivation(pubKey, path.get(1).num());
|
||||||
} else {
|
} else {
|
||||||
keyPath = pubKey.getChildDerivation(path.get(1).num());
|
keyPath = getChildDerivation(pubKey, path.get(1).num());
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] pubKeyBytes = pubKey.getKey(keyPath).getPubKey();
|
byte[] pubKeyBytes = pubKey.getKey(keyPath).getPubKey();
|
||||||
|
@ -170,15 +234,15 @@ public class OutputDescriptor {
|
||||||
// See https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md
|
// See https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md
|
||||||
public static OutputDescriptor getOutputDescriptor(String descriptor) {
|
public static OutputDescriptor getOutputDescriptor(String descriptor) {
|
||||||
if(descriptor.startsWith("pkh") || descriptor.startsWith("xpub")) {
|
if(descriptor.startsWith("pkh") || descriptor.startsWith("xpub")) {
|
||||||
return new OutputDescriptor("pkh", getExtendedPublicKeys(descriptor));
|
return getOutputDescriptorImpl("pkh", 0, descriptor);
|
||||||
} else if(descriptor.startsWith("wpkh") || descriptor.startsWith("zpub")) {
|
} else if(descriptor.startsWith("wpkh") || descriptor.startsWith("zpub")) {
|
||||||
return new OutputDescriptor("wpkh", getExtendedPublicKeys(descriptor));
|
return getOutputDescriptorImpl("wpkh", 0, descriptor);
|
||||||
} else if(descriptor.startsWith("sh(wpkh") || descriptor.startsWith("ypub")) {
|
} else if(descriptor.startsWith("sh(wpkh") || descriptor.startsWith("ypub")) {
|
||||||
return new OutputDescriptor("sh(wpkh", getExtendedPublicKeys(descriptor));
|
return getOutputDescriptorImpl("sh(wpkh", 0, descriptor);
|
||||||
} else if(descriptor.startsWith("sh(multi") || descriptor.startsWith("Ypub")) {
|
} else if(descriptor.startsWith("sh(multi") || descriptor.startsWith("Ypub")) {
|
||||||
return new OutputDescriptor("sh(multi", getMultsigThreshold(descriptor), getExtendedPublicKeys(descriptor));
|
return getOutputDescriptorImpl("sh(multi", getMultsigThreshold(descriptor), descriptor);
|
||||||
} else if(descriptor.startsWith("wsh(multi") || descriptor.startsWith("Zpub")) {
|
} else if(descriptor.startsWith("wsh(multi") || descriptor.startsWith("Zpub")) {
|
||||||
return new OutputDescriptor("wsh(multi", getMultsigThreshold(descriptor), getExtendedPublicKeys(descriptor));
|
return getOutputDescriptorImpl("wsh(multi", getMultsigThreshold(descriptor), descriptor);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Could not parse output descriptor:" + descriptor);
|
throw new IllegalArgumentException("Could not parse output descriptor:" + descriptor);
|
||||||
}
|
}
|
||||||
|
@ -194,8 +258,9 @@ public class OutputDescriptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<ExtendedPublicKey, KeyDerivation> getExtendedPublicKeys(String descriptor) {
|
private static OutputDescriptor getOutputDescriptorImpl(String script, int multisigThreshold, String descriptor) {
|
||||||
Map<ExtendedPublicKey, KeyDerivation> keys = new LinkedHashMap<>();
|
Map<ExtendedPublicKey, KeyDerivation> keyDerivationMap = new LinkedHashMap<>();
|
||||||
|
Map<ExtendedPublicKey, String> keyChildDerivationMap = 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;
|
||||||
|
@ -218,11 +283,12 @@ public class OutputDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyDerivation keyDerivation = new KeyDerivation(masterFingerprint, keyDerivationPath);
|
KeyDerivation keyDerivation = new KeyDerivation(masterFingerprint, keyDerivationPath);
|
||||||
ExtendedPublicKey extendedPublicKey = ExtendedPublicKey.fromDescriptor(extPubKey, childDerivationPath);
|
ExtendedPublicKey extendedPublicKey = ExtendedPublicKey.fromDescriptor(extPubKey);
|
||||||
keys.put(extendedPublicKey, keyDerivation);
|
keyDerivationMap.put(extendedPublicKey, keyDerivation);
|
||||||
|
keyChildDerivationMap.put(extendedPublicKey, childDerivationPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return keys;
|
return new OutputDescriptor(script, multisigThreshold, keyDerivationMap, keyChildDerivationMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
@ -235,10 +301,13 @@ public class OutputDescriptor {
|
||||||
joiner.add(Integer.toString(multisigThreshold));
|
joiner.add(Integer.toString(multisigThreshold));
|
||||||
for(ExtendedPublicKey pubKey : extendedPublicKeys.keySet()) {
|
for(ExtendedPublicKey pubKey : extendedPublicKeys.keySet()) {
|
||||||
joiner.add(pubKey.toString());
|
joiner.add(pubKey.toString());
|
||||||
|
joiner.add(mapChildrenDerivations.get(pubKey));
|
||||||
}
|
}
|
||||||
builder.append(joiner.toString());
|
builder.append(joiner.toString());
|
||||||
} else {
|
} else {
|
||||||
builder.append(getSingletonExtendedPublicKey());
|
ExtendedPublicKey extendedPublicKey = getSingletonExtendedPublicKey();
|
||||||
|
builder.append(extendedPublicKey);
|
||||||
|
builder.append(mapChildrenDerivations.get(extendedPublicKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.append(")");
|
builder.append(")");
|
||||||
|
|
|
@ -221,7 +221,7 @@ 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(Base58.encodeChecked(entry.getKeyData()), null);
|
ExtendedPublicKey pubKey = ExtendedPublicKey.fromDescriptor(Base58.encodeChecked(entry.getKeyData()));
|
||||||
this.extendedPublicKeys.put(pubKey, keyDerivation);
|
this.extendedPublicKeys.put(pubKey, keyDerivation);
|
||||||
log.debug("Pubkey with master fingerprint " + keyDerivation.getMasterFingerprint() + " at path " + keyDerivation.getDerivationPath() + ": " + pubKey.getExtendedPublicKey());
|
log.debug("Pubkey with master fingerprint " + keyDerivation.getMasterFingerprint() + " at path " + keyDerivation.getDerivationPath() + ": " + pubKey.getExtendedPublicKey());
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -8,7 +8,7 @@ public class OutputDescriptorTest {
|
||||||
@Test
|
@Test
|
||||||
public void electrumP2PKH() {
|
public void electrumP2PKH() {
|
||||||
OutputDescriptor descriptor = OutputDescriptor.getOutputDescriptor("xpub661MyMwAqRbcFT5HwyRoP5hebbeRDvy2RGDTH2uxFyDPaf5FLtu4njuishddViQxTABZKzoWKuwpy6MsgfPvTw9pKnRGDL5eBxDej9kF54Z");
|
OutputDescriptor descriptor = OutputDescriptor.getOutputDescriptor("xpub661MyMwAqRbcFT5HwyRoP5hebbeRDvy2RGDTH2uxFyDPaf5FLtu4njuishddViQxTABZKzoWKuwpy6MsgfPvTw9pKnRGDL5eBxDej9kF54Z");
|
||||||
Assert.assertEquals("pkh(xpub6BemYiVEULcbpkxh3wp6KUzfzGPFL7JNcxbfQcXxGnJ6sPugTkR69neX8RT9iXdMHFV1FCge72a21WpoHjgoeBTcZju3JKyFf9DztGT2FhE/0/*)", descriptor.toString());
|
Assert.assertEquals("pkh(xpub661MyMwAqRbcFT5HwyRoP5hebbeRDvy2RGDTH2uxFyDPaf5FLtu4njuishddViQxTABZKzoWKuwpy6MsgfPvTw9pKnRGDL5eBxDej9kF54Z/0/*)", descriptor.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -20,7 +20,7 @@ public class OutputDescriptorTest {
|
||||||
@Test
|
@Test
|
||||||
public void electrumP2WPKH() {
|
public void electrumP2WPKH() {
|
||||||
OutputDescriptor descriptor = OutputDescriptor.getOutputDescriptor("zpub6njbcfTHEfK4U96Z8dBaTULdb1LGWMtj73yYZ76kfmE9nuf3KhNSsXfzDefz5KV6TreWjnQbgvnSmSttudzTugesV2HFunYu7gWYJUD4eoR");
|
OutputDescriptor descriptor = OutputDescriptor.getOutputDescriptor("zpub6njbcfTHEfK4U96Z8dBaTULdb1LGWMtj73yYZ76kfmE9nuf3KhNSsXfzDefz5KV6TreWjnQbgvnSmSttudzTugesV2HFunYu7gWYJUD4eoR");
|
||||||
Assert.assertEquals("wpkh(xpub6CqLiu9VMua6V5yFXtXrfZgJqWsG2a8dQdBuk34KFdCCYXvCtx41CmWugPJVZNzBXyHCWy8uHgVUMpePCxh2S3VXueYG8dWLDh49dQ9MJGu/0/*)", descriptor.toString());
|
Assert.assertEquals("wpkh(xpub69551L7SwJE6mYiKTucL3J9dF53Nd7ujGpw6zKJyukUPgi2apP3KdQMiBEkp5WBFeaQuEqDUmc5LzsfmUFASKDHfkLtQjxuvaEPFXNDF4Kg/0/*)", descriptor.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -44,7 +44,7 @@ public class OutputDescriptorTest {
|
||||||
@Test
|
@Test
|
||||||
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(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)", descriptor.toString());
|
||||||
ExtendedPublicKey extendedPublicKey = descriptor.getSingletonExtendedPublicKey();
|
ExtendedPublicKey extendedPublicKey = descriptor.getSingletonExtendedPublicKey();
|
||||||
KeyDerivation derivation = descriptor.getKeyDerivation(extendedPublicKey);
|
KeyDerivation derivation = descriptor.getKeyDerivation(extendedPublicKey);
|
||||||
Assert.assertEquals("d34db33f", derivation.getMasterFingerprint());
|
Assert.assertEquals("d34db33f", derivation.getMasterFingerprint());
|
||||||
|
|
Loading…
Reference in a new issue