From 40bc6f4c465e574c5ccdd6d333fad319f6697468 Mon Sep 17 00:00:00 2001 From: Adam Mackler Date: Sun, 8 Jun 2014 00:49:18 -0400 Subject: [PATCH] Add a method to deserialize a DeterministicKey from a byte-array rather than from the base-58 encoding thereof. --- .../bitcoin/crypto/DeterministicKey.java | 90 ++++++++++--------- .../crypto/ChildKeyDerivationTest.java | 12 ++- 2 files changed, 58 insertions(+), 44 deletions(-) diff --git a/core/src/main/java/com/google/bitcoin/crypto/DeterministicKey.java b/core/src/main/java/com/google/bitcoin/crypto/DeterministicKey.java index 5bbc425b..d239f0ba 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/DeterministicKey.java +++ b/core/src/main/java/com/google/bitcoin/crypto/DeterministicKey.java @@ -347,54 +347,58 @@ public class DeterministicKey extends ECKey { public static DeterministicKey deserializeB58(@Nullable DeterministicKey parent, String base58) { try { - ByteBuffer buffer = ByteBuffer.wrap(Base58.decodeChecked(base58)); - int header = buffer.getInt(); - if (header != HEADER_PRIV && header != HEADER_PUB) - throw new IllegalArgumentException("Unknown header bytes: " + base58.substring(0, 4)); - boolean pub = header == HEADER_PUB; - byte depth = buffer.get(); - byte[] parentFingerprint = new byte[4]; - buffer.get(parentFingerprint); - final int i = buffer.getInt(); - final ChildNumber childNumber = new ChildNumber(i); - ImmutableList path; - if (parent != null) { - if (Arrays.equals(parentFingerprint, HDUtils.longTo4ByteArray(0))) - throw new IllegalArgumentException("Parent was provided but this key doesn't have one"); - if (!Arrays.equals(parent.getFingerprint(), parentFingerprint)) - throw new IllegalArgumentException("Parent fingerprints don't match"); - path = HDUtils.append(parent.getPath(), childNumber); - if (path.size() != depth) - throw new IllegalArgumentException("Depth does not match"); - } else { - if (depth == 0) { - path = ImmutableList.of(); - } else if (depth == 1) { - // We have been given a key that is not a root key, yet we also don't have any object representing - // the parent. This can happen when deserializing an account key for a watching wallet. In this case, - // we assume that the parent has a path of zero. - path = ImmutableList.of(childNumber); - } else { - throw new IllegalArgumentException("Depth is " + depth + " and no parent key was provided, so we " + - "cannot reconstruct the key path from the provided data."); - } - } - byte[] chainCode = new byte[32]; - buffer.get(chainCode); - byte[] data = new byte[33]; - buffer.get(data); - checkArgument(!buffer.hasRemaining(), "Found unexpected data in key"); - if (pub) { - ECPoint point = ECKey.CURVE.getCurve().decodePoint(data); - return new DeterministicKey(path, chainCode, point, null, parent); - } else { - return new DeterministicKey(path, chainCode, new BigInteger(1, data), parent); - } + return deserialize(parent, Base58.decodeChecked(base58)); } catch (AddressFormatException e) { throw new IllegalArgumentException(e); } } + public static DeterministicKey deserialize(@Nullable DeterministicKey parent, byte[] serializedKey) { + ByteBuffer buffer = ByteBuffer.wrap(serializedKey); + int header = buffer.getInt(); + if (header != HEADER_PRIV && header != HEADER_PUB) + throw new IllegalArgumentException("Unknown header bytes: " + toBase58(serializedKey).substring(0, 4)); + boolean pub = header == HEADER_PUB; + byte depth = buffer.get(); + byte[] parentFingerprint = new byte[4]; + buffer.get(parentFingerprint); + final int i = buffer.getInt(); + final ChildNumber childNumber = new ChildNumber(i); + ImmutableList path; + if (parent != null) { + if (Arrays.equals(parentFingerprint, HDUtils.longTo4ByteArray(0))) + throw new IllegalArgumentException("Parent was provided but this key doesn't have one"); + if (!Arrays.equals(parent.getFingerprint(), parentFingerprint)) + throw new IllegalArgumentException("Parent fingerprints don't match"); + path = HDUtils.append(parent.getPath(), childNumber); + if (path.size() != depth) + throw new IllegalArgumentException("Depth does not match"); + } else { + if (depth == 0) { + path = ImmutableList.of(); + } else if (depth == 1) { + // We have been given a key that is not a root key, yet we also don't have any object representing + // the parent. This can happen when deserializing an account key for a watching wallet. In this case, + // we assume that the parent has a path of zero. + path = ImmutableList.of(childNumber); + } else { + throw new IllegalArgumentException("Depth is " + depth + " and no parent key was provided, so we " + + "cannot reconstruct the key path from the provided data."); + } + } + byte[] chainCode = new byte[32]; + buffer.get(chainCode); + byte[] data = new byte[33]; + buffer.get(data); + checkArgument(!buffer.hasRemaining(), "Found unexpected data in key"); + if (pub) { + ECPoint point = ECKey.CURVE.getCurve().decodePoint(data); + return new DeterministicKey(path, chainCode, point, null, parent); + } else { + return new DeterministicKey(path, chainCode, new BigInteger(1, data), parent); + } + } + /** * Verifies equality of all fields but NOT the parent pointer (thus the same key derived in two separate heirarchy * objects will equal each other. diff --git a/core/src/test/java/com/google/bitcoin/crypto/ChildKeyDerivationTest.java b/core/src/test/java/com/google/bitcoin/crypto/ChildKeyDerivationTest.java index e008c3c3..ca02483e 100644 --- a/core/src/test/java/com/google/bitcoin/crypto/ChildKeyDerivationTest.java +++ b/core/src/test/java/com/google/bitcoin/crypto/ChildKeyDerivationTest.java @@ -165,22 +165,32 @@ public class ChildKeyDerivationTest { } @Test - public void serializeToText() { + public void serializeToTextAndBytes() { DeterministicKey key1 = HDKeyDerivation.createMasterPrivateKey("satoshi lives!".getBytes()); DeterministicKey key2 = HDKeyDerivation.deriveChildKey(key1, ChildNumber.ZERO_HARDENED); { final String pub58 = key1.serializePubB58(); final String priv58 = key1.serializePrivB58(); + final byte[] pub = key1.serializePublic(); + final byte[] priv = key1.serializePrivate(); assertEquals("xpub661MyMwAqRbcF7mq7Aejj5xZNzFfgi3ABamE9FedDHVmViSzSxYTgAQGcATDo2J821q7Y9EAagjg5EP3L7uBZk11PxZU3hikL59dexfLkz3", pub58); assertEquals("xprv9s21ZrQH143K2dhN197jMx1ppxRBHFKJpMqdLsF1ewxncv7quRED8N5nksxphju3W7naj1arF56L5PUEWfuSk8h73Sb2uh7bSwyXNrjzhAZ", priv58); + assertArrayEquals(new byte[]{4, -120, -78, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57, -68, 93, -104, -97, 31, -105, -18, 109, 112, 104, 45, -77, -77, 18, 85, -29, -120, 86, -113, 26, 48, -18, -79, -110, -6, -27, 87, 86, 24, 124, 99, 3, 96, -33, -14, 67, -19, -47, 16, 76, -49, -11, -30, -123, 7, 56, 101, 91, 74, 125, -127, 61, 42, -103, 90, -93, 66, -36, 2, -126, -107, 30, 24, -111}, pub); + assertArrayEquals(new byte[]{4, -120, -83, -28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57, -68, 93, -104, -97, 31, -105, -18, 109, 112, 104, 45, -77, -77, 18, 85, -29, -120, 86, -113, 26, 48, -18, -79, -110, -6, -27, 87, 86, 24, 124, 99, 0, -96, -75, 47, 90, -49, 92, -74, 92, -128, -125, 23, 38, -10, 97, -66, -19, 50, -112, 30, -111, -57, -124, 118, -86, 126, -35, -4, -51, 19, 109, 67, 116}, priv); assertEquals(DeterministicKey.deserializeB58(null, priv58), key1); assertEquals(DeterministicKey.deserializeB58(null, pub58).getPubKeyPoint(), key1.getPubKeyPoint()); + assertEquals(DeterministicKey.deserialize(null, priv), key1); + assertEquals(DeterministicKey.deserialize(null, pub).getPubKeyPoint(), key1.getPubKeyPoint()); } { final String pub58 = key2.serializePubB58(); final String priv58 = key2.serializePrivB58(); + final byte[] pub = key2.serializePublic(); + final byte[] priv = key2.serializePrivate(); assertEquals(DeterministicKey.deserializeB58(key1, priv58), key2); assertEquals(DeterministicKey.deserializeB58(key1, pub58).getPubKeyPoint(), key2.getPubKeyPoint()); + assertEquals(DeterministicKey.deserialize(key1, priv), key2); + assertEquals(DeterministicKey.deserialize(key1, pub).getPubKeyPoint(), key2.getPubKeyPoint()); } }