/**
* Copyright 2013 Matija Mazi.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.matthewmitchell.peercoinj.crypto;
import com.matthewmitchell.peercoinj.core.ECKey;
import com.matthewmitchell.peercoinj.core.Sha256Hash;
import org.junit.Test;
import org.spongycastle.crypto.params.KeyParameter;
import static com.matthewmitchell.peercoinj.core.Utils.HEX;
import static org.junit.Assert.*;
/**
* This test is adapted from Armory's BIP 32 tests.
*/
public class ChildKeyDerivationTest {
private static final int HDW_CHAIN_EXTERNAL = 0;
private static final int HDW_CHAIN_INTERNAL = 1;
@Test
public void testChildKeyDerivation() throws Exception {
String ckdTestVectors[] = {
// test case 1:
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"04" + "6a04ab98d9e4774ad806e302dddeb63b" +
"ea16b5cb5f223ee77478e861bb583eb3" +
"36b6fbcb60b5b3d4f1551ac45e5ffc49" +
"36466e7d98f6c7c0ec736539f74691a6",
"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
// test case 2:
"be05d9ded0a73f81b814c93792f753b35c575fe446760005d44e0be13ba8935a",
"02" + "b530da16bbff1428c33020e87fc9e699" +
"cc9c753a63b8678ce647b7457397acef",
"7012bc411228495f25d666d55fdce3f10a93908b5f9b9b7baa6e7573603a7bda"
};
for(int i = 0; i < 1; i++) {
byte[] priv = HEX.decode(ckdTestVectors[3 * i]);
byte[] pub = HEX.decode(ckdTestVectors[3 * i + 1]);
byte[] chain = HEX.decode(ckdTestVectors[3 * i + 2]); // chain code
//////////////////////////////////////////////////////////////////////////
// Start with an extended PRIVATE key
DeterministicKey ekprv = HDKeyDerivation.createMasterPrivKeyFromBytes(priv, chain);
// Create two accounts
DeterministicKey ekprv_0 = HDKeyDerivation.deriveChildKey(ekprv, 0);
DeterministicKey ekprv_1 = HDKeyDerivation.deriveChildKey(ekprv, 1);
// Create internal and external chain on Account 0
DeterministicKey ekprv_0_EX = HDKeyDerivation.deriveChildKey(ekprv_0, HDW_CHAIN_EXTERNAL);
DeterministicKey ekprv_0_IN = HDKeyDerivation.deriveChildKey(ekprv_0, HDW_CHAIN_INTERNAL);
// Create three addresses on external chain
DeterministicKey ekprv_0_EX_0 = HDKeyDerivation.deriveChildKey(ekprv_0_EX, 0);
DeterministicKey ekprv_0_EX_1 = HDKeyDerivation.deriveChildKey(ekprv_0_EX, 1);
DeterministicKey ekprv_0_EX_2 = HDKeyDerivation.deriveChildKey(ekprv_0_EX, 2);
// Create three addresses on internal chain
DeterministicKey ekprv_0_IN_0 = HDKeyDerivation.deriveChildKey(ekprv_0_IN, 0);
DeterministicKey ekprv_0_IN_1 = HDKeyDerivation.deriveChildKey(ekprv_0_IN, 1);
DeterministicKey ekprv_0_IN_2 = HDKeyDerivation.deriveChildKey(ekprv_0_IN, 2);
// Now add a few more addresses with very large indices
DeterministicKey ekprv_1_IN = HDKeyDerivation.deriveChildKey(ekprv_1, HDW_CHAIN_INTERNAL);
DeterministicKey ekprv_1_IN_4095 = HDKeyDerivation.deriveChildKey(ekprv_1_IN, 4095);
// ExtendedHierarchicKey ekprv_1_IN_4bil = HDKeyDerivation.deriveChildKey(ekprv_1_IN, 4294967295L);
//////////////////////////////////////////////////////////////////////////
// Repeat the above with PUBLIC key
DeterministicKey ekpub = HDKeyDerivation.createMasterPubKeyFromBytes(HDUtils.toCompressed(pub), chain);
// Create two accounts
DeterministicKey ekpub_0 = HDKeyDerivation.deriveChildKey(ekpub, 0);
DeterministicKey ekpub_1 = HDKeyDerivation.deriveChildKey(ekpub, 1);
// Create internal and external chain on Account 0
DeterministicKey ekpub_0_EX = HDKeyDerivation.deriveChildKey(ekpub_0, HDW_CHAIN_EXTERNAL);
DeterministicKey ekpub_0_IN = HDKeyDerivation.deriveChildKey(ekpub_0, HDW_CHAIN_INTERNAL);
// Create three addresses on external chain
DeterministicKey ekpub_0_EX_0 = HDKeyDerivation.deriveChildKey(ekpub_0_EX, 0);
DeterministicKey ekpub_0_EX_1 = HDKeyDerivation.deriveChildKey(ekpub_0_EX, 1);
DeterministicKey ekpub_0_EX_2 = HDKeyDerivation.deriveChildKey(ekpub_0_EX, 2);
// Create three addresses on internal chain
DeterministicKey ekpub_0_IN_0 = HDKeyDerivation.deriveChildKey(ekpub_0_IN, 0);
DeterministicKey ekpub_0_IN_1 = HDKeyDerivation.deriveChildKey(ekpub_0_IN, 1);
DeterministicKey ekpub_0_IN_2 = HDKeyDerivation.deriveChildKey(ekpub_0_IN, 2);
// Now add a few more addresses with very large indices
DeterministicKey ekpub_1_IN = HDKeyDerivation.deriveChildKey(ekpub_1, HDW_CHAIN_INTERNAL);
DeterministicKey ekpub_1_IN_4095 = HDKeyDerivation.deriveChildKey(ekpub_1_IN, 4095);
// ExtendedHierarchicKey ekpub_1_IN_4bil = HDKeyDerivation.deriveChildKey(ekpub_1_IN, 4294967295L);
assertEquals(hexEncodePub(ekprv.getPubOnly()), hexEncodePub(ekpub));
assertEquals(hexEncodePub(ekprv_0.getPubOnly()), hexEncodePub(ekpub_0));
assertEquals(hexEncodePub(ekprv_1.getPubOnly()), hexEncodePub(ekpub_1));
assertEquals(hexEncodePub(ekprv_0_IN.getPubOnly()), hexEncodePub(ekpub_0_IN));
assertEquals(hexEncodePub(ekprv_0_IN_0.getPubOnly()), hexEncodePub(ekpub_0_IN_0));
assertEquals(hexEncodePub(ekprv_0_IN_1.getPubOnly()), hexEncodePub(ekpub_0_IN_1));
assertEquals(hexEncodePub(ekprv_0_IN_2.getPubOnly()), hexEncodePub(ekpub_0_IN_2));
assertEquals(hexEncodePub(ekprv_0_EX_0.getPubOnly()), hexEncodePub(ekpub_0_EX_0));
assertEquals(hexEncodePub(ekprv_0_EX_1.getPubOnly()), hexEncodePub(ekpub_0_EX_1));
assertEquals(hexEncodePub(ekprv_0_EX_2.getPubOnly()), hexEncodePub(ekpub_0_EX_2));
assertEquals(hexEncodePub(ekprv_1_IN.getPubOnly()), hexEncodePub(ekpub_1_IN));
assertEquals(hexEncodePub(ekprv_1_IN_4095.getPubOnly()), hexEncodePub(ekpub_1_IN_4095));
//assertEquals(hexEncodePub(ekprv_1_IN_4bil.getPubOnly()), hexEncodePub(ekpub_1_IN_4bil));
}
}
@Test
public void inverseEqualsNormal() throws Exception {
DeterministicKey key1 = HDKeyDerivation.createMasterPrivateKey("Wired / Aug 13th 2014 / Snowden: I Left the NSA Clues, But They Couldn't Find Them".getBytes());
HDKeyDerivation.RawKeyBytes key2 = HDKeyDerivation.deriveChildKeyBytesFromPublic(key1.getPubOnly(), ChildNumber.ZERO, HDKeyDerivation.PublicDeriveMode.NORMAL);
HDKeyDerivation.RawKeyBytes key3 = HDKeyDerivation.deriveChildKeyBytesFromPublic(key1.getPubOnly(), ChildNumber.ZERO, HDKeyDerivation.PublicDeriveMode.WITH_INVERSION);
assertArrayEquals(key2.keyBytes, key3.keyBytes);
assertArrayEquals(key2.chainCode, key3.chainCode);
}
@Test
public void encryptedDerivation() throws Exception {
// Check that encrypting a parent key in the heirarchy and then deriving from it yields a DeterministicKey
// with no private key component, and that the private key bytes are derived on demand.
KeyCrypter scrypter = new KeyCrypterScrypt();
KeyParameter aesKey = scrypter.deriveKey("we never went to the moon");
DeterministicKey key1 = HDKeyDerivation.createMasterPrivateKey("it was all a hoax".getBytes());
DeterministicKey encryptedKey1 = key1.encrypt(scrypter, aesKey, null);
DeterministicKey decryptedKey1 = encryptedKey1.decrypt(aesKey);
assertEquals(key1, decryptedKey1);
DeterministicKey key2 = HDKeyDerivation.deriveChildKey(key1, ChildNumber.ZERO);
DeterministicKey derivedKey2 = HDKeyDerivation.deriveChildKey(encryptedKey1, ChildNumber.ZERO);
assertTrue(derivedKey2.isEncrypted()); // parent is encrypted.
DeterministicKey decryptedKey2 = derivedKey2.decrypt(aesKey);
assertFalse(decryptedKey2.isEncrypted());
assertEquals(key2, decryptedKey2);
Sha256Hash hash = Sha256Hash.create("the mainstream media won't cover it. why is that?".getBytes());
try {
derivedKey2.sign(hash);
fail();
} catch (ECKey.KeyIsEncryptedException e) {
// Ignored.
}
ECKey.ECDSASignature signature = derivedKey2.sign(hash, aesKey);
assertTrue(derivedKey2.verify(hash, signature));
}
@Test
public void pubOnlyDerivation() throws Exception {
DeterministicKey key1 = HDKeyDerivation.createMasterPrivateKey("satoshi lives!".getBytes());
DeterministicKey key2 = HDKeyDerivation.deriveChildKey(key1, ChildNumber.ZERO_HARDENED);
DeterministicKey key3 = HDKeyDerivation.deriveChildKey(key2, ChildNumber.ZERO);
DeterministicKey pubkey3 = HDKeyDerivation.deriveChildKey(key2.getPubOnly(), ChildNumber.ZERO);
assertEquals(key3.getPubKeyPoint(), pubkey3.getPubKeyPoint());
}
@Test
public void serializeToTextAndBytes() {
DeterministicKey key1 = HDKeyDerivation.createMasterPrivateKey("satoshi lives!".getBytes());
DeterministicKey key2 = HDKeyDerivation.deriveChildKey(key1, ChildNumber.ZERO_HARDENED);
// Creation time can't survive the xpub serialization format unfortunately.
key1.setCreationTimeSeconds(0);
key2.setCreationTimeSeconds(0);
{
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());
}
}
private static String hexEncodePub(DeterministicKey pubKey) {
return HEX.encode(pubKey.getPubKey());
}
}