mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 18:03:12 +01:00
Merge pull request #4568 from oscarguindzberg/segwitWallet
Add segwit support to the BTC wallet
This commit is contained in:
commit
35e0c34c74
@ -28,7 +28,7 @@ configure(subprojects) {
|
||||
|
||||
ext { // in alphabetical order
|
||||
bcVersion = '1.63'
|
||||
bitcoinjVersion = '44ddbdc'
|
||||
bitcoinjVersion = 'a733034'
|
||||
btcdCli4jVersion = '27b94333'
|
||||
codecVersion = '1.13'
|
||||
easybindVersion = '1.0.3'
|
||||
|
@ -389,6 +389,8 @@ public class BisqSetup {
|
||||
if (requestWalletPasswordHandler != null) {
|
||||
requestWalletPasswordHandler.accept(aesKey -> {
|
||||
walletsManager.setAesKey(aesKey);
|
||||
walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().btcWallet(),
|
||||
aesKey);
|
||||
if (preferences.isResyncSpvRequested()) {
|
||||
if (showFirstPopupIfResyncSPVRequestedHandler != null)
|
||||
showFirstPopupIfResyncSPVRequestedHandler.run();
|
||||
|
@ -105,7 +105,7 @@ public class WalletAppSetup {
|
||||
Runnable downloadCompleteHandler,
|
||||
Runnable walletInitializedHandler) {
|
||||
log.info("Initialize WalletAppSetup with BitcoinJ version {} and hash of BitcoinJ commit {}",
|
||||
VersionMessage.BITCOINJ_VERSION, "44ddbdc");
|
||||
VersionMessage.BITCOINJ_VERSION, "a733034");
|
||||
|
||||
ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
|
||||
btcInfoBinding = EasyBind.combine(walletsSetup.downloadPercentageProperty(),
|
||||
|
@ -26,8 +26,8 @@ import com.google.protobuf.ByteString;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.LegacyAddress;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.bitcoinj.script.Script;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@ -74,6 +74,9 @@ public final class AddressEntry implements PersistablePayload {
|
||||
|
||||
private long coinLockedInMultiSig;
|
||||
|
||||
@Getter
|
||||
private boolean segwit;
|
||||
|
||||
@Nullable
|
||||
transient private DeterministicKey keyPair;
|
||||
@Nullable
|
||||
@ -86,18 +89,24 @@ public final class AddressEntry implements PersistablePayload {
|
||||
// Constructor, initialization
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public AddressEntry(DeterministicKey keyPair, Context context) {
|
||||
this(keyPair, context, null);
|
||||
public AddressEntry(DeterministicKey keyPair, Context context, boolean segwit) {
|
||||
this(keyPair, context, null, segwit);
|
||||
}
|
||||
|
||||
public AddressEntry(@NotNull DeterministicKey keyPair,
|
||||
Context context,
|
||||
@Nullable String offerId) {
|
||||
@Nullable String offerId,
|
||||
boolean segwit) {
|
||||
if (segwit && (!Context.AVAILABLE.equals(context) || offerId != null)) {
|
||||
throw new IllegalArgumentException("Segwit addresses are only allowed for " +
|
||||
"AVAILABLE entries without an offerId");
|
||||
}
|
||||
this.keyPair = keyPair;
|
||||
this.context = context;
|
||||
this.offerId = offerId;
|
||||
pubKey = keyPair.getPubKey();
|
||||
pubKeyHash = keyPair.getPubKeyHash();
|
||||
this.segwit = segwit;
|
||||
}
|
||||
|
||||
|
||||
@ -109,12 +118,14 @@ public final class AddressEntry implements PersistablePayload {
|
||||
byte[] pubKeyHash,
|
||||
Context context,
|
||||
@Nullable String offerId,
|
||||
Coin coinLockedInMultiSig) {
|
||||
Coin coinLockedInMultiSig,
|
||||
boolean segwit) {
|
||||
this.pubKey = pubKey;
|
||||
this.pubKeyHash = pubKeyHash;
|
||||
this.context = context;
|
||||
this.offerId = offerId;
|
||||
this.coinLockedInMultiSig = coinLockedInMultiSig.value;
|
||||
this.segwit = segwit;
|
||||
}
|
||||
|
||||
public static AddressEntry fromProto(protobuf.AddressEntry proto) {
|
||||
@ -122,7 +133,8 @@ public final class AddressEntry implements PersistablePayload {
|
||||
proto.getPubKeyHash().toByteArray(),
|
||||
ProtoUtil.enumFromProto(AddressEntry.Context.class, proto.getContext().name()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getOfferId()),
|
||||
Coin.valueOf(proto.getCoinLockedInMultiSig()));
|
||||
Coin.valueOf(proto.getCoinLockedInMultiSig()),
|
||||
proto.getSegwit());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -131,7 +143,8 @@ public final class AddressEntry implements PersistablePayload {
|
||||
.setPubKey(ByteString.copyFrom(pubKey))
|
||||
.setPubKeyHash(ByteString.copyFrom(pubKeyHash))
|
||||
.setContext(protobuf.AddressEntry.Context.valueOf(context.name()))
|
||||
.setCoinLockedInMultiSig(coinLockedInMultiSig);
|
||||
.setCoinLockedInMultiSig(coinLockedInMultiSig)
|
||||
.setSegwit(segwit);
|
||||
Optional.ofNullable(offerId).ifPresent(builder::setOfferId);
|
||||
return builder.build();
|
||||
}
|
||||
@ -175,7 +188,7 @@ public final class AddressEntry implements PersistablePayload {
|
||||
@Nullable
|
||||
public Address getAddress() {
|
||||
if (address == null && keyPair != null)
|
||||
address = LegacyAddress.fromKey(Config.baseCurrencyNetworkParameters(), keyPair);
|
||||
address = Address.fromKey(Config.baseCurrencyNetworkParameters(), keyPair, segwit ? Script.ScriptType.P2WPKH : Script.ScriptType.P2PKH);
|
||||
return address;
|
||||
}
|
||||
|
||||
@ -198,6 +211,7 @@ public final class AddressEntry implements PersistablePayload {
|
||||
", context=" + context +
|
||||
", offerId='" + offerId + '\'' +
|
||||
", coinLockedInMultiSig=" + coinLockedInMultiSig +
|
||||
", segwit=" + segwit +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import bisq.common.proto.persistable.PersistedDataHost;
|
||||
import com.google.protobuf.Message;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.LegacyAddress;
|
||||
import org.bitcoinj.core.SegwitAddress;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.bitcoinj.script.Script;
|
||||
@ -35,6 +35,8 @@ import com.google.inject.Inject;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
@ -107,11 +109,13 @@ public final class AddressEntryList implements PersistableEnvelope, PersistedDat
|
||||
if (!entrySet.isEmpty()) {
|
||||
Set<AddressEntry> toBeRemoved = new HashSet<>();
|
||||
entrySet.forEach(addressEntry -> {
|
||||
Script.ScriptType scriptType = addressEntry.isSegwit() ? Script.ScriptType.P2WPKH
|
||||
: Script.ScriptType.P2PKH;
|
||||
DeterministicKey keyFromPubHash = (DeterministicKey) wallet.findKeyFromPubKeyHash(
|
||||
addressEntry.getPubKeyHash(),
|
||||
Script.ScriptType.P2PKH);
|
||||
addressEntry.getPubKeyHash(), scriptType);
|
||||
if (keyFromPubHash != null) {
|
||||
Address addressFromKey = LegacyAddress.fromKey(Config.baseCurrencyNetworkParameters(), keyFromPubHash);
|
||||
Address addressFromKey = Address.fromKey(Config.baseCurrencyNetworkParameters(), keyFromPubHash,
|
||||
scriptType);
|
||||
// We want to ensure key and address matches in case we have address in entry available already
|
||||
if (addressEntry.getAddress() == null || addressFromKey.equals(addressEntry.getAddress())) {
|
||||
addressEntry.setDeterministicKey(keyFromPubHash);
|
||||
@ -133,7 +137,8 @@ public final class AddressEntryList implements PersistableEnvelope, PersistedDat
|
||||
toBeRemoved.forEach(entrySet::remove);
|
||||
} else {
|
||||
// As long the old arbitration domain is not removed from the code base we still support it here.
|
||||
entrySet.add(new AddressEntry(wallet.freshReceiveKey(), AddressEntry.Context.ARBITRATOR));
|
||||
DeterministicKey key = (DeterministicKey) wallet.findKeyFromAddress(wallet.freshReceiveAddress(Script.ScriptType.P2PKH));
|
||||
entrySet.add(new AddressEntry(key, AddressEntry.Context.ARBITRATOR, false));
|
||||
}
|
||||
|
||||
// In case we restore from seed words and have balance we need to add the relevant addresses to our list.
|
||||
@ -147,7 +152,7 @@ public final class AddressEntryList implements PersistableEnvelope, PersistedDat
|
||||
DeterministicKey key = (DeterministicKey) wallet.findKeyFromAddress(address);
|
||||
if (key != null) {
|
||||
// Address will be derived from key in getAddress method
|
||||
entrySet.add(new AddressEntry(key, AddressEntry.Context.AVAILABLE));
|
||||
entrySet.add(new AddressEntry(key, AddressEntry.Context.AVAILABLE, address instanceof SegwitAddress));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -192,7 +197,8 @@ public final class AddressEntryList implements PersistableEnvelope, PersistedDat
|
||||
public void swapToAvailable(AddressEntry addressEntry) {
|
||||
boolean setChangedByRemove = entrySet.remove(addressEntry);
|
||||
boolean setChangedByAdd = entrySet.add(new AddressEntry(addressEntry.getKeyPair(),
|
||||
AddressEntry.Context.AVAILABLE));
|
||||
AddressEntry.Context.AVAILABLE,
|
||||
addressEntry.isSegwit()));
|
||||
if (setChangedByRemove || setChangedByAdd) {
|
||||
requestPersistence();
|
||||
}
|
||||
@ -202,7 +208,7 @@ public final class AddressEntryList implements PersistableEnvelope, PersistedDat
|
||||
AddressEntry.Context context,
|
||||
String offerId) {
|
||||
boolean setChangedByRemove = entrySet.remove(addressEntry);
|
||||
final AddressEntry newAddressEntry = new AddressEntry(addressEntry.getKeyPair(), context, offerId);
|
||||
final AddressEntry newAddressEntry = new AddressEntry(addressEntry.getKeyPair(), context, offerId, addressEntry.isSegwit());
|
||||
boolean setChangedByAdd = entrySet.add(newAddressEntry);
|
||||
if (setChangedByRemove || setChangedByAdd)
|
||||
requestPersistence();
|
||||
@ -225,10 +231,10 @@ public final class AddressEntryList implements PersistableEnvelope, PersistedDat
|
||||
.map(output -> output.getScriptPubKey().getToAddress(wallet.getNetworkParameters()))
|
||||
.filter(Objects::nonNull)
|
||||
.filter(this::isAddressNotInEntries)
|
||||
.map(address -> (DeterministicKey) wallet.findKeyFromPubKeyHash(address.getHash(),
|
||||
Script.ScriptType.P2PKH))
|
||||
.filter(Objects::nonNull)
|
||||
.map(deterministicKey -> new AddressEntry(deterministicKey, AddressEntry.Context.AVAILABLE))
|
||||
.map(address -> Pair.of(address, (DeterministicKey) wallet.findKeyFromAddress(address)))
|
||||
.filter(pair -> pair.getRight() != null)
|
||||
.map(pair -> new AddressEntry(pair.getRight(), AddressEntry.Context.AVAILABLE,
|
||||
pair.getLeft() instanceof SegwitAddress))
|
||||
.forEach(this::addAddressEntry);
|
||||
}
|
||||
|
||||
|
@ -47,10 +47,11 @@ public class BisqKeyChainGroupStructure implements KeyChainGroupStructure {
|
||||
new ChildNumber(142, true),
|
||||
ChildNumber.ZERO_HARDENED);
|
||||
|
||||
public static final ImmutableList<ChildNumber> BIP44_BSQ_SEGWIT_ACCOUNT_PATH = ImmutableList.of(
|
||||
new ChildNumber(44, true),
|
||||
new ChildNumber(142, true),
|
||||
ChildNumber.ONE_HARDENED);
|
||||
// We don't use segwit for BSQ
|
||||
// public static final ImmutableList<ChildNumber> BIP44_BSQ_SEGWIT_ACCOUNT_PATH = ImmutableList.of(
|
||||
// new ChildNumber(44, true),
|
||||
// new ChildNumber(142, true),
|
||||
// ChildNumber.ONE_HARDENED);
|
||||
|
||||
private boolean isBsqWallet;
|
||||
|
||||
@ -71,7 +72,8 @@ public class BisqKeyChainGroupStructure implements KeyChainGroupStructure {
|
||||
if (outputScriptType == null || outputScriptType == Script.ScriptType.P2PKH)
|
||||
return BIP44_BSQ_NON_SEGWIT_ACCOUNT_PATH;
|
||||
else if (outputScriptType == Script.ScriptType.P2WPKH)
|
||||
return BIP44_BSQ_SEGWIT_ACCOUNT_PATH;
|
||||
//return BIP44_BSQ_SEGWIT_ACCOUNT_PATH;
|
||||
throw new IllegalArgumentException(outputScriptType.toString());
|
||||
else
|
||||
throw new IllegalArgumentException(outputScriptType.toString());
|
||||
}
|
||||
|
@ -22,11 +22,13 @@ import bisq.core.btc.nodes.ProxySocketFactory;
|
||||
import bisq.core.btc.wallet.BisqRiskAnalysis;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.file.FileUtil;
|
||||
|
||||
import com.google.common.io.Closeables;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import org.bitcoinj.core.listeners.*;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.crypto.KeyCrypter;
|
||||
import org.bitcoinj.net.BlockingClientManager;
|
||||
import org.bitcoinj.net.discovery.*;
|
||||
import org.bitcoinj.script.Script;
|
||||
@ -35,6 +37,11 @@ import org.bitcoinj.wallet.*;
|
||||
|
||||
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import org.slf4j.*;
|
||||
|
||||
import javax.annotation.*;
|
||||
@ -103,6 +110,8 @@ public class WalletConfig extends AbstractIdleService {
|
||||
@Getter
|
||||
@Setter
|
||||
private int minBroadcastConnections;
|
||||
@Getter
|
||||
private BooleanProperty migratedWalletToSegwit = new SimpleBooleanProperty(false);
|
||||
|
||||
/**
|
||||
* Creates a new WalletConfig, with a newly created {@link Context}. Files will be stored in the given directory.
|
||||
@ -293,6 +302,22 @@ public class WalletConfig extends AbstractIdleService {
|
||||
vPeerGroup.addWallet(vBsqWallet);
|
||||
onSetupCompleted();
|
||||
|
||||
if (migratedWalletToSegwit.get()) {
|
||||
startPeerGroup();
|
||||
} else {
|
||||
migratedWalletToSegwit.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
startPeerGroup();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} catch (BlockStoreException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void startPeerGroup() {
|
||||
Futures.addCallback((ListenableFuture<?>) vPeerGroup.startAsync(), new FutureCallback<Object>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Object result) {
|
||||
@ -307,9 +332,6 @@ public class WalletConfig extends AbstractIdleService {
|
||||
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
} catch (BlockStoreException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Wallet createOrLoadWallet(boolean shouldReplayWallet, File walletFile, boolean isBsqWallet) throws Exception {
|
||||
@ -321,7 +343,7 @@ public class WalletConfig extends AbstractIdleService {
|
||||
wallet = loadWallet(shouldReplayWallet, walletFile, isBsqWallet);
|
||||
} else {
|
||||
wallet = createWallet(isBsqWallet);
|
||||
wallet.freshReceiveKey();
|
||||
//wallet.freshReceiveKey();
|
||||
|
||||
// Currently the only way we can be sure that an extension is aware of its containing wallet is by
|
||||
// deserializing the extension (see WalletExtension#deserializeWalletExtension(Wallet, byte[]))
|
||||
@ -341,8 +363,7 @@ public class WalletConfig extends AbstractIdleService {
|
||||
|
||||
private Wallet loadWallet(boolean shouldReplayWallet, File walletFile, boolean isBsqWallet) throws Exception {
|
||||
Wallet wallet;
|
||||
FileInputStream walletStream = new FileInputStream(walletFile);
|
||||
try {
|
||||
try (FileInputStream walletStream = new FileInputStream(walletFile)) {
|
||||
WalletExtension[] extArray = new WalletExtension[]{};
|
||||
Protos.Wallet proto = WalletProtobufSerializer.parseToProto(walletStream);
|
||||
final WalletProtobufSerializer serializer;
|
||||
@ -352,16 +373,15 @@ public class WalletConfig extends AbstractIdleService {
|
||||
wallet = serializer.readWallet(params, extArray, proto);
|
||||
if (shouldReplayWallet)
|
||||
wallet.reset();
|
||||
} finally {
|
||||
walletStream.close();
|
||||
if (!isBsqWallet) {
|
||||
maybeAddSegwitKeychain(wallet, null);
|
||||
}
|
||||
}
|
||||
return wallet;
|
||||
}
|
||||
|
||||
protected Wallet createWallet(boolean isBsqWallet) {
|
||||
// Change preferredOutputScriptType of btc wallet to P2WPKH to start using segwit
|
||||
// Script.ScriptType preferredOutputScriptType = isBsqWallet ? Script.ScriptType.P2PKH : Script.ScriptType.P2WPKH;
|
||||
Script.ScriptType preferredOutputScriptType = Script.ScriptType.P2PKH;
|
||||
Script.ScriptType preferredOutputScriptType = isBsqWallet ? Script.ScriptType.P2PKH : Script.ScriptType.P2WPKH;
|
||||
KeyChainGroupStructure structure = new BisqKeyChainGroupStructure(isBsqWallet);
|
||||
KeyChainGroup.Builder kcgBuilder = KeyChainGroup.builder(params, structure);
|
||||
if (restoreFromSeed != null) {
|
||||
@ -488,4 +508,38 @@ public class WalletConfig extends AbstractIdleService {
|
||||
public File directory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void maybeAddSegwitKeychain(Wallet wallet, KeyParameter aesKey) {
|
||||
if (BisqKeyChainGroupStructure.BIP44_BTC_NON_SEGWIT_ACCOUNT_PATH.equals(wallet.getActiveKeyChain().getAccountPath())) {
|
||||
if (wallet.isEncrypted() && aesKey == null) {
|
||||
// wait for the aesKey to be set and this method to be invoked again.
|
||||
return;
|
||||
}
|
||||
// Do a backup of the wallet
|
||||
File backup = new File(directory, WalletsSetup.PRE_SEGWIT_WALLET_BACKUP);
|
||||
try {
|
||||
FileUtil.copyFile(new File(directory, "bisq_BTC.wallet"), backup);
|
||||
} catch (IOException e) {
|
||||
log.error(e.toString(), e);
|
||||
}
|
||||
|
||||
// Btc wallet does not have a native segwit keychain, we should add one.
|
||||
DeterministicSeed seed = wallet.getKeyChainSeed();
|
||||
if (aesKey != null) {
|
||||
// If wallet is encrypted, decrypt the seed.
|
||||
KeyCrypter keyCrypter = wallet.getKeyCrypter();
|
||||
seed = seed.decrypt(keyCrypter, DeterministicKeyChain.DEFAULT_PASSPHRASE_FOR_MNEMONIC, aesKey);
|
||||
}
|
||||
DeterministicKeyChain nativeSegwitKeyChain = DeterministicKeyChain.builder().seed(seed)
|
||||
.outputScriptType(Script.ScriptType.P2WPKH)
|
||||
.accountPath(new BisqKeyChainGroupStructure(false).accountPathFor(Script.ScriptType.P2WPKH)).build();
|
||||
if (aesKey != null) {
|
||||
// If wallet is encrypted, encrypt the new keychain.
|
||||
KeyCrypter keyCrypter = wallet.getKeyCrypter();
|
||||
nativeSegwitKeyChain = nativeSegwitKeyChain.toEncrypted(keyCrypter, aesKey);
|
||||
}
|
||||
wallet.addAndActivateHDChain(nativeSegwitKeyChain);
|
||||
}
|
||||
migratedWalletToSegwit.set(true);
|
||||
}
|
||||
}
|
||||
|
@ -109,6 +109,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
@Slf4j
|
||||
public class WalletsSetup {
|
||||
|
||||
public static final String PRE_SEGWIT_WALLET_BACKUP = "pre_segwit_bisq_BTC.wallet.backup";
|
||||
|
||||
@Getter
|
||||
public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty();
|
||||
|
||||
@ -421,6 +423,13 @@ public class WalletsSetup {
|
||||
log.error("Could not delete directory " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
File segwitBackup = new File(walletDir, PRE_SEGWIT_WALLET_BACKUP);
|
||||
try {
|
||||
FileUtil.deleteFileIfExists(segwitBackup);
|
||||
} catch (IOException e) {
|
||||
log.error(e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -64,7 +64,7 @@ class BtcCoinSelector extends BisqDefaultCoinSelector {
|
||||
Address address = WalletService.getAddressFromOutput(output);
|
||||
return addresses.contains(address);
|
||||
} else {
|
||||
log.warn("transactionOutput.getScriptPubKey() not isSentToAddress or isPayToScriptHash");
|
||||
log.warn("transactionOutput.getScriptPubKey() is not P2PKH nor P2SH nor P2WH");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import bisq.common.handlers.ErrorMessageHandler;
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.LegacyAddress;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
@ -41,6 +42,7 @@ import org.bitcoinj.core.TransactionOutPoint;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.bitcoinj.script.ScriptBuilder;
|
||||
import org.bitcoinj.wallet.SendRequest;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
@ -576,46 +578,49 @@ public class BtcWalletService extends WalletService {
|
||||
if (addressEntry.isPresent()) {
|
||||
return addressEntry.get();
|
||||
} else {
|
||||
// We still use non-segwit addresses for the trade protocol.
|
||||
// We try to use available and not yet used entries
|
||||
Optional<AddressEntry> emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> AddressEntry.Context.AVAILABLE == e.getContext())
|
||||
.filter(e -> isAddressUnused(e.getAddress()))
|
||||
.filter(e -> Script.ScriptType.P2PKH.equals(e.getAddress().getOutputScriptType()))
|
||||
.findAny();
|
||||
if (emptyAvailableAddressEntry.isPresent()) {
|
||||
return addressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId);
|
||||
} else {
|
||||
AddressEntry entry = new AddressEntry(wallet.freshReceiveKey(), context, offerId);
|
||||
DeterministicKey key = (DeterministicKey) wallet.findKeyFromAddress(wallet.freshReceiveAddress(Script.ScriptType.P2PKH));
|
||||
AddressEntry entry = new AddressEntry(key, context, offerId, false);
|
||||
addressEntryList.addAddressEntry(entry);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AddressEntry getOrCreateAddressEntry(AddressEntry.Context context, Optional<AddressEntry> addressEntry) {
|
||||
if (addressEntry.isPresent()) {
|
||||
return addressEntry.get();
|
||||
} else {
|
||||
AddressEntry entry = new AddressEntry(wallet.freshReceiveKey(), context);
|
||||
addressEntryList.addAddressEntry(entry);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
public AddressEntry getArbitratorAddressEntry() {
|
||||
AddressEntry.Context context = AddressEntry.Context.ARBITRATOR;
|
||||
Optional<AddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> context == e.getContext())
|
||||
.findAny();
|
||||
return getOrCreateAddressEntry(context, addressEntry);
|
||||
return getOrCreateAddressEntry(context, addressEntry, false);
|
||||
}
|
||||
|
||||
public AddressEntry getFreshAddressEntry() {
|
||||
return getFreshAddressEntry(true);
|
||||
}
|
||||
|
||||
public AddressEntry getFreshAddressEntry(boolean segwit) {
|
||||
AddressEntry.Context context = AddressEntry.Context.AVAILABLE;
|
||||
Optional<AddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> context == e.getContext())
|
||||
.filter(e -> isAddressUnused(e.getAddress()))
|
||||
.filter(e -> {
|
||||
boolean isSegwitOutputScriptType = Script.ScriptType.P2WPKH.equals(e.getAddress().getOutputScriptType());
|
||||
// We need to ensure that we take only addressEntries which matches our segWit flag
|
||||
boolean isMatchingOutputScriptType = isSegwitOutputScriptType == segwit;
|
||||
return isMatchingOutputScriptType;
|
||||
})
|
||||
.findAny();
|
||||
return getOrCreateAddressEntry(context, addressEntry);
|
||||
return getOrCreateAddressEntry(context, addressEntry, segwit);
|
||||
}
|
||||
|
||||
public void recoverAddressEntry(String offerId, String address, AddressEntry.Context context) {
|
||||
@ -623,6 +628,22 @@ public class BtcWalletService extends WalletService {
|
||||
addressEntryList.swapAvailableToAddressEntryWithOfferId(addressEntry, context, offerId));
|
||||
}
|
||||
|
||||
private AddressEntry getOrCreateAddressEntry(AddressEntry.Context context, Optional<AddressEntry> addressEntry, boolean segwit) {
|
||||
if (addressEntry.isPresent()) {
|
||||
return addressEntry.get();
|
||||
} else {
|
||||
DeterministicKey key;
|
||||
if (segwit) {
|
||||
key = (DeterministicKey) wallet.findKeyFromAddress(wallet.freshReceiveAddress(Script.ScriptType.P2WPKH));
|
||||
} else {
|
||||
key = (DeterministicKey) wallet.findKeyFromAddress(wallet.freshReceiveAddress(Script.ScriptType.P2PKH));
|
||||
}
|
||||
AddressEntry entry = new AddressEntry(key, context, segwit);
|
||||
addressEntryList.addAddressEntry(entry);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<AddressEntry> findAddressEntry(String address, AddressEntry.Context context) {
|
||||
return getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> address.equals(e.getAddressString()))
|
||||
@ -969,7 +990,7 @@ public class BtcWalletService extends WalletService {
|
||||
counter++;
|
||||
fee = txFeeForWithdrawalPerByte.multiply(txSize);
|
||||
// We use a dummy address for the output
|
||||
final String dummyReceiver = getFreshAddressEntry().getAddressString();
|
||||
final String dummyReceiver = LegacyAddress.fromKey(params, new ECKey()).toBase58();
|
||||
SendRequest sendRequest = getSendRequestForMultipleAddresses(fromAddresses, dummyReceiver, amount, fee, null, aesKey);
|
||||
wallet.completeTx(sendRequest);
|
||||
tx = sendRequest.tx;
|
||||
@ -998,7 +1019,7 @@ public class BtcWalletService extends WalletService {
|
||||
public int getEstimatedFeeTxSize(List<Coin> outputValues, Coin txFee)
|
||||
throws InsufficientMoneyException, AddressFormatException {
|
||||
Transaction transaction = new Transaction(params);
|
||||
Address dummyAddress = LegacyAddress.fromKey(params, wallet.currentReceiveKey());
|
||||
Address dummyAddress = LegacyAddress.fromKey(params, new ECKey());
|
||||
outputValues.forEach(outputValue -> transaction.addOutput(outputValue, dummyAddress));
|
||||
|
||||
SendRequest sendRequest = SendRequest.forTx(transaction);
|
||||
|
@ -44,6 +44,7 @@ import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionInput;
|
||||
import org.bitcoinj.core.TransactionOutPoint;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.core.TransactionWitness;
|
||||
import org.bitcoinj.core.Utils;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.bitcoinj.crypto.TransactionSignature;
|
||||
@ -153,7 +154,7 @@ public class TradeWalletService {
|
||||
Transaction tradingFeeTx = new Transaction(params);
|
||||
SendRequest sendRequest = null;
|
||||
try {
|
||||
tradingFeeTx.addOutput(tradingFee, LegacyAddress.fromBase58(params, feeReceiverAddress));
|
||||
tradingFeeTx.addOutput(tradingFee, Address.fromString(params, feeReceiverAddress));
|
||||
// the reserved amount we need for the trade we send to our trade reservedForTradeAddress
|
||||
tradingFeeTx.addOutput(reservedFundsForOffer, reservedForTradeAddress);
|
||||
|
||||
@ -516,7 +517,7 @@ public class TradeWalletService {
|
||||
TransactionOutput takerTransactionOutput = null;
|
||||
if (takerChangeOutputValue > 0 && takerChangeAddressString != null) {
|
||||
takerTransactionOutput = new TransactionOutput(params, preparedDepositTx, Coin.valueOf(takerChangeOutputValue),
|
||||
LegacyAddress.fromBase58(params, takerChangeAddressString));
|
||||
Address.fromString(params, takerChangeAddressString));
|
||||
}
|
||||
|
||||
if (makerIsBuyer) {
|
||||
@ -599,8 +600,13 @@ public class TradeWalletService {
|
||||
// Add buyer inputs and apply signature
|
||||
// We grab the signature from the makersDepositTx and apply it to the new tx input
|
||||
for (int i = 0; i < buyerInputs.size(); i++) {
|
||||
TransactionInput transactionInput = makersDepositTx.getInputs().get(i);
|
||||
depositTx.addInput(getTransactionInput(depositTx, getMakersScriptSigProgram(transactionInput), buyerInputs.get(i)));
|
||||
TransactionInput makersInput = makersDepositTx.getInputs().get(i);
|
||||
byte[] makersScriptSigProgram = getMakersScriptSigProgram(makersInput);
|
||||
TransactionInput input = getTransactionInput(depositTx, makersScriptSigProgram, buyerInputs.get(i));
|
||||
if (!TransactionWitness.EMPTY.equals(makersInput.getWitness())) {
|
||||
input.setWitness(makersInput.getWitness());
|
||||
}
|
||||
depositTx.addInput(input);
|
||||
}
|
||||
|
||||
// Add seller inputs
|
||||
@ -662,9 +668,14 @@ public class TradeWalletService {
|
||||
|
||||
// We add takers signature from his inputs and add it to out tx which was already signed earlier.
|
||||
for (int i = 0; i < numTakersInputs; i++) {
|
||||
TransactionInput input = takersDepositTx.getInput(i);
|
||||
Script scriptSig = input.getScriptSig();
|
||||
myDepositTx.getInput(i).setScriptSig(scriptSig);
|
||||
TransactionInput takersInput = takersDepositTx.getInput(i);
|
||||
Script takersScriptSig = takersInput.getScriptSig();
|
||||
TransactionInput txInput = myDepositTx.getInput(i);
|
||||
txInput.setScriptSig(takersScriptSig);
|
||||
TransactionWitness witness = takersInput.getWitness();
|
||||
if (!TransactionWitness.EMPTY.equals(witness)) {
|
||||
txInput.setWitness(witness);
|
||||
}
|
||||
}
|
||||
|
||||
WalletService.printTx("sellerAsMakerFinalizesDepositTx", myDepositTx);
|
||||
@ -686,7 +697,7 @@ public class TradeWalletService {
|
||||
delayedPayoutTx.addInput(p2SHMultiSigOutput);
|
||||
applyLockTime(lockTime, delayedPayoutTx);
|
||||
Coin outputAmount = p2SHMultiSigOutput.getValue().subtract(minerFee);
|
||||
delayedPayoutTx.addOutput(outputAmount, LegacyAddress.fromBase58(params, donationAddressString));
|
||||
delayedPayoutTx.addOutput(outputAmount, Address.fromString(params, donationAddressString));
|
||||
WalletService.printTx("Unsigned delayedPayoutTx ToDonationAddress", delayedPayoutTx);
|
||||
WalletService.verifyTransaction(delayedPayoutTx);
|
||||
return delayedPayoutTx;
|
||||
@ -938,10 +949,10 @@ public class TradeWalletService {
|
||||
Transaction payoutTx = new Transaction(params);
|
||||
payoutTx.addInput(p2SHMultiSigOutput);
|
||||
if (buyerPayoutAmount.isPositive()) {
|
||||
payoutTx.addOutput(buyerPayoutAmount, LegacyAddress.fromBase58(params, buyerAddressString));
|
||||
payoutTx.addOutput(buyerPayoutAmount, Address.fromString(params, buyerAddressString));
|
||||
}
|
||||
if (sellerPayoutAmount.isPositive()) {
|
||||
payoutTx.addOutput(sellerPayoutAmount, LegacyAddress.fromBase58(params, sellerAddressString));
|
||||
payoutTx.addOutput(sellerPayoutAmount, Address.fromString(params, sellerAddressString));
|
||||
}
|
||||
|
||||
// take care of sorting!
|
||||
@ -1001,10 +1012,10 @@ public class TradeWalletService {
|
||||
payoutTx.addInput(new TransactionInput(params, depositTx, p2SHMultiSigOutputScript.getProgram(), new TransactionOutPoint(params, 0, spendTxHash), msOutput));
|
||||
|
||||
if (buyerPayoutAmount.isPositive()) {
|
||||
payoutTx.addOutput(buyerPayoutAmount, LegacyAddress.fromBase58(params, buyerAddressString));
|
||||
payoutTx.addOutput(buyerPayoutAmount, Address.fromString(params, buyerAddressString));
|
||||
}
|
||||
if (sellerPayoutAmount.isPositive()) {
|
||||
payoutTx.addOutput(sellerPayoutAmount, LegacyAddress.fromBase58(params, sellerAddressString));
|
||||
payoutTx.addOutput(sellerPayoutAmount, Address.fromString(params, sellerAddressString));
|
||||
}
|
||||
|
||||
// take care of sorting!
|
||||
@ -1082,7 +1093,7 @@ public class TradeWalletService {
|
||||
checkNotNull(input.getValue(), "input.getValue() must not be null");
|
||||
|
||||
return new RawTransactionInput(input.getOutpoint().getIndex(),
|
||||
input.getConnectedOutput().getParentTransaction().bitcoinSerialize(),
|
||||
input.getConnectedOutput().getParentTransaction().bitcoinSerialize(false),
|
||||
input.getValue().value);
|
||||
}
|
||||
|
||||
@ -1146,10 +1157,10 @@ public class TradeWalletService {
|
||||
Transaction transaction = new Transaction(params);
|
||||
transaction.addInput(p2SHMultiSigOutput);
|
||||
if (buyerPayoutAmount.isPositive()) {
|
||||
transaction.addOutput(buyerPayoutAmount, LegacyAddress.fromBase58(params, buyerAddressString));
|
||||
transaction.addOutput(buyerPayoutAmount, Address.fromString(params, buyerAddressString));
|
||||
}
|
||||
if (sellerPayoutAmount.isPositive()) {
|
||||
transaction.addOutput(sellerPayoutAmount, LegacyAddress.fromBase58(params, sellerAddressString));
|
||||
transaction.addOutput(sellerPayoutAmount, Address.fromString(params, sellerAddressString));
|
||||
}
|
||||
checkArgument(transaction.getOutputs().size() >= 1, "We need at least one output.");
|
||||
return transaction;
|
||||
@ -1165,6 +1176,8 @@ public class TradeWalletService {
|
||||
if (sigKey.isEncrypted()) {
|
||||
checkNotNull(aesKey);
|
||||
}
|
||||
|
||||
if (ScriptPattern.isP2PK(scriptPubKey) || ScriptPattern.isP2PKH(scriptPubKey)) {
|
||||
Sha256Hash hash = transaction.hashForSignature(inputIndex, scriptPubKey, Transaction.SigHash.ALL, false);
|
||||
ECKey.ECDSASignature signature = sigKey.sign(hash, aesKey);
|
||||
TransactionSignature txSig = new TransactionSignature(signature, Transaction.SigHash.ALL, false);
|
||||
@ -1172,6 +1185,18 @@ public class TradeWalletService {
|
||||
input.setScriptSig(ScriptBuilder.createInputScript(txSig));
|
||||
} else if (ScriptPattern.isP2PKH(scriptPubKey)) {
|
||||
input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey));
|
||||
}
|
||||
} else if (ScriptPattern.isP2WPKH(scriptPubKey)) {
|
||||
// TODO: Consider using this alternative way to build the scriptCode (taken from bitcoinj master)
|
||||
// Script scriptCode = ScriptBuilder.createP2PKHOutputScript(sigKey)
|
||||
Script scriptCode = new ScriptBuilder().data(
|
||||
ScriptBuilder.createOutputScript(LegacyAddress.fromKey(transaction.getParams(), sigKey)).getProgram())
|
||||
.build();
|
||||
Coin value = input.getValue();
|
||||
TransactionSignature txSig = transaction.calculateWitnessSignature(inputIndex, sigKey, scriptCode, value,
|
||||
Transaction.SigHash.ALL, false);
|
||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||
input.setWitness(TransactionWitness.redeemP2WPKH(txSig, sigKey));
|
||||
} else {
|
||||
throw new SigningException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey);
|
||||
}
|
||||
|
@ -37,12 +37,14 @@ import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Context;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.LegacyAddress;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionConfidence;
|
||||
import org.bitcoinj.core.TransactionInput;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.core.TransactionWitness;
|
||||
import org.bitcoinj.core.VerificationException;
|
||||
import org.bitcoinj.core.listeners.NewBestBlockListener;
|
||||
import org.bitcoinj.core.listeners.TransactionConfidenceEventListener;
|
||||
@ -51,6 +53,7 @@ import org.bitcoinj.crypto.KeyCrypter;
|
||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||
import org.bitcoinj.crypto.TransactionSignature;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.bitcoinj.script.ScriptBuilder;
|
||||
import org.bitcoinj.script.ScriptChunk;
|
||||
import org.bitcoinj.script.ScriptException;
|
||||
import org.bitcoinj.script.ScriptPattern;
|
||||
@ -241,7 +244,7 @@ public abstract class WalletService {
|
||||
int inputIndex) throws TransactionVerificationException {
|
||||
try {
|
||||
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
|
||||
input.getScriptSig().correctlySpends(transaction, inputIndex, input.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
|
||||
input.getScriptSig().correctlySpends(transaction, inputIndex, input.getWitness(), input.getValue(), input.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
log.error(t.getMessage());
|
||||
@ -265,7 +268,7 @@ public abstract class WalletService {
|
||||
// We assume if it's already signed, it's hopefully got a SIGHASH type that will not invalidate when
|
||||
// we sign missing pieces (to check this would require either assuming any signatures are signing
|
||||
// standard output types or a way to get processed signatures out of script execution)
|
||||
txIn.getScriptSig().correctlySpends(tx, index, txIn.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
|
||||
txIn.getScriptSig().correctlySpends(tx, index, txIn.getWitness(), txIn.getValue(), txIn.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
|
||||
log.warn("Input {} already correctly spends output, assuming SIGHASH type used will be safe and skipping signing.", index);
|
||||
return;
|
||||
} catch (ScriptException e) {
|
||||
@ -288,7 +291,7 @@ public abstract class WalletService {
|
||||
// We assume if it's already signed, it's hopefully got a SIGHASH type that will not invalidate when
|
||||
// we sign missing pieces (to check this would require either assuming any signatures are signing
|
||||
// standard output types or a way to get processed signatures out of script execution)
|
||||
txIn.getScriptSig().correctlySpends(tx, index, txIn.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
|
||||
txIn.getScriptSig().correctlySpends(tx, index, txIn.getWitness(), txIn.getValue(), txIn.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
|
||||
log.warn("Input {} already correctly spends output, assuming SIGHASH type used will be safe and skipping signing.", index);
|
||||
return;
|
||||
} catch (ScriptException e) {
|
||||
@ -312,6 +315,8 @@ public abstract class WalletService {
|
||||
|
||||
Script inputScript = txIn.getScriptSig();
|
||||
byte[] script = redeemData.redeemScript.getProgram();
|
||||
|
||||
if (ScriptPattern.isP2PK(scriptPubKey) || ScriptPattern.isP2PKH(scriptPubKey)) {
|
||||
try {
|
||||
TransactionSignature signature = partialTx.calculateSignature(index, key, script, Transaction.SigHash.ALL, false);
|
||||
inputScript = scriptPubKey.getScriptSigWithSignature(inputScript, signature.encodeToBitcoin(), 0);
|
||||
@ -321,6 +326,27 @@ public abstract class WalletService {
|
||||
} catch (ECKey.MissingPrivateKeyException e1) {
|
||||
log.warn("No private key in keypair for input {}", index);
|
||||
}
|
||||
} else if (ScriptPattern.isP2WPKH(scriptPubKey)) {
|
||||
try {
|
||||
// TODO: Consider using this alternative way to build the scriptCode (taken from bitcoinj master)
|
||||
// Script scriptCode = ScriptBuilder.createP2PKHOutputScript(key);
|
||||
Script scriptCode = new ScriptBuilder().data(
|
||||
ScriptBuilder.createOutputScript(LegacyAddress.fromKey(tx.getParams(), key)).getProgram())
|
||||
.build();
|
||||
Coin value = txIn.getValue();
|
||||
TransactionSignature txSig = tx.calculateWitnessSignature(index, key, scriptCode, value,
|
||||
Transaction.SigHash.ALL, false);
|
||||
txIn.setScriptSig(ScriptBuilder.createEmpty());
|
||||
txIn.setWitness(TransactionWitness.redeemP2WPKH(txSig, key));
|
||||
} catch (ECKey.KeyIsEncryptedException e1) {
|
||||
throw e1;
|
||||
} catch (ECKey.MissingPrivateKeyException e1) {
|
||||
log.warn("No private key in keypair for input {}", index);
|
||||
}
|
||||
} else {
|
||||
// log.error("Unexpected script type.");
|
||||
throw new RuntimeException("Unexpected script type.");
|
||||
}
|
||||
} else {
|
||||
log.warn("Missing connected output, assuming input {} is already signed.", index);
|
||||
}
|
||||
@ -585,14 +611,9 @@ public abstract class WalletService {
|
||||
return wallet.checkAESKey(aesKey);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DeterministicKey findKeyFromPubKeyHash(byte[] pubKeyHash) {
|
||||
return wallet.getActiveKeyChain().findKeyFromPubHash(pubKeyHash);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DeterministicKey findKeyFromPubKey(byte[] pubKey) {
|
||||
return wallet.getActiveKeyChain().findKeyFromPubKey(pubKey);
|
||||
return (DeterministicKey) wallet.findKeyFromPubKey(pubKey);
|
||||
}
|
||||
|
||||
public boolean isEncrypted() {
|
||||
|
@ -992,6 +992,7 @@ funds.deposit.fundWallet=Fund your wallet
|
||||
funds.deposit.withdrawFromWallet=Send funds from wallet
|
||||
funds.deposit.amount=Amount in BTC (optional)
|
||||
funds.deposit.generateAddress=Generate new address
|
||||
funds.deposit.generateAddressSegwit=Native segwit format (Bech32)
|
||||
funds.deposit.selectUnused=Please select an unused address from the table above rather than generating a new one.
|
||||
|
||||
funds.withdrawal.arbitrationFee=Arbitration fee
|
||||
|
@ -41,8 +41,12 @@ import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.DevEnv;
|
||||
import bisq.common.config.Config;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.SegwitAddress;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import net.glxn.qrgen.QRCode;
|
||||
@ -54,6 +58,7 @@ import javax.inject.Named;
|
||||
import javafx.fxml.FXML;
|
||||
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
@ -85,10 +90,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.addAddressTextField;
|
||||
import static bisq.desktop.util.FormBuilder.addButton;
|
||||
import static bisq.desktop.util.FormBuilder.addInputTextField;
|
||||
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
|
||||
import static bisq.desktop.util.FormBuilder.*;
|
||||
|
||||
@FxmlView
|
||||
public class DepositView extends ActivatableView<VBox, Void> {
|
||||
@ -102,6 +104,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
||||
private ImageView qrCodeImageView;
|
||||
private AddressTextField addressTextField;
|
||||
private Button generateNewAddressButton;
|
||||
private CheckBox generateNewAddressSegwitCheckbox;
|
||||
private TitledGroupBg titledGroupBg;
|
||||
private InputTextField amountTextField;
|
||||
|
||||
@ -195,16 +198,26 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
||||
addressTextField.setManaged(false);
|
||||
amountTextField.setManaged(false);
|
||||
|
||||
generateNewAddressSegwitCheckbox = addCheckBox(gridPane, ++gridRow,
|
||||
Res.get("funds.deposit.generateAddressSegwit"), -20);
|
||||
generateNewAddressSegwitCheckbox.setAllowIndeterminate(false);
|
||||
generateNewAddressSegwitCheckbox.setSelected(true);
|
||||
GridPane.setColumnIndex(generateNewAddressSegwitCheckbox, 0);
|
||||
GridPane.setHalignment(generateNewAddressSegwitCheckbox, HPos.LEFT);
|
||||
|
||||
generateNewAddressButton = addButton(gridPane, ++gridRow, Res.get("funds.deposit.generateAddress"), -20);
|
||||
GridPane.setColumnIndex(generateNewAddressButton, 0);
|
||||
GridPane.setHalignment(generateNewAddressButton, HPos.LEFT);
|
||||
|
||||
generateNewAddressButton.setOnAction(event -> {
|
||||
boolean hasUnUsedAddress = observableList.stream().anyMatch(e -> e.getNumTxOutputs() == 0);
|
||||
boolean segwit = generateNewAddressSegwitCheckbox.isSelected();
|
||||
NetworkParameters params = Config.baseCurrencyNetworkParameters();
|
||||
boolean hasUnUsedAddress = observableList.stream().anyMatch(e -> e.getNumTxOutputs() == 0
|
||||
&& (Address.fromString(params, e.getAddressString()) instanceof SegwitAddress) == segwit);
|
||||
if (hasUnUsedAddress) {
|
||||
new Popup().warning(Res.get("funds.deposit.selectUnused")).show();
|
||||
} else {
|
||||
AddressEntry newSavingsAddressEntry = walletService.getFreshAddressEntry();
|
||||
AddressEntry newSavingsAddressEntry = walletService.getFreshAddressEntry(segwit);
|
||||
updateList();
|
||||
observableList.stream()
|
||||
.filter(depositListItem -> depositListItem.getAddressString().equals(newSavingsAddressEntry.getAddressString()))
|
||||
|
@ -20,7 +20,7 @@ dependencyVerification {
|
||||
'com.fasterxml.jackson.core:jackson-core:39a74610521d7fb9eb3f437bb8739bbf47f6435be12d17bf954c731a0c6352bb',
|
||||
'com.fasterxml.jackson.core:jackson-databind:fcf3c2b0c332f5f54604f7e27fa7ee502378a2cc5df6a944bbfae391872c32ff',
|
||||
'com.github.JesusMcCloud:jtorctl:389d61b1b5a85eb2f23c582c3913ede49f80c9f2b553e4762382c836270e57e5',
|
||||
'com.github.bisq-network:bitcoinj:85d609e9bbaa93de0a9ca1ab436f578c14f7cfa1876b50878046d9f624b48a6b',
|
||||
'com.github.bisq-network:bitcoinj:b8b6e4b8010f2b8d4aac7141c0809dea6d102c3ff3c06ceba78c2626d531b0af',
|
||||
'com.github.cd2357.netlayer:tor.external:7c70846d36465279c2664f147a0f2d47202c5d67c6a2075225194779c3fbe122',
|
||||
'com.github.cd2357.netlayer:tor.native:84b449191d535a3c2187f7f7f3bb9bcb7d1097f07c6bf8c4f2b3331c20107d9a',
|
||||
'com.github.cd2357.netlayer:tor:ff92e4a7b59d1b480e0427fcfcf3f82a6fd69be68eec91c6360774d599e3c2e0',
|
||||
|
@ -1239,6 +1239,7 @@ message AddressEntry {
|
||||
bytes pub_key = 9;
|
||||
bytes pub_key_hash = 10;
|
||||
int64 coin_locked_in_multi_sig = 11;
|
||||
bool segwit = 12;
|
||||
}
|
||||
|
||||
message NavigationPath {
|
||||
|
Loading…
Reference in New Issue
Block a user