v1.6.2 to fix byte padding bug in short source fingerprint integers

This commit is contained in:
Craig Raw 2021-07-20 11:16:15 +02:00
parent 0bdd3ebc83
commit 06af9b8ee2
7 changed files with 72 additions and 5 deletions

View file

@ -12,7 +12,7 @@ Hummingbird requires a minimum of Java 8.
Hummingbird is hosted in Maven Central and can be added as a dependency with the following: Hummingbird is hosted in Maven Central and can be added as a dependency with the following:
``` ```
implementation('com.sparrowwallet:hummingbird:1.6.1') implementation('com.sparrowwallet:hummingbird:1.6.2')
``` ```
## Usage ## Usage

View file

@ -16,7 +16,7 @@ apply plugin: 'com.bmuschko.nexus'
archivesBaseName = 'hummingbird' archivesBaseName = 'hummingbird'
group 'com.sparrowwallet' group 'com.sparrowwallet'
version '1.6.1' version '1.6.2'
repositories { repositories {
mavenCentral() mavenCentral()

View file

@ -47,12 +47,13 @@ public class CryptoAccount extends RegistryItem {
Map cryptoAccountMap = (Map)cbor; Map cryptoAccountMap = (Map)cbor;
UnsignedInteger uintMasterFingerprint = (UnsignedInteger)cryptoAccountMap.get(new UnsignedInteger(MASTER_FINGERPRINT_KEY)); UnsignedInteger uintMasterFingerprint = (UnsignedInteger)cryptoAccountMap.get(new UnsignedInteger(MASTER_FINGERPRINT_KEY));
byte[] masterFingerprint = bigIntegerToBytes(uintMasterFingerprint.getValue(), 4);
Array outputDescriptors = (Array)cryptoAccountMap.get(new UnsignedInteger(OUTPUT_DESCRIPTORS_KEY)); Array outputDescriptors = (Array)cryptoAccountMap.get(new UnsignedInteger(OUTPUT_DESCRIPTORS_KEY));
List<CryptoOutput> cryptoOutputs = new ArrayList<>(outputDescriptors.getDataItems().size()); List<CryptoOutput> cryptoOutputs = new ArrayList<>(outputDescriptors.getDataItems().size());
for(DataItem item : outputDescriptors.getDataItems()) { for(DataItem item : outputDescriptors.getDataItems()) {
cryptoOutputs.add(CryptoOutput.fromCbor(item)); cryptoOutputs.add(CryptoOutput.fromCbor(item));
} }
return new CryptoAccount(uintMasterFingerprint.getValue().toByteArray(), cryptoOutputs); return new CryptoAccount(masterFingerprint, cryptoOutputs);
} }
} }

View file

@ -176,7 +176,7 @@ public class CryptoHDKey extends RegistryItem {
} else if(intKey == CHILDREN_KEY) { } else if(intKey == CHILDREN_KEY) {
children = CryptoKeypath.fromCbor(map.get(uintKey)); children = CryptoKeypath.fromCbor(map.get(uintKey));
} else if(intKey == PARENT_FINGERPRINT_KEY) { } else if(intKey == PARENT_FINGERPRINT_KEY) {
parentFingerprint = ((UnsignedInteger)map.get(uintKey)).getValue().toByteArray(); parentFingerprint = bigIntegerToBytes(((UnsignedInteger)map.get(uintKey)).getValue(), 4);
} else if(intKey == NAME_KEY) { } else if(intKey == NAME_KEY) {
name = ((UnicodeString)map.get(uintKey)).getString(); name = ((UnicodeString)map.get(uintKey)).getString();
} else if(intKey == NOTE_KEY) { } else if(intKey == NOTE_KEY) {

View file

@ -99,7 +99,7 @@ public class CryptoKeypath extends RegistryItem {
} }
} }
} else if(intKey == SOURCE_FINGERPRINT_KEY) { } else if(intKey == SOURCE_FINGERPRINT_KEY) {
sourceFingerprint = ((UnsignedInteger)map.get(key)).getValue().toByteArray(); sourceFingerprint = bigIntegerToBytes(((UnsignedInteger)map.get(key)).getValue(), 4);
} else if(intKey == DEPTH_KEY) { } else if(intKey == DEPTH_KEY) {
depth = ((UnsignedInteger)map.get(key)).getValue().intValue(); depth = ((UnsignedInteger)map.get(key)).getValue().intValue();
} }

View file

@ -5,6 +5,8 @@ import co.nstant.in.cbor.CborException;
import com.sparrowwallet.hummingbird.UR; import com.sparrowwallet.hummingbird.UR;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.util.Arrays;
public abstract class RegistryItem implements CborSerializable { public abstract class RegistryItem implements CborSerializable {
public abstract RegistryType getRegistryType(); public abstract RegistryType getRegistryType();
@ -19,4 +21,34 @@ public abstract class RegistryItem implements CborSerializable {
throw new IllegalArgumentException(e); throw new IllegalArgumentException(e);
} }
} }
/**
* <p>
* The regular {@link BigInteger#toByteArray()} includes the sign bit of the number and
* might result in an extra byte addition. This method removes this extra byte.
* </p>
* @param b the integer to format into a byte array
* @param numBytes the desired size of the resulting byte array
* @return numBytes byte long array.
*/
protected static byte[] bigIntegerToBytes(BigInteger b, int numBytes) {
if(b.signum() < 0) {
throw new IllegalArgumentException("b must be positive or zero");
}
if(numBytes <= 0) {
throw new IllegalArgumentException("numBytes must be positive");
}
byte[] src = b.toByteArray();
byte[] dest = new byte[numBytes];
boolean isFirstByteOnlyForSign = src[0] == 0;
int length = isFirstByteOnlyForSign ? src.length - 1 : src.length;
if(length > numBytes) {
throw new IllegalArgumentException("The given number does not fit in " + numBytes);
}
int srcPos = isFirstByteOnlyForSign ? 1 : 0;
int destPos = numBytes - length;
System.arraycopy(src, srcPos, dest, destPos, length);
Arrays.fill(src, (byte)0);
return dest;
}
} }

View file

@ -61,4 +61,38 @@ public class CryptoHDKeyTest {
String ur = "ur:crypto-hdkey/otaxhdclaxpsfswtmnsknejlceuogoqdaelbmhwnptlrecwpeehhfnpsfzbauecatleotsheptaahdcxvsbbhlrpdivdmelovygscttbstjpnllpasmtcaecmyvswpwftssffxrkcabsmdcxamtaaddyoeadlaaocylpgrstlfiewtseje"; String ur = "ur:crypto-hdkey/otaxhdclaxpsfswtmnsknejlceuogoqdaelbmhwnptlrecwpeehhfnpsfzbauecatleotsheptaahdcxvsbbhlrpdivdmelovygscttbstjpnllpasmtcaecmyvswpwftssffxrkcabsmdcxamtaaddyoeadlaaocylpgrstlfiewtseje";
Assert.assertEquals(ur, cryptoHDKey.toUR().toString()); Assert.assertEquals(ur, cryptoHDKey.toUR().toString());
} }
@Test
public void testZeroMasterFingerprint() throws CborException {
String hex = "a303582103ac3df08ec59f6f1cdc55b3007f90f1a98435ec345c3cac400ede1dd533d75fa9045820e8145db627e79188e14c1fd6c772998509961d358fe8ecf3d7cc43bb1d0f952006d90130a201800200";
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("03ac3df08ec59f6f1cdc55b3007f90f1a98435ec345c3cac400ede1dd533d75fa9", TestUtils.bytesToHex(cryptoHDKey.getKey()));
Assert.assertEquals("e8145db627e79188e14c1fd6c772998509961d358fe8ecf3d7cc43bb1d0f9520", TestUtils.bytesToHex(cryptoHDKey.getChainCode()));
Assert.assertNull(cryptoHDKey.getOrigin().getPath());
Assert.assertEquals("00000000", TestUtils.bytesToHex(cryptoHDKey.getOrigin().getSourceFingerprint()));
Assert.assertEquals(hex.toLowerCase(), TestUtils.encode(cryptoHDKey.toCbor()));
String ur = "ur:crypto-hdkey/otaxhdclaxpsfswtmnsknejlceuogoqdaelbmhwnptlrecwpeehhfnpsfzbauecatleotsheptaahdcxvsbbhlrpdivdmelovygscttbstjpnllpasmtcaecmyvswpwftssffxrkcabsmdcxamtaaddyoeadlaaoaebkoxdive";
Assert.assertEquals(ur, cryptoHDKey.toUR().toString());
}
@Test
public void testShortMasterFingerprint() throws CborException {
String hex = "A303582103AC3DF08EC59F6F1CDC55B3007F90F1A98435EC345C3CAC400EDE1DD533D75FA9045820E8145DB627E79188E14C1FD6C772998509961D358FE8ECF3D7CC43BB1D0F952006D90130A201800218FF";
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("03ac3df08ec59f6f1cdc55b3007f90f1a98435ec345c3cac400ede1dd533d75fa9", TestUtils.bytesToHex(cryptoHDKey.getKey()));
Assert.assertEquals("e8145db627e79188e14c1fd6c772998509961d358fe8ecf3d7cc43bb1d0f9520", TestUtils.bytesToHex(cryptoHDKey.getChainCode()));
Assert.assertNull(cryptoHDKey.getOrigin().getPath());
Assert.assertEquals("000000ff", TestUtils.bytesToHex(cryptoHDKey.getOrigin().getSourceFingerprint()));
Assert.assertEquals(hex.toLowerCase(), TestUtils.encode(cryptoHDKey.toCbor()));
String ur = "ur:crypto-hdkey/otaxhdclaxpsfswtmnsknejlceuogoqdaelbmhwnptlrecwpeehhfnpsfzbauecatleotsheptaahdcxvsbbhlrpdivdmelovygscttbstjpnllpasmtcaecmyvswpwftssffxrkcabsmdcxamtaaddyoeadlaaocszmtnkocyct";
Assert.assertEquals(ur, cryptoHDKey.toUR().toString());
}
} }