Rename DeterministicKey.getPubOnly() to dropPrivateBytes() to reflect what it's actually trying to do, and add dropParent() as well for the cases where we actually need a truly privkey-lacking object. Update the call sites to do whatever is appropriate for those situations.

This commit is contained in:
Mike Hearn
2015-04-07 19:26:20 +02:00
parent a1612b0a8f
commit 1e6ce4b1ba
5 changed files with 66 additions and 42 deletions

View File

@@ -43,7 +43,7 @@ public class DeterministicKey extends ECKey {
private final DeterministicKey parent;
private final ImmutableList<ChildNumber> childNumberPath;
private final int depth;
private final int parentFingerprint; // 0 if this key is root node of key hierarchy
private int parentFingerprint; // 0 if this key is root node of key hierarchy
/** 32 bytes */
private final byte[] chainCode;
@@ -240,13 +240,32 @@ public class DeterministicKey extends ECKey {
}
/**
* Returns the same key with the private part removed. May return the same instance.
* Returns the same key with the private bytes removed. May return the same instance. The purpose of this is to save
* memory: the private key can always be very efficiently rederived from a parent that a private key, so storing
* all the private keys in RAM is a poor tradeoff especially on constrained devices. This means that the returned
* key may still be usable for signing and so on, so don't expect it to be a true pubkey-only object! If you want
* that then you should follow this call with a call to {@link #dropParent()}.
*/
public DeterministicKey getPubOnly() {
if (isPubKeyOnly()) return this;
return new DeterministicKey(getPath(), getChainCode(), pub, null, parent);
public DeterministicKey dropPrivateBytes() {
if (isPubKeyOnly())
return this;
else
return new DeterministicKey(getPath(), getChainCode(), pub, null, parent);
}
/**
* <p>Returns the same key with the parent pointer removed (it still knows its own path and the parent fingerprint).</p>
*
* <p>If this key doesn't have private key bytes stored/cached itself, but could rederive them from the parent, then
* the new key returned by this method won't be able to do that. Thus, using dropPrivateBytes().dropParent() on a
* regular DeterministicKey will yield a new DeterministicKey that cannot sign or do other things involving the
* private key at all.</p>
*/
public DeterministicKey dropParent() {
DeterministicKey key = new DeterministicKey(getPath(), getChainCode(), pub, priv, null);
key.parentFingerprint = parentFingerprint;
return key;
}
static byte[] addChecksum(byte[] input) {
int inputLength = input.length;

View File

@@ -45,6 +45,6 @@ public class KeyChainTransactionSigner extends CustomTransactionSigner {
protected SignatureAndKey getSignature(Sha256Hash sighash, List<ChildNumber> derivationPath) {
ImmutableList<ChildNumber> keyPath = ImmutableList.copyOf(derivationPath);
DeterministicKey key = keyChain.getKeyByPath(keyPath, true);
return new SignatureAndKey(key.sign(sighash), key.getPubOnly());
return new SignatureAndKey(key.sign(sighash), key.dropPrivateBytes().dropParent());
}
}

View File

@@ -31,7 +31,6 @@ import com.google.protobuf.ByteString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.math.ec.ECPoint;
import javax.annotation.Nullable;
import java.math.BigInteger;
@@ -309,7 +308,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
* this method to watch an arbitrary fragment of some other tree, this limitation may be removed in future.
*/
public DeterministicKeyChain(DeterministicKey watchingKey, long creationTimeSeconds) {
checkArgument(watchingKey.isPubKeyOnly(), "Private subtrees not currently supported");
checkArgument(watchingKey.isPubKeyOnly(), "Private subtrees not currently supported: if you got this key from DKC.getWatchingKey() then use .dropPrivate().dropParent() on it first.");
checkArgument(watchingKey.getPath().size() == 1, "You can only watch an account key currently");
basicKeyChain = new BasicKeyChain();
this.creationTimeSeconds = creationTimeSeconds;
@@ -402,7 +401,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
if (key.getPath().size() != 3) continue; // Not a leaf key.
DeterministicKey parent = hierarchy.get(checkNotNull(key.getParent()).getPath(), false, false);
// Clone the key to the new encrypted hierarchy.
key = new DeterministicKey(key.getPubOnly(), parent);
key = new DeterministicKey(key.dropPrivateBytes(), parent);
hierarchy.putKey(key);
basicKeyChain.importKey(key);
}
@@ -616,13 +615,17 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
}
/**
* <p>An alias for <code>getKeyByPath(DeterministicKeyChain.ACCOUNT_ZERO_PATH).getPubOnly()</code>.
* Use this when you would like to create a watching key chain that follows this one, but can't spend money from it.
* <p>An alias for <code>getKeyByPath(DeterministicKeyChain.ACCOUNT_ZERO_PATH)</code>.</p>
*
* <p>Use this when you would like to create a watching key chain that follows this one, but can't spend money from it.
* The returned key can be serialized and then passed into {@link #watch(org.bitcoinj.crypto.DeterministicKey)}
* on another system to watch the hierarchy.</p>
*
* <p>Note that the returned key is not pubkey only unless this key chain already is: the returned key can still
* be used for signing etc if the private key bytes are available.</p>
*/
public DeterministicKey getWatchingKey() {
return getKeyByPath(ACCOUNT_ZERO_PATH).getPubOnly();
return getKeyByPath(ACCOUNT_ZERO_PATH);
}
@Override
@@ -969,7 +972,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
checkState(key.isEncrypted());
DeterministicKey parent = chain.hierarchy.get(checkNotNull(key.getParent()).getPath(), false, false);
// Clone the key to the new decrypted hierarchy.
key = new DeterministicKey(key.getPubOnly(), parent);
key = new DeterministicKey(key.dropPrivateBytes(), parent);
chain.hierarchy.putKey(key);
chain.basicKeyChain.importKey(key);
}
@@ -1143,7 +1146,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
int nextChild = numChildren;
for (int i = 0; i < needed; i++) {
DeterministicKey key = HDKeyDerivation.deriveThisOrNextChildKey(parent, nextChild);
key = key.getPubOnly();
key = key.dropPrivateBytes();
hierarchy.putKey(key);
result.add(key);
nextChild = key.getChildNumber().num() + 1;

View File

@@ -17,16 +17,12 @@
package org.bitcoinj.crypto;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.params.UnitTestParams;
import org.junit.Test;
import org.spongycastle.crypto.params.KeyParameter;
import org.bitcoinj.core.*;
import org.bitcoinj.params.*;
import org.junit.*;
import org.spongycastle.crypto.params.*;
import static org.bitcoinj.core.Utils.HEX;
import static org.bitcoinj.core.Utils.*;
import static org.junit.Assert.*;
/**
@@ -113,27 +109,27 @@ public class ChildKeyDerivationTest {
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));
assertEquals(hexEncodePub(ekprv.dropPrivateBytes().dropParent()), hexEncodePub(ekpub));
assertEquals(hexEncodePub(ekprv_0.dropPrivateBytes().dropParent()), hexEncodePub(ekpub_0));
assertEquals(hexEncodePub(ekprv_1.dropPrivateBytes().dropParent()), hexEncodePub(ekpub_1));
assertEquals(hexEncodePub(ekprv_0_IN.dropPrivateBytes().dropParent()), hexEncodePub(ekpub_0_IN));
assertEquals(hexEncodePub(ekprv_0_IN_0.dropPrivateBytes().dropParent()), hexEncodePub(ekpub_0_IN_0));
assertEquals(hexEncodePub(ekprv_0_IN_1.dropPrivateBytes().dropParent()), hexEncodePub(ekpub_0_IN_1));
assertEquals(hexEncodePub(ekprv_0_IN_2.dropPrivateBytes().dropParent()), hexEncodePub(ekpub_0_IN_2));
assertEquals(hexEncodePub(ekprv_0_EX_0.dropPrivateBytes().dropParent()), hexEncodePub(ekpub_0_EX_0));
assertEquals(hexEncodePub(ekprv_0_EX_1.dropPrivateBytes().dropParent()), hexEncodePub(ekpub_0_EX_1));
assertEquals(hexEncodePub(ekprv_0_EX_2.dropPrivateBytes().dropParent()), hexEncodePub(ekpub_0_EX_2));
assertEquals(hexEncodePub(ekprv_1_IN.dropPrivateBytes().dropParent()), hexEncodePub(ekpub_1_IN));
assertEquals(hexEncodePub(ekprv_1_IN_4095.dropPrivateBytes().dropParent()), hexEncodePub(ekpub_1_IN_4095));
//assertEquals(hexEncodePub(ekprv_1_IN_4bil.dropPrivateBytes()), 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);
HDKeyDerivation.RawKeyBytes key2 = HDKeyDerivation.deriveChildKeyBytesFromPublic(key1.dropPrivateBytes().dropParent(), ChildNumber.ZERO, HDKeyDerivation.PublicDeriveMode.NORMAL);
HDKeyDerivation.RawKeyBytes key3 = HDKeyDerivation.deriveChildKeyBytesFromPublic(key1.dropPrivateBytes().dropParent(), ChildNumber.ZERO, HDKeyDerivation.PublicDeriveMode.WITH_INVERSION);
assertArrayEquals(key2.keyBytes, key3.keyBytes);
assertArrayEquals(key2.chainCode, key3.chainCode);
}
@@ -176,8 +172,14 @@ public class ChildKeyDerivationTest {
assertFalse(key2.isPubKeyOnly());
DeterministicKey key3 = HDKeyDerivation.deriveChildKey(key2, ChildNumber.ZERO);
assertFalse(key3.isPubKeyOnly());
DeterministicKey pubkey2 = key2.getPubOnly();
assertTrue(pubkey2.isPubKeyOnly());
key2 = key2.dropPrivateBytes();
assertFalse(key2.isPubKeyOnly()); // still got private key bytes from the parents!
// pubkey2 got its cached private key bytes (if any) dropped, and now it'll lose its parent too, so now it
// becomes a true pubkey-only object.
DeterministicKey pubkey2 = key2.dropParent();
DeterministicKey pubkey3 = HDKeyDerivation.deriveChildKey(pubkey2, ChildNumber.ZERO);
assertTrue(pubkey3.isPubKeyOnly());
assertEquals(key3.getPubKeyPoint(), pubkey3.getPubKeyPoint());

View File

@@ -273,7 +273,7 @@ public class DeterministicKeyChainTest {
@Test(expected = IllegalStateException.class)
public void watchingCannotEncrypt() throws Exception {
final DeterministicKey accountKey = chain.getKeyByPath(DeterministicKeyChain.ACCOUNT_ZERO_PATH);
chain = DeterministicKeyChain.watch(accountKey.getPubOnly());
chain = DeterministicKeyChain.watch(accountKey.dropPrivateBytes().dropParent());
chain = chain.toEncrypted("this doesn't make any sense");
}
@@ -303,7 +303,7 @@ public class DeterministicKeyChainTest {
DeterministicKey[] keys = new DeterministicKey[100];
for (int i = 0; i < keys.length; i++)
keys[i] = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
chain = DeterministicKeyChain.watch(chain.getWatchingKey());
chain = DeterministicKeyChain.watch(chain.getWatchingKey().dropPrivateBytes().dropParent());
int e = chain.numBloomFilterEntries();
BloomFilter filter = chain.getFilter(e, 0.001, 1);
for (DeterministicKey key : keys)