diff --git a/src/main/java/com/sparrowwallet/hummingbird/registry/CborSerializable.java b/src/main/java/com/sparrowwallet/hummingbird/registry/CborSerializable.java new file mode 100644 index 0000000..ecd4ff1 --- /dev/null +++ b/src/main/java/com/sparrowwallet/hummingbird/registry/CborSerializable.java @@ -0,0 +1,7 @@ +package com.sparrowwallet.hummingbird.registry; + +import co.nstant.in.cbor.model.DataItem; + +public interface CborSerializable { + DataItem toCbor(); +} diff --git a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoAccount.java b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoAccount.java index ebf6de9..d26a5a0 100644 --- a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoAccount.java +++ b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoAccount.java @@ -2,11 +2,12 @@ package com.sparrowwallet.hummingbird.registry; import co.nstant.in.cbor.model.*; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -public class CryptoAccount { +public class CryptoAccount extends RegistryItem { public static final long MASTER_FINGERPRINT_KEY = 1; public static final long OUTPUT_DESCRIPTORS_KEY = 2; @@ -26,6 +27,22 @@ public class CryptoAccount { return outputDescriptors; } + public DataItem toCbor() { + Map map = new Map(); + map.put(new UnsignedInteger(MASTER_FINGERPRINT_KEY), new UnsignedInteger(new BigInteger(1, masterFingerprint))); + Array array = new Array(); + for(CryptoOutput cryptoOutput : outputDescriptors) { + array.add(cryptoOutput.toCbor()); + } + map.put(new UnsignedInteger(OUTPUT_DESCRIPTORS_KEY), array); + return map; + } + + @Override + public RegistryType getRegistryType() { + return RegistryType.CRYPTO_ACCOUNT; + } + public static CryptoAccount fromCbor(DataItem cbor) { Map cryptoAccountMap = (Map)cbor; diff --git a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoAddress.java b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoAddress.java index 43ddaa7..91978ea 100644 --- a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoAddress.java +++ b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoAddress.java @@ -2,7 +2,7 @@ package com.sparrowwallet.hummingbird.registry; import co.nstant.in.cbor.model.*; -public class CryptoAddress { +public class CryptoAddress extends RegistryItem { public static final long INFO = 1; public static final long TYPE = 2; public static final long DATA = 3; @@ -29,9 +29,26 @@ public class CryptoAddress { return data; } + public DataItem toCbor() { + Map map = new Map(); + if(info != null) { + map.put(new UnsignedInteger(INFO), info.toCbor()); + } + if(type != null) { + map.put(new UnsignedInteger(TYPE), new UnsignedInteger(type.ordinal())); + } + map.put(new UnsignedInteger(DATA), new ByteString(data)); + return map; + } + + @Override + public RegistryType getRegistryType() { + return RegistryType.CRYPTO_ADDRESS; + } + public static CryptoAddress fromCbor(DataItem item) { CryptoCoinInfo info = null; - Type type = Type.P2PKH; + Type type = null; byte[] data = null; Map map = (Map)item; diff --git a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoBip39.java b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoBip39.java index 4146903..d932f42 100644 --- a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoBip39.java +++ b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoBip39.java @@ -5,7 +5,7 @@ import co.nstant.in.cbor.model.*; import java.util.ArrayList; import java.util.List; -public class CryptoBip39 { +public class CryptoBip39 extends RegistryItem { public static final long WORDS = 1; public static final long LANG = 2; @@ -25,9 +25,27 @@ public class CryptoBip39 { return language; } + public DataItem toCbor() { + Map map = new Map(); + Array wordsArray = new Array(); + for(String word : words) { + wordsArray.add(new UnicodeString(word)); + } + map.put(new UnsignedInteger(WORDS), wordsArray); + if(language != null) { + map.put(new UnsignedInteger(LANG), new UnicodeString(language)); + } + return map; + } + + @Override + public RegistryType getRegistryType() { + return RegistryType.CRYPTO_BIP39; + } + public static CryptoBip39 fromCbor(DataItem item) { List words = new ArrayList<>(); - String language = "en"; + String language = null; Map map = (Map)item; for(DataItem key : map.getKeys()) { diff --git a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoCoinInfo.java b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoCoinInfo.java index 5ae36d0..2ee44c1 100644 --- a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoCoinInfo.java +++ b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoCoinInfo.java @@ -4,29 +4,45 @@ 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 class CryptoCoinInfo extends RegistryItem { public static final int TYPE_KEY = 1; public static final int NETWORK_KEY = 2; - private final int type; - private final int network; + private final Integer type; + private final Integer network; - public CryptoCoinInfo(int type, int network) { + public CryptoCoinInfo(Integer type, Integer network) { this.type = type; this.network = network; } public Type getType() { - return Type.values()[type]; + return type == null ? Type.BITCOIN : Type.values()[type]; } public Network getNetwork() { - return Network.values()[network]; + return network == null ? Network.MAINNET : Network.values()[network]; + } + + public DataItem toCbor() { + Map map = new Map(); + if(type != null) { + map.put(new UnsignedInteger(TYPE_KEY), new UnsignedInteger(type)); + } + if(network != null) { + map.put(new UnsignedInteger(NETWORK_KEY), new UnsignedInteger(network)); + } + return map; + } + + @Override + public RegistryType getRegistryType() { + return RegistryType.CRYPTO_COIN_INFO; } public static CryptoCoinInfo fromCbor(DataItem item) { - int type = 0; - int network = 0; + Integer type = null; + Integer network = null; Map map = (Map)item; for(DataItem key : map.getKeys()) { diff --git a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoECKey.java b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoECKey.java index 41b96a2..edb9881 100644 --- a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoECKey.java +++ b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoECKey.java @@ -2,36 +2,53 @@ package com.sparrowwallet.hummingbird.registry; import co.nstant.in.cbor.model.*; -public class CryptoECKey { +public class CryptoECKey extends RegistryItem { 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 Integer curve; + private final Boolean privateKey; private final byte[] data; - public CryptoECKey(int curve, boolean privateKey, byte[] data) { + public CryptoECKey(Integer curve, Boolean privateKey, byte[] data) { this.curve = curve; this.privateKey = privateKey; this.data = data; } public int getCurve() { - return curve; + return curve == null ? 0 : curve; } public boolean isPrivateKey() { - return privateKey; + return privateKey == null ? false : privateKey; } public byte[] getData() { return data; } + public DataItem toCbor() { + Map map = new Map(); + if(curve != null) { + map.put(new UnsignedInteger(CURVE), new UnsignedInteger(curve)); + } + if(privateKey != null) { + map.put(new UnsignedInteger(PRIVATE), privateKey ? SimpleValue.TRUE : SimpleValue.FALSE); + } + map.put(new UnsignedInteger(DATA), new ByteString(data)); + return map; + } + + @Override + public RegistryType getRegistryType() { + return RegistryType.CRYPTO_ECKEY; + } + public static CryptoECKey fromCbor(DataItem item) { - int curve = 0; - boolean privateKey = false; + Integer curve = null; + Boolean privateKey = null; byte[] data = null; Map map = (Map)item; diff --git a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoHDKey.java b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoHDKey.java index b78f786..3c3b368 100644 --- a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoHDKey.java +++ b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoHDKey.java @@ -2,9 +2,10 @@ package com.sparrowwallet.hummingbird.registry; import co.nstant.in.cbor.model.*; +import java.math.BigInteger; import java.util.Arrays; -public class CryptoHDKey { +public class CryptoHDKey extends RegistryItem { public static final int IS_MASTER_KEY = 1; public static final int IS_PRIVATE_KEY = 2; public static final int KEY_DATA_KEY = 3; @@ -13,15 +14,19 @@ public class CryptoHDKey { public static final int ORIGIN_KEY = 6; public static final int CHILDREN_KEY = 7; public static final int PARENT_FINGERPRINT_KEY = 8; + public static final int NAME_KEY = 9; + public static final int NOTE_KEY = 10; private final boolean master; - private final boolean privateKey; + private final Boolean privateKey; private final byte[] key; private final byte[] chainCode; private final CryptoCoinInfo useInfo; private final CryptoKeypath origin; private final CryptoKeypath children; private final byte[] parentFingerprint; + private final String name; + private final String note; public CryptoHDKey(byte[] key, byte[] chainCode) { this.master = true; @@ -32,9 +37,15 @@ public class CryptoHDKey { this.origin = null; this.children = null; this.parentFingerprint = null; + this.name = null; + this.note = null; } - public CryptoHDKey(boolean privateKey, byte[] key, byte[] chainCode, CryptoCoinInfo useInfo, CryptoKeypath origin, CryptoKeypath children, byte[] parentFingerprint) { + public CryptoHDKey(Boolean privateKey, byte[] key, byte[] chainCode, CryptoCoinInfo useInfo, CryptoKeypath origin, CryptoKeypath children, byte[] parentFingerprint) { + this(privateKey, key, chainCode, useInfo, origin, children, parentFingerprint, null, null); + } + + public CryptoHDKey(Boolean privateKey, byte[] key, byte[] chainCode, CryptoCoinInfo useInfo, CryptoKeypath origin, CryptoKeypath children, byte[] parentFingerprint, String name, String note) { this.master = false; this.privateKey = privateKey; this.key = key; @@ -43,6 +54,8 @@ public class CryptoHDKey { this.origin = origin; this.children = children; this.parentFingerprint = parentFingerprint == null ? null : Arrays.copyOfRange(parentFingerprint, parentFingerprint.length - 4, parentFingerprint.length); + this.name = name; + this.note = note; } public boolean isMaster() { @@ -50,7 +63,7 @@ public class CryptoHDKey { } public boolean isPrivateKey() { - return privateKey; + return privateKey == null ? false : privateKey; } public byte[] getKey() { @@ -77,15 +90,72 @@ public class CryptoHDKey { return parentFingerprint; } + public String getName() { + return name; + } + + public String getNote() { + return note; + } + + public DataItem toCbor() { + Map map = new Map(); + if(master) { + map.put(new UnsignedInteger(IS_MASTER_KEY), SimpleValue.TRUE); + map.put(new UnsignedInteger(KEY_DATA_KEY), new ByteString(key)); + map.put(new UnsignedInteger(CHAIN_CODE_KEY), new ByteString(chainCode)); + } else { + if(privateKey != null) { + map.put(new UnsignedInteger(IS_PRIVATE_KEY), privateKey ? SimpleValue.TRUE : SimpleValue.FALSE); + } + map.put(new UnsignedInteger(KEY_DATA_KEY), new ByteString(key)); + if(chainCode != null) { + map.put(new UnsignedInteger(CHAIN_CODE_KEY), new ByteString(chainCode)); + } + if(useInfo != null) { + DataItem useInfoItem = useInfo.toCbor(); + useInfoItem.setTag(RegistryType.CRYPTO_COIN_INFO.getTag()); + map.put(new UnsignedInteger(USE_INFO_KEY), useInfoItem); + } + if(origin != null) { + DataItem originItem = origin.toCbor(); + originItem.setTag(RegistryType.CRYPTO_KEYPATH.getTag()); + map.put(new UnsignedInteger(ORIGIN_KEY), originItem); + } + if(children != null) { + DataItem childrenItem = children.toCbor(); + childrenItem.setTag(RegistryType.CRYPTO_KEYPATH.getTag()); + map.put(new UnsignedInteger(CHILDREN_KEY), childrenItem); + } + if(parentFingerprint != null) { + map.put(new UnsignedInteger(PARENT_FINGERPRINT_KEY), new UnsignedInteger(new BigInteger(1, parentFingerprint))); + } + if(name != null) { + map.put(new UnsignedInteger(NAME_KEY), new UnicodeString(name)); + } + if(note != null) { + map.put(new UnsignedInteger(NOTE_KEY), new UnicodeString(note)); + } + } + return map; + } + + @Override + public RegistryType getRegistryType() { + return RegistryType.CRYPTO_HDKEY; + } + public static CryptoHDKey fromCbor(DataItem item) { boolean isMasterKey = false; - boolean isPrivateKey = false; + Boolean isPrivateKey = null; byte[] keyData = null; byte[] chainCode = null; CryptoCoinInfo useInfo = null; CryptoKeypath origin = null; CryptoKeypath children = null; byte[] parentFingerprint = null; + String name = null; + String note = null; Map map = (Map)item; for(DataItem key : map.getKeys()) { @@ -107,6 +177,10 @@ public class CryptoHDKey { children = CryptoKeypath.fromCbor(map.get(uintKey)); } else if(intKey == PARENT_FINGERPRINT_KEY) { parentFingerprint = ((UnsignedInteger)map.get(uintKey)).getValue().toByteArray(); + } else if(intKey == NAME_KEY) { + name = ((UnicodeString)map.get(uintKey)).getString(); + } else if(intKey == NOTE_KEY) { + note = ((UnicodeString)map.get(uintKey)).getString(); } } @@ -115,9 +189,13 @@ public class CryptoHDKey { } if(isMasterKey) { + if(chainCode == null) { + throw new IllegalStateException("Chain code data is null"); + } + return new CryptoHDKey(keyData, chainCode); } else { - return new CryptoHDKey(isPrivateKey, keyData, chainCode, useInfo, origin, children, parentFingerprint); + return new CryptoHDKey(isPrivateKey, keyData, chainCode, useInfo, origin, children, parentFingerprint, name, note); } } } diff --git a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoKeypath.java b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoKeypath.java index c5bac08..b710af6 100644 --- a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoKeypath.java +++ b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoKeypath.java @@ -2,12 +2,13 @@ package com.sparrowwallet.hummingbird.registry; import co.nstant.in.cbor.model.*; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.StringJoiner; -public class CryptoKeypath { +public class CryptoKeypath extends RegistryItem { public static final int COMPONENTS_KEY = 1; public static final int SOURCE_FINGERPRINT_KEY = 2; public static final int DEPTH_KEY = 3; @@ -50,6 +51,34 @@ public class CryptoKeypath { return depth; } + public DataItem toCbor() { + Map map = new Map(); + Array componentArray = new Array(); + for(PathComponent pathComponent : components) { + if(pathComponent.isWildcard()) { + componentArray.add(new Array()); + } else { + componentArray.add(new UnsignedInteger(pathComponent.getIndex())); + } + componentArray.add(pathComponent.isHardened() ? SimpleValue.TRUE : SimpleValue.FALSE); + } + if(!componentArray.getDataItems().isEmpty()) { + map.put(new UnsignedInteger(COMPONENTS_KEY), componentArray); + } + if(sourceFingerprint != null) { + map.put(new UnsignedInteger(SOURCE_FINGERPRINT_KEY), new UnsignedInteger(new BigInteger(1, sourceFingerprint))); + } + if(depth != null) { + map.put(new UnsignedInteger(DEPTH_KEY), new UnsignedInteger(depth)); + } + return map; + } + + @Override + public RegistryType getRegistryType() { + return RegistryType.CRYPTO_KEYPATH; + } + public static CryptoKeypath fromCbor(DataItem item) { List components = new ArrayList<>(); byte[] sourceFingerprint = null; diff --git a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoOutput.java b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoOutput.java index 29903c1..d1dcccd 100644 --- a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoOutput.java +++ b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoOutput.java @@ -8,7 +8,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -public class CryptoOutput { +public class CryptoOutput extends RegistryItem { private final List scriptExpressions; //Only one of the following will be not null @@ -53,6 +53,37 @@ public class CryptoOutput { return multiKey; } + public DataItem toCbor() { + DataItem item = null; + if(multiKey != null) { + item = multiKey.toCbor(); + } else if(ecKey != null) { + item = ecKey.toCbor(); + item.setTag(RegistryType.CRYPTO_ECKEY.getTag()); + } else if(hdKey != null) { + item = hdKey.toCbor(); + item.setTag(RegistryType.CRYPTO_HDKEY.getTag()); + } + + Tag tag = item.getTag(); + for(int i = scriptExpressions.size() - 1; i >= 0; i--) { + Tag newTag = new Tag(scriptExpressions.get(i).getTagValue()); + if(tag == null) { + item.setTag(newTag); + } else { + tag.setTag(newTag); + } + tag = newTag; + } + + return item; + } + + @Override + public RegistryType getRegistryType() { + return RegistryType.CRYPTO_OUTPUT; + } + public static CryptoOutput fromCbor(DataItem cbor) { List expressions = new ArrayList<>(); diff --git a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoPSBT.java b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoPSBT.java index 5f33714..f0fab6d 100644 --- a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoPSBT.java +++ b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoPSBT.java @@ -3,7 +3,7 @@ package com.sparrowwallet.hummingbird.registry; import co.nstant.in.cbor.model.ByteString; import co.nstant.in.cbor.model.DataItem; -public class CryptoPSBT { +public class CryptoPSBT extends RegistryItem { private final byte[] psbt; public CryptoPSBT(byte[] psbt) { @@ -14,6 +14,15 @@ public class CryptoPSBT { return psbt; } + public DataItem toCbor() { + return new ByteString(psbt); + } + + @Override + public RegistryType getRegistryType() { + return RegistryType.CRYPTO_PSBT; + } + public static CryptoPSBT fromCbor(DataItem item) { return new CryptoPSBT(((ByteString)item).getBytes()); } diff --git a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoSeed.java b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoSeed.java index 815c59e..e7d6325 100644 --- a/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoSeed.java +++ b/src/main/java/com/sparrowwallet/hummingbird/registry/CryptoSeed.java @@ -1,22 +1,29 @@ 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 co.nstant.in.cbor.model.*; import java.util.Date; -public class CryptoSeed { - public static final long PAYLOAD = 1; - public static final long BIRTHDATE = 2; +public class CryptoSeed extends RegistryItem { + public static final long PAYLOAD_KEY = 1; + public static final long BIRTHDATE_KEY = 2; + public static final long NAME_KEY = 3; + public static final long NOTE_KEY = 4; private final byte[] seed; private final Date birthdate; + private final String name; + private final String note; public CryptoSeed(byte[] seed, Date birthdate) { + this(seed, birthdate, null, null); + } + + public CryptoSeed(byte[] seed, Date birthdate, String name, String note) { this.seed = seed; this.birthdate = birthdate; + this.name = name; + this.note = note; } public byte[] getSeed() { @@ -27,18 +34,54 @@ public class CryptoSeed { return birthdate; } + public String getName() { + return name; + } + + public String getNote() { + return note; + } + + public DataItem toCbor() { + Map map = new Map(); + map.put(new UnsignedInteger(PAYLOAD_KEY), new ByteString(seed)); + if(birthdate != null) { + DataItem birthdateItem = new UnsignedInteger(birthdate.getTime() / (1000 * 60 * 60 * 24)); + birthdateItem.setTag(100); + map.put(new UnsignedInteger(BIRTHDATE_KEY), birthdateItem); + } + if(name != null) { + map.put(new UnsignedInteger(NAME_KEY), new UnicodeString(name)); + } + if(note != null) { + map.put(new UnsignedInteger(NOTE_KEY), new UnicodeString(note)); + } + return map; + } + + @Override + public RegistryType getRegistryType() { + return RegistryType.CRYPTO_SEED; + } + public static CryptoSeed fromCbor(DataItem item) { byte[] seed = null; Date birthdate = null; + String name = null; + String note = null; Map map = (Map)item; for(DataItem key : map.getKeys()) { UnsignedInteger uintKey = (UnsignedInteger)key; int intKey = uintKey.getValue().intValue(); - if(intKey == PAYLOAD) { + if(intKey == PAYLOAD_KEY) { seed = ((ByteString)map.get(key)).getBytes(); - } else if(intKey == BIRTHDATE) { + } else if(intKey == BIRTHDATE_KEY) { birthdate = new Date(((UnsignedInteger)map.get(key)).getValue().longValue() * 1000 * 60 * 60 * 24); + } else if(intKey == NAME_KEY) { + name = ((UnicodeString)map.get(key)).getString(); + } else if(intKey == NOTE_KEY) { + note = ((UnicodeString)map.get(key)).getString(); } } @@ -46,6 +89,6 @@ public class CryptoSeed { throw new IllegalStateException("Seed is null"); } - return new CryptoSeed(seed, birthdate); + return new CryptoSeed(seed, birthdate, name, note); } } diff --git a/src/main/java/com/sparrowwallet/hummingbird/registry/MultiKey.java b/src/main/java/com/sparrowwallet/hummingbird/registry/MultiKey.java index 22b3ca7..e61eab6 100644 --- a/src/main/java/com/sparrowwallet/hummingbird/registry/MultiKey.java +++ b/src/main/java/com/sparrowwallet/hummingbird/registry/MultiKey.java @@ -8,7 +8,7 @@ import co.nstant.in.cbor.model.UnsignedInteger; import java.util.ArrayList; import java.util.List; -public class MultiKey { +public class MultiKey implements CborSerializable { public static final int THRESHOLD_KEY = 1; public static final int KEYS_KEY = 2; @@ -34,6 +34,27 @@ public class MultiKey { return hdKeys; } + public DataItem toCbor() { + Map map = new Map(); + map.put(new UnsignedInteger(THRESHOLD_KEY), new UnsignedInteger(threshold)); + Array array = new Array(); + if(ecKeys != null && !ecKeys.isEmpty()) { + for(CryptoECKey cryptoECKey : ecKeys) { + DataItem eckeyItem = cryptoECKey.toCbor(); + eckeyItem.setTag(RegistryType.CRYPTO_ECKEY.getTag()); + array.add(eckeyItem); + } + } else if(hdKeys != null) { + for(CryptoHDKey cryptoHDKey : hdKeys) { + DataItem hdkeyItem = cryptoHDKey.toCbor(); + hdkeyItem.setTag(RegistryType.CRYPTO_HDKEY.getTag()); + array.add(hdkeyItem); + } + } + map.put(new UnsignedInteger(KEYS_KEY), array); + return map; + } + public static MultiKey fromCbor(DataItem item) { int threshold = 0; List ecKeys = new ArrayList<>(); @@ -57,6 +78,10 @@ public class MultiKey { } } + if(ecKeys.isEmpty() && hdKeys.isEmpty()) { + throw new IllegalStateException("One or more of eckey or hdkey must be specified"); + } + return new MultiKey(threshold, ecKeys, hdKeys); } } diff --git a/src/main/java/com/sparrowwallet/hummingbird/registry/RegistryItem.java b/src/main/java/com/sparrowwallet/hummingbird/registry/RegistryItem.java new file mode 100644 index 0000000..c8592df --- /dev/null +++ b/src/main/java/com/sparrowwallet/hummingbird/registry/RegistryItem.java @@ -0,0 +1,22 @@ +package com.sparrowwallet.hummingbird.registry; + +import co.nstant.in.cbor.CborEncoder; +import co.nstant.in.cbor.CborException; +import com.sparrowwallet.hummingbird.UR; + +import java.io.ByteArrayOutputStream; + +public abstract class RegistryItem implements CborSerializable { + public abstract RegistryType getRegistryType(); + + public UR toUR() { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + CborEncoder encoder = new CborEncoder(baos); + encoder.encode(toCbor()); + return new UR(getRegistryType(), baos.toByteArray()); + } catch(CborException | UR.InvalidTypeException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/src/test/java/com/sparrowwallet/hummingbird/TestUtils.java b/src/test/java/com/sparrowwallet/hummingbird/TestUtils.java index 9e885bf..78770e1 100644 --- a/src/test/java/com/sparrowwallet/hummingbird/TestUtils.java +++ b/src/test/java/com/sparrowwallet/hummingbird/TestUtils.java @@ -1,5 +1,11 @@ package com.sparrowwallet.hummingbird; +import co.nstant.in.cbor.CborEncoder; +import co.nstant.in.cbor.CborException; +import co.nstant.in.cbor.model.DataItem; + +import java.io.ByteArrayOutputStream; + public class TestUtils { public static byte[] hexToBytes(String s) { int len = s.length(); @@ -21,4 +27,11 @@ public class TestUtils { } return new String(hexChars); } + + public static String encode(DataItem item) throws CborException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + CborEncoder encoder = new CborEncoder(baos); + encoder.encode(item); + return bytesToHex(baos.toByteArray()); + } } diff --git a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoAccountTest.java b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoAccountTest.java index 4db4606..5cfa94a 100644 --- a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoAccountTest.java +++ b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoAccountTest.java @@ -69,6 +69,10 @@ public class CryptoAccountTest { Assert.assertEquals("48'/0'/0'/2'", cryptoOutput6.getHdKey().getOrigin().getPath()); Assert.assertEquals("59b69b2a", TestUtils.bytesToHex(cryptoOutput6.getHdKey().getParentFingerprint())); Assert.assertNull(cryptoOutput6.getHdKey().getChildren()); + + Assert.assertEquals(hex.toLowerCase(), TestUtils.encode(cryptoAccount.toCbor())); + String ur = "ur:crypto-account/oeadcyemrewytyaolntaadmutaaddloxaxhdclaxwmfmdeiamecsdsemgtvsjzcncygrkowtrontzschgezokstswkkscfmklrtauteyaahdcxiehfonurdppfyntapejpproypegrdawkgmaewejlsfdtsrfybdehcaflmtrlbdhpamtaaddyoyadlncsdwykaeykaeykaycynlytsnyltaadmhtaadmwtaaddloxaxhdclaostvelfemdyynwydwyaievosrgmambklovabdgypdglldvespsthysadamhpmjeinaahdcxntdllnaaeykoytdacygegwhgjsiyonpywmcmrpwphsvodsrerozsbyaxluzcoxdpamtaaddyoyadlncsehykaeykaeykaycypdbskeuytaadmwtaaddloxaxhdclaxzcfxeegdrpmogrgwkbzctlttweadkiengrwlhtprremouoluutqdpfbncedkynfhaahdcxjpwevdeogthttkmeswzcolcpsaahcfnshkhtehytclmnteatmoteadtlwynnftloamtaaddyoyadlncsghykaeykaeykaycybthlvytstaadmhtaaddloxaxhdclaxhhsnhdrpftdwuocntilydibehnecmovdfekpjkclcslasbhkpawsaddmcmmnahnyaahdcxlotedtndfymyltclhlmtpfsadscnhtztaolbnnkistaedegwfmmedreetnwmcycnamtaaddyoyadlfcsdpykaycyemrewytytaadmhtaadmetaaddloxaxhdclaxdwkswmztpytnswtsecnblfbayajkdldeclqzzolrsnhljedsgminetytbnahatbyaahdcxkkguwsvyimjkvwteytwztyswvendtpmncpasfrrylprnhtkblndrgrmkoyjtbkrpamtaaddyoyadlocsdyykaeykaeykadykaycyhkrpnddrtaadmetaaddloxaxhdclaohnhffmvsbndslrfgclpfjejyatbdpebacnzokotofxntaoemvskpaowmryfnotfgaahdcxdlnbvecentssfsssgylnhkrstoytecrdlyadrekirfaybglahltalsrfcaeerobwamtaaddyoyadlocsdyykaeykaeykaoykaycyhkrpnddrgdaogykb"; + Assert.assertEquals(ur, cryptoAccount.toUR().toString()); } @Test diff --git a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoAddressTest.java b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoAddressTest.java index 96984ad..b3b66aa 100644 --- a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoAddressTest.java +++ b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoAddressTest.java @@ -17,5 +17,8 @@ public class CryptoAddressTest { List items = CborDecoder.decode(data); CryptoAddress cryptoAddress = CryptoAddress.fromCbor(items.get(0)); Assert.assertEquals("77bff20c60e522dfaa3350c39b030a5d004e839a", TestUtils.bytesToHex(cryptoAddress.getData())); + Assert.assertEquals(hex.toLowerCase(), TestUtils.encode(cryptoAddress.toCbor())); + String ur = "ur:crypto-address/oyaxghktrswzbnhnvwcpurpkeogdsrndaxbkhlaegllsnyolrsemgu"; + Assert.assertEquals(ur, cryptoAddress.toUR().toString()); } } diff --git a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoBip39Test.java b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoBip39Test.java index 871b089..cd57462 100644 --- a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoBip39Test.java +++ b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoBip39Test.java @@ -13,11 +13,14 @@ import java.util.List; public class CryptoBip39Test { @Test public void testSeed() throws CborException { - String hex = "A2018C66736869656C646567726F75706565726F6465656177616B65646C6F636B6773617573616765646361736865676C6172656477617665646372657765666C616D6565676C6F76650262656E1947DA"; + String hex = "A2018C66736869656C646567726F75706565726F6465656177616B65646C6F636B6773617573616765646361736865676C6172656477617665646372657765666C616D6565676C6F76650262656E"; byte[] data = TestUtils.hexToBytes(hex); List items = CborDecoder.decode(data); CryptoBip39 cryptoSeed = CryptoBip39.fromCbor(items.get(0)); Assert.assertEquals(Arrays.asList("shield", "group", "erode", "awake", "lock", "sausage", "cash", "glare", "wave", "crew", "flame", "glove"), cryptoSeed.getWords()); Assert.assertEquals("en", cryptoSeed.getLanguage()); + Assert.assertEquals(hex.toLowerCase(), TestUtils.encode(cryptoSeed.toCbor())); + String ur = "ur:crypto-bip39/oeadlkiyjkisinihjzieihiojpjlkpjoihihjpjlieihihhskthsjeihiejzjliajeiojkhskpjkhsioihieiahsjkisihiojzhsjpihiekthskoihieiajpihktihiyjzhsjnihihiojzjlkoihaoidihjtrkkndede"; + Assert.assertEquals(ur, cryptoSeed.toUR().toString()); } } diff --git a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoECKeyTest.java b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoECKeyTest.java index 250574a..365cbb2 100644 --- a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoECKeyTest.java +++ b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoECKeyTest.java @@ -19,5 +19,8 @@ public class CryptoECKeyTest { Assert.assertEquals(0, cryptoECKey.getCurve()); Assert.assertTrue(cryptoECKey.isPrivateKey()); Assert.assertEquals("8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa", TestUtils.bytesToHex(cryptoECKey.getData())); + Assert.assertEquals(hex.toLowerCase(), TestUtils.encode(cryptoECKey.toCbor())); + String ur = "ur:crypto-eckey/oeaoykaxhdcxlkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypkrphsmyid"; + Assert.assertEquals(ur, cryptoECKey.toUR().toString()); } } diff --git a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoHDKeyTest.java b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoHDKeyTest.java index 144d17c..a03bb15 100644 --- a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoHDKeyTest.java +++ b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoHDKeyTest.java @@ -19,6 +19,9 @@ public class CryptoHDKeyTest { Assert.assertTrue(cryptoHDKey.isMaster()); Assert.assertEquals("00e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", TestUtils.bytesToHex(cryptoHDKey.getKey())); Assert.assertEquals("873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508", TestUtils.bytesToHex(cryptoHDKey.getChainCode())); + Assert.assertEquals(hex.toLowerCase(), TestUtils.encode(cryptoHDKey.toCbor())); + String ur = "ur:crypto-hdkey/otadykaxhdclaevswfdmjpfswpwkahcywspsmndwmusoskprbbehetchsnpfcybbmwrhchspfxjeecaahdcxltfszmlyrtdlgmhfcnzcctvwcmkbpsftgonbgauefsehgrqzdmvodizmweemtlaybakiylat"; + Assert.assertEquals(ur, cryptoHDKey.toUR().toString()); } @Test @@ -35,5 +38,8 @@ public class CryptoHDKeyTest { Assert.assertEquals("44'/1'/1'/0/1", cryptoHDKey.getOrigin().getPath()); Assert.assertEquals("e9181cf3", TestUtils.bytesToHex(cryptoHDKey.getParentFingerprint())); Assert.assertNull(cryptoHDKey.getChildren()); + Assert.assertEquals(hex.toLowerCase(), TestUtils.encode(cryptoHDKey.toCbor())); + String ur = "ur:crypto-hdkey/onaxhdclaojlvoechgferkdpqdiabdrflawshlhdmdcemtfnlrctghchbdolvwsednvdztbgolaahdcxtottgostdkhfdahdlykkecbbweskrymwflvdylgerkloswtbrpfdbsticmwylklpahtaadehoyaoadamtaaddyoyadlecsdwykadykadykaewkadwkaycywlcscewfihbdaehn"; + Assert.assertEquals(ur, cryptoHDKey.toUR().toString()); } } diff --git a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoOutputTest.java b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoOutputTest.java index 0f047f3..e8880c1 100644 --- a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoOutputTest.java +++ b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoOutputTest.java @@ -20,6 +20,9 @@ public class CryptoOutputTest { CryptoOutput cryptoOutput = CryptoOutput.fromCbor(items.get(0)); Assert.assertEquals(Collections.singletonList(ScriptExpression.PUBLIC_KEY_HASH), cryptoOutput.getScriptExpressions()); Assert.assertEquals("02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", TestUtils.bytesToHex(cryptoOutput.getEcKey().getData())); + Assert.assertEquals(hex, TestUtils.encode(cryptoOutput.toCbor())); + String ur = "ur:crypto-output/taadmutaadeyoyaxhdclaoswaalbmwfpwekijndyfefzjtmdrtketphhktmngrlkwsfnospypsasrhhhjonnvwtsqzwljy"; + Assert.assertEquals(ur, cryptoOutput.toUR().toString()); } @Test @@ -30,6 +33,9 @@ public class CryptoOutputTest { CryptoOutput cryptoOutput = CryptoOutput.fromCbor(items.get(0)); Assert.assertEquals(Arrays.asList(ScriptExpression.SCRIPT_HASH, ScriptExpression.WITNESS_PUBLIC_KEY_HASH), cryptoOutput.getScriptExpressions()); Assert.assertEquals("03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556", TestUtils.bytesToHex(cryptoOutput.getEcKey().getData())); + Assert.assertEquals(hex, TestUtils.encode(cryptoOutput.toCbor())); + String ur = "ur:crypto-output/taadmhtaadmwtaadeyoyaxhdclaxzmytkgtlkphywyoxcxfeftbbecgmectelfynfldllpisoyludlahknbbhndtkphfhlehmust"; + Assert.assertEquals(ur, cryptoOutput.toUR().toString()); } @Test @@ -47,6 +53,10 @@ public class CryptoOutputTest { CryptoECKey secondKey = cryptoOutput.getMultiKey().getEcKeys().get(1); Assert.assertEquals("03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe", TestUtils.bytesToHex(secondKey.getData())); + + Assert.assertEquals(hex, TestUtils.encode(cryptoOutput.toCbor())); + String ur = "ur:crypto-output/taadmhtaadmtoeadaoaolftaadeyoyaxhdclaodladvwvyhhsgeccapewflrfhrlbsfndlbkcwutahvwpeloleioksglwfvybkdradtaadeyoyaxhdclaxpstylrvowtstynguaspmchlenegonyryvtmsmtmsgshgvdbbsrhebybtztdisfrnpfadremh"; + Assert.assertEquals(ur, cryptoOutput.toUR().toString()); } @Test @@ -63,6 +73,9 @@ public class CryptoOutputTest { Assert.assertEquals("78412e3a", TestUtils.bytesToHex(cryptoOutput.getHdKey().getParentFingerprint())); Assert.assertEquals("1/*", cryptoOutput.getHdKey().getChildren().getPath()); Assert.assertNull(cryptoOutput.getHdKey().getChildren().getSourceFingerprint()); + Assert.assertEquals(hex, TestUtils.encode(cryptoOutput.toCbor())); + String ur = "ur:crypto-output/taadmutaaddlonaxhdclaotdqdinaeesjzmolfzsbbidlpiyhddlcximhltirfsptlvsmohscsamsgzoaxadwtaahdcxiaksataxbtgotictnybnqdoslsmdbztsmtryatjoialnolweuramsfdtolhtbadtamtaaddyoeadlncsdwykaeykaeykaocytegtqdfhattaaddyoyadlradwklawkaycyksfpdmftpyaaeelb"; + Assert.assertEquals(ur, cryptoOutput.toUR().toString()); } @Test @@ -92,5 +105,9 @@ public class CryptoOutputTest { Assert.assertNull(secondKey.getOrigin().getDepth()); Assert.assertEquals("0/0/*", secondKey.getChildren().getPath()); Assert.assertNull(secondKey.getChildren().getSourceFingerprint()); + + Assert.assertEquals(hex, TestUtils.encode(cryptoOutput.toCbor())); + String ur = "ur:crypto-output/taadmetaadmtoeadadaolftaaddloxaxhdclaxsbsgptsolkltkndsmskiaelfhhmdimcnmnlgutzotecpsfveylgrbdhptbpsveosaahdcxhnganelacwldjnlschnyfxjyplrllfdrplpswdnbuyctlpwyfmmhgsgtwsrymtldamtaaddyoyaxaeattaaddyoyadlnadwkaewklawktaaddloxaxhdclaoztnnhtwtpslgndfnwpzedrlomnclchrdfsayntlplplojznslfjejecpptlgbgwdaahdcxwtmhnyzmpkkbvdpyvwutglbeahmktyuogusnjonththhdwpsfzvdfpdlcndlkensamtaaddyoeadlfaewkaocyrycmrnvwattaaddyoyadlnaewkaewklawkkkztdlon"; + Assert.assertEquals(ur, cryptoOutput.toUR().toString()); } } diff --git a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoPSBTTest.java b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoPSBTTest.java new file mode 100644 index 0000000..e90a016 --- /dev/null +++ b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoPSBTTest.java @@ -0,0 +1,25 @@ +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 CryptoPSBTTest { + @Test + public void testEncode() throws CborException { + String hex = "58A770736274FF01009A020000000258E87A21B56DAF0C23BE8E7070456C336F7CBAA5C8757924F545887BB2ABDD750000000000FFFFFFFF838D0427D0EC650A68AA46BB0B098AEA4422C071B2CA78352A077959D07CEA1D0100000000FFFFFFFF0270AAF00800000000160014D85C2B71D0060B09C9886AEB815E50991DDA124D00E1F5050000000016001400AEA9A2E5F0F876A588DF5546E8742D1D87008F000000000000000000"; + byte[] data = TestUtils.hexToBytes(hex); + List items = CborDecoder.decode(data); + CryptoPSBT cryptoPSBT = CryptoPSBT.fromCbor(items.get(0)); + String psbtHex = "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000"; + Assert.assertArrayEquals(TestUtils.hexToBytes(psbtHex), cryptoPSBT.getPsbt()); + Assert.assertEquals(hex.toLowerCase(), TestUtils.encode(cryptoPSBT.toCbor())); + String ur = "ur:crypto-psbt/hdosjojkidjyzmadaenyaoaeaeaeaohdvsknclrejnpebncnrnmnjojofejzeojlkerdonspkpkkdkykfelokgprpyutkpaeaeaeaeaezmzmzmzmlslgaaditiwpihbkispkfgrkbdaslewdfycprtjsprsgksecdratkkhktikewdcaadaeaeaeaezmzmzmzmaojopkwtayaeaeaeaecmaebbtphhdnjstiambdassoloimwmlyhygdnlcatnbggtaevyykahaeaeaeaecmaebbaeplptoevwwtyakoonlourgofgvsjydpcaltaemyaeaeaeaeaeaeaeaeaebkgdcarh"; + Assert.assertEquals(ur, cryptoPSBT.toUR().toString()); + } +} diff --git a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoSeedTest.java b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoSeedTest.java index a2c436e..04e1906 100644 --- a/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoSeedTest.java +++ b/src/test/java/com/sparrowwallet/hummingbird/registry/CryptoSeedTest.java @@ -22,5 +22,8 @@ public class CryptoSeedTest { CryptoSeed cryptoSeed = CryptoSeed.fromCbor(items.get(0)); Assert.assertEquals("c7098580125e2ab0981253468b2dbc52", TestUtils.bytesToHex(cryptoSeed.getSeed())); Assert.assertEquals("12 May 2020", dateFormat.format(cryptoSeed.getBirthdate())); + Assert.assertEquals(hex.toLowerCase(), TestUtils.encode(cryptoSeed.toCbor())); + String ur = "ur:crypto-seed/oeadgdstaslplabghydrpfmkbggufgludprfgmaotpiecffltnlpqdenos"; + Assert.assertEquals(ur, cryptoSeed.toUR().toString()); } }