/** * Return the fingerprint of this key's parent as an int value, or zero if this key is the root * node of the key hierarchy. Raise an exception if the arguments are inconsistent. This method * exists to avoid code repetition in the constructors. */ private int ascertainParentFingerprint(DeterministicKey parentKey, int parentFingerprint) throws IllegalArgumentException { if (parentFingerprint != 0) { if (parent != null) checkArgument( parent.getFingerprint() == parentFingerprint, "parent fingerprint mismatch", Integer.toHexString(parent.getFingerprint()), Integer.toHexString(parentFingerprint)); return parentFingerprint; } else return 0; }
/** * Deserialize an HD Key. * * @param parent The parent node in the given key's deterministic hierarchy. */ public static DeterministicKey deserialize( NetworkParameters params, byte[] serializedKey, @Nullable DeterministicKey parent) { ByteBuffer buffer = ByteBuffer.wrap(serializedKey); int header = buffer.getInt(); if (header != params.getBip32HeaderPriv() && header != params.getBip32HeaderPub()) throw new IllegalArgumentException( "Unknown header bytes: " + toBase58(serializedKey).substring(0, 4)); boolean pub = header == params.getBip32HeaderPub(); int depth = buffer.get() & 0xFF; // convert signed byte to positive int since depth cannot be negative final int parentFingerprint = buffer.getInt(); final int i = buffer.getInt(); final ChildNumber childNumber = new ChildNumber(i); ImmutableList<ChildNumber> path; if (parent != null) { if (parentFingerprint == 0) throw new IllegalArgumentException("Parent was provided but this key doesn't have one"); if (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 >= 1) // We have been given a key that is not a root key, yet we lack the object representing the // parent. // This can happen when deserializing an account key for a watching wallet. In this case, // we assume that // the client wants to conceal the key's position in the hierarchy. The path is truncated // at the // parent's node. path = ImmutableList.of(childNumber); else path = ImmutableList.of(); } 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) { return new DeterministicKey( path, chainCode, new LazyECPoint(ECKey.CURVE.getCurve(), data), parent, depth, parentFingerprint); } else { return new DeterministicKey( path, chainCode, new BigInteger(1, data), parent, depth, parentFingerprint); } }
public DeterministicKey encrypt( KeyCrypter keyCrypter, KeyParameter aesKey, @Nullable DeterministicKey newParent) throws KeyCrypterException { // Same as the parent code, except we construct a DeterministicKey instead of an ECKey. checkNotNull(keyCrypter); if (newParent != null) checkArgument(newParent.isEncrypted()); final byte[] privKeyBytes = getPrivKeyBytes(); checkState(privKeyBytes != null, "Private key is not available"); EncryptedData encryptedPrivateKey = keyCrypter.encrypt(privKeyBytes, aesKey); DeterministicKey key = new DeterministicKey( childNumberPath, chainCode, keyCrypter, pub, encryptedPrivateKey, newParent); if (newParent == null) key.setCreationTimeSeconds(getCreationTimeSeconds()); return key; }
@Override public DeterministicKey decrypt(KeyCrypter keyCrypter, KeyParameter aesKey) throws KeyCrypterException { checkNotNull(keyCrypter); // Check that the keyCrypter matches the one used to encrypt the keys, if set. if (this.keyCrypter != null && !this.keyCrypter.equals(keyCrypter)) throw new KeyCrypterException( "The keyCrypter being used to decrypt the key is different to the one that was used to encrypt it"); BigInteger privKey = findOrDeriveEncryptedPrivateKey(keyCrypter, aesKey); DeterministicKey key = new DeterministicKey(childNumberPath, chainCode, privKey, parent); if (!Arrays.equals(key.getPubKey(), getPubKey())) throw new KeyCrypterException("Provided AES key is wrong"); if (parent == null) key.setCreationTimeSeconds(getCreationTimeSeconds()); return key; }
public static List<TransactionOutput> getMyOutputs(Transaction tx, DeterministicKey key) { List<TransactionOutput> mines = new ArrayList<>(); for (TransactionOutput curr : tx.getOutputs()) { boolean isMine = false; String to = null; Address add = curr.getAddressFromP2PKHScript(BitcoinNetwork.getInstance().get().getParams()); if (add != null) to = add.toString(); else { add = curr.getAddressFromP2SH(BitcoinNetwork.getInstance().get().getParams()); if (add != null) to = add.toString(); } if (to != null) { // VERIFICATION BY ADDRESS isMine = to.equals(keyToStringAddress(key)); } else { // VERIFICATION BY PUBKEY final byte[] pubKeyCurr = curr.getScriptPubKey().getPubKey(); isMine = Arrays.equals(key.getPubKey(), pubKeyCurr); } if (isMine) { mines.add(curr); break; } } return mines; }
/** * Returns this keys {@link org.bitcoinj.crypto.KeyCrypter} <b>or</b> the keycrypter of its parent * key. */ @Override @Nullable public KeyCrypter getKeyCrypter() { if (keyCrypter != null) return keyCrypter; else if (parent != null) return parent.getKeyCrypter(); else return null; }
private void checkEncryptedKeyChain(SimpleHDKeyChain encChain, DeterministicKey key1) { // Check we can look keys up and extend the chain without the AES key being provided. DeterministicKey encKey1 = encChain.findKeyFromPubKey(key1.getPubKey()); DeterministicKey encKey2 = encChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); assertFalse(key1.isEncrypted()); assertTrue(encKey1.isEncrypted()); assertEquals(encKey1.getPubKeyPoint(), key1.getPubKeyPoint()); final KeyParameter aesKey = checkNotNull(encChain.getKeyCrypter()).deriveKey("open secret"); encKey1.sign(Sha256Hash.ZERO_HASH, aesKey); encKey2.sign(Sha256Hash.ZERO_HASH, aesKey); assertTrue(encChain.checkAESKey(aesKey)); assertFalse(encChain.checkPassword("access denied")); assertTrue(encChain.checkPassword("open secret")); }
/** Constructs a key from its components. This is not normally something you should use. */ public DeterministicKey( ImmutableList<ChildNumber> childNumberPath, byte[] chainCode, BigInteger priv, @Nullable DeterministicKey parent) { super(priv, compressPoint(ECKey.publicPointFromPrivate(priv))); checkArgument(chainCode.length == 32); this.parent = parent; this.childNumberPath = checkNotNull(childNumberPath); this.chainCode = Arrays.copyOf(chainCode, chainCode.length); this.depth = this.childNumberPath.size(); this.parentFingerprint = (parent != null) ? parent.getFingerprint() : 0; }
private BigInteger derivePrivateKeyDownwards( DeterministicKey cursor, byte[] parentalPrivateKeyBytes) { DeterministicKey downCursor = new DeterministicKey( cursor.childNumberPath, cursor.chainCode, cursor.pub, new BigInteger(1, parentalPrivateKeyBytes), cursor.parent); // Now we have to rederive the keys along the path back to ourselves. That path can be found by // just truncating // our path with the length of the parents path. ImmutableList<ChildNumber> path = childNumberPath.subList(cursor.getPath().size(), childNumberPath.size()); for (ChildNumber num : path) { downCursor = HDKeyDerivation.deriveChildKey(downCursor, num); } // downCursor is now the same key as us, but with private key bytes. checkState(downCursor.pub.equals(pub)); return checkNotNull(downCursor.priv); }
@Test public void testRoundTripMarriedWallet() throws Exception { // create 2-of-2 married wallet myWallet = new Wallet(params); final DeterministicKeyChain partnerChain = new DeterministicKeyChain(new SecureRandom()); DeterministicKey partnerKey = DeterministicKey.deserializeB58( null, partnerChain.getWatchingKey().serializePubB58(params), params); MarriedKeyChain chain = MarriedKeyChain.builder() .random(new SecureRandom()) .followingKeys(partnerKey) .threshold(2) .build(); myWallet.addAndActivateHDChain(chain); myAddress = myWallet.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS); Wallet wallet1 = roundTrip(myWallet); assertEquals(0, wallet1.getTransactions(true).size()); assertEquals(Coin.ZERO, wallet1.getBalance()); assertEquals(2, wallet1.getActiveKeyChain().getSigsRequiredToSpend()); assertEquals(myAddress, wallet1.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS)); }
public void serializeUnencrypted(SimpleHDKeyChain keyChain, String expectedSerialization) throws UnreadableWalletException { keyChain.setLookaheadSize(10); keyChain.maybeLookAhead(); DeterministicKey key1 = keyChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); DeterministicKey key2 = keyChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); DeterministicKey key3 = keyChain.getKey(KeyChain.KeyPurpose.CHANGE); List<Protos.Key> keys = keyChain.toProtobuf(); // 1 master key, 1 account key, 2 internal keys, 3 derived, 20 lookahead and 5 lookahead // threshold. int numItems = 1 // master key/account key + 2 // ext/int parent keys + (keyChain.getLookaheadSize() + keyChain.getLookaheadThreshold()) * 2 // lookahead zone on each chain ; assertEquals(numItems, keys.size()); // Get another key that will be lost during round-tripping, to ensure we can derive it again. DeterministicKey key4 = keyChain.getKey(KeyChain.KeyPurpose.CHANGE); String sb = protoToString(keys); assertEquals(expectedSerialization, sb); // Round trip the data back and forth to check it is preserved. int oldLookaheadSize = keyChain.getLookaheadSize(); keyChain = SimpleHDKeyChain.fromProtobuf(keys, null); assertEquals(expectedSerialization, protoToString(keyChain.toProtobuf())); assertEquals(key1, keyChain.findKeyFromPubHash(key1.getPubKeyHash())); assertEquals(key2, keyChain.findKeyFromPubHash(key2.getPubKeyHash())); assertEquals(key3, keyChain.findKeyFromPubHash(key3.getPubKeyHash())); assertEquals(key4, keyChain.getKey(KeyChain.KeyPurpose.CHANGE)); key1.sign(Sha256Hash.ZERO_HASH); key2.sign(Sha256Hash.ZERO_HASH); key3.sign(Sha256Hash.ZERO_HASH); key4.sign(Sha256Hash.ZERO_HASH); assertEquals(oldLookaheadSize, keyChain.getLookaheadSize()); }
/** * The creation time of a deterministic key is equal to that of its parent, unless this key is the * root of a tree in which case the time is stored alongside the key as per normal, see {@link * org.bitcoinj.core.ECKey#getCreationTimeSeconds()}. */ @Override public long getCreationTimeSeconds() { if (parent != null) return parent.getCreationTimeSeconds(); else return super.getCreationTimeSeconds(); }
@Override public ListenableFuture<ECKey.ECDSASignature> signHash(final Sha256Hash hash) { return Futures.immediateFuture(ECKey.fromPrivate(hdWallet.getPrivKey()).sign(hash)); }
/** * A deterministic key is considered to be encrypted if it has access to encrypted private key * bytes, OR if its parent does. The reason is because the parent would be encrypted under the * same key and this key knows how to rederive its own private key bytes from the parent, if * needed. */ @Override public boolean isEncrypted() { return priv == null && (super.isEncrypted() || (parent != null && parent.isEncrypted())); }
/** * A deterministic key is considered to be 'public key only' if it hasn't got a private key part * and it cannot be rederived. If the hierarchy is encrypted this returns true. */ @Override public boolean isPubKeyOnly() { return super.isPubKeyOnly() && (parent == null || parent.isPubKeyOnly()); }
/** * Returns the same key with the parent pointer removed (it still knows its own path and the * parent fingerprint). * * <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. */ public DeterministicKey dropParent() { DeterministicKey key = new DeterministicKey(getPath(), getChainCode(), pub, priv, null); key.parentFingerprint = parentFingerprint; return key; }
public void encryption(SimpleHDKeyChain unencChain) throws UnreadableWalletException { DeterministicKey key1 = unencChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); SimpleHDKeyChain encChain = unencChain.toEncrypted("open secret"); DeterministicKey encKey1 = encChain.findKeyFromPubKey(key1.getPubKey()); checkEncryptedKeyChain(encChain, key1); // Round-trip to ensure de/serialization works and that we can store two chains and they both // deserialize. List<Protos.Key> serialized = encChain.toProtobuf(); System.out.println(protoToString(serialized)); encChain = SimpleHDKeyChain.fromProtobuf(serialized, encChain.getKeyCrypter()); checkEncryptedKeyChain(encChain, unencChain.findKeyFromPubKey(key1.getPubKey())); DeterministicKey encKey2 = encChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); // Decrypt and check the keys match. SimpleHDKeyChain decChain = encChain.toDecrypted("open secret"); DeterministicKey decKey1 = decChain.findKeyFromPubHash(encKey1.getPubKeyHash()); DeterministicKey decKey2 = decChain.findKeyFromPubHash(encKey2.getPubKeyHash()); assertEquals(decKey1.getPubKeyPoint(), encKey1.getPubKeyPoint()); assertEquals(decKey2.getPubKeyPoint(), encKey2.getPubKeyPoint()); assertFalse(decKey1.isEncrypted()); assertFalse(decKey2.isEncrypted()); assertNotEquals(encKey1.getParent(), decKey1.getParent()); // parts of a different hierarchy // Check we can once again derive keys from the decrypted chain. decChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).sign(Sha256Hash.ZERO_HASH); decChain.getKey(KeyChain.KeyPurpose.CHANGE).sign(Sha256Hash.ZERO_HASH); }
@Override public ListenableFuture<byte[]> getIdentifier() { return Futures.immediateFuture(hdWallet.getIdentifier()); }