add ur registry objects

This commit is contained in:
Craig Raw 2020-11-10 08:54:00 +02:00
parent 5b5e2ec1a5
commit d6386ba86a
23 changed files with 1164 additions and 9 deletions

View file

@ -6,8 +6,8 @@ import co.nstant.in.cbor.CborEncoder;
import co.nstant.in.cbor.CborException; import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.ByteString; import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.DataItem; import co.nstant.in.cbor.model.DataItem;
import com.sparrowwallet.hummingbird.registry.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -18,12 +18,14 @@ import java.util.Objects;
*/ */
public class UR { public class UR {
public static final String UR_PREFIX = "ur"; public static final String UR_PREFIX = "ur";
public static final String BYTES_TYPE = "bytes";
public static final String CRYPTO_PSBT_TYPE = "crypto-psbt";
private final String type; private final String type;
private final byte[] data; private final byte[] data;
public UR(RegistryType registryType, byte[] data) throws InvalidTypeException {
this(registryType.toString(), data);
}
public UR(String type, byte[] data) throws InvalidTypeException { public UR(String type, byte[] data) throws InvalidTypeException {
if(!isURType(type)) { if(!isURType(type)) {
throw new InvalidTypeException("Invalid UR type: " + type); throw new InvalidTypeException("Invalid UR type: " + type);
@ -37,14 +39,52 @@ public class UR {
return type; return type;
} }
public byte[] getCbor() { public RegistryType getRegistryType() {
return RegistryType.fromString(type);
}
public byte[] getCborBytes() {
return data; return data;
} }
public Object decodeFromRegistry() throws InvalidCBORException {
RegistryType registryType = getRegistryType();
try {
List<DataItem> dataItems = CborDecoder.decode(getCborBytes());
DataItem item = dataItems.get(0);
if(registryType == RegistryType.BYTES) {
return ((ByteString)item).getBytes();
} else if(registryType == RegistryType.CRYPTO_SEED) {
return CryptoSeed.fromCbor(item);
} else if(registryType == RegistryType.CRYPTO_BIP39) {
return CryptoBip39.fromCbor(item);
} else if(registryType == RegistryType.CRYPTO_HDKEY) {
return CryptoHDKey.fromCbor(item);
} else if(registryType == RegistryType.CRYPTO_KEYPATH) {
return CryptoKeypath.fromCbor(item);
} else if(registryType == RegistryType.CRYPTO_COIN_INFO) {
return CryptoCoinInfo.fromCbor(item);
} else if(registryType == RegistryType.CRYPTO_ECKEY) {
return CryptoECKey.fromCbor(item);
} else if(registryType == RegistryType.CRYPTO_ADDRESS) {
return CryptoAddress.fromCbor(item);
} else if(registryType == RegistryType.CRYPTO_OUTPUT) {
return CryptoOutput.fromCbor(item);
} else if(registryType == RegistryType.CRYPTO_PSBT) {
return CryptoPSBT.fromCbor(item);
}
} catch(CborException e) {
throw new InvalidCBORException(e.getMessage());
}
return null;
}
public byte[] toBytes() throws InvalidCBORException { public byte[] toBytes() throws InvalidCBORException {
try { try {
ByteArrayInputStream bais = new ByteArrayInputStream(getCbor()); List<DataItem> dataItems = CborDecoder.decode(getCborBytes());
List<DataItem> dataItems = new CborDecoder(bais).decode();
if(!(dataItems.get(0) instanceof ByteString)) { if(!(dataItems.get(0) instanceof ByteString)) {
throw new IllegalArgumentException("First element of CBOR is not a byte string"); throw new IllegalArgumentException("First element of CBOR is not a byte string");
} }
@ -72,7 +112,7 @@ public class UR {
} }
public static UR fromBytes(byte[] data) throws InvalidTypeException, InvalidCBORException { public static UR fromBytes(byte[] data) throws InvalidTypeException, InvalidCBORException {
return fromBytes(BYTES_TYPE, data); return fromBytes(RegistryType.BYTES.toString(), data);
} }
public static UR fromBytes(String type, byte[] data) throws InvalidTypeException, InvalidCBORException { public static UR fromBytes(String type, byte[] data) throws InvalidTypeException, InvalidCBORException {

View file

@ -15,7 +15,7 @@ public class UREncoder {
public UREncoder(UR ur, int maxFragmentLen, int minFragmentLen, long firstSeqNum) { public UREncoder(UR ur, int maxFragmentLen, int minFragmentLen, long firstSeqNum) {
this.ur = ur; this.ur = ur;
this.fountainEncoder = new FountainEncoder(ur.getCbor(), maxFragmentLen, minFragmentLen, firstSeqNum); this.fountainEncoder = new FountainEncoder(ur.getCborBytes(), maxFragmentLen, minFragmentLen, firstSeqNum);
} }
public boolean isComplete() { public boolean isComplete() {
@ -48,7 +48,7 @@ public class UREncoder {
} }
public static String encode(UR ur) { public static String encode(UR ur) {
String encoded = Bytewords.encode(ur.getCbor(), Bytewords.Style.MINIMAL); String encoded = Bytewords.encode(ur.getCborBytes(), Bytewords.Style.MINIMAL);
return encodeUR(ur.getType(), encoded); return encodeUR(ur.getType(), encoded);
} }

View file

@ -0,0 +1,41 @@
package com.sparrowwallet.hummingbird.registry;
import co.nstant.in.cbor.model.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CryptoAccount {
public static final long MASTER_FINGERPRINT_KEY = 1;
public static final long OUTPUT_DESCRIPTORS_KEY = 2;
private final byte[] masterFingerprint;
private final List<CryptoOutput> outputDescriptors;
public CryptoAccount(byte[] masterFingerprint, List<CryptoOutput> outputDescriptors) {
this.masterFingerprint = Arrays.copyOfRange(masterFingerprint, masterFingerprint.length - 4, masterFingerprint.length);
this.outputDescriptors = outputDescriptors;
}
public byte[] getMasterFingerprint() {
return masterFingerprint;
}
public List<CryptoOutput> getOutputDescriptors() {
return outputDescriptors;
}
public static CryptoAccount fromCbor(DataItem cbor) {
Map cryptoAccountMap = (Map)cbor;
UnsignedInteger uintMasterFingerprint = (UnsignedInteger)cryptoAccountMap.get(new UnsignedInteger(MASTER_FINGERPRINT_KEY));
Array outputDescriptors = (Array)cryptoAccountMap.get(new UnsignedInteger(OUTPUT_DESCRIPTORS_KEY));
List<CryptoOutput> cryptoOutputs = new ArrayList<>(outputDescriptors.getDataItems().size());
for(DataItem item : outputDescriptors.getDataItems()) {
cryptoOutputs.add(CryptoOutput.fromCbor(item));
}
return new CryptoAccount(uintMasterFingerprint.getValue().toByteArray(), cryptoOutputs);
}
}

View file

@ -0,0 +1,60 @@
package com.sparrowwallet.hummingbird.registry;
import co.nstant.in.cbor.model.*;
public class CryptoAddress {
public static final long INFO = 1;
public static final long TYPE = 2;
public static final long DATA = 3;
private final CryptoCoinInfo info;
private final Type type;
private final byte[] data;
public CryptoAddress(CryptoCoinInfo info, Type type, byte[] data) {
this.info = info;
this.type = type;
this.data = data;
}
public CryptoCoinInfo getInfo() {
return info;
}
public Type getType() {
return type;
}
public byte[] getData() {
return data;
}
public static CryptoAddress fromCbor(DataItem item) {
CryptoCoinInfo info = null;
Type type = null;
byte[] data = null;
Map map = (Map)item;
for(DataItem key : map.getKeys()) {
UnsignedInteger uintKey = (UnsignedInteger)key;
int intKey = uintKey.getValue().intValue();
if(intKey == INFO) {
info = CryptoCoinInfo.fromCbor(map.get(key));
} else if(intKey == TYPE) {
type = Type.values()[((UnsignedInteger)map.get(key)).getValue().intValue()];
} else if(intKey == DATA) {
data = ((ByteString)map.get(key)).getBytes();
}
}
if(data == null) {
throw new IllegalStateException("Data is null");
}
return new CryptoAddress(info, type, data);
}
public enum Type {
P2PKH, P2SH, P2WPKH
}
}

View file

@ -0,0 +1,52 @@
package com.sparrowwallet.hummingbird.registry;
import co.nstant.in.cbor.model.*;
import java.util.ArrayList;
import java.util.List;
public class CryptoBip39 {
public static final long WORDS = 1;
public static final long LANG = 2;
private final List<String> words;
private final String language;
public CryptoBip39(List<String> words, String language) {
this.words = words;
this.language = language;
}
public List<String> getWords() {
return words;
}
public String getLanguage() {
return language;
}
public static CryptoBip39 fromCbor(DataItem item) {
List<String> words = new ArrayList<>();
String language = "en";
Map map = (Map)item;
for(DataItem key : map.getKeys()) {
UnsignedInteger uintKey = (UnsignedInteger)key;
int intKey = uintKey.getValue().intValue();
if(intKey == WORDS) {
Array wordsArray = (Array)map.get(key);
for(DataItem wordItem : wordsArray.getDataItems()) {
words.add(((UnicodeString)wordItem).getString());
}
} else if(intKey == LANG) {
language = ((UnicodeString)map.get(key)).getString();
}
}
if(words.isEmpty()) {
throw new IllegalStateException("No BIP39 words");
}
return new CryptoBip39(words, language);
}
}

View file

@ -0,0 +1,53 @@
package com.sparrowwallet.hummingbird.registry;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.Map;
import co.nstant.in.cbor.model.UnsignedInteger;
public class CryptoCoinInfo {
public static final int TYPE_KEY = 1;
public static final int NETWORK_KEY = 2;
private final int type;
private final int network;
public CryptoCoinInfo(int type, int network) {
this.type = type;
this.network = network;
}
public Type getType() {
return Type.values()[type];
}
public Network getNetwork() {
return Network.values()[network];
}
public static CryptoCoinInfo fromCbor(DataItem item) {
int type = 0;
int network = 0;
Map map = (Map)item;
for(DataItem key : map.getKeys()) {
UnsignedInteger uintKey = (UnsignedInteger)key;
int intKey = uintKey.getValue().intValue();
if(intKey == TYPE_KEY) {
type = ((UnsignedInteger)map.get(key)).getValue().intValue();
} else if(intKey == NETWORK_KEY) {
network = ((UnsignedInteger)map.get(key)).getValue().intValue();
}
}
return new CryptoCoinInfo(type, network);
}
public enum Type {
BITCOIN
}
public enum Network {
MAINNET, TESTNET
}
}

View file

@ -0,0 +1,56 @@
package com.sparrowwallet.hummingbird.registry;
import co.nstant.in.cbor.model.*;
public class CryptoECKey {
public static final long CURVE = 1;
public static final long PRIVATE = 2;
public static final long DATA = 3;
private final int curve;
private final boolean privateKey;
private final byte[] data;
public CryptoECKey(int curve, boolean privateKey, byte[] data) {
this.curve = curve;
this.privateKey = privateKey;
this.data = data;
}
public int getCurve() {
return curve;
}
public boolean isPrivateKey() {
return privateKey;
}
public byte[] getData() {
return data;
}
public static CryptoECKey fromCbor(DataItem item) {
int curve = 0;
boolean privateKey = false;
byte[] data = null;
Map map = (Map)item;
for(DataItem key : map.getKeys()) {
UnsignedInteger uintKey = (UnsignedInteger)key;
int intKey = uintKey.getValue().intValue();
if(intKey == CURVE) {
curve = ((UnsignedInteger)map.get(key)).getValue().intValue();
} else if(intKey == PRIVATE) {
privateKey = (map.get(key) == SimpleValue.TRUE);
} else if(intKey == DATA) {
data = ((ByteString)map.get(key)).getBytes();
}
}
if(data == null) {
throw new IllegalStateException("Data is null");
}
return new CryptoECKey(curve, privateKey, data);
}
}

View file

@ -0,0 +1,110 @@
package com.sparrowwallet.hummingbird.registry;
import co.nstant.in.cbor.model.*;
public class CryptoHDKey {
public static final int IS_MASTER_KEY = 1;
public static final int IS_PRIVATE_KEY = 2;
public static final int KEY_DATA_KEY = 3;
public static final int CHAIN_CODE_KEY = 4;
public static final int USE_INFO_KEY = 5;
public static final int ORIGIN_KEY = 6;
public static final int CHILDREN_KEY = 7;
private final boolean master;
private final boolean privateKey;
private final byte[] key;
private final byte[] chainCode;
private final CryptoCoinInfo useInfo;
private final CryptoKeypath origin;
private final CryptoKeypath children;
public CryptoHDKey(byte[] key, byte[] chainCode) {
this.master = true;
this.privateKey = true;
this.key = key;
this.chainCode = chainCode;
this.useInfo = null;
this.origin = null;
this.children = null;
}
public CryptoHDKey(boolean privateKey, byte[] key, byte[] chainCode, CryptoCoinInfo useInfo, CryptoKeypath origin, CryptoKeypath children) {
this.master = false;
this.privateKey = privateKey;
this.key = key;
this.chainCode = chainCode;
this.useInfo = useInfo;
this.origin = origin;
this.children = children;
}
public boolean isMaster() {
return master;
}
public boolean isPrivateKey() {
return privateKey;
}
public byte[] getKey() {
return key;
}
public byte[] getChainCode() {
return chainCode;
}
public CryptoCoinInfo getUseInfo() {
return useInfo;
}
public CryptoKeypath getOrigin() {
return origin;
}
public CryptoKeypath getChildren() {
return children;
}
public static CryptoHDKey fromCbor(DataItem item) {
boolean isMasterKey = false;
boolean isPrivateKey = false;
byte[] keyData = null;
byte[] chainCode = null;
CryptoCoinInfo useInfo = null;
CryptoKeypath origin = null;
CryptoKeypath children = null;
Map map = (Map)item;
for(DataItem key : map.getKeys()) {
UnsignedInteger uintKey = (UnsignedInteger)key;
int intKey = uintKey.getValue().intValue();
if(intKey == IS_MASTER_KEY) {
isMasterKey = (map.get(uintKey) == SimpleValue.TRUE);
} else if(intKey == IS_PRIVATE_KEY) {
isPrivateKey = (map.get(uintKey) == SimpleValue.TRUE);
} else if(intKey == KEY_DATA_KEY) {
keyData = ((ByteString)map.get(uintKey)).getBytes();
} else if(intKey == CHAIN_CODE_KEY) {
chainCode = ((ByteString)map.get(uintKey)).getBytes();
} else if(intKey == USE_INFO_KEY) {
useInfo = CryptoCoinInfo.fromCbor(map.get(uintKey));
} else if(intKey == ORIGIN_KEY) {
origin = CryptoKeypath.fromCbor(map.get(uintKey));
} else if(intKey == CHILDREN_KEY) {
children = CryptoKeypath.fromCbor(map.get(uintKey));
}
}
if(keyData == null) {
throw new IllegalStateException("Key data is null");
}
if(isMasterKey) {
return new CryptoHDKey(keyData, chainCode);
} else {
return new CryptoHDKey(isPrivateKey, keyData, chainCode, useInfo, origin, children);
}
}
}

View file

@ -0,0 +1,83 @@
package com.sparrowwallet.hummingbird.registry;
import co.nstant.in.cbor.model.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;
public class CryptoKeypath {
public static final int COMPONENTS_KEY = 1;
public static final int PARENT_FINGERPRINT_KEY = 2;
public static final int DEPTH_KEY = 3;
private final List<PathComponent> components;
private final byte[] parentFingerprint;
private final Integer depth;
public CryptoKeypath(List<PathComponent> components, byte[] parentFingerprint) {
this(components, parentFingerprint, 0);
}
public CryptoKeypath(List<PathComponent> components, byte[] parentFingerprint, Integer depth) {
this.components = components;
this.parentFingerprint = parentFingerprint == null ? null : Arrays.copyOfRange(parentFingerprint, parentFingerprint.length - 4, parentFingerprint.length);
this.depth = depth;
}
public List<PathComponent> getComponents() {
return components;
}
public String getPath() {
if(components.isEmpty()) {
return null;
}
StringJoiner joiner = new StringJoiner("/");
for(PathComponent component : components) {
joiner.add((component.isWildcard() ? "*" : component.getIndex()) + (component.isHardened() ? "'" : ""));
}
return joiner.toString();
}
public byte[] getParentFingerprint() {
return parentFingerprint;
}
public Integer getDepth() {
return depth;
}
public static CryptoKeypath fromCbor(DataItem item) {
List<PathComponent> components = new ArrayList<>();
byte[] parentFingerprint = null;
Integer depth = null;
Map map = (Map)item;
for(DataItem key : map.getKeys()) {
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));
}
}
} else if(intKey == PARENT_FINGERPRINT_KEY) {
parentFingerprint = ((UnsignedInteger)map.get(key)).getValue().toByteArray();
} else if(intKey == DEPTH_KEY) {
depth = ((UnsignedInteger)map.get(key)).getValue().intValue();
}
}
return new CryptoKeypath(components, parentFingerprint, depth);
}
}

View file

@ -0,0 +1,84 @@
package com.sparrowwallet.hummingbird.registry;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.Map;
import co.nstant.in.cbor.model.Tag;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CryptoOutput {
private final List<ScriptExpression> scriptExpressions;
//Only one of the following will be not null
private final CryptoECKey ecKey;
private final CryptoHDKey hdKey;
private final MultiKey multiKey;
public CryptoOutput(List<ScriptExpression> scriptExpressions, CryptoECKey ecKey) {
this.scriptExpressions = scriptExpressions;
this.ecKey = ecKey;
this.hdKey = null;
this.multiKey = null;
}
public CryptoOutput(List<ScriptExpression> scriptExpressions, CryptoHDKey hdKey) {
this.scriptExpressions = scriptExpressions;
this.ecKey = null;
this.hdKey = hdKey;
this.multiKey = null;
}
public CryptoOutput(List<ScriptExpression> scriptExpressions, MultiKey multiKey) {
this.scriptExpressions = scriptExpressions;
this.ecKey = null;
this.hdKey = null;
this.multiKey = multiKey;
}
public List<ScriptExpression> getScriptExpressions() {
return scriptExpressions;
}
public CryptoECKey getEcKey() {
return ecKey;
}
public CryptoHDKey getHdKey() {
return hdKey;
}
public MultiKey getMultiKey() {
return multiKey;
}
public static CryptoOutput fromCbor(DataItem cbor) {
List<ScriptExpression> expressions = new ArrayList<>();
Tag tag = cbor.getTag();
do {
if(tag.getValue() != RegistryType.CRYPTO_HDKEY.getTag() && tag.getValue() != RegistryType.CRYPTO_ECKEY.getTag()) {
expressions.add(ScriptExpression.fromTagValue(tag.getValue()));
}
tag = tag.getTag();
} while(tag != null);
boolean isMultiKey = expressions.get(0) == ScriptExpression.MULTISIG || expressions.get(0) == ScriptExpression.SORTED_MULTISIG;
Collections.reverse(expressions);
Map map = (Map)cbor;
if(isMultiKey) {
MultiKey multiKey = MultiKey.fromCbor(map);
return new CryptoOutput(expressions, multiKey);
} else if(cbor.getTag().getValue() == RegistryType.CRYPTO_ECKEY.getTag()) {
CryptoECKey cryptoECKey = CryptoECKey.fromCbor(map);
return new CryptoOutput(expressions, cryptoECKey);
} else if(cbor.getTag().getValue() == RegistryType.CRYPTO_HDKEY.getTag()) {
CryptoHDKey cryptoHDKey = CryptoHDKey.fromCbor(map);
return new CryptoOutput(expressions, cryptoHDKey);
}
throw new IllegalStateException("Unknown tag for data item: " + cbor.getTag().getValue());
}
}

View file

@ -0,0 +1,20 @@
package com.sparrowwallet.hummingbird.registry;
import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.DataItem;
public class CryptoPSBT {
private final byte[] psbt;
public CryptoPSBT(byte[] psbt) {
this.psbt = psbt;
}
public byte[] getPsbt() {
return psbt;
}
public static CryptoPSBT fromCbor(DataItem item) {
return new CryptoPSBT(((ByteString)item).getBytes());
}
}

View file

@ -0,0 +1,51 @@
package com.sparrowwallet.hummingbird.registry;
import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.Map;
import co.nstant.in.cbor.model.UnsignedInteger;
import java.util.Date;
public class CryptoSeed {
public static final long PAYLOAD = 1;
public static final long BIRTHDATE = 2;
private final byte[] seed;
private final Date birthdate;
public CryptoSeed(byte[] seed, Date birthdate) {
this.seed = seed;
this.birthdate = birthdate;
}
public byte[] getSeed() {
return seed;
}
public Date getBirthdate() {
return birthdate;
}
public static CryptoSeed fromCbor(DataItem item) {
byte[] seed = null;
Date birthdate = null;
Map map = (Map)item;
for(DataItem key : map.getKeys()) {
UnsignedInteger uintKey = (UnsignedInteger)key;
int intKey = uintKey.getValue().intValue();
if(intKey == PAYLOAD) {
seed = ((ByteString)map.get(key)).getBytes();
} else if(intKey == BIRTHDATE) {
birthdate = new Date(((UnsignedInteger)map.get(key)).getValue().longValue() * 1000 * 60 * 60 * 24);
}
}
if(seed == null) {
throw new IllegalStateException("Seed is null");
}
return new CryptoSeed(seed, birthdate);
}
}

View file

@ -0,0 +1,62 @@
package com.sparrowwallet.hummingbird.registry;
import co.nstant.in.cbor.model.Array;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.Map;
import co.nstant.in.cbor.model.UnsignedInteger;
import java.util.ArrayList;
import java.util.List;
public class MultiKey {
public static final int THRESHOLD_KEY = 1;
public static final int KEYS_KEY = 2;
private final int threshold;
private final List<CryptoECKey> ecKeys;
private final List<CryptoHDKey> hdKeys;
public MultiKey(int threshold, List<CryptoECKey> ecKeys, List<CryptoHDKey> hdKeys) {
this.threshold = threshold;
this.ecKeys = ecKeys;
this.hdKeys = hdKeys;
}
public int getThreshold() {
return threshold;
}
public List<CryptoECKey> getEcKeys() {
return ecKeys;
}
public List<CryptoHDKey> getHdKeys() {
return hdKeys;
}
public static MultiKey fromCbor(DataItem item) {
int threshold = 0;
List<CryptoECKey> ecKeys = new ArrayList<>();
List<CryptoHDKey> hdKeys = new ArrayList<>();
Map map = (Map)item;
for(DataItem key : map.getKeys()) {
UnsignedInteger intKey = (UnsignedInteger)key;
if(intKey.getValue().intValue() == THRESHOLD_KEY) {
threshold = ((UnsignedInteger)map.get(key)).getValue().intValue();
}
if(intKey.getValue().intValue() == KEYS_KEY) {
Array keysArray = (Array)map.get(key);
for(DataItem keyExp : keysArray.getDataItems()) {
if(keyExp.getTag().getValue() == RegistryType.CRYPTO_ECKEY.getTag()) {
ecKeys.add(CryptoECKey.fromCbor(keyExp));
} else if(keyExp.getTag().getValue() == RegistryType.CRYPTO_HDKEY.getTag()) {
hdKeys.add(CryptoHDKey.fromCbor(keyExp));
}
}
}
}
return new MultiKey(threshold, ecKeys, hdKeys);
}
}

View file

@ -0,0 +1,37 @@
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;
}
}

View file

@ -0,0 +1,63 @@
package com.sparrowwallet.hummingbird.registry;
public enum RegistryType {
BYTES("bytes", null, byte[].class),
CBOR_PNG("cbor-png", null, null),
CBOR_SVG("cbor-svg", null, null),
COSE_SIGN("cose-sign", 98, null),
COSE_SIGN1("cose-sign1", 18, null),
COSE_ENCRYPT("cose-encrypt", 96, null),
COSE_ENCRYPT0("cose-encrypt0", 16, null),
COSE_MAC("cose-mac", 97, null),
COSE_MAC0("cose-mac0", 17, null),
COSE_KEY("cose-key", null, null),
COSE_KEYSET("cose-keyset", null, null),
CRYPTO_SEED("crypto-seed", 300, CryptoSeed.class),
CRYPTO_BIP39("crypto-bip39", 301, CryptoBip39.class),
CRYPTO_HDKEY("crypto-hdkey", 303, CryptoHDKey.class),
CRYPTO_KEYPATH("crypto-keypath", 304, CryptoKeypath.class),
CRYPTO_COIN_INFO("crypto-coin-info", 305, CryptoCoinInfo.class),
CRYPTO_ECKEY("crypto-eckey", 306, CryptoECKey.class),
CRYPTO_ADDRESS("crypto-address", 307, CryptoAddress.class),
CRYPTO_OUTPUT("crypto-output", 308, CryptoOutput.class),
CRYPTO_SSKR("crypto-sskr", 309, null),
CRYPTO_PSBT("crypto-psbt", 310, CryptoPSBT.class),
CRYPTO_ACCOUNT("crypto-account", 311, CryptoAccount.class);
private final String type;
private final Integer tag;
private final Class registryClass;
private RegistryType(String type, Integer tag, Class registryClass) {
this.type = type;
this.tag = tag;
this.registryClass = registryClass;
}
public String getType() {
return type;
}
public Integer getTag() {
return tag;
}
public Class getRegistryClass() {
return registryClass;
}
@Override
public String toString() {
return type;
}
public static RegistryType fromString(String type) {
for(RegistryType registryType : values()) {
if(registryType.toString().equals(type.toLowerCase())) {
return registryType;
}
}
throw new IllegalArgumentException("Unknown UR registry type: " + type);
}
}

View file

@ -0,0 +1,40 @@
package com.sparrowwallet.hummingbird.registry;
public enum ScriptExpression {
SCRIPT_HASH(400, "sh"),
WITNESS_SCRIPT_HASH(401, "wsh"),
PUBLIC_KEY(402, "pk"),
PUBLIC_KEY_HASH(403, "pkh"),
WITNESS_PUBLIC_KEY_HASH(404, "wpkh"),
COMBO(405, "combo"),
MULTISIG(406, "multi"),
SORTED_MULTISIG(407, "sorted"),
ADDRESS(307, "addr"),
RAW_SCRIPT(408, "raw");
private final int tagValue;
private final String expression;
private ScriptExpression(int tagValue, String expression) {
this.tagValue = tagValue;
this.expression = expression;
}
public int getTagValue() {
return tagValue;
}
public String getExpression() {
return expression;
}
public static ScriptExpression fromTagValue(long value) {
for(ScriptExpression expression : ScriptExpression.values()) {
if(expression.tagValue == value) {
return expression;
}
}
throw new IllegalArgumentException("Unknown tag value " + value);
}
}

View file

@ -0,0 +1,79 @@
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 org.junit.Assert;
import org.junit.Test;
import java.util.List;
public class CryptoAccountTest {
@Test
public void testSeed() throws CborException {
String hex = "A2011A37B5EED40286D90193D9012FA303582103EB3E2863911826374DE86C231A4B76F0B89DFA174AFB78D7F478199884D9DD320458206456A5DF2DB0F6D9AF72B2A1AF4B25F45200ED6FCC29C3440B311D4796B70B5B06D90130A20186182CF500F500F5021A99F9CDF7D90190D90194D9012FA303582102C7E4823730F6EE2CF864E2C352060A88E60B51A84E89E4C8C75EC22590AD6B690458209D2F86043276F9251A4A4F577166A5ABEB16B6EC61E226B5B8FA11038BFDA42D06D90130A201861831F500F500F5021AA80F7CDBD90194D9012FA303582103FD433450B6924B4F7EFDD5D1ED017D364BE95AB2B592DC8BDDB3B00C1C24F63F04582072EDE7334D5ACF91C6FDA622C205199C595A31F9218ED30792D301D5EE9E3A8806D90130A201861854F500F500F5021A0D5DE1D7D90190D9012FA3035821035CCD58B63A2CDC23D0812710603592E7457573211880CB59B1EF012E168E059A04582088D3299B448F87215D96B0C226235AFC027F9E7DC700284F3E912A34DAEB1A2306D90130A20182182DF5021A37B5EED4D90190D90191D9012FA3035821032C78EBFCABDAC6D735A0820EF8732F2821B4FB84CD5D6B26526938F90C0507110458207953EFE16A73E5D3F9F2D4C6E49BD88E22093BBD85BE5A7E862A4B98A16E0AB606D90130A201881830F500F500F501F5021A59B69B2AD90191D9012FA30358210260563EE80C26844621B06B74070BAF0E23FB76CE439D0237E87502EBBD3CA3460458202FA0E41C9DC43DC4518659BFCEF935BA8101B57DBC0812805DD983BC1D34B81306D90130A201881830F500F500F502F5021A59B69B2A";
byte[] data = TestUtils.hexToBytes(hex);
List<DataItem> items = CborDecoder.decode(data);
CryptoAccount cryptoAccount = CryptoAccount.fromCbor(items.get(0));
Assert.assertEquals("37b5eed4", TestUtils.bytesToHex(cryptoAccount.getMasterFingerprint()));
CryptoOutput cryptoOutput1 = cryptoAccount.getOutputDescriptors().get(0);
Assert.assertEquals(List.of(ScriptExpression.PUBLIC_KEY_HASH), cryptoOutput1.getScriptExpressions());
Assert.assertEquals("03eb3e2863911826374de86c231a4b76f0b89dfa174afb78d7f478199884d9dd32", TestUtils.bytesToHex(cryptoOutput1.getHdKey().getKey()));
Assert.assertEquals("6456a5df2db0f6d9af72b2a1af4b25f45200ed6fcc29c3440b311d4796b70b5b", TestUtils.bytesToHex(cryptoOutput1.getHdKey().getChainCode()));
Assert.assertEquals("44'/0'/0'", cryptoOutput1.getHdKey().getOrigin().getPath());
Assert.assertEquals("99f9cdf7", TestUtils.bytesToHex(cryptoOutput1.getHdKey().getOrigin().getParentFingerprint()));
Assert.assertNull(cryptoOutput1.getHdKey().getChildren());
CryptoOutput cryptoOutput2 = cryptoAccount.getOutputDescriptors().get(1);
Assert.assertEquals(List.of(ScriptExpression.SCRIPT_HASH, ScriptExpression.WITNESS_PUBLIC_KEY_HASH), cryptoOutput2.getScriptExpressions());
Assert.assertEquals("02c7e4823730f6ee2cf864e2c352060a88e60b51a84e89e4c8c75ec22590ad6b69", TestUtils.bytesToHex(cryptoOutput2.getHdKey().getKey()));
Assert.assertEquals("9d2f86043276f9251a4a4f577166a5abeb16b6ec61e226b5b8fa11038bfda42d", TestUtils.bytesToHex(cryptoOutput2.getHdKey().getChainCode()));
Assert.assertEquals("49'/0'/0'", cryptoOutput2.getHdKey().getOrigin().getPath());
Assert.assertEquals("a80f7cdb", TestUtils.bytesToHex(cryptoOutput2.getHdKey().getOrigin().getParentFingerprint()));
Assert.assertNull(cryptoOutput2.getHdKey().getChildren());
CryptoOutput cryptoOutput3 = cryptoAccount.getOutputDescriptors().get(2);
Assert.assertEquals(List.of(ScriptExpression.WITNESS_PUBLIC_KEY_HASH), cryptoOutput3.getScriptExpressions());
Assert.assertEquals("03fd433450b6924b4f7efdd5d1ed017d364be95ab2b592dc8bddb3b00c1c24f63f", TestUtils.bytesToHex(cryptoOutput3.getHdKey().getKey()));
Assert.assertEquals("72ede7334d5acf91c6fda622c205199c595a31f9218ed30792d301d5ee9e3a88", TestUtils.bytesToHex(cryptoOutput3.getHdKey().getChainCode()));
Assert.assertEquals("84'/0'/0'", cryptoOutput3.getHdKey().getOrigin().getPath());
Assert.assertEquals("0d5de1d7", TestUtils.bytesToHex(cryptoOutput3.getHdKey().getOrigin().getParentFingerprint()));
Assert.assertNull(cryptoOutput3.getHdKey().getChildren());
CryptoOutput cryptoOutput4 = cryptoAccount.getOutputDescriptors().get(3);
Assert.assertEquals(List.of(ScriptExpression.SCRIPT_HASH), cryptoOutput4.getScriptExpressions());
Assert.assertEquals("035ccd58b63a2cdc23d0812710603592e7457573211880cb59b1ef012e168e059a", TestUtils.bytesToHex(cryptoOutput4.getHdKey().getKey()));
Assert.assertEquals("88d3299b448f87215d96b0c226235afc027f9e7dc700284f3e912a34daeb1a23", TestUtils.bytesToHex(cryptoOutput4.getHdKey().getChainCode()));
Assert.assertEquals("45'", cryptoOutput4.getHdKey().getOrigin().getPath());
Assert.assertEquals("37b5eed4", TestUtils.bytesToHex(cryptoOutput4.getHdKey().getOrigin().getParentFingerprint()));
Assert.assertNull(cryptoOutput4.getHdKey().getChildren());
CryptoOutput cryptoOutput5 = cryptoAccount.getOutputDescriptors().get(4);
Assert.assertEquals(List.of(ScriptExpression.SCRIPT_HASH, ScriptExpression.WITNESS_SCRIPT_HASH), cryptoOutput5.getScriptExpressions());
Assert.assertEquals("032c78ebfcabdac6d735a0820ef8732f2821b4fb84cd5d6b26526938f90c050711", TestUtils.bytesToHex(cryptoOutput5.getHdKey().getKey()));
Assert.assertEquals("7953efe16a73e5d3f9f2d4c6e49bd88e22093bbd85be5a7e862a4b98a16e0ab6", TestUtils.bytesToHex(cryptoOutput5.getHdKey().getChainCode()));
Assert.assertEquals("48'/0'/0'/1'", cryptoOutput5.getHdKey().getOrigin().getPath());
Assert.assertEquals("59b69b2a", TestUtils.bytesToHex(cryptoOutput5.getHdKey().getOrigin().getParentFingerprint()));
Assert.assertNull(cryptoOutput5.getHdKey().getChildren());
CryptoOutput cryptoOutput6 = cryptoAccount.getOutputDescriptors().get(5);
Assert.assertEquals(List.of(ScriptExpression.WITNESS_SCRIPT_HASH), cryptoOutput6.getScriptExpressions());
Assert.assertEquals("0260563ee80c26844621b06b74070baf0e23fb76ce439d0237e87502ebbd3ca346", TestUtils.bytesToHex(cryptoOutput6.getHdKey().getKey()));
Assert.assertEquals("2fa0e41c9dc43dc4518659bfcef935ba8101b57dbc0812805dd983bc1d34b813", TestUtils.bytesToHex(cryptoOutput6.getHdKey().getChainCode()));
Assert.assertEquals("48'/0'/0'/2'", cryptoOutput6.getHdKey().getOrigin().getPath());
Assert.assertEquals("59b69b2a", TestUtils.bytesToHex(cryptoOutput6.getHdKey().getOrigin().getParentFingerprint()));
Assert.assertNull(cryptoOutput6.getHdKey().getChildren());
}
@Test
public void testAccount() throws Exception {
byte[] cbor = TestUtils.hexToBytes("A2011A37B5EED40286D90193D9012FA303582103EB3E2863911826374DE86C231A4B76F0B89DFA174AFB78D7F478199884D9DD320458206456A5DF2DB0F6D9AF72B2A1AF4B25F45200ED6FCC29C3440B311D4796B70B5B06D90130A20186182CF500F500F5021A99F9CDF7D90190D90194D9012FA303582102C7E4823730F6EE2CF864E2C352060A88E60B51A84E89E4C8C75EC22590AD6B690458209D2F86043276F9251A4A4F577166A5ABEB16B6EC61E226B5B8FA11038BFDA42D06D90130A201861831F500F500F5021AA80F7CDBD90194D9012FA303582103FD433450B6924B4F7EFDD5D1ED017D364BE95AB2B592DC8BDDB3B00C1C24F63F04582072EDE7334D5ACF91C6FDA622C205199C595A31F9218ED30792D301D5EE9E3A8806D90130A201861854F500F500F5021A0D5DE1D7D90190D9012FA3035821035CCD58B63A2CDC23D0812710603592E7457573211880CB59B1EF012E168E059A04582088D3299B448F87215D96B0C226235AFC027F9E7DC700284F3E912A34DAEB1A2306D90130A20182182DF5021A37B5EED4D90190D90191D9012FA3035821032C78EBFCABDAC6D735A0820EF8732F2821B4FB84CD5D6B26526938F90C0507110458207953EFE16A73E5D3F9F2D4C6E49BD88E22093BBD85BE5A7E862A4B98A16E0AB606D90130A201881830F500F500F501F5021A59B69B2AD90191D9012FA30358210260563EE80C26844621B06B74070BAF0E23FB76CE439D0237E87502EBBD3CA3460458202FA0E41C9DC43DC4518659BFCEF935BA8101B57DBC0812805DD983BC1D34B81306D90130A201881830F500F500F502F5021A59B69B2A");
UR ur = new UR("crypto-account", cbor);
String encoded = UREncoder.encode(ur);
Assert.assertEquals("ur:crypto-account/oeadcyemrewytyaolntaadmutaaddlotaxhdclaxwmfmdeiamecsdsemgtvsjzcncygrkowtrontzschgezokstswkkscfmklrtauteyaahdcxiehfonurdppfyntapejpproypegrdawkgmaewejlsfdtsrfybdehcaflmtrlbdhpamtaaddyoeadlncsdwykaeykaeykaocynlytsnyltaadmhtaadmwtaaddlotaxhdclaostvelfemdyynwydwyaievosrgmambklovabdgypdglldvespsthysadamhpmjeinaahdcxntdllnaaeykoytdacygegwhgjsiyonpywmcmrpwphsvodsrerozsbyaxluzcoxdpamtaaddyoeadlncsehykaeykaeykaocypdbskeuytaadmwtaaddlotaxhdclaxzcfxeegdrpmogrgwkbzctlttweadkiengrwlhtprremouoluutqdpfbncedkynfhaahdcxjpwevdeogthttkmeswzcolcpsaahcfnshkhtehytclmnteatmoteadtlwynnftloamtaaddyoeadlncsghykaeykaeykaocybthlvytstaadmhtaaddlotaxhdclaxhhsnhdrpftdwuocntilydibehnecmovdfekpjkclcslasbhkpawsaddmcmmnahnyaahdcxlotedtndfymyltclhlmtpfsadscnhtztaolbnnkistaedegwfmmedreetnwmcycnamtaaddyoeadlfcsdpykaocyemrewytytaadmhtaadmetaaddlotaxhdclaxdwkswmztpytnswtsecnblfbayajkdldeclqzzolrsnhljedsgminetytbnahatbyaahdcxkkguwsvyimjkvwteytwztyswvendtpmncpasfrrylprnhtkblndrgrmkoyjtbkrpamtaaddyoeadlocsdyykaeykaeykadykaocyhkrpnddrtaadmetaaddlotaxhdclaohnhffmvsbndslrfgclpfjejyatbdpebacnzokotofxntaoemvskpaowmryfnotfgaahdcxdlnbvecentssfsssgylnhkrstoytecrdlyadrekirfaybglahltalsrfcaeerobwamtaaddyoeadlocsdyykaeykaeykaoykaocyhkrpnddrasqdckhh", encoded);
}
}

View file

@ -0,0 +1,21 @@
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 org.junit.Assert;
import org.junit.Test;
import java.util.List;
public class CryptoAddressTest {
@Test
public void testAddress() throws CborException {
String hex = "A1035477BFF20C60E522DFAA3350C39B030A5D004E839A";
byte[] data = TestUtils.hexToBytes(hex);
List<DataItem> items = CborDecoder.decode(data);
CryptoAddress cryptoAddress = CryptoAddress.fromCbor(items.get(0));
Assert.assertEquals("77bff20c60e522dfaa3350c39b030a5d004e839a", TestUtils.bytesToHex(cryptoAddress.getData()));
}
}

View file

@ -0,0 +1,22 @@
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 org.junit.Assert;
import org.junit.Test;
import java.util.List;
public class CryptoBip39Test {
@Test
public void testSeed() throws CborException {
String hex = "A2018C66736869656C646567726F75706565726F6465656177616B65646C6F636B6773617573616765646361736865676C6172656477617665646372657765666C616D6565676C6F76650262656E1947DA";
byte[] data = TestUtils.hexToBytes(hex);
List<DataItem> items = CborDecoder.decode(data);
CryptoBip39 cryptoSeed = CryptoBip39.fromCbor(items.get(0));
Assert.assertEquals(List.of("shield", "group", "erode", "awake", "lock", "sausage", "cash", "glare", "wave", "crew", "flame", "glove"), cryptoSeed.getWords());
Assert.assertEquals("en", cryptoSeed.getLanguage());
}
}

View file

@ -0,0 +1,23 @@
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 org.junit.Assert;
import org.junit.Test;
import java.util.List;
public class CryptoECKeyTest {
@Test
public void testSeed() throws CborException {
String hex = "A202F50358208C05C4B4F3E88840A4F4B5F155CFD69473EA169F3D0431B7A6787A23777F08AA";
byte[] data = TestUtils.hexToBytes(hex);
List<DataItem> items = CborDecoder.decode(data);
CryptoECKey cryptoECKey = CryptoECKey.fromCbor(items.get(0));
Assert.assertEquals(0, cryptoECKey.getCurve());
Assert.assertTrue(cryptoECKey.isPrivateKey());
Assert.assertEquals("8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa", TestUtils.bytesToHex(cryptoECKey.getData()));
}
}

View file

@ -0,0 +1,39 @@
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 org.junit.Assert;
import org.junit.Test;
import java.util.List;
public class CryptoHDKeyTest {
@Test
public void testMasterKey() throws CborException {
String hex = "A301F503582100E8F32E723DECF4051AEFAC8E2C93C9C5B214313817CDB01A1494B917C8436B35045820873DFF81C02F525623FD1FE5167EAC3A55A049DE3D314BB42EE227FFED37D508";
byte[] data = TestUtils.hexToBytes(hex);
List<DataItem> items = CborDecoder.decode(data);
CryptoHDKey cryptoHDKey = CryptoHDKey.fromCbor(items.get(0));
Assert.assertTrue(cryptoHDKey.isMaster());
Assert.assertEquals("00e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", TestUtils.bytesToHex(cryptoHDKey.getKey()));
Assert.assertEquals("873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508", TestUtils.bytesToHex(cryptoHDKey.getChainCode()));
}
@Test
public void testPublicTestnet() throws CborException {
String hex = "A4035821026FE2355745BB2DB3630BBC80EF5D58951C963C841F54170BA6E5C12BE7FC12A6045820CED155C72456255881793514EDC5BD9447E7F74ABB88C6D6B6480FD016EE8C8505D90131A1020106D90130A2018A182CF501F501F500F401F4021AE9181CF3";
byte[] data = TestUtils.hexToBytes(hex);
List<DataItem> items = CborDecoder.decode(data);
CryptoHDKey cryptoHDKey = CryptoHDKey.fromCbor(items.get(0));
Assert.assertFalse(cryptoHDKey.isMaster());
Assert.assertFalse(cryptoHDKey.isPrivateKey());
Assert.assertEquals("026fe2355745bb2db3630bbc80ef5d58951c963c841f54170ba6e5c12be7fc12a6", TestUtils.bytesToHex(cryptoHDKey.getKey()));
Assert.assertEquals("ced155c72456255881793514edc5bd9447e7f74abb88c6d6b6480fd016ee8c85", TestUtils.bytesToHex(cryptoHDKey.getChainCode()));
Assert.assertEquals(cryptoHDKey.getUseInfo().getNetwork(), CryptoCoinInfo.Network.TESTNET);
Assert.assertEquals("44'/1'/1'/0/1", cryptoHDKey.getOrigin().getPath());
Assert.assertEquals("e9181cf3", TestUtils.bytesToHex(cryptoHDKey.getOrigin().getParentFingerprint()));
Assert.assertNull(cryptoHDKey.getChildren());
}
}

View file

@ -0,0 +1,93 @@
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 org.junit.Assert;
import org.junit.Test;
import java.util.List;
public class CryptoOutputTest {
@Test
public void testP2PKHECKey() throws CborException {
String hex = "d90193d90132a103582102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5";
byte[] data = TestUtils.hexToBytes(hex);
List<DataItem> items = CborDecoder.decode(data);
CryptoOutput cryptoOutput = CryptoOutput.fromCbor(items.get(0));
Assert.assertEquals(List.of(ScriptExpression.PUBLIC_KEY_HASH), cryptoOutput.getScriptExpressions());
Assert.assertEquals("02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", TestUtils.bytesToHex(cryptoOutput.getEcKey().getData()));
}
@Test
public void testP2SHP2WPKHECKey() throws CborException {
String hex = "d90190d90194d90132a103582103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556";
byte[] data = TestUtils.hexToBytes(hex);
List<DataItem> items = CborDecoder.decode(data);
CryptoOutput cryptoOutput = CryptoOutput.fromCbor(items.get(0));
Assert.assertEquals(List.of(ScriptExpression.SCRIPT_HASH, ScriptExpression.WITNESS_PUBLIC_KEY_HASH), cryptoOutput.getScriptExpressions());
Assert.assertEquals("03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556", TestUtils.bytesToHex(cryptoOutput.getEcKey().getData()));
}
@Test
public void testMultiECKey() throws CborException {
String hex = "d90190d90196a201020282d90132a1035821022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01d90132a103582103acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe";
byte[] data = TestUtils.hexToBytes(hex);
List<DataItem> items = CborDecoder.decode(data);
CryptoOutput cryptoOutput = CryptoOutput.fromCbor(items.get(0));
Assert.assertEquals(List.of(ScriptExpression.SCRIPT_HASH, ScriptExpression.MULTISIG), cryptoOutput.getScriptExpressions());
Assert.assertNull(cryptoOutput.getHdKey());
Assert.assertEquals(2, cryptoOutput.getMultiKey().getThreshold());
CryptoECKey firstKey = cryptoOutput.getMultiKey().getEcKeys().get(0);
Assert.assertEquals("022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01", TestUtils.bytesToHex(firstKey.getData()));
CryptoECKey secondKey = cryptoOutput.getMultiKey().getEcKeys().get(1);
Assert.assertEquals("03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe", TestUtils.bytesToHex(secondKey.getData()));
}
@Test
public void testP2PKH() throws CborException {
String hex = "d90193d9012fa403582102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0045820637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2906d90130a20186182cf500f500f5021ad34db33f07d90130a1018401f480f4";
byte[] data = TestUtils.hexToBytes(hex);
List<DataItem> items = CborDecoder.decode(data);
CryptoOutput cryptoOutput = CryptoOutput.fromCbor(items.get(0));
Assert.assertEquals(List.of(ScriptExpression.PUBLIC_KEY_HASH), cryptoOutput.getScriptExpressions());
Assert.assertEquals("02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0", TestUtils.bytesToHex(cryptoOutput.getHdKey().getKey()));
Assert.assertEquals("637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29", TestUtils.bytesToHex(cryptoOutput.getHdKey().getChainCode()));
Assert.assertEquals("44'/0'/0'", cryptoOutput.getHdKey().getOrigin().getPath());
Assert.assertEquals("d34db33f", TestUtils.bytesToHex(cryptoOutput.getHdKey().getOrigin().getParentFingerprint()));
Assert.assertEquals("1/*", cryptoOutput.getHdKey().getChildren().getPath());
Assert.assertNull(cryptoOutput.getHdKey().getChildren().getParentFingerprint());
}
@Test
public void testMulti() throws CborException {
String hex = "d90191d90196a201010282d9012fa403582103cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a704582060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd968906d90130a1030007d90130a1018601f400f480f4d9012fa403582102fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea045820f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c06d90130a2018200f4021abd16bee507d90130a1018600f400f480f4";
byte[] data = TestUtils.hexToBytes(hex);
List<DataItem> items = CborDecoder.decode(data);
CryptoOutput cryptoOutput = CryptoOutput.fromCbor(items.get(0));
Assert.assertEquals(List.of(ScriptExpression.WITNESS_SCRIPT_HASH, ScriptExpression.MULTISIG), cryptoOutput.getScriptExpressions());
Assert.assertNull(cryptoOutput.getHdKey());
Assert.assertEquals(1, cryptoOutput.getMultiKey().getThreshold());
CryptoHDKey firstKey = cryptoOutput.getMultiKey().getHdKeys().get(0);
Assert.assertEquals("03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7", TestUtils.bytesToHex(firstKey.getKey()));
Assert.assertEquals("60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689", TestUtils.bytesToHex(firstKey.getChainCode()));
Assert.assertNull(firstKey.getOrigin().getPath());
Assert.assertNull(firstKey.getOrigin().getParentFingerprint());
Assert.assertEquals(Integer.valueOf(0), firstKey.getOrigin().getDepth());
Assert.assertEquals("1/0/*", firstKey.getChildren().getPath());
Assert.assertNull(firstKey.getChildren().getParentFingerprint());
CryptoHDKey secondKey = cryptoOutput.getMultiKey().getHdKeys().get(1);
Assert.assertEquals("02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea", TestUtils.bytesToHex(secondKey.getKey()));
Assert.assertEquals("f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c", TestUtils.bytesToHex(secondKey.getChainCode()));
Assert.assertEquals("0", secondKey.getOrigin().getPath());
Assert.assertEquals("bd16bee5", TestUtils.bytesToHex(secondKey.getOrigin().getParentFingerprint()));
Assert.assertNull(secondKey.getOrigin().getDepth());
Assert.assertEquals("0/0/*", secondKey.getChildren().getPath());
Assert.assertNull(secondKey.getChildren().getParentFingerprint());
}
}

View file

@ -0,0 +1,26 @@
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 org.junit.Assert;
import org.junit.Test;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.List;
public class CryptoSeedTest {
private final DateFormat dateFormat = new SimpleDateFormat("dd MMM yyyy");
@Test
public void testSeed() throws CborException {
String hex = "A20150C7098580125E2AB0981253468B2DBC5202D8641947DA";
byte[] data = TestUtils.hexToBytes(hex);
List<DataItem> items = CborDecoder.decode(data);
CryptoSeed cryptoSeed = CryptoSeed.fromCbor(items.get(0));
Assert.assertEquals("c7098580125e2ab0981253468b2dbc52", TestUtils.bytesToHex(cryptoSeed.getSeed()));
Assert.assertEquals("12 May 2020", dateFormat.format(cryptoSeed.getBirthdate()));
}
}