mirror of
https://github.com/sparrowwallet/hummingbird.git
synced 2024-11-02 18:46:45 +00:00
add ur registry objects
This commit is contained in:
parent
5b5e2ec1a5
commit
d6386ba86a
23 changed files with 1164 additions and 9 deletions
|
@ -6,8 +6,8 @@ import co.nstant.in.cbor.CborEncoder;
|
|||
import co.nstant.in.cbor.CborException;
|
||||
import co.nstant.in.cbor.model.ByteString;
|
||||
import co.nstant.in.cbor.model.DataItem;
|
||||
import com.sparrowwallet.hummingbird.registry.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
@ -18,12 +18,14 @@ import java.util.Objects;
|
|||
*/
|
||||
public class 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 byte[] data;
|
||||
|
||||
public UR(RegistryType registryType, byte[] data) throws InvalidTypeException {
|
||||
this(registryType.toString(), data);
|
||||
}
|
||||
|
||||
public UR(String type, byte[] data) throws InvalidTypeException {
|
||||
if(!isURType(type)) {
|
||||
throw new InvalidTypeException("Invalid UR type: " + type);
|
||||
|
@ -37,14 +39,52 @@ public class UR {
|
|||
return type;
|
||||
}
|
||||
|
||||
public byte[] getCbor() {
|
||||
public RegistryType getRegistryType() {
|
||||
return RegistryType.fromString(type);
|
||||
}
|
||||
|
||||
public byte[] getCborBytes() {
|
||||
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 {
|
||||
try {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(getCbor());
|
||||
List<DataItem> dataItems = new CborDecoder(bais).decode();
|
||||
List<DataItem> dataItems = CborDecoder.decode(getCborBytes());
|
||||
if(!(dataItems.get(0) instanceof ByteString)) {
|
||||
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 {
|
||||
return fromBytes(BYTES_TYPE, data);
|
||||
return fromBytes(RegistryType.BYTES.toString(), data);
|
||||
}
|
||||
|
||||
public static UR fromBytes(String type, byte[] data) throws InvalidTypeException, InvalidCBORException {
|
||||
|
|
|
@ -15,7 +15,7 @@ public class UREncoder {
|
|||
|
||||
public UREncoder(UR ur, int maxFragmentLen, int minFragmentLen, long firstSeqNum) {
|
||||
this.ur = ur;
|
||||
this.fountainEncoder = new FountainEncoder(ur.getCbor(), maxFragmentLen, minFragmentLen, firstSeqNum);
|
||||
this.fountainEncoder = new FountainEncoder(ur.getCborBytes(), maxFragmentLen, minFragmentLen, firstSeqNum);
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
|
@ -48,7 +48,7 @@ public class UREncoder {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue