mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 01:56:44 +00:00
introduce multipath derivations to output descriptors and use h for hardened indexes
This commit is contained in:
parent
ebf7128ae5
commit
8e49247832
3 changed files with 50 additions and 13 deletions
|
@ -68,13 +68,17 @@ public class KeyDerivation {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String writePath(List<ChildNumber> pathList) {
|
public static String writePath(List<ChildNumber> pathList) {
|
||||||
String path = "m";
|
return writePath(pathList, true);
|
||||||
for (ChildNumber child: pathList) {
|
|
||||||
path += "/";
|
|
||||||
path += child.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
public static String writePath(List<ChildNumber> pathList, boolean useApostrophes) {
|
||||||
|
StringBuilder path = new StringBuilder("m");
|
||||||
|
for(ChildNumber child: pathList) {
|
||||||
|
path.append("/");
|
||||||
|
path.append(child.toString(useApostrophes));
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isValid(String derivationPath) {
|
public static boolean isValid(String derivationPath) {
|
||||||
|
|
|
@ -22,10 +22,11 @@ public class OutputDescriptor {
|
||||||
private static final String INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
|
private static final String INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
|
||||||
private static final String CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
private static final String CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
||||||
|
|
||||||
private static final Pattern XPUB_PATTERN = Pattern.compile("(\\[[^\\]]+\\])?(.pub[^/\\,)]{100,112})(/[/\\d*'hH]+)?");
|
private static final Pattern XPUB_PATTERN = Pattern.compile("(\\[[^\\]]+\\])?(.pub[^/\\,)]{100,112})(/[/\\d*'hH<>;]+)?");
|
||||||
private static final Pattern PUBKEY_PATTERN = Pattern.compile("(\\[[^\\]]+\\])?(0[23][0-9a-fA-F]{32})");
|
private static final Pattern PUBKEY_PATTERN = Pattern.compile("(\\[[^\\]]+\\])?(0[23][0-9a-fA-F]{32})");
|
||||||
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-Fa-f0-9]{8})([/\\d'hH]+)\\]");
|
private static final Pattern KEY_ORIGIN_PATTERN = Pattern.compile("\\[([A-Fa-f0-9]{8})([/\\d'hH]+)\\]");
|
||||||
|
private static final Pattern MULTIPATH_PATTERN = Pattern.compile("<([\\d*'hH;]+)>");
|
||||||
private static final Pattern CHECKSUM_PATTERN = Pattern.compile("#([" + CHECKSUM_CHARSET + "]{8})$");
|
private static final Pattern CHECKSUM_PATTERN = Pattern.compile("#([" + CHECKSUM_CHARSET + "]{8})$");
|
||||||
|
|
||||||
private final ScriptType scriptType;
|
private final ScriptType scriptType;
|
||||||
|
@ -258,12 +259,25 @@ public class OutputDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OutputDescriptor getOutputDescriptor(Wallet wallet, KeyPurpose keyPurpose, Integer index) {
|
public static OutputDescriptor getOutputDescriptor(Wallet wallet, KeyPurpose keyPurpose, Integer index) {
|
||||||
|
return getOutputDescriptor(wallet, keyPurpose == null ? null : List.of(keyPurpose), index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OutputDescriptor getOutputDescriptor(Wallet wallet, List<KeyPurpose> keyPurposes, Integer index) {
|
||||||
Map<ExtendedKey, KeyDerivation> extendedKeyDerivationMap = new LinkedHashMap<>();
|
Map<ExtendedKey, KeyDerivation> extendedKeyDerivationMap = new LinkedHashMap<>();
|
||||||
Map<ExtendedKey, String> extendedKeyChildDerivationMap = new LinkedHashMap<>();
|
Map<ExtendedKey, String> extendedKeyChildDerivationMap = new LinkedHashMap<>();
|
||||||
for(Keystore keystore : wallet.getKeystores()) {
|
for(Keystore keystore : wallet.getKeystores()) {
|
||||||
extendedKeyDerivationMap.put(keystore.getExtendedPublicKey(), keystore.getKeyDerivation());
|
extendedKeyDerivationMap.put(keystore.getExtendedPublicKey(), keystore.getKeyDerivation());
|
||||||
if(keyPurpose != null) {
|
if(keyPurposes != null) {
|
||||||
extendedKeyChildDerivationMap.put(keystore.getExtendedPublicKey(), keyPurpose.getPathIndex().num() + "/" + (index == null ? "*" : index));
|
String chain;
|
||||||
|
if(keyPurposes.size() == 1) {
|
||||||
|
chain = Integer.toString(keyPurposes.get(0).getPathIndex().num());
|
||||||
|
} else {
|
||||||
|
StringJoiner joiner = new StringJoiner(";");
|
||||||
|
keyPurposes.forEach(keyPurpose -> joiner.add(Integer.toString(keyPurpose.getPathIndex().num())));
|
||||||
|
chain = "<" + joiner + ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
extendedKeyChildDerivationMap.put(keystore.getExtendedPublicKey(), chain + "/" + (index == null ? "*" : index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,7 +409,7 @@ public class OutputDescriptor {
|
||||||
private static BigInteger polyMod(BigInteger c, int val)
|
private static BigInteger polyMod(BigInteger c, int val)
|
||||||
{
|
{
|
||||||
byte c0 = c.shiftRight(35).byteValue();
|
byte c0 = c.shiftRight(35).byteValue();
|
||||||
c = c.and(new BigInteger("7ffffffff", 16)).shiftLeft(5).or(BigInteger.valueOf(val));
|
c = c.and(new BigInteger("7ffffffff", 16)).shiftLeft(5).xor(BigInteger.valueOf(val));
|
||||||
|
|
||||||
if((c0 & 1) > 0) {
|
if((c0 & 1) > 0) {
|
||||||
c = c.xor(new BigInteger("f5dee51989", 16));
|
c = c.xor(new BigInteger("f5dee51989", 16));
|
||||||
|
@ -461,10 +475,10 @@ public class OutputDescriptor {
|
||||||
|
|
||||||
Utils.LexicographicByteArrayComparator lexicographicByteArrayComparator = new Utils.LexicographicByteArrayComparator();
|
Utils.LexicographicByteArrayComparator lexicographicByteArrayComparator = new Utils.LexicographicByteArrayComparator();
|
||||||
sortedKeys.sort((o1, o2) -> {
|
sortedKeys.sort((o1, o2) -> {
|
||||||
List<ChildNumber> derivation1 = KeyDerivation.parsePath(mapChildrenDerivations.get(o1));
|
List<ChildNumber> derivation1 = getDerivations(mapChildrenDerivations.get(o1)).get(0);
|
||||||
derivation1.add(0, o1.getKeyChildNumber());
|
derivation1.add(0, o1.getKeyChildNumber());
|
||||||
ECKey key1 = o1.getKey(derivation1);
|
ECKey key1 = o1.getKey(derivation1);
|
||||||
List<ChildNumber> derivation2 = KeyDerivation.parsePath(mapChildrenDerivations.get(o2));
|
List<ChildNumber> derivation2 = getDerivations(mapChildrenDerivations.get(o2)).get(0);
|
||||||
derivation2.add(0, o2.getKeyChildNumber());
|
derivation2.add(0, o2.getKeyChildNumber());
|
||||||
ECKey key2 = o2.getKey(derivation2);
|
ECKey key2 = o2.getKey(derivation2);
|
||||||
return lexicographicByteArrayComparator.compare(key1.getPubKey(), key2.getPubKey());
|
return lexicographicByteArrayComparator.compare(key1.getPubKey(), key2.getPubKey());
|
||||||
|
@ -473,6 +487,21 @@ public class OutputDescriptor {
|
||||||
return sortedKeys;
|
return sortedKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<List<ChildNumber>> getDerivations(String childDerivation) {
|
||||||
|
Matcher matcher = MULTIPATH_PATTERN.matcher(childDerivation);
|
||||||
|
if(matcher.find()) {
|
||||||
|
String multipath = matcher.group(1);
|
||||||
|
String[] paths = multipath.split(";");
|
||||||
|
List<List<ChildNumber>> derivations = new ArrayList<>();
|
||||||
|
for(String path : paths) {
|
||||||
|
derivations.add(KeyDerivation.parsePath(childDerivation.replace(matcher.group(), path)));
|
||||||
|
}
|
||||||
|
return derivations;
|
||||||
|
} else {
|
||||||
|
return List.of(KeyDerivation.parsePath(childDerivation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String toString(ExtendedKey pubKey, boolean addKeyOrigin) {
|
private String toString(ExtendedKey pubKey, boolean addKeyOrigin) {
|
||||||
StringBuilder keyBuilder = new StringBuilder();
|
StringBuilder keyBuilder = new StringBuilder();
|
||||||
KeyDerivation keyDerivation = extendedPublicKeys.get(pubKey);
|
KeyDerivation keyDerivation = extendedPublicKeys.get(pubKey);
|
||||||
|
@ -482,7 +511,7 @@ public class OutputDescriptor {
|
||||||
keyBuilder.append(keyDerivation.getMasterFingerprint());
|
keyBuilder.append(keyDerivation.getMasterFingerprint());
|
||||||
keyBuilder.append("/");
|
keyBuilder.append("/");
|
||||||
}
|
}
|
||||||
keyBuilder.append(keyDerivation.getDerivationPath().replaceFirst("^m?/", ""));
|
keyBuilder.append(keyDerivation.getDerivationPath().replaceFirst("^m?/", "").replace('\'', 'h'));
|
||||||
keyBuilder.append("]");
|
keyBuilder.append("]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,11 @@ public class ChildNumber {
|
||||||
public int i() { return i; }
|
public int i() { return i; }
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format(Locale.US, "%d%s", num(), isHardened() ? "'" : "");
|
return toString(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString(boolean useApostrophes) {
|
||||||
|
return String.format(Locale.US, "%d%s", num(), isHardened() ? (useApostrophes ? "'" : "h") : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
|
|
Loading…
Reference in a new issue