introduce multipath derivations to output descriptors and use h for hardened indexes

This commit is contained in:
Craig Raw 2021-11-13 15:04:37 +02:00
parent ebf7128ae5
commit 8e49247832
3 changed files with 50 additions and 13 deletions

View file

@ -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) {

View file

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

View file

@ -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) {