mirror of
https://github.com/sparrowwallet/hummingbird.git
synced 2024-11-02 18:46:45 +00:00
support range and pair crypto-hdkey path components
This commit is contained in:
parent
9bc8f6b021
commit
99d779c657
8 changed files with 205 additions and 62 deletions
|
@ -1,6 +1,7 @@
|
|||
package com.sparrowwallet.hummingbird.registry;
|
||||
|
||||
import co.nstant.in.cbor.model.*;
|
||||
import com.sparrowwallet.hummingbird.registry.pathcomponent.PathComponent;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
|
@ -38,7 +39,7 @@ public class CryptoKeypath extends RegistryItem {
|
|||
|
||||
StringJoiner joiner = new StringJoiner("/");
|
||||
for(PathComponent component : components) {
|
||||
joiner.add((component.isWildcard() ? "*" : component.getIndex()) + (component.isHardened() ? "'" : ""));
|
||||
joiner.add(component.toString());
|
||||
}
|
||||
return joiner.toString();
|
||||
}
|
||||
|
@ -53,16 +54,7 @@ public class CryptoKeypath extends RegistryItem {
|
|||
|
||||
public DataItem toCbor() {
|
||||
Map map = new Map();
|
||||
Array componentArray = new Array();
|
||||
for(PathComponent pathComponent : components) {
|
||||
if(pathComponent.isWildcard()) {
|
||||
componentArray.add(new Array());
|
||||
} else {
|
||||
componentArray.add(new UnsignedInteger(pathComponent.getIndex()));
|
||||
}
|
||||
componentArray.add(pathComponent.isHardened() ? SimpleValue.TRUE : SimpleValue.FALSE);
|
||||
}
|
||||
map.put(new UnsignedInteger(COMPONENTS_KEY), componentArray);
|
||||
map.put(new UnsignedInteger(COMPONENTS_KEY), PathComponent.toCbor(components));
|
||||
if(sourceFingerprint != null) {
|
||||
map.put(new UnsignedInteger(SOURCE_FINGERPRINT_KEY), new UnsignedInteger(new BigInteger(1, sourceFingerprint)));
|
||||
}
|
||||
|
@ -87,17 +79,7 @@ public class CryptoKeypath extends RegistryItem {
|
|||
UnsignedInteger uintKey = (UnsignedInteger)key;
|
||||
int intKey = uintKey.getValue().intValue();
|
||||
if(intKey == COMPONENTS_KEY) {
|
||||
Array componentArray = (Array)map.get(key);
|
||||
for(int i = 0; i < componentArray.getDataItems().size(); i+=2) {
|
||||
boolean hardened = (componentArray.getDataItems().get(i+1) == SimpleValue.TRUE);
|
||||
DataItem pathSeg = componentArray.getDataItems().get(i);
|
||||
if(pathSeg instanceof UnsignedInteger) {
|
||||
UnsignedInteger uintIndex = (UnsignedInteger)pathSeg;
|
||||
components.add(new PathComponent(uintIndex.getValue().intValue(), hardened));
|
||||
} else if(pathSeg instanceof Array) {
|
||||
components.add(new PathComponent(hardened));
|
||||
}
|
||||
}
|
||||
components = PathComponent.fromCbor(map.get(key));
|
||||
} else if(intKey == SOURCE_FINGERPRINT_KEY) {
|
||||
sourceFingerprint = bigIntegerToBytes(((UnsignedInteger)map.get(key)).getValue(), 4);
|
||||
} else if(intKey == DEPTH_KEY) {
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
package com.sparrowwallet.hummingbird.registry;
|
||||
|
||||
public class PathComponent {
|
||||
public static final int HARDENED_BIT = 0x80000000;
|
||||
|
||||
private final int index;
|
||||
private final boolean wildcard;
|
||||
private final boolean hardened;
|
||||
|
||||
public PathComponent(int index, boolean hardened) {
|
||||
this.index = index;
|
||||
this.wildcard = false;
|
||||
this.hardened = hardened;
|
||||
|
||||
if((index & HARDENED_BIT) != 0) {
|
||||
throw new IllegalArgumentException("Invalid index " + index + " - most significant bit cannot be set");
|
||||
}
|
||||
}
|
||||
|
||||
public PathComponent(boolean hardened) {
|
||||
this.index = 0;
|
||||
this.wildcard = true;
|
||||
this.hardened = hardened;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public boolean isWildcard() {
|
||||
return wildcard;
|
||||
}
|
||||
|
||||
public boolean isHardened() {
|
||||
return hardened;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.sparrowwallet.hummingbird.registry.pathcomponent;
|
||||
|
||||
public class IndexPathComponent extends PathComponent {
|
||||
private final int index;
|
||||
private final boolean hardened;
|
||||
|
||||
public IndexPathComponent(int index, boolean hardened) {
|
||||
this.index = index;
|
||||
this.hardened = hardened;
|
||||
|
||||
if((index & HARDENED_BIT) != 0) {
|
||||
throw new IllegalArgumentException("Invalid index " + index + " - most significant bit cannot be set");
|
||||
}
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public boolean isHardened() {
|
||||
return hardened;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return index + (hardened ? "'" : "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.sparrowwallet.hummingbird.registry.pathcomponent;
|
||||
|
||||
public class PairPathComponent extends PathComponent {
|
||||
private final IndexPathComponent external;
|
||||
private final IndexPathComponent internal;
|
||||
|
||||
public PairPathComponent(IndexPathComponent external, IndexPathComponent internal) {
|
||||
this.external = external;
|
||||
this.internal = internal;
|
||||
}
|
||||
|
||||
public IndexPathComponent getExternal() {
|
||||
return external;
|
||||
}
|
||||
|
||||
public IndexPathComponent getInternal() {
|
||||
return internal;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "<" + external.toString() + ";" + internal.toString() + ">";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package com.sparrowwallet.hummingbird.registry.pathcomponent;
|
||||
|
||||
import co.nstant.in.cbor.model.Array;
|
||||
import co.nstant.in.cbor.model.DataItem;
|
||||
import co.nstant.in.cbor.model.SimpleValue;
|
||||
import co.nstant.in.cbor.model.UnsignedInteger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class PathComponent {
|
||||
public static final int HARDENED_BIT = 0x80000000;
|
||||
|
||||
public static DataItem toCbor(List<PathComponent> components) {
|
||||
Array componentArray = new Array();
|
||||
for(PathComponent pathComponent : components) {
|
||||
if(pathComponent instanceof WildcardPathComponent) {
|
||||
WildcardPathComponent wildcardPathComponent = (WildcardPathComponent)pathComponent;
|
||||
componentArray.add(new Array());
|
||||
componentArray.add(wildcardPathComponent.isHardened() ? SimpleValue.TRUE : SimpleValue.FALSE);
|
||||
} else if(pathComponent instanceof RangePathComponent) {
|
||||
RangePathComponent rangePathComponent = (RangePathComponent)pathComponent;
|
||||
Array array = new Array();
|
||||
array.add(new UnsignedInteger(rangePathComponent.getStart()));
|
||||
array.add(new UnsignedInteger(rangePathComponent.getEnd()));
|
||||
componentArray.add(array);
|
||||
componentArray.add(rangePathComponent.isHardened() ? SimpleValue.TRUE : SimpleValue.FALSE);
|
||||
} else if(pathComponent instanceof PairPathComponent) {
|
||||
PairPathComponent pairPathComponent = (PairPathComponent)pathComponent;
|
||||
Array array = new Array();
|
||||
array.add(new UnsignedInteger(pairPathComponent.getExternal().getIndex()));
|
||||
array.add(pairPathComponent.getExternal().isHardened() ? SimpleValue.TRUE : SimpleValue.FALSE);
|
||||
array.add(new UnsignedInteger(pairPathComponent.getInternal().getIndex()));
|
||||
array.add(pairPathComponent.getInternal().isHardened() ? SimpleValue.TRUE : SimpleValue.FALSE);
|
||||
componentArray.add(array);
|
||||
} else if(pathComponent instanceof IndexPathComponent) {
|
||||
IndexPathComponent indexPathComponent = (IndexPathComponent)pathComponent;
|
||||
componentArray.add(new UnsignedInteger(indexPathComponent.getIndex()));
|
||||
componentArray.add(indexPathComponent.isHardened() ? SimpleValue.TRUE : SimpleValue.FALSE);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown path component of " + pathComponent.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
return componentArray;
|
||||
}
|
||||
|
||||
public static List<PathComponent> fromCbor(DataItem item) {
|
||||
List<PathComponent> components = new ArrayList<>();
|
||||
|
||||
Array componentArray = (Array)item;
|
||||
for(int i = 0; i < componentArray.getDataItems().size(); i++) {
|
||||
DataItem component = componentArray.getDataItems().get(i);
|
||||
if(component instanceof Array) {
|
||||
Array subcomponentArray = (Array)component;
|
||||
if(subcomponentArray.getDataItems().isEmpty()) {
|
||||
boolean hardened = (componentArray.getDataItems().get(++i) == SimpleValue.TRUE);
|
||||
components.add(new WildcardPathComponent(hardened));
|
||||
} else if(subcomponentArray.getDataItems().size() == 2) {
|
||||
boolean hardened = (componentArray.getDataItems().get(++i) == SimpleValue.TRUE);
|
||||
UnsignedInteger startIndex = (UnsignedInteger)subcomponentArray.getDataItems().get(0);
|
||||
UnsignedInteger endIndex = (UnsignedInteger)subcomponentArray.getDataItems().get(1);
|
||||
components.add(new RangePathComponent(startIndex.getValue().intValue(), endIndex.getValue().intValue(), hardened));
|
||||
} else if(subcomponentArray.getDataItems().size() == 4) {
|
||||
UnsignedInteger externalIndex = (UnsignedInteger)subcomponentArray.getDataItems().get(0);
|
||||
boolean externalHardened = (subcomponentArray.getDataItems().get(1) == SimpleValue.TRUE);
|
||||
IndexPathComponent externalPathComponent = new IndexPathComponent(externalIndex.getValue().intValue(), externalHardened);
|
||||
UnsignedInteger internalIndex = (UnsignedInteger)subcomponentArray.getDataItems().get(2);
|
||||
boolean internalHardened = (subcomponentArray.getDataItems().get(3) == SimpleValue.TRUE);
|
||||
IndexPathComponent internalPathComponent = new IndexPathComponent(internalIndex.getValue().intValue(), internalHardened);
|
||||
components.add(new PairPathComponent(externalPathComponent, internalPathComponent));
|
||||
}
|
||||
} else if(component instanceof UnsignedInteger) {
|
||||
UnsignedInteger index = (UnsignedInteger)component;
|
||||
boolean hardened = (componentArray.getDataItems().get(++i) == SimpleValue.TRUE);
|
||||
components.add(new IndexPathComponent(index.getValue().intValue(), hardened));
|
||||
}
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package com.sparrowwallet.hummingbird.registry.pathcomponent;
|
||||
|
||||
public class RangePathComponent extends PathComponent {
|
||||
private final int start;
|
||||
private final int end;
|
||||
private final boolean hardened;
|
||||
|
||||
public RangePathComponent(int start, int end, boolean hardened) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.hardened = hardened;
|
||||
|
||||
if((start & HARDENED_BIT) != 0 || (end & HARDENED_BIT) != 0) {
|
||||
throw new IllegalArgumentException("Invalid range [" + start + ", " + end + "] - most significant bit cannot be set");
|
||||
}
|
||||
|
||||
if(start >= end) {
|
||||
throw new IllegalArgumentException("Invalid range [" + start + ", " + end + "] - start must be lower than end");
|
||||
}
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public boolean isHardened() {
|
||||
return hardened;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[" + start + (hardened ? "'" : "") + "-" + end + (hardened ? "'" : "") + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.sparrowwallet.hummingbird.registry.pathcomponent;
|
||||
|
||||
public class WildcardPathComponent extends PathComponent {
|
||||
private final boolean hardened;
|
||||
|
||||
public WildcardPathComponent(boolean hardened) {
|
||||
this.hardened = hardened;
|
||||
}
|
||||
|
||||
public boolean isHardened() {
|
||||
return hardened;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "*";
|
||||
}
|
||||
}
|
|
@ -3,9 +3,7 @@ package com.sparrowwallet.hummingbird.registry;
|
|||
import co.nstant.in.cbor.CborDecoder;
|
||||
import co.nstant.in.cbor.CborException;
|
||||
import co.nstant.in.cbor.model.DataItem;
|
||||
import com.sparrowwallet.hummingbird.TestUtils;
|
||||
import com.sparrowwallet.hummingbird.UR;
|
||||
import com.sparrowwallet.hummingbird.UREncoder;
|
||||
import com.sparrowwallet.hummingbird.*;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -159,4 +157,18 @@ public class CryptoAccountTest {
|
|||
String encoded = UREncoder.encode(ur);
|
||||
Assert.assertEquals("ur:crypto-account/oeadcyemrewytyaolntaadmutaaddloxaxhdclaxwmfmdeiamecsdsemgtvsjzcncygrkowtrontzschgezokstswkkscfmklrtauteyaahdcxiehfonurdppfyntapejpproypegrdawkgmaewejlsfdtsrfybdehcaflmtrlbdhpamtaaddyoyadlncsdwykaeykaeykaycynlytsnyltaadmhtaadmwtaaddloxaxhdclaostvelfemdyynwydwyaievosrgmambklovabdgypdglldvespsthysadamhpmjeinaahdcxntdllnaaeykoytdacygegwhgjsiyonpywmcmrpwphsvodsrerozsbyaxluzcoxdpamtaaddyoyadlncsehykaeykaeykaycypdbskeuytaadmwtaaddloxaxhdclaxzcfxeegdrpmogrgwkbzctlttweadkiengrwlhtprremouoluutqdpfbncedkynfhaahdcxjpwevdeogthttkmeswzcolcpsaahcfnshkhtehytclmnteatmoteadtlwynnftloamtaaddyoyadlncsghykaeykaeykaycybthlvytstaadmhtaaddloxaxhdclaxhhsnhdrpftdwuocntilydibehnecmovdfekpjkclcslasbhkpawsaddmcmmnahnyaahdcxlotedtndfymyltclhlmtpfsadscnhtztaolbnnkistaedegwfmmedreetnwmcycnamtaaddyoyadlfcsdpykaycyemrewytytaadmhtaadmetaaddloxaxhdclaxdwkswmztpytnswtsecnblfbayajkdldeclqzzolrsnhljedsgminetytbnahatbyaahdcxkkguwsvyimjkvwteytwztyswvendtpmncpasfrrylprnhtkblndrgrmkoyjtbkrpamtaaddyoyadlocsdyykaeykaeykadykaycyhkrpnddrtaadmetaaddloxaxhdclaohnhffmvsbndslrfgclpfjejyatbdpebacnzokotofxntaoemvskpaowmryfnotfgaahdcxdlnbvecentssfsssgylnhkrstoytecrdlyadrekirfaybglahltalsrfcaeerobwamtaaddyoyadlocsdyykaeykaeykaoykaycyhkrpnddrgdaogykb", encoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPairPathComponent() throws Exception {
|
||||
String ur = "ur:crypto-account/oeadcylpvefyjeaolttaadeetaadmutaaddlonaxhdclaxhkfzdphtkplevtqzprkgnnsacagesgctzmspytctdstocsbgmkamurrdpffmtsseaahdcxpdnbdysgfeaycfsegwmhwfjewzfwdmesrlqdhglagahkytcflbrtsedraefwbweyamtaaddyoeadlncsdwykaeykaeykaocylpvefyjeattaaddyoyadlslraewkadwklawkaycypsjpsbfntaadeetaadmhtaadmwtaaddlonaxhdclaouypakomsrlhlqzvwlymesadevybkueqdoxhdcximrdhgfhtybwvyhdkeyklddpmwaahdcxwnvsaxgsdldtmurknyfpcedyiaiopautyafrpejoaxjncfhhhtpfdetteotnknsramtaaddyoeadlncsehykaeykaeykaocylpvefyjeattaaddyoyadlslraewkadwklawkaycyoeyagmmstaadeetaadmwtaaddlonaxhdclaorernoyonguhlfsdecnmohnuydwnnchjpuyroftdnaadkatcfkirdtkispsiajycxaahdcxlyutkbnymklbdskesbihbelysedwlupklfmhnntlfwsegsvwwspkghkgvwheiejoamtaaddyoeadlncsghykaeykaeykaocylpvefyjeattaaddyoyadlslraewkadwklawkaycywyqzwzkptaadeetaadmhtaadnytaaddlonaxhdclaoiorpcxecynfloxtamhlsaarkkotpclcydahtamluhnimkertlgftknoerymobaneaahdcxrsatfdqdenvtspvdieindaztrpcazsknzsoywkghfhtyswdkkpjstbneayfncxoxamtaaddyoeadlfcsdpykaocylpvefyjeattaaddyoyadlslraewkadwklawkaycylpvefyjetaadeetaadmhtaadmetaadnytaaddlonaxhdclaoldpfglrkwntlvwlarsdmnbzefmghkeeeoteerdpfframuoidstpacswmnnutcwkkaahdcxhkrozmjneceoldplnluetscyjnbdhsldenontncafgdkmdvlsnidamlnvwfzsstaamtaaddyoeadlocsdyykaeykaeykadykaocylpvefyjeattaaddyoyadlslraewkadwklawkaycyrhimryuotaadeetaadmetaadnytaaddlonaxhdclaowysfahwsqdclsnfmwmjlgoeerhzoteherosnmyhkinolrlspdadsasswlarpvtpfaahdcxwybnryoewshycwghlfhhwtbscxpyylenpkolmoftgymksbdpvsgtlbdefhrswnyaamtaaddyoeadlocsdyykaeykaeykaoykaocylpvefyjeattaaddyoyadlslraewkadwklawkaycyrhimryuotaadeetaadnltaaddlonaxhdclaxpmctzmjzemjspsplgrlgtyvssffrzcrlgogojnmncwbdtytelblyhlflmtdnkifdaahdcxgytkgujnjtaszejprdpmoskseehkamvlcersoxdlghsglagmjkjewmrlpfonwldwamtaaddyoeadlncshfykaeykaeykaocylpvefyjeattaaddyoyadlslraewkadwklawkaycynsmwdtfzryrorncp";
|
||||
URDecoder urDecoder = new URDecoder();
|
||||
urDecoder.receivePart(ur);
|
||||
URDecoder.Result urResult = urDecoder.getResult();
|
||||
if(urResult.type == ResultType.SUCCESS) {
|
||||
if(urResult.ur.getRegistryType().equals(RegistryType.CRYPTO_ACCOUNT)) {
|
||||
CryptoAccount cryptoAccount = (CryptoAccount)urResult.ur.decodeFromRegistry();
|
||||
Assert.assertEquals("<0;1>/*", cryptoAccount.getOutputDescriptors().get(0).getHdKey().getChildren().getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue