move child derivation paths into outputdescriptor

This commit is contained in:
Craig Raw 2020-04-18 12:38:05 +02:00
parent 075707f1ad
commit f7f15cebc7
4 changed files with 101 additions and 94 deletions

View file

@ -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;
} }

View file

@ -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(")");

View file

@ -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;

View file

@ -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());