mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 10:06:45 +00:00
support creating wallets from descriptors containing master xprvs
This commit is contained in:
parent
579c86b1a7
commit
6f90d0fa82
2 changed files with 71 additions and 14 deletions
|
@ -34,6 +34,7 @@ public class OutputDescriptor {
|
||||||
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;
|
private final Map<ExtendedKey, String> mapExtendedPublicKeyLabels;
|
||||||
|
private final Map<ExtendedKey, ExtendedKey> extendedMasterPrivateKeys;
|
||||||
|
|
||||||
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));
|
||||||
|
@ -56,11 +57,16 @@ public class OutputDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutputDescriptor(ScriptType scriptType, int multisigThreshold, Map<ExtendedKey, KeyDerivation> extendedPublicKeys, Map<ExtendedKey, String> mapChildrenDerivations, Map<ExtendedKey, String> mapExtendedPublicKeyLabels) {
|
public OutputDescriptor(ScriptType scriptType, int multisigThreshold, Map<ExtendedKey, KeyDerivation> extendedPublicKeys, Map<ExtendedKey, String> mapChildrenDerivations, Map<ExtendedKey, String> mapExtendedPublicKeyLabels) {
|
||||||
|
this(scriptType, multisigThreshold, extendedPublicKeys, mapChildrenDerivations, mapExtendedPublicKeyLabels, new LinkedHashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputDescriptor(ScriptType scriptType, int multisigThreshold, Map<ExtendedKey, KeyDerivation> extendedPublicKeys, Map<ExtendedKey, String> mapChildrenDerivations, Map<ExtendedKey, String> mapExtendedPublicKeyLabels, Map<ExtendedKey, ExtendedKey> extendedMasterPrivateKeys) {
|
||||||
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;
|
this.mapExtendedPublicKeyLabels = mapExtendedPublicKeyLabels;
|
||||||
|
this.extendedMasterPrivateKeys = extendedMasterPrivateKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<ExtendedKey> getExtendedPublicKeys() {
|
public Set<ExtendedKey> getExtendedPublicKeys() {
|
||||||
|
@ -255,11 +261,26 @@ public class OutputDescriptor {
|
||||||
wallet.setScriptType(scriptType);
|
wallet.setScriptType(scriptType);
|
||||||
|
|
||||||
for(Map.Entry<ExtendedKey,KeyDerivation> extKeyEntry : extendedPublicKeys.entrySet()) {
|
for(Map.Entry<ExtendedKey,KeyDerivation> extKeyEntry : extendedPublicKeys.entrySet()) {
|
||||||
|
ExtendedKey xpub = extKeyEntry.getKey();
|
||||||
Keystore keystore = new Keystore();
|
Keystore keystore = new Keystore();
|
||||||
keystore.setSource(KeystoreSource.SW_WATCH);
|
if(extendedMasterPrivateKeys.containsKey(xpub)) {
|
||||||
keystore.setWalletModel(WalletModel.SPARROW);
|
ExtendedKey xprv = extendedMasterPrivateKeys.get(xpub);
|
||||||
keystore.setKeyDerivation(extKeyEntry.getValue());
|
MasterPrivateExtendedKey masterPrivateExtendedKey = new MasterPrivateExtendedKey(xprv.getKey().getPrivKeyBytes(), xprv.getKey().getChainCode());
|
||||||
keystore.setExtendedPublicKey(extKeyEntry.getKey());
|
String childDerivation = mapChildrenDerivations.get(xpub) == null ? scriptType.getDefaultDerivationPath() : mapChildrenDerivations.get(xpub);
|
||||||
|
if(childDerivation.endsWith("/0/*") || childDerivation.endsWith("/1/*")) {
|
||||||
|
childDerivation = childDerivation.substring(0, childDerivation.length() - 4);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
keystore = Keystore.fromMasterPrivateExtendedKey(masterPrivateExtendedKey, KeyDerivation.parsePath(childDerivation));
|
||||||
|
} catch(MnemonicException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
keystore.setSource(KeystoreSource.SW_WATCH);
|
||||||
|
keystore.setWalletModel(WalletModel.SPARROW);
|
||||||
|
keystore.setKeyDerivation(extKeyEntry.getValue());
|
||||||
|
keystore.setExtendedPublicKey(xpub);
|
||||||
|
}
|
||||||
setKeystoreLabel(keystore);
|
setKeystoreLabel(keystore);
|
||||||
wallet.makeLabelsUnique(keystore);
|
wallet.makeLabelsUnique(keystore);
|
||||||
wallet.getKeystores().add(keystore);
|
wallet.getKeystores().add(keystore);
|
||||||
|
@ -377,11 +398,12 @@ public class OutputDescriptor {
|
||||||
|
|
||||||
Map<ExtendedKey, KeyDerivation> keyDerivationMap = new LinkedHashMap<>();
|
Map<ExtendedKey, KeyDerivation> keyDerivationMap = new LinkedHashMap<>();
|
||||||
Map<ExtendedKey, String> keyChildDerivationMap = new LinkedHashMap<>();
|
Map<ExtendedKey, String> keyChildDerivationMap = new LinkedHashMap<>();
|
||||||
|
Map<ExtendedKey, ExtendedKey> masterPrivateKeyMap = 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;
|
String extKey;
|
||||||
String childDerivationPath = null;
|
String childDerivationPath = null;
|
||||||
|
|
||||||
if(matcher.group(1) != null) {
|
if(matcher.group(1) != null) {
|
||||||
|
@ -397,23 +419,28 @@ public class OutputDescriptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extPubKey = matcher.group(2);
|
extKey = matcher.group(2);
|
||||||
if(matcher.group(3) != null) {
|
if(matcher.group(3) != null) {
|
||||||
childDerivationPath = matcher.group(3);
|
childDerivationPath = matcher.group(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyDerivation keyDerivation = new KeyDerivation(masterFingerprint, keyDerivationPath);
|
KeyDerivation keyDerivation = new KeyDerivation(masterFingerprint, keyDerivationPath);
|
||||||
try {
|
try {
|
||||||
ExtendedKey extendedPublicKey = ExtendedKey.fromDescriptor(extPubKey);
|
ExtendedKey extendedKey = ExtendedKey.fromDescriptor(extKey);
|
||||||
if(extendedPublicKey.getKey().hasPrivKey()) {
|
if(extendedKey.getKey().hasPrivKey()) {
|
||||||
|
ExtendedKey privateExtendedKey = extendedKey;
|
||||||
List<ChildNumber> derivation = keyDerivation.getDerivation();
|
List<ChildNumber> derivation = keyDerivation.getDerivation();
|
||||||
int depth = derivation.size() == 0 ? scriptType.getDefaultDerivation().size() : derivation.size();
|
int depth = derivation.size() == 0 ? scriptType.getDefaultDerivation().size() : derivation.size();
|
||||||
DeterministicKey prvKey = extendedPublicKey.getKey();
|
DeterministicKey prvKey = extendedKey.getKey();
|
||||||
DeterministicKey pubKey = new DeterministicKey(prvKey.getPath(), prvKey.getChainCode(), prvKey.getPubKey(), depth, extendedPublicKey.getParentFingerprint());
|
DeterministicKey pubKey = new DeterministicKey(prvKey.getPath(), prvKey.getChainCode(), prvKey.getPubKey(), depth, extendedKey.getParentFingerprint());
|
||||||
extendedPublicKey = new ExtendedKey(pubKey, pubKey.getParentFingerprint(), extendedPublicKey.getKeyChildNumber());
|
extendedKey = new ExtendedKey(pubKey, pubKey.getParentFingerprint(), extendedKey.getKeyChildNumber());
|
||||||
|
|
||||||
|
if(derivation.size() == 0) {
|
||||||
|
masterPrivateKeyMap.put(extendedKey, privateExtendedKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
keyDerivationMap.put(extendedPublicKey, keyDerivation);
|
keyDerivationMap.put(extendedKey, keyDerivation);
|
||||||
keyChildDerivationMap.put(extendedPublicKey, childDerivationPath);
|
keyChildDerivationMap.put(extendedKey, childDerivationPath);
|
||||||
} catch(ProtocolException e) {
|
} catch(ProtocolException e) {
|
||||||
throw new ProtocolException("Invalid xpub: " + e.getMessage());
|
throw new ProtocolException("Invalid xpub: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -426,7 +453,7 @@ public class OutputDescriptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OutputDescriptor(scriptType, multisigThreshold, keyDerivationMap, keyChildDerivationMap);
|
return new OutputDescriptor(scriptType, multisigThreshold, keyDerivationMap, keyChildDerivationMap, new LinkedHashMap<>(), masterPrivateKeyMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String normalize(String descriptor) {
|
public static String normalize(String descriptor) {
|
||||||
|
|
|
@ -134,4 +134,34 @@ public class OutputDescriptorTest {
|
||||||
Assert.assertEquals("Unique 1", wallet.getKeystores().get(0).getLabel());
|
Assert.assertEquals("Unique 1", wallet.getKeystores().get(0).getLabel());
|
||||||
Assert.assertEquals("Unique 2", wallet.getKeystores().get(1).getLabel());
|
Assert.assertEquals("Unique 2", wallet.getKeystores().get(1).getLabel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMasterPrivateKey() {
|
||||||
|
String desc = "wpkh(xprv9s21ZrQH143K2x63uS9B5XiQqBKDs5ke5jF7dH7cwKaAycKs72VyR7zfBAqQFAnWMwpW6w2eJKc4pKfkMebXv1qi5cs5eQ1N9n2rwbsp94g)";
|
||||||
|
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(desc);
|
||||||
|
Wallet wallet = outputDescriptor.toWallet();
|
||||||
|
Assert.assertEquals("fe05631b", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
|
||||||
|
Assert.assertEquals("m/84'/0'/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
|
||||||
|
Assert.assertEquals("xpub6DTvSp2zaQ3DHrB19BnXTPEsMhnsVPKFgb47x8tkg1VjuwkKvyEeL3Jc4ojgiVUit2ron1SqkQph1hVPtGfREGkiZ8KCbN2TGXnoXHnQ12E", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMasterPrivateKeyWithChildDerivation() {
|
||||||
|
String desc = "wpkh(xprv9s21ZrQH143K2x63uS9B5XiQqBKDs5ke5jF7dH7cwKaAycKs72VyR7zfBAqQFAnWMwpW6w2eJKc4pKfkMebXv1qi5cs5eQ1N9n2rwbsp94g/84'/1'/0'/0/*)";
|
||||||
|
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(desc);
|
||||||
|
Wallet wallet = outputDescriptor.toWallet();
|
||||||
|
Assert.assertEquals("fe05631b", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
|
||||||
|
Assert.assertEquals("m/84'/1'/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
|
||||||
|
Assert.assertEquals("xpub6BwAZuXFhV4oufDPGLi89BXMWkFSWDY8EGjLN7GReoKcBQC2MV9A6siCKefwMitca3YnvRCWKWp2RJoDeG9djtucWkH2EibPEvpm2fyNLK3", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMasterPrivateKeyWithNonBip32ChildDerivation() {
|
||||||
|
String desc = "wpkh(xprv9s21ZrQH143K2x63uS9B5XiQqBKDs5ke5jF7dH7cwKaAycKs72VyR7zfBAqQFAnWMwpW6w2eJKc4pKfkMebXv1qi5cs5eQ1N9n2rwbsp94g/84'/1'/0'/3/*)";
|
||||||
|
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(desc);
|
||||||
|
Wallet wallet = outputDescriptor.toWallet();
|
||||||
|
Assert.assertEquals("fe05631b", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
|
||||||
|
Assert.assertEquals("m/84'/1'/0'/3/0", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
|
||||||
|
Assert.assertEquals("xpub6FmRnopYz7J3zbEmKVnrxkuqQUqoL6wbAffNQJrDeXF29nJaTzUruDWbwG4Q3UR7MWpw3GfbqVnt65GbHsYJitzQpTCLkv8oh8dtcW9bNmr", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue