/* * This file is part of Bitsquare. * * Bitsquare is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * Bitsquare 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 Affero General Public * License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>. */ package io.bitsquare.btc; import io.bitsquare.app.Version; import io.bitsquare.common.persistance.Persistable; import io.bitsquare.common.util.Utilities; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.crypto.DeterministicKey; import org.bitcoinj.params.MainNetParams; import org.bitcoinj.params.RegTestParams; import org.bitcoinj.params.TestNet3Params; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.io.IOException; import java.io.ObjectInputStream; import static com.google.common.base.Preconditions.checkNotNull; /** * Every trade use a addressEntry with a dedicated address for all transactions related to the trade. * That way we have a kind of separated trade wallet, isolated from other transactions and avoiding coin merge. * If we would not avoid coin merge the user would lose privacy between trades. */ public final class AddressEntry implements Persistable { // That object is saved to disc. We need to take care of changes to not break deserialization. private static final long serialVersionUID = Version.LOCAL_DB_VERSION; private static final Logger log = LoggerFactory.getLogger(AddressEntry.class); public enum Context { ARBITRATOR, AVAILABLE, OFFER_FUNDING, RESERVED_FOR_TRADE, //reserved MULTI_SIG, //locked TRADE_PAYOUT, DAO_SHARE, DAO_DIVIDEND } // keyPair can be null in case the object is created from deserialization as it is transient. // It will be restored when the wallet is ready at setDeterministicKey // So after startup it never must be null @Nullable transient private DeterministicKey keyPair; // Only set if its a TRADE Context @Nullable private final String offerId; private final Context context; private final byte[] pubKey; private final byte[] pubKeyHash; private final String paramId; @Nullable private Coin coinLockedInMultiSig; transient private NetworkParameters params; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, initialization /////////////////////////////////////////////////////////////////////////////////////////// // If created without offerId (arbitrator) public AddressEntry(DeterministicKey keyPair, NetworkParameters params, Context context) { this(keyPair, params, context, null); } // If created with offerId public AddressEntry(@Nullable DeterministicKey keyPair, NetworkParameters params, Context context, @Nullable String offerId) { this.keyPair = keyPair; this.params = params; this.context = context; this.offerId = offerId; paramId = params.getId(); checkNotNull(keyPair); pubKey = keyPair.getPubKey(); pubKeyHash = keyPair.getPubKeyHash(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { try { in.defaultReadObject(); if (MainNetParams.ID_MAINNET.equals(paramId)) params = MainNetParams.get(); else if (MainNetParams.ID_TESTNET.equals(paramId)) params = TestNet3Params.get(); else if (MainNetParams.ID_REGTEST.equals(paramId)) params = RegTestParams.get(); } catch (Throwable t) { log.warn("Cannot be deserialized." + t.getMessage()); } } // Set after wallet is ready public void setDeterministicKey(DeterministicKey deterministicKey) { this.keyPair = deterministicKey; } /////////////////////////////////////////////////////////////////////////////////////////// // Getters /////////////////////////////////////////////////////////////////////////////////////////// @Nullable public String getOfferId() { return offerId; } // For display we usually only display the first 8 characters. @Nullable public String getShortOfferId() { return offerId != null ? Utilities.getShortId(offerId) : null; } public Context getContext() { return context; } @Nullable public String getAddressString() { return getAddress() != null ? getAddress().toString() : null; } @Nullable public DeterministicKey getKeyPair() { return keyPair != null ? keyPair : null; } @Nullable public Address getAddress() { return keyPair != null ? keyPair.toAddress(params) : null; } public byte[] getPubKeyHash() { return pubKeyHash; } public byte[] getPubKey() { return pubKey; } public boolean isOpenOffer() { return context == Context.OFFER_FUNDING || context == Context.RESERVED_FOR_TRADE; } public boolean isTrade() { return context == Context.MULTI_SIG || context == Context.TRADE_PAYOUT; } public boolean isTradable() { return isOpenOffer() || isTrade(); } public void setCoinLockedInMultiSig(Coin coinLockedInMultiSig) { this.coinLockedInMultiSig = coinLockedInMultiSig; } @Nullable public Coin getCoinLockedInMultiSig() { return coinLockedInMultiSig; } @Override public String toString() { return "AddressEntry{" + "offerId='" + getOfferId() + '\'' + ", context=" + context + ", address=" + getAddressString() + '}'; } }