add support for named xpubs in output descriptors

This commit is contained in:
Craig Raw 2023-08-30 14:21:06 +02:00
parent 8313d16e97
commit bae4ce6605
2 changed files with 53 additions and 0 deletions

View file

@ -33,11 +33,16 @@ public class OutputDescriptor {
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;
private final Map<ExtendedKey, String> mapExtendedPublicKeyLabels;
public OutputDescriptor(ScriptType scriptType, ExtendedKey extendedPublicKey, KeyDerivation keyDerivation) { public OutputDescriptor(ScriptType scriptType, ExtendedKey extendedPublicKey, KeyDerivation keyDerivation) {
this(scriptType, Collections.singletonMap(extendedPublicKey, keyDerivation)); this(scriptType, Collections.singletonMap(extendedPublicKey, keyDerivation));
} }
public OutputDescriptor(ScriptType scriptType, ExtendedKey extendedPublicKey, KeyDerivation keyDerivation, String extendedPublicKeyLabel) {
this(scriptType, 0, Collections.singletonMap(extendedPublicKey, keyDerivation), new LinkedHashMap<>(), extendedPublicKeyLabel == null ? new LinkedHashMap<>() : Collections.singletonMap(extendedPublicKey, extendedPublicKeyLabel));
}
public OutputDescriptor(ScriptType scriptType, Map<ExtendedKey, KeyDerivation> extendedPublicKeys) { public OutputDescriptor(ScriptType scriptType, Map<ExtendedKey, KeyDerivation> extendedPublicKeys) {
this(scriptType, 0, extendedPublicKeys); this(scriptType, 0, extendedPublicKeys);
} }
@ -47,10 +52,15 @@ public class OutputDescriptor {
} }
public OutputDescriptor(ScriptType scriptType, 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(scriptType, multisigThreshold, extendedPublicKeys, mapChildrenDerivations, new LinkedHashMap<>());
}
public OutputDescriptor(ScriptType scriptType, int multisigThreshold, Map<ExtendedKey, KeyDerivation> extendedPublicKeys, Map<ExtendedKey, String> mapChildrenDerivations, Map<ExtendedKey, String> mapExtendedPublicKeyLabels) {
this.scriptType = scriptType; this.scriptType = scriptType;
this.multisigThreshold = multisigThreshold; this.multisigThreshold = multisigThreshold;
this.extendedPublicKeys = extendedPublicKeys; this.extendedPublicKeys = extendedPublicKeys;
this.mapChildrenDerivations = mapChildrenDerivations; this.mapChildrenDerivations = mapChildrenDerivations;
this.mapExtendedPublicKeyLabels = mapExtendedPublicKeyLabels;
} }
public Set<ExtendedKey> getExtendedPublicKeys() { public Set<ExtendedKey> getExtendedPublicKeys() {
@ -69,6 +79,10 @@ public class OutputDescriptor {
return mapChildrenDerivations.get(extendedPublicKey); return mapChildrenDerivations.get(extendedPublicKey);
} }
public String getExtendedPublicKeyLabel(ExtendedKey extendedPublicKey) {
return mapExtendedPublicKeyLabels.get(extendedPublicKey);
}
public boolean describesMultipleAddresses(ExtendedKey extendedPublicKey) { public boolean describesMultipleAddresses(ExtendedKey extendedPublicKey) {
return getChildDerivationPath(extendedPublicKey) == null || getChildDerivationPath(extendedPublicKey).endsWith("/*"); return getChildDerivationPath(extendedPublicKey) == null || getChildDerivationPath(extendedPublicKey).endsWith("/*");
} }
@ -246,6 +260,7 @@ public class OutputDescriptor {
keystore.setWalletModel(WalletModel.SPARROW); keystore.setWalletModel(WalletModel.SPARROW);
keystore.setKeyDerivation(extKeyEntry.getValue()); keystore.setKeyDerivation(extKeyEntry.getValue());
keystore.setExtendedPublicKey(extKeyEntry.getKey()); keystore.setExtendedPublicKey(extKeyEntry.getKey());
setKeystoreLabel(keystore);
wallet.makeLabelsUnique(keystore); wallet.makeLabelsUnique(keystore);
wallet.getKeystores().add(keystore); wallet.getKeystores().add(keystore);
} }
@ -269,12 +284,23 @@ public class OutputDescriptor {
Keystore keystore = new Keystore(); Keystore keystore = new Keystore();
keystore.setKeyDerivation(new KeyDerivation(masterFingerprint, KeyDerivation.writePath(getKeyDerivation(extendedKey).getDerivation()))); keystore.setKeyDerivation(new KeyDerivation(masterFingerprint, KeyDerivation.writePath(getKeyDerivation(extendedKey).getDerivation())));
keystore.setExtendedPublicKey(extendedKey); keystore.setExtendedPublicKey(extendedKey);
setKeystoreLabel(keystore);
wallet.getKeystores().add(keystore); wallet.getKeystores().add(keystore);
wallet.setDefaultPolicy(Policy.getPolicy(isCosigner() ? PolicyType.MULTI : PolicyType.SINGLE, wallet.getScriptType(), wallet.getKeystores(), 1)); wallet.setDefaultPolicy(Policy.getPolicy(isCosigner() ? PolicyType.MULTI : PolicyType.SINGLE, wallet.getScriptType(), wallet.getKeystores(), 1));
return wallet; return wallet;
} }
public void setKeystoreLabel(Keystore keystore) {
if(keystore.getExtendedPublicKey() != null && mapExtendedPublicKeyLabels.get(keystore.getExtendedPublicKey()) != null) {
String label = mapExtendedPublicKeyLabels.get(keystore.getExtendedPublicKey()).trim();
if(label.length() > Keystore.MAX_LABEL_LENGTH) {
label = label.substring(0, Keystore.MAX_LABEL_LENGTH);
}
keystore.setLabel(label);
}
}
public static String toDescriptorString(Address address) { public static String toDescriptorString(Address address) {
return "addr(" + address + ")"; return "addr(" + address + ")";
} }

View file

@ -1,9 +1,13 @@
package com.sparrowwallet.drongo; package com.sparrowwallet.drongo;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Wallet;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set; import java.util.Set;
public class OutputDescriptorTest { public class OutputDescriptorTest {
@ -107,4 +111,27 @@ public class OutputDescriptorTest {
public void testPubKeyMulti() { public void testPubKeyMulti() {
OutputDescriptor descriptor = OutputDescriptor.getOutputDescriptor("sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))"); OutputDescriptor descriptor = OutputDescriptor.getOutputDescriptor("sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))");
} }
@Test
public void testUniqueLabels() {
Map<ExtendedKey, KeyDerivation> extendedKeys = new LinkedHashMap<>();
Map<ExtendedKey, String> extendedKeyLabels = new LinkedHashMap<>();
ExtendedKey ext1 = ExtendedKey.fromDescriptor("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB");
KeyDerivation kd1 = new KeyDerivation("04fefef0", "m/48'/0'/0'/2'");
extendedKeys.put(ext1, kd1);
extendedKeyLabels.put(ext1, "Unique");
ExtendedKey ext2 = ExtendedKey.fromDescriptor("xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH");
KeyDerivation kd2 = new KeyDerivation("04ba1ef0", "m/48'/0'/0'/2'");
extendedKeys.put(ext2, kd2);
extendedKeyLabels.put(ext2, "Unique");
OutputDescriptor descriptor = new OutputDescriptor(ScriptType.P2WSH, 2, extendedKeys, new LinkedHashMap<>(), extendedKeyLabels);
Assert.assertEquals("wsh(sortedmulti(2,[04fefef0/48h/0h/0h/2h]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB,[04ba1ef0/48h/0h/0h/2h]xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH))", descriptor.toString());
Wallet wallet = descriptor.toWallet();
Assert.assertEquals("Unique 1", wallet.getKeystores().get(0).getLabel());
Assert.assertEquals("Unique 2", wallet.getKeystores().get(1).getLabel());
}
} }