com.bonsai.btcreceive.HDChain.java Source code

Java tutorial

Introduction

Here is the source code for com.bonsai.btcreceive.HDChain.java

Source

// Copyright (C) 2014  Bonsai Software, Inc.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package com.bonsai.btcreceive;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;

import com.google.bitcoin.core.Address;
import com.google.bitcoin.core.ECKey;
import com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.core.Utils;
import com.google.bitcoin.core.Wallet;
import com.google.bitcoin.crypto.DeterministicKey;
import com.google.bitcoin.crypto.HDKeyDerivation;
import com.google.bitcoin.crypto.KeyCrypter;

public class HDChain {

    private static Logger mLogger = LoggerFactory.getLogger(HDChain.class);

    private NetworkParameters mParams;
    private DeterministicKey mChainKey;
    private boolean mIsReceive;
    private String mChainName;

    private ArrayList<HDAddress> mAddrs;

    static private final int DESIRED_MARGIN = 32;
    static private final int MAX_UNUSED_GAP = 8;

    public HDChain(NetworkParameters params, DeterministicKey accountKey, JSONObject chainNode)
            throws RuntimeException, JSONException {

        mParams = params;

        mChainName = chainNode.getString("name");
        mIsReceive = chainNode.getBoolean("isReceive");

        int chainnum = mIsReceive ? 0 : 1;

        mChainKey = HDKeyDerivation.deriveChildKey(accountKey, chainnum);

        mLogger.info("deserialized HDChain " + mChainName + ": " + mChainKey.getPath());

        mAddrs = new ArrayList<HDAddress>();
        JSONArray addrobjs = chainNode.getJSONArray("addrs");
        for (int ii = 0; ii < addrobjs.length(); ++ii) {
            JSONObject addrNode = addrobjs.getJSONObject(ii);
            mAddrs.add(new HDAddress(mParams, mChainKey, addrNode));
        }
    }

    public JSONObject dumps() {
        try {
            JSONObject obj = new JSONObject();

            obj.put("name", mChainName);
            obj.put("isReceive", mIsReceive);

            JSONArray addrs = new JSONArray();
            for (HDAddress addr : mAddrs)
                addrs.put(addr.dumps());

            obj.put("addrs", addrs);

            return obj;
        } catch (JSONException ex) {
            throw new RuntimeException(ex); // Shouldn't happen.
        }
    }

    public HDChain(NetworkParameters params, DeterministicKey accountKey, boolean isReceive, String chainName) {

        mParams = params;
        mIsReceive = isReceive;
        int chainnum = mIsReceive ? 0 : 1;
        mChainKey = HDKeyDerivation.deriveChildKey(accountKey, chainnum);
        mChainName = chainName;

        int numAddrs = DESIRED_MARGIN;

        mLogger.info("created HDChain " + mChainName);

        mAddrs = new ArrayList<HDAddress>();
        for (int ii = 0; ii < numAddrs; ++ii)
            mAddrs.add(new HDAddress(mParams, mChainKey, ii));
    }

    public static int maxSafeExtend() {
        return DESIRED_MARGIN - MAX_UNUSED_GAP;
    }

    public boolean isReceive() {
        return mIsReceive;
    }

    public List<HDAddress> getAddresses() {
        return mAddrs;
    }

    public int numAddrs() {
        return mAddrs.size();
    }

    public void gatherAllKeys(KeyCrypter keyCrypter, KeyParameter aesKey, long creationTime, List<ECKey> keys) {
        for (HDAddress hda : mAddrs)
            hda.gatherKey(keyCrypter, aesKey, creationTime, keys);
    }

    public void applyOutput(byte[] pubkey, byte[] pubkeyhash, long value, boolean avail) {
        for (HDAddress hda : mAddrs)
            hda.applyOutput(pubkey, pubkeyhash, value, avail);
    }

    public void applyInput(byte[] pubkey, long value) {
        for (HDAddress hda : mAddrs)
            hda.applyInput(pubkey, value);
    }

    public void clearBalance() {
        for (HDAddress hda : mAddrs)
            hda.clearBalance();
    }

    public long balance() {
        long balance = 0;
        for (HDAddress hda : mAddrs)
            balance += hda.getBalance();
        return balance;
    }

    public long available() {
        long available = 0;
        for (HDAddress hda : mAddrs)
            available += hda.getAvailable();
        return available;
    }

    public void logBalance() {
        for (HDAddress hda : mAddrs)
            hda.logBalance();
    }

    public Address nextUnusedAddress() {
        for (HDAddress hda : mAddrs) {
            if (hda.isUnused())
                return hda.getAddress();
        }
        throw new RuntimeException("no unused address available");
    }

    public boolean hasPubKey(byte[] pubkey, byte[] pubkeyhash) {
        for (HDAddress hda : mAddrs) {
            if (hda.isMatch(pubkey, pubkeyhash))
                return true;
        }
        return false;
    }

    private int marginSize() {
        int count = 0;
        ListIterator li = mAddrs.listIterator(mAddrs.size());
        while (li.hasPrevious()) {
            HDAddress hda = (HDAddress) li.previous();
            if (!hda.isUnused())
                return count;
            ++count;
        }
        return count;
    }

    // Returns the number of addresses added.
    public int ensureMargins(Wallet wallet, KeyCrypter keyCrypter, KeyParameter aesKey) {
        // How many unused addresses do we have at the end of the chain?
        int numUnused = marginSize();

        // Do we have an ample margin?
        if (numUnused >= DESIRED_MARGIN) {
            return 0;
        } else {
            // How many addresses do we need to add?
            int numAdd = DESIRED_MARGIN - numUnused;

            mLogger.info(String.format("%s expanding margin, adding %d addrs", mChainKey.getPath(), numAdd));

            // Set the new keys creation time to now.
            long now = Utils.now().getTime() / 1000;

            // Add the addresses ...
            int newSize = mAddrs.size() + numAdd;
            ArrayList<ECKey> keys = new ArrayList<ECKey>();
            for (int ii = mAddrs.size(); ii < newSize; ++ii) {
                HDAddress hda = new HDAddress(mParams, mChainKey, ii);
                mAddrs.add(hda);
                hda.gatherKey(keyCrypter, aesKey, now, keys);
            }
            mLogger.info(String.format("adding %d keys", keys.size()));
            wallet.addKeys(keys);

            return numAdd;
        }
    }

    // Finds an address (if present) and returns a description
    // of it's wallet location.
    public HDAddressDescription findAddress(Address addr) {
        for (HDAddress hda : mAddrs) {
            if (hda.matchAddress(addr)) {
                // Caller will fill in the accountId.
                return new HDAddressDescription(this, hda);
            }
        }
        return null;
    }
}