mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-27 02:26:44 +00:00
refactor and update outputdescriptor to use script type
This commit is contained in:
parent
3115669c46
commit
0516426dc0
4 changed files with 143 additions and 93 deletions
|
@ -207,7 +207,7 @@ public class ExtendedKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException("Unrecognised extended key header for extended key: " + xpub);
|
throw new IllegalArgumentException("Unrecognised extended key header for extended key: " + xkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Header fromScriptType(ScriptType scriptType, boolean privateKey) {
|
public static Header fromScriptType(ScriptType scriptType, boolean privateKey) {
|
||||||
|
|
|
@ -3,9 +3,8 @@ package com.sparrowwallet.drongo;
|
||||||
import com.sparrowwallet.drongo.address.*;
|
import com.sparrowwallet.drongo.address.*;
|
||||||
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||||
import com.sparrowwallet.drongo.crypto.DeterministicKey;
|
import com.sparrowwallet.drongo.crypto.DeterministicKey;
|
||||||
|
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||||
import com.sparrowwallet.drongo.protocol.Script;
|
import com.sparrowwallet.drongo.protocol.Script;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptChunk;
|
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptOpCodes;
|
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -15,29 +14,29 @@ import java.util.regex.Pattern;
|
||||||
import static com.sparrowwallet.drongo.KeyDerivation.parsePath;
|
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[^/\\)]{100,112})(/[/\\d*'hH]+)?");
|
||||||
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-Fa-f0-9]{8})([/\\d'hH]+)\\]");
|
||||||
|
|
||||||
private final String script;
|
private final ScriptType scriptType;
|
||||||
private final int multisigThreshold;
|
private final int multisigThreshold;
|
||||||
private final Map<ExtendedKey, KeyDerivation> extendedPublicKeys;
|
private final Map<ExtendedKey, KeyDerivation> extendedPublicKeys;
|
||||||
private final Map<ExtendedKey, String> mapChildrenDerivations;
|
private final Map<ExtendedKey, String> mapChildrenDerivations;
|
||||||
|
|
||||||
public OutputDescriptor(String script, ExtendedKey extendedPublicKey, KeyDerivation keyDerivation) {
|
public OutputDescriptor(ScriptType scriptType, ExtendedKey extendedPublicKey, KeyDerivation keyDerivation) {
|
||||||
this(script, Collections.singletonMap(extendedPublicKey, keyDerivation));
|
this(scriptType, Collections.singletonMap(extendedPublicKey, keyDerivation));
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutputDescriptor(String script, Map<ExtendedKey, KeyDerivation> extendedPublicKeys) {
|
public OutputDescriptor(ScriptType scriptType, Map<ExtendedKey, KeyDerivation> extendedPublicKeys) {
|
||||||
this(script, 0, extendedPublicKeys);
|
this(scriptType, 0, extendedPublicKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutputDescriptor(String script, int multisigThreshold, Map<ExtendedKey, KeyDerivation> extendedPublicKeys) {
|
public OutputDescriptor(ScriptType scriptType, int multisigThreshold, Map<ExtendedKey, KeyDerivation> extendedPublicKeys) {
|
||||||
this(script, multisigThreshold, extendedPublicKeys, new LinkedHashMap<>());
|
this(scriptType, multisigThreshold, extendedPublicKeys, new LinkedHashMap<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutputDescriptor(String script, int multisigThreshold, Map<ExtendedKey, KeyDerivation> extendedPublicKeys, Map<ExtendedKey, String> mapChildrenDerivations) {
|
public OutputDescriptor(ScriptType scriptType, int multisigThreshold, Map<ExtendedKey, KeyDerivation> extendedPublicKeys, Map<ExtendedKey, String> mapChildrenDerivations) {
|
||||||
this.script = script;
|
this.scriptType = scriptType;
|
||||||
this.multisigThreshold = multisigThreshold;
|
this.multisigThreshold = multisigThreshold;
|
||||||
this.extendedPublicKeys = extendedPublicKeys;
|
this.extendedPublicKeys = extendedPublicKeys;
|
||||||
this.mapChildrenDerivations = mapChildrenDerivations;
|
this.mapChildrenDerivations = mapChildrenDerivations;
|
||||||
|
@ -51,6 +50,10 @@ public class OutputDescriptor {
|
||||||
return extendedPublicKeys.get(extendedPublicKey);
|
return extendedPublicKeys.get(extendedPublicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMultisigThreshold() {
|
||||||
|
return multisigThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
public String getChildDerivationPath(ExtendedKey extendedPublicKey) {
|
public String getChildDerivationPath(ExtendedKey extendedPublicKey) {
|
||||||
return mapChildrenDerivations.get(extendedPublicKey);
|
return mapChildrenDerivations.get(extendedPublicKey);
|
||||||
}
|
}
|
||||||
|
@ -81,8 +84,8 @@ public class OutputDescriptor {
|
||||||
return getChildDerivation(extendedPublicKey.getKey().getChildNumber(), childDerivationPath.replace("0/*", "1/*"), wildCardReplacement);
|
return getChildDerivation(extendedPublicKey.getKey().getChildNumber(), childDerivationPath.replace("0/*", "1/*"), wildCardReplacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(extendedPublicKey.getKeyChildNumber().num() == 1 && childDerivationPath.endsWith("/*")) {
|
if(childDerivationPath.endsWith("/*")) {
|
||||||
return getChildDerivation(new ChildNumber(1, extendedPublicKey.getKey().getChildNumber().isHardened()), childDerivationPath, wildCardReplacement);
|
return getChildDerivation(extendedPublicKey.getKey().getChildNumber(), childDerivationPath, wildCardReplacement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,8 +121,8 @@ public class OutputDescriptor {
|
||||||
return extendedPublicKeys.keySet().iterator().next();
|
return extendedPublicKeys.keySet().iterator().next();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getScript() {
|
public ScriptType getScriptType() {
|
||||||
return script;
|
return scriptType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean describesMultipleAddresses() {
|
public boolean describesMultipleAddresses() {
|
||||||
|
@ -178,101 +181,83 @@ public class OutputDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Address getAddress(DeterministicKey childKey) {
|
public Address getAddress(DeterministicKey childKey) {
|
||||||
Address address = null;
|
return scriptType.getAddress(childKey);
|
||||||
if(script.equals("pkh")) {
|
|
||||||
address = new P2PKHAddress(childKey.getPubKeyHash());
|
|
||||||
} else if(script.equals("sh(wpkh")) {
|
|
||||||
Script receivingP2wpkhScript = ScriptType.P2WPKH.getOutputScript(childKey.getPubKeyHash());
|
|
||||||
address = P2SHAddress.fromProgram(receivingP2wpkhScript.getProgram());
|
|
||||||
} else if(script.equals("wpkh")) {
|
|
||||||
address = new P2WPKHAddress(childKey.getPubKeyHash());
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Cannot determine address for script " + script);
|
|
||||||
}
|
|
||||||
|
|
||||||
return address;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Address getAddress(Script multisigScript) {
|
private Address getAddress(Script multisigScript) {
|
||||||
Address address = null;
|
return scriptType.getAddress(multisigScript);
|
||||||
if(script.equals("sh(multi")) {
|
|
||||||
address = P2SHAddress.fromProgram(multisigScript.getProgram());
|
|
||||||
} else if(script.equals("wsh(multi")) {
|
|
||||||
address = P2WSHAddress.fromProgram(multisigScript.getProgram());
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Cannot determine address for multisig script " + script);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return address;
|
private Script getMultisigScript(List<ChildNumber> childPath) {
|
||||||
}
|
List<ECKey> keys = new ArrayList<>();
|
||||||
|
|
||||||
private Script getMultisigScript(List<ChildNumber> path) {
|
|
||||||
List<ScriptChunk> chunks = new ArrayList<>();
|
|
||||||
chunks.add(new ScriptChunk(Script.encodeToOpN(multisigThreshold), null));
|
|
||||||
|
|
||||||
for(ExtendedKey pubKey : extendedPublicKeys.keySet()) {
|
for(ExtendedKey pubKey : extendedPublicKeys.keySet()) {
|
||||||
List<ChildNumber> keyPath = null;
|
List<ChildNumber> keyPath = getKeyPath(pubKey, childPath);
|
||||||
if(path.get(0).num() == 0) {
|
|
||||||
keyPath = getReceivingDerivation(pubKey, path.get(1).num());
|
keys.add(pubKey.getKey(keyPath));
|
||||||
} else if(path.get(0).num() == 1) {
|
}
|
||||||
keyPath = getChangeDerivation(pubKey, path.get(1).num());
|
|
||||||
|
return ScriptType.MULTISIG.getOutputScript(multisigThreshold, keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ChildNumber> getKeyPath(ExtendedKey pubKey, List<ChildNumber> childPath) {
|
||||||
|
List<ChildNumber> keyPath;
|
||||||
|
if(childPath.get(0).num() == 0) {
|
||||||
|
keyPath = getReceivingDerivation(pubKey, childPath.get(1).num());
|
||||||
|
} else if(childPath.get(0).num() == 1) {
|
||||||
|
keyPath = getChangeDerivation(pubKey, childPath.get(1).num());
|
||||||
} else {
|
} else {
|
||||||
keyPath = getChildDerivation(pubKey, path.get(1).num());
|
keyPath = getChildDerivation(pubKey, childPath.get(1).num());
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] pubKeyBytes = pubKey.getKey(keyPath).getPubKey();
|
return keyPath;
|
||||||
chunks.add(new ScriptChunk(pubKeyBytes.length, pubKeyBytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
chunks.add(new ScriptChunk(Script.encodeToOpN(extendedPublicKeys.size()), null));
|
|
||||||
chunks.add(new ScriptChunk(ScriptOpCodes.OP_CHECKMULTISIG, null));
|
|
||||||
|
|
||||||
return new Script(chunks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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")) {
|
ScriptType scriptType = ScriptType.fromDescriptor(descriptor);
|
||||||
return getOutputDescriptorImpl("pkh", 0, descriptor);
|
if(scriptType == null) {
|
||||||
} else if(descriptor.startsWith("wpkh") || descriptor.startsWith("zpub")) {
|
ExtendedKey.Header header = ExtendedKey.Header.fromExtendedKey(descriptor);
|
||||||
return getOutputDescriptorImpl("wpkh", 0, descriptor);
|
scriptType = header.getDefaultScriptType();
|
||||||
} else if(descriptor.startsWith("sh(wpkh") || descriptor.startsWith("ypub")) {
|
|
||||||
return getOutputDescriptorImpl("sh(wpkh", 0, descriptor);
|
|
||||||
} else if(descriptor.startsWith("sh(multi") || descriptor.startsWith("Ypub")) {
|
|
||||||
return getOutputDescriptorImpl("sh(multi", getMultsigThreshold(descriptor), descriptor);
|
|
||||||
} else if(descriptor.startsWith("wsh(multi") || descriptor.startsWith("Zpub")) {
|
|
||||||
return getOutputDescriptorImpl("wsh(multi", getMultsigThreshold(descriptor), descriptor);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Could not parse output descriptor:" + descriptor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getMultsigThreshold(String descriptor) {
|
if(scriptType == null) {
|
||||||
|
throw new IllegalArgumentException("Cannot determine script type from descriptor: " + descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
int threshold = getMultisigThreshold(descriptor);
|
||||||
|
return getOutputDescriptorImpl(scriptType, threshold, descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getMultisigThreshold(String descriptor) {
|
||||||
Matcher matcher = MULTI_PATTERN.matcher(descriptor);
|
Matcher matcher = MULTI_PATTERN.matcher(descriptor);
|
||||||
if(matcher.find()) {
|
if(matcher.find()) {
|
||||||
String threshold = matcher.group(1);
|
String threshold = matcher.group(1);
|
||||||
return Integer.parseInt(threshold);
|
return Integer.parseInt(threshold);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Could not find multisig threshold in output descriptor:" + descriptor);
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OutputDescriptor getOutputDescriptorImpl(String script, int multisigThreshold, String descriptor) {
|
private static OutputDescriptor getOutputDescriptorImpl(ScriptType scriptType, int multisigThreshold, String descriptor) {
|
||||||
Map<ExtendedKey, KeyDerivation> keyDerivationMap = new LinkedHashMap<>();
|
Map<ExtendedKey, KeyDerivation> keyDerivationMap = new LinkedHashMap<>();
|
||||||
Map<ExtendedKey, String> keyChildDerivationMap = new LinkedHashMap<>();
|
Map<ExtendedKey, 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;
|
||||||
String keyDerivationPath = null;
|
String keyDerivationPath = null;
|
||||||
String extPubKey = null;
|
String extPubKey;
|
||||||
String childDerivationPath = "/0/*";
|
String childDerivationPath = "/0/*";
|
||||||
|
|
||||||
if(matcher.group(1) != null) {
|
if(matcher.group(1) != null) {
|
||||||
String keyOrigin = matcher.group(1);
|
String keyOrigin = matcher.group(1);
|
||||||
Matcher keyOriginMatcher = KEY_ORIGIN_PATTERN.matcher(keyOrigin);
|
Matcher keyOriginMatcher = KEY_ORIGIN_PATTERN.matcher(keyOrigin);
|
||||||
if(keyOriginMatcher.matches()) {
|
if(keyOriginMatcher.matches()) {
|
||||||
masterFingerprint = keyOriginMatcher.group(1);
|
byte[] masterFingerprintBytes = Utils.hexToBytes(keyOriginMatcher.group(1));
|
||||||
keyDerivationPath = "m" + keyOriginMatcher.group(2);
|
if(masterFingerprintBytes.length != 4) {
|
||||||
|
throw new IllegalArgumentException("Master fingerprint must be 4 bytes: " + Utils.bytesToHex(masterFingerprintBytes));
|
||||||
|
}
|
||||||
|
masterFingerprint = Utils.bytesToHex(masterFingerprintBytes);
|
||||||
|
keyDerivationPath = KeyDerivation.writePath(KeyDerivation.parsePath(keyOriginMatcher.group(2)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,34 +272,55 @@ public class OutputDescriptor {
|
||||||
keyChildDerivationMap.put(extendedPublicKey, childDerivationPath);
|
keyChildDerivationMap.put(extendedPublicKey, childDerivationPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OutputDescriptor(script, multisigThreshold, keyDerivationMap, keyChildDerivationMap);
|
return new OutputDescriptor(scriptType, multisigThreshold, keyDerivationMap, keyChildDerivationMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
builder.append(script);
|
builder.append(scriptType.getDescriptor());
|
||||||
builder.append("(");
|
|
||||||
|
|
||||||
if(isMultisig()) {
|
if(isMultisig()) {
|
||||||
|
builder.append(ScriptType.MULTISIG.getDescriptor());
|
||||||
StringJoiner joiner = new StringJoiner(",");
|
StringJoiner joiner = new StringJoiner(",");
|
||||||
joiner.add(Integer.toString(multisigThreshold));
|
joiner.add(Integer.toString(multisigThreshold));
|
||||||
for(ExtendedKey pubKey : extendedPublicKeys.keySet()) {
|
for(ExtendedKey pubKey : extendedPublicKeys.keySet()) {
|
||||||
joiner.add(pubKey.toString());
|
String extKeyString = toString(pubKey);
|
||||||
joiner.add(mapChildrenDerivations.get(pubKey));
|
joiner.add(extKeyString);
|
||||||
}
|
}
|
||||||
builder.append(joiner.toString());
|
builder.append(joiner.toString());
|
||||||
|
builder.append(ScriptType.MULTISIG.getCloseDescriptor());
|
||||||
} else {
|
} else {
|
||||||
ExtendedKey extendedPublicKey = getSingletonExtendedPublicKey();
|
ExtendedKey extendedPublicKey = getSingletonExtendedPublicKey();
|
||||||
builder.append(extendedPublicKey);
|
builder.append(toString(extendedPublicKey));
|
||||||
builder.append(mapChildrenDerivations.get(extendedPublicKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.append(")");
|
|
||||||
|
|
||||||
if(script.contains("(")){
|
|
||||||
builder.append(")");
|
|
||||||
}
|
}
|
||||||
|
builder.append(scriptType.getCloseDescriptor());
|
||||||
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String toString(ExtendedKey pubKey) {
|
||||||
|
StringBuilder keyBuilder = new StringBuilder();
|
||||||
|
KeyDerivation keyDerivation = extendedPublicKeys.get(pubKey);
|
||||||
|
if(keyDerivation != null && keyDerivation.getDerivationPath() != null) {
|
||||||
|
keyBuilder.append("[");
|
||||||
|
if(keyDerivation.getMasterFingerprint() != null) {
|
||||||
|
keyBuilder.append(keyDerivation.getMasterFingerprint());
|
||||||
|
keyBuilder.append("/");
|
||||||
|
}
|
||||||
|
keyBuilder.append(keyDerivation.getDerivationPath().replaceFirst("^m?/", ""));
|
||||||
|
keyBuilder.append("]");
|
||||||
|
}
|
||||||
|
|
||||||
|
keyBuilder.append(pubKey.toString());
|
||||||
|
String childDerivation = mapChildrenDerivations.get(pubKey);
|
||||||
|
if(childDerivation != null) {
|
||||||
|
if(!childDerivation.startsWith("/")) {
|
||||||
|
keyBuilder.append("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
keyBuilder.append(childDerivation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyBuilder.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1102,9 +1102,12 @@ public enum ScriptType {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ScriptType fromDescriptor(String descriptor) {
|
public static ScriptType fromDescriptor(String descriptor) {
|
||||||
for(ScriptType type : values()) {
|
List<ScriptType> scriptTypes = Arrays.asList(values());
|
||||||
if(type.getDescriptor().equals(descriptor.toLowerCase())) {
|
scriptTypes.sort((o1, o2) -> o2.getDescriptor().length() - o1.getDescriptor().length());
|
||||||
return type;
|
|
||||||
|
for(ScriptType scriptType : scriptTypes) {
|
||||||
|
if(descriptor.toLowerCase().startsWith(scriptType.getDescriptor())) {
|
||||||
|
return scriptType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,9 @@ package com.sparrowwallet.drongo;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class OutputDescriptorTest {
|
public class OutputDescriptorTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -44,10 +47,48 @@ 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(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)", descriptor.toString());
|
Assert.assertEquals("pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)", descriptor.toString());
|
||||||
ExtendedKey extendedPublicKey = descriptor.getSingletonExtendedPublicKey();
|
ExtendedKey extendedPublicKey = descriptor.getSingletonExtendedPublicKey();
|
||||||
KeyDerivation derivation = descriptor.getKeyDerivation(extendedPublicKey);
|
KeyDerivation derivation = descriptor.getKeyDerivation(extendedPublicKey);
|
||||||
Assert.assertEquals("d34db33f", derivation.getMasterFingerprint());
|
Assert.assertEquals("d34db33f", derivation.getMasterFingerprint());
|
||||||
Assert.assertEquals("m/44'/0'/0'", derivation.getDerivationPath());
|
Assert.assertEquals("m/44'/0'/0'", derivation.getDerivationPath());
|
||||||
|
Assert.assertEquals("14qCH92HCyDDBFFZdhDt1WMfrMDYnBFYMF", descriptor.getAddress(descriptor.getChangeDerivation(0)).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void singleP2SH_P2WPKH() {
|
||||||
|
OutputDescriptor descriptor = OutputDescriptor.getOutputDescriptor("sh(wpkh([f09a3b29/49h/0h/0h]xpub6CjUWYtkq9KT1zkM5NPMxoJTCMm8JSFw7JPyMG6YLBzv5AsCTkASnsVyJhqL1aaqF5XSsFinHK3FDi8RoeEWcTG3DQA2TjqrZ6HJtatYbsU/0/*))");
|
||||||
|
Assert.assertEquals("sh(wpkh([f09a3b29/49'/0'/0']xpub6CjUWYtkq9KT1zkM5NPMxoJTCMm8JSFw7JPyMG6YLBzv5AsCTkASnsVyJhqL1aaqF5XSsFinHK3FDi8RoeEWcTG3DQA2TjqrZ6HJtatYbsU/0/*))", descriptor.toString());
|
||||||
|
ExtendedKey extendedPublicKey = descriptor.getSingletonExtendedPublicKey();
|
||||||
|
KeyDerivation derivation = descriptor.getKeyDerivation(extendedPublicKey);
|
||||||
|
Assert.assertEquals("f09a3b29", derivation.getMasterFingerprint());
|
||||||
|
Assert.assertEquals("m/49'/0'/0'", derivation.getDerivationPath());
|
||||||
|
Assert.assertEquals("31sNBFoYAaFggvNBAnnnLAc5ygfjZRCK6s", descriptor.getAddress(descriptor.getChangeDerivation(0)).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void multisigP2WSH() {
|
||||||
|
OutputDescriptor descriptor = OutputDescriptor.getOutputDescriptor("wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/*))");
|
||||||
|
Assert.assertEquals("wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/*))", descriptor.toString());
|
||||||
|
Assert.assertEquals(2, descriptor.getMultisigThreshold());
|
||||||
|
Assert.assertEquals("bc1qf5l7g5t5v2tp2wnwfeqlktkds7zvprmm7afjn6f85fdesc2pwedsh42kcl", descriptor.getAddress(KeyDerivation.parsePath("0/0")).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void multisigP2WSH2() {
|
||||||
|
OutputDescriptor descriptor = OutputDescriptor.getOutputDescriptor("wsh(sortedmulti(2,[04fefef0/48h/0h/0h/2h]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/0/*,[04ba1ef0/48h/0h/0h/2h]xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/*))");
|
||||||
|
Set<ExtendedKey> extendedPublicKeys = descriptor.getExtendedPublicKeys();
|
||||||
|
Iterator<ExtendedKey> iter = extendedPublicKeys.iterator();
|
||||||
|
ExtendedKey extendedPublicKey1 = iter.next();
|
||||||
|
Assert.assertEquals("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", extendedPublicKey1.toString());
|
||||||
|
KeyDerivation derivation = descriptor.getKeyDerivation(extendedPublicKey1);
|
||||||
|
Assert.assertEquals("04fefef0", derivation.getMasterFingerprint());
|
||||||
|
Assert.assertEquals("m/48'/0'/0'/2'", derivation.getDerivationPath());
|
||||||
|
|
||||||
|
ExtendedKey extendedPublicKey2 = iter.next();
|
||||||
|
Assert.assertEquals("xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", extendedPublicKey2.toString());
|
||||||
|
KeyDerivation derivation2 = descriptor.getKeyDerivation(extendedPublicKey2);
|
||||||
|
Assert.assertEquals("04ba1ef0", derivation2.getMasterFingerprint());
|
||||||
|
Assert.assertEquals("m/48'/0'/0'/2'", derivation2.getDerivationPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue